| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965 |
- import "./chunk-Y2F7D3TJ.js";
- // ../../../../ZhiYi/uni-app/uni-app-demo/node_modules/hls.js/dist/hls.mjs
- var isFiniteNumber = Number.isFinite || function(value) {
- return typeof value === "number" && isFinite(value);
- };
- var isSafeInteger = Number.isSafeInteger || function(value) {
- return typeof value === "number" && Math.abs(value) <= MAX_SAFE_INTEGER;
- };
- var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
- var ErrorTypes = function(ErrorTypes2) {
- ErrorTypes2["NETWORK_ERROR"] = "networkError";
- ErrorTypes2["MEDIA_ERROR"] = "mediaError";
- ErrorTypes2["KEY_SYSTEM_ERROR"] = "keySystemError";
- ErrorTypes2["MUX_ERROR"] = "muxError";
- ErrorTypes2["OTHER_ERROR"] = "otherError";
- return ErrorTypes2;
- }({});
- var ErrorDetails = function(ErrorDetails2) {
- ErrorDetails2["KEY_SYSTEM_NO_KEYS"] = "keySystemNoKeys";
- ErrorDetails2["KEY_SYSTEM_NO_ACCESS"] = "keySystemNoAccess";
- ErrorDetails2["KEY_SYSTEM_NO_SESSION"] = "keySystemNoSession";
- ErrorDetails2["KEY_SYSTEM_NO_CONFIGURED_LICENSE"] = "keySystemNoConfiguredLicense";
- ErrorDetails2["KEY_SYSTEM_LICENSE_REQUEST_FAILED"] = "keySystemLicenseRequestFailed";
- ErrorDetails2["KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED"] = "keySystemServerCertificateRequestFailed";
- ErrorDetails2["KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED"] = "keySystemServerCertificateUpdateFailed";
- ErrorDetails2["KEY_SYSTEM_SESSION_UPDATE_FAILED"] = "keySystemSessionUpdateFailed";
- ErrorDetails2["KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED"] = "keySystemStatusOutputRestricted";
- ErrorDetails2["KEY_SYSTEM_STATUS_INTERNAL_ERROR"] = "keySystemStatusInternalError";
- ErrorDetails2["KEY_SYSTEM_DESTROY_MEDIA_KEYS_ERROR"] = "keySystemDestroyMediaKeysError";
- ErrorDetails2["KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR"] = "keySystemDestroyCloseSessionError";
- ErrorDetails2["KEY_SYSTEM_DESTROY_REMOVE_SESSION_ERROR"] = "keySystemDestroyRemoveSessionError";
- ErrorDetails2["MANIFEST_LOAD_ERROR"] = "manifestLoadError";
- ErrorDetails2["MANIFEST_LOAD_TIMEOUT"] = "manifestLoadTimeOut";
- ErrorDetails2["MANIFEST_PARSING_ERROR"] = "manifestParsingError";
- ErrorDetails2["MANIFEST_INCOMPATIBLE_CODECS_ERROR"] = "manifestIncompatibleCodecsError";
- ErrorDetails2["LEVEL_EMPTY_ERROR"] = "levelEmptyError";
- ErrorDetails2["LEVEL_LOAD_ERROR"] = "levelLoadError";
- ErrorDetails2["LEVEL_LOAD_TIMEOUT"] = "levelLoadTimeOut";
- ErrorDetails2["LEVEL_PARSING_ERROR"] = "levelParsingError";
- ErrorDetails2["LEVEL_SWITCH_ERROR"] = "levelSwitchError";
- ErrorDetails2["AUDIO_TRACK_LOAD_ERROR"] = "audioTrackLoadError";
- ErrorDetails2["AUDIO_TRACK_LOAD_TIMEOUT"] = "audioTrackLoadTimeOut";
- ErrorDetails2["SUBTITLE_LOAD_ERROR"] = "subtitleTrackLoadError";
- ErrorDetails2["SUBTITLE_TRACK_LOAD_TIMEOUT"] = "subtitleTrackLoadTimeOut";
- ErrorDetails2["FRAG_LOAD_ERROR"] = "fragLoadError";
- ErrorDetails2["FRAG_LOAD_TIMEOUT"] = "fragLoadTimeOut";
- ErrorDetails2["FRAG_DECRYPT_ERROR"] = "fragDecryptError";
- ErrorDetails2["FRAG_PARSING_ERROR"] = "fragParsingError";
- ErrorDetails2["FRAG_GAP"] = "fragGap";
- ErrorDetails2["REMUX_ALLOC_ERROR"] = "remuxAllocError";
- ErrorDetails2["KEY_LOAD_ERROR"] = "keyLoadError";
- ErrorDetails2["KEY_LOAD_TIMEOUT"] = "keyLoadTimeOut";
- ErrorDetails2["BUFFER_ADD_CODEC_ERROR"] = "bufferAddCodecError";
- ErrorDetails2["BUFFER_INCOMPATIBLE_CODECS_ERROR"] = "bufferIncompatibleCodecsError";
- ErrorDetails2["BUFFER_APPEND_ERROR"] = "bufferAppendError";
- ErrorDetails2["BUFFER_APPENDING_ERROR"] = "bufferAppendingError";
- ErrorDetails2["BUFFER_STALLED_ERROR"] = "bufferStalledError";
- ErrorDetails2["BUFFER_FULL_ERROR"] = "bufferFullError";
- ErrorDetails2["BUFFER_SEEK_OVER_HOLE"] = "bufferSeekOverHole";
- ErrorDetails2["BUFFER_NUDGE_ON_STALL"] = "bufferNudgeOnStall";
- ErrorDetails2["ASSET_LIST_LOAD_ERROR"] = "assetListLoadError";
- ErrorDetails2["ASSET_LIST_LOAD_TIMEOUT"] = "assetListLoadTimeout";
- ErrorDetails2["ASSET_LIST_PARSING_ERROR"] = "assetListParsingError";
- ErrorDetails2["INTERSTITIAL_ASSET_ITEM_ERROR"] = "interstitialAssetItemError";
- ErrorDetails2["INTERNAL_EXCEPTION"] = "internalException";
- ErrorDetails2["INTERNAL_ABORTED"] = "aborted";
- ErrorDetails2["ATTACH_MEDIA_ERROR"] = "attachMediaError";
- ErrorDetails2["UNKNOWN"] = "unknown";
- return ErrorDetails2;
- }({});
- var Events = function(Events2) {
- Events2["MEDIA_ATTACHING"] = "hlsMediaAttaching";
- Events2["MEDIA_ATTACHED"] = "hlsMediaAttached";
- Events2["MEDIA_DETACHING"] = "hlsMediaDetaching";
- Events2["MEDIA_DETACHED"] = "hlsMediaDetached";
- Events2["MEDIA_ENDED"] = "hlsMediaEnded";
- Events2["STALL_RESOLVED"] = "hlsStallResolved";
- Events2["BUFFER_RESET"] = "hlsBufferReset";
- Events2["BUFFER_CODECS"] = "hlsBufferCodecs";
- Events2["BUFFER_CREATED"] = "hlsBufferCreated";
- Events2["BUFFER_APPENDING"] = "hlsBufferAppending";
- Events2["BUFFER_APPENDED"] = "hlsBufferAppended";
- Events2["BUFFER_EOS"] = "hlsBufferEos";
- Events2["BUFFERED_TO_END"] = "hlsBufferedToEnd";
- Events2["BUFFER_FLUSHING"] = "hlsBufferFlushing";
- Events2["BUFFER_FLUSHED"] = "hlsBufferFlushed";
- Events2["MANIFEST_LOADING"] = "hlsManifestLoading";
- Events2["MANIFEST_LOADED"] = "hlsManifestLoaded";
- Events2["MANIFEST_PARSED"] = "hlsManifestParsed";
- Events2["LEVEL_SWITCHING"] = "hlsLevelSwitching";
- Events2["LEVEL_SWITCHED"] = "hlsLevelSwitched";
- Events2["LEVEL_LOADING"] = "hlsLevelLoading";
- Events2["LEVEL_LOADED"] = "hlsLevelLoaded";
- Events2["LEVEL_UPDATED"] = "hlsLevelUpdated";
- Events2["LEVEL_PTS_UPDATED"] = "hlsLevelPtsUpdated";
- Events2["LEVELS_UPDATED"] = "hlsLevelsUpdated";
- Events2["AUDIO_TRACKS_UPDATED"] = "hlsAudioTracksUpdated";
- Events2["AUDIO_TRACK_SWITCHING"] = "hlsAudioTrackSwitching";
- Events2["AUDIO_TRACK_SWITCHED"] = "hlsAudioTrackSwitched";
- Events2["AUDIO_TRACK_LOADING"] = "hlsAudioTrackLoading";
- Events2["AUDIO_TRACK_LOADED"] = "hlsAudioTrackLoaded";
- Events2["AUDIO_TRACK_UPDATED"] = "hlsAudioTrackUpdated";
- Events2["SUBTITLE_TRACKS_UPDATED"] = "hlsSubtitleTracksUpdated";
- Events2["SUBTITLE_TRACKS_CLEARED"] = "hlsSubtitleTracksCleared";
- Events2["SUBTITLE_TRACK_SWITCH"] = "hlsSubtitleTrackSwitch";
- Events2["SUBTITLE_TRACK_LOADING"] = "hlsSubtitleTrackLoading";
- Events2["SUBTITLE_TRACK_LOADED"] = "hlsSubtitleTrackLoaded";
- Events2["SUBTITLE_TRACK_UPDATED"] = "hlsSubtitleTrackUpdated";
- Events2["SUBTITLE_FRAG_PROCESSED"] = "hlsSubtitleFragProcessed";
- Events2["CUES_PARSED"] = "hlsCuesParsed";
- Events2["NON_NATIVE_TEXT_TRACKS_FOUND"] = "hlsNonNativeTextTracksFound";
- Events2["INIT_PTS_FOUND"] = "hlsInitPtsFound";
- Events2["FRAG_LOADING"] = "hlsFragLoading";
- Events2["FRAG_LOAD_EMERGENCY_ABORTED"] = "hlsFragLoadEmergencyAborted";
- Events2["FRAG_LOADED"] = "hlsFragLoaded";
- Events2["FRAG_DECRYPTED"] = "hlsFragDecrypted";
- Events2["FRAG_PARSING_INIT_SEGMENT"] = "hlsFragParsingInitSegment";
- Events2["FRAG_PARSING_USERDATA"] = "hlsFragParsingUserdata";
- Events2["FRAG_PARSING_METADATA"] = "hlsFragParsingMetadata";
- Events2["FRAG_PARSED"] = "hlsFragParsed";
- Events2["FRAG_BUFFERED"] = "hlsFragBuffered";
- Events2["FRAG_CHANGED"] = "hlsFragChanged";
- Events2["FPS_DROP"] = "hlsFpsDrop";
- Events2["FPS_DROP_LEVEL_CAPPING"] = "hlsFpsDropLevelCapping";
- Events2["MAX_AUTO_LEVEL_UPDATED"] = "hlsMaxAutoLevelUpdated";
- Events2["ERROR"] = "hlsError";
- Events2["DESTROYING"] = "hlsDestroying";
- Events2["KEY_LOADING"] = "hlsKeyLoading";
- Events2["KEY_LOADED"] = "hlsKeyLoaded";
- Events2["LIVE_BACK_BUFFER_REACHED"] = "hlsLiveBackBufferReached";
- Events2["BACK_BUFFER_REACHED"] = "hlsBackBufferReached";
- Events2["STEERING_MANIFEST_LOADED"] = "hlsSteeringManifestLoaded";
- Events2["ASSET_LIST_LOADING"] = "hlsAssetListLoading";
- Events2["ASSET_LIST_LOADED"] = "hlsAssetListLoaded";
- Events2["INTERSTITIALS_UPDATED"] = "hlsInterstitialsUpdated";
- Events2["INTERSTITIALS_BUFFERED_TO_BOUNDARY"] = "hlsInterstitialsBufferedToBoundary";
- Events2["INTERSTITIAL_ASSET_PLAYER_CREATED"] = "hlsInterstitialAssetPlayerCreated";
- Events2["INTERSTITIAL_STARTED"] = "hlsInterstitialStarted";
- Events2["INTERSTITIAL_ASSET_STARTED"] = "hlsInterstitialAssetStarted";
- Events2["INTERSTITIAL_ASSET_ENDED"] = "hlsInterstitialAssetEnded";
- Events2["INTERSTITIAL_ASSET_ERROR"] = "hlsInterstitialAssetError";
- Events2["INTERSTITIAL_ENDED"] = "hlsInterstitialEnded";
- Events2["INTERSTITIALS_PRIMARY_RESUMED"] = "hlsInterstitialsPrimaryResumed";
- Events2["PLAYOUT_LIMIT_REACHED"] = "hlsPlayoutLimitReached";
- Events2["EVENT_CUE_ENTER"] = "hlsEventCueEnter";
- return Events2;
- }({});
- var PlaylistContextType = {
- MANIFEST: "manifest",
- LEVEL: "level",
- AUDIO_TRACK: "audioTrack",
- SUBTITLE_TRACK: "subtitleTrack"
- };
- var PlaylistLevelType = {
- MAIN: "main",
- AUDIO: "audio",
- SUBTITLE: "subtitle"
- };
- var EWMA = class {
- // About half of the estimated value will be from the last |halfLife| samples by weight.
- constructor(halfLife, estimate = 0, weight = 0) {
- this.halfLife = void 0;
- this.alpha_ = void 0;
- this.estimate_ = void 0;
- this.totalWeight_ = void 0;
- this.halfLife = halfLife;
- this.alpha_ = halfLife ? Math.exp(Math.log(0.5) / halfLife) : 0;
- this.estimate_ = estimate;
- this.totalWeight_ = weight;
- }
- sample(weight, value) {
- const adjAlpha = Math.pow(this.alpha_, weight);
- this.estimate_ = value * (1 - adjAlpha) + adjAlpha * this.estimate_;
- this.totalWeight_ += weight;
- }
- getTotalWeight() {
- return this.totalWeight_;
- }
- getEstimate() {
- if (this.alpha_) {
- const zeroFactor = 1 - Math.pow(this.alpha_, this.totalWeight_);
- if (zeroFactor) {
- return this.estimate_ / zeroFactor;
- }
- }
- return this.estimate_;
- }
- };
- var EwmaBandWidthEstimator = class {
- constructor(slow, fast, defaultEstimate, defaultTTFB = 100) {
- this.defaultEstimate_ = void 0;
- this.minWeight_ = void 0;
- this.minDelayMs_ = void 0;
- this.slow_ = void 0;
- this.fast_ = void 0;
- this.defaultTTFB_ = void 0;
- this.ttfb_ = void 0;
- this.defaultEstimate_ = defaultEstimate;
- this.minWeight_ = 1e-3;
- this.minDelayMs_ = 50;
- this.slow_ = new EWMA(slow);
- this.fast_ = new EWMA(fast);
- this.defaultTTFB_ = defaultTTFB;
- this.ttfb_ = new EWMA(slow);
- }
- update(slow, fast) {
- const {
- slow_,
- fast_,
- ttfb_
- } = this;
- if (slow_.halfLife !== slow) {
- this.slow_ = new EWMA(slow, slow_.getEstimate(), slow_.getTotalWeight());
- }
- if (fast_.halfLife !== fast) {
- this.fast_ = new EWMA(fast, fast_.getEstimate(), fast_.getTotalWeight());
- }
- if (ttfb_.halfLife !== slow) {
- this.ttfb_ = new EWMA(slow, ttfb_.getEstimate(), ttfb_.getTotalWeight());
- }
- }
- sample(durationMs, numBytes) {
- durationMs = Math.max(durationMs, this.minDelayMs_);
- const numBits = 8 * numBytes;
- const durationS = durationMs / 1e3;
- const bandwidthInBps = numBits / durationS;
- this.fast_.sample(durationS, bandwidthInBps);
- this.slow_.sample(durationS, bandwidthInBps);
- }
- sampleTTFB(ttfb) {
- const seconds = ttfb / 1e3;
- const weight = Math.sqrt(2) * Math.exp(-Math.pow(seconds, 2) / 2);
- this.ttfb_.sample(weight, Math.max(ttfb, 5));
- }
- canEstimate() {
- return this.fast_.getTotalWeight() >= this.minWeight_;
- }
- getEstimate() {
- if (this.canEstimate()) {
- return Math.min(this.fast_.getEstimate(), this.slow_.getEstimate());
- } else {
- return this.defaultEstimate_;
- }
- }
- getEstimateTTFB() {
- if (this.ttfb_.getTotalWeight() >= this.minWeight_) {
- return this.ttfb_.getEstimate();
- } else {
- return this.defaultTTFB_;
- }
- }
- get defaultEstimate() {
- return this.defaultEstimate_;
- }
- destroy() {
- }
- };
- function _defineProperty(e, r, t) {
- return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
- value: t,
- enumerable: true,
- configurable: true,
- writable: true
- }) : e[r] = t, e;
- }
- function _extends() {
- return _extends = Object.assign ? Object.assign.bind() : function(n) {
- for (var e = 1; e < arguments.length; e++) {
- var t = arguments[e];
- for (var r in t)
- ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
- }
- return n;
- }, _extends.apply(null, arguments);
- }
- function ownKeys(e, r) {
- var t = Object.keys(e);
- if (Object.getOwnPropertySymbols) {
- var o = Object.getOwnPropertySymbols(e);
- r && (o = o.filter(function(r2) {
- return Object.getOwnPropertyDescriptor(e, r2).enumerable;
- })), t.push.apply(t, o);
- }
- return t;
- }
- function _objectSpread2(e) {
- for (var r = 1; r < arguments.length; r++) {
- var t = null != arguments[r] ? arguments[r] : {};
- r % 2 ? ownKeys(Object(t), true).forEach(function(r2) {
- _defineProperty(e, r2, t[r2]);
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r2) {
- Object.defineProperty(e, r2, Object.getOwnPropertyDescriptor(t, r2));
- });
- }
- return e;
- }
- function _toPrimitive(t, r) {
- if ("object" != typeof t || !t)
- return t;
- var e = t[Symbol.toPrimitive];
- if (void 0 !== e) {
- var i = e.call(t, r);
- if ("object" != typeof i)
- return i;
- throw new TypeError("@@toPrimitive must return a primitive value.");
- }
- return ("string" === r ? String : Number)(t);
- }
- function _toPropertyKey(t) {
- var i = _toPrimitive(t, "string");
- return "symbol" == typeof i ? i : i + "";
- }
- var Logger = class {
- constructor(label, logger2) {
- this.trace = void 0;
- this.debug = void 0;
- this.log = void 0;
- this.warn = void 0;
- this.info = void 0;
- this.error = void 0;
- const lb = `[${label}]:`;
- this.trace = noop;
- this.debug = logger2.debug.bind(null, lb);
- this.log = logger2.log.bind(null, lb);
- this.warn = logger2.warn.bind(null, lb);
- this.info = logger2.info.bind(null, lb);
- this.error = logger2.error.bind(null, lb);
- }
- };
- var noop = function noop2() {
- };
- var fakeLogger = {
- trace: noop,
- debug: noop,
- log: noop,
- warn: noop,
- info: noop,
- error: noop
- };
- function createLogger() {
- return _extends({}, fakeLogger);
- }
- function consolePrintFn(type, id) {
- const func = self.console[type];
- return func ? func.bind(self.console, `${id ? "[" + id + "] " : ""}[${type}] >`) : noop;
- }
- function getLoggerFn(key, debugConfig, id) {
- return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
- }
- var exportedLogger = createLogger();
- function enableLogs(debugConfig, context, id) {
- const newLogger = createLogger();
- if (typeof console === "object" && debugConfig === true || typeof debugConfig === "object") {
- const keys = [
- // Remove out from list here to hard-disable a log-level
- // 'trace',
- "debug",
- "log",
- "info",
- "warn",
- "error"
- ];
- keys.forEach((key) => {
- newLogger[key] = getLoggerFn(key, debugConfig, id);
- });
- try {
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.6.13"}`);
- } catch (e) {
- return createLogger();
- }
- keys.forEach((key) => {
- exportedLogger[key] = getLoggerFn(key, debugConfig);
- });
- } else {
- _extends(exportedLogger, newLogger);
- }
- return newLogger;
- }
- var logger = exportedLogger;
- function getMediaSource(preferManagedMediaSource = true) {
- if (typeof self === "undefined")
- return void 0;
- const mms = (preferManagedMediaSource || !self.MediaSource) && self.ManagedMediaSource;
- return mms || self.MediaSource || self.WebKitMediaSource;
- }
- function isManagedMediaSource(source) {
- return typeof self !== "undefined" && source === self.ManagedMediaSource;
- }
- function isCompatibleTrackChange(currentTracks, requiredTracks) {
- const trackNames = Object.keys(currentTracks);
- const requiredTrackNames = Object.keys(requiredTracks);
- const trackCount = trackNames.length;
- const requiredTrackCount = requiredTrackNames.length;
- return !trackCount || !requiredTrackCount || trackCount === requiredTrackCount && !trackNames.some((name) => requiredTrackNames.indexOf(name) === -1);
- }
- function utf8ArrayToStr(array, exitOnNull = false) {
- if (typeof TextDecoder !== "undefined") {
- const decoder = new TextDecoder("utf-8");
- const decoded = decoder.decode(array);
- if (exitOnNull) {
- const idx = decoded.indexOf("\0");
- return idx !== -1 ? decoded.substring(0, idx) : decoded;
- }
- return decoded.replace(/\0/g, "");
- }
- const len = array.length;
- let c;
- let char2;
- let char3;
- let out = "";
- let i = 0;
- while (i < len) {
- c = array[i++];
- if (c === 0 && exitOnNull) {
- return out;
- } else if (c === 0 || c === 3) {
- continue;
- }
- switch (c >> 4) {
- case 0:
- case 1:
- case 2:
- case 3:
- case 4:
- case 5:
- case 6:
- case 7:
- out += String.fromCharCode(c);
- break;
- case 12:
- case 13:
- char2 = array[i++];
- out += String.fromCharCode((c & 31) << 6 | char2 & 63);
- break;
- case 14:
- char2 = array[i++];
- char3 = array[i++];
- out += String.fromCharCode((c & 15) << 12 | (char2 & 63) << 6 | (char3 & 63) << 0);
- break;
- }
- }
- return out;
- }
- function arrayToHex(array) {
- let str = "";
- for (let i = 0; i < array.length; i++) {
- let h = array[i].toString(16);
- if (h.length < 2) {
- h = "0" + h;
- }
- str += h;
- }
- return str;
- }
- function hexToArrayBuffer(str) {
- return Uint8Array.from(str.replace(/^0x/, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")).buffer;
- }
- function getDefaultExportFromCjs(x) {
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
- }
- var urlToolkit = { exports: {} };
- var hasRequiredUrlToolkit;
- function requireUrlToolkit() {
- if (hasRequiredUrlToolkit)
- return urlToolkit.exports;
- hasRequiredUrlToolkit = 1;
- (function(module, exports) {
- (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 || {};
- baseURL = baseURL.trim();
- relativeURL = relativeURL.trim();
- if (!relativeURL) {
- 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) {
- 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] !== "/") {
- 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) {
- builtParts.netLoc = baseParts.netLoc;
- if (relativeParts.path[0] !== "/") {
- if (!relativeParts.path) {
- builtParts.path = baseParts.path;
- if (!relativeParts.params) {
- builtParts.params = baseParts.params;
- if (!relativeParts.query) {
- builtParts.query = baseParts.query;
- }
- }
- } else {
- 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) {
- path = path.split("").reverse().join("").replace(SLASH_DOT_REGEX, "");
- 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;
- })();
- })(urlToolkit);
- return urlToolkit.exports;
- }
- var urlToolkitExports = requireUrlToolkit();
- var LoadStats = class {
- constructor() {
- this.aborted = false;
- this.loaded = 0;
- this.retry = 0;
- this.total = 0;
- this.chunkCount = 0;
- this.bwEstimate = 0;
- this.loading = {
- start: 0,
- first: 0,
- end: 0
- };
- this.parsing = {
- start: 0,
- end: 0
- };
- this.buffering = {
- start: 0,
- first: 0,
- end: 0
- };
- }
- };
- var ElementaryStreamTypes = {
- AUDIO: "audio",
- VIDEO: "video",
- AUDIOVIDEO: "audiovideo"
- };
- var BaseSegment = class {
- constructor(base) {
- this._byteRange = null;
- this._url = null;
- this._stats = null;
- this._streams = null;
- this.base = void 0;
- this.relurl = void 0;
- if (typeof base === "string") {
- base = {
- url: base
- };
- }
- this.base = base;
- makeEnumerable(this, "stats");
- }
- // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array
- setByteRange(value, previous) {
- const params = value.split("@", 2);
- let start;
- if (params.length === 1) {
- start = (previous == null ? void 0 : previous.byteRangeEndOffset) || 0;
- } else {
- start = parseInt(params[1]);
- }
- this._byteRange = [start, parseInt(params[0]) + start];
- }
- get baseurl() {
- return this.base.url;
- }
- get byteRange() {
- if (this._byteRange === null) {
- return [];
- }
- return this._byteRange;
- }
- get byteRangeStartOffset() {
- return this.byteRange[0];
- }
- get byteRangeEndOffset() {
- return this.byteRange[1];
- }
- get elementaryStreams() {
- if (this._streams === null) {
- this._streams = {
- [ElementaryStreamTypes.AUDIO]: null,
- [ElementaryStreamTypes.VIDEO]: null,
- [ElementaryStreamTypes.AUDIOVIDEO]: null
- };
- }
- return this._streams;
- }
- set elementaryStreams(value) {
- this._streams = value;
- }
- get hasStats() {
- return this._stats !== null;
- }
- get hasStreams() {
- return this._streams !== null;
- }
- get stats() {
- if (this._stats === null) {
- this._stats = new LoadStats();
- }
- return this._stats;
- }
- set stats(value) {
- this._stats = value;
- }
- get url() {
- if (!this._url && this.baseurl && this.relurl) {
- this._url = urlToolkitExports.buildAbsoluteURL(this.baseurl, this.relurl, {
- alwaysNormalize: true
- });
- }
- return this._url || "";
- }
- set url(value) {
- this._url = value;
- }
- clearElementaryStreamInfo() {
- const {
- elementaryStreams
- } = this;
- elementaryStreams[ElementaryStreamTypes.AUDIO] = null;
- elementaryStreams[ElementaryStreamTypes.VIDEO] = null;
- elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null;
- }
- };
- function isMediaFragment(frag) {
- return frag.sn !== "initSegment";
- }
- var Fragment = class extends BaseSegment {
- constructor(type, base) {
- super(base);
- this._decryptdata = null;
- this._programDateTime = null;
- this._ref = null;
- this._bitrate = void 0;
- this.rawProgramDateTime = null;
- this.tagList = [];
- this.duration = 0;
- this.sn = 0;
- this.levelkeys = void 0;
- this.type = void 0;
- this.loader = null;
- this.keyLoader = null;
- this.level = -1;
- this.cc = 0;
- this.startPTS = void 0;
- this.endPTS = void 0;
- this.startDTS = void 0;
- this.endDTS = void 0;
- this.start = 0;
- this.playlistOffset = 0;
- this.deltaPTS = void 0;
- this.maxStartPTS = void 0;
- this.minEndPTS = void 0;
- this.data = void 0;
- this.bitrateTest = false;
- this.title = null;
- this.initSegment = null;
- this.endList = void 0;
- this.gap = void 0;
- this.urlId = 0;
- this.type = type;
- }
- get byteLength() {
- if (this.hasStats) {
- const total = this.stats.total;
- if (total) {
- return total;
- }
- }
- if (this.byteRange.length) {
- const start = this.byteRange[0];
- const end = this.byteRange[1];
- if (isFiniteNumber(start) && isFiniteNumber(end)) {
- return end - start;
- }
- }
- return null;
- }
- get bitrate() {
- if (this.byteLength) {
- return this.byteLength * 8 / this.duration;
- }
- if (this._bitrate) {
- return this._bitrate;
- }
- return null;
- }
- set bitrate(value) {
- this._bitrate = value;
- }
- get decryptdata() {
- var _this$_decryptdata;
- const {
- levelkeys
- } = this;
- if (!levelkeys || levelkeys.NONE) {
- return null;
- }
- if (levelkeys.identity) {
- if (!this._decryptdata) {
- this._decryptdata = levelkeys.identity.getDecryptData(this.sn);
- }
- } else if (!((_this$_decryptdata = this._decryptdata) != null && _this$_decryptdata.keyId)) {
- const keyFormats = Object.keys(levelkeys);
- if (keyFormats.length === 1) {
- const levelKey = this._decryptdata = levelkeys[keyFormats[0]] || null;
- if (levelKey) {
- this._decryptdata = levelKey.getDecryptData(this.sn, levelkeys);
- }
- }
- }
- return this._decryptdata;
- }
- get end() {
- return this.start + this.duration;
- }
- get endProgramDateTime() {
- if (this.programDateTime === null) {
- return null;
- }
- const duration = !isFiniteNumber(this.duration) ? 0 : this.duration;
- return this.programDateTime + duration * 1e3;
- }
- get encrypted() {
- var _this$_decryptdata2;
- if ((_this$_decryptdata2 = this._decryptdata) != null && _this$_decryptdata2.encrypted) {
- return true;
- } else if (this.levelkeys) {
- var _this$levelkeys$keyFo;
- const keyFormats = Object.keys(this.levelkeys);
- const len = keyFormats.length;
- if (len > 1 || len === 1 && (_this$levelkeys$keyFo = this.levelkeys[keyFormats[0]]) != null && _this$levelkeys$keyFo.encrypted) {
- return true;
- }
- }
- return false;
- }
- get programDateTime() {
- if (this._programDateTime === null && this.rawProgramDateTime) {
- this.programDateTime = Date.parse(this.rawProgramDateTime);
- }
- return this._programDateTime;
- }
- set programDateTime(value) {
- if (!isFiniteNumber(value)) {
- this._programDateTime = this.rawProgramDateTime = null;
- return;
- }
- this._programDateTime = value;
- }
- get ref() {
- if (!isMediaFragment(this)) {
- return null;
- }
- if (!this._ref) {
- this._ref = {
- base: this.base,
- start: this.start,
- duration: this.duration,
- sn: this.sn,
- programDateTime: this.programDateTime
- };
- }
- return this._ref;
- }
- addStart(value) {
- this.setStart(this.start + value);
- }
- setStart(value) {
- this.start = value;
- if (this._ref) {
- this._ref.start = value;
- }
- }
- setDuration(value) {
- this.duration = value;
- if (this._ref) {
- this._ref.duration = value;
- }
- }
- setKeyFormat(keyFormat) {
- const levelkeys = this.levelkeys;
- if (levelkeys) {
- var _this$_decryptdata3;
- const key = levelkeys[keyFormat];
- if (key && !((_this$_decryptdata3 = this._decryptdata) != null && _this$_decryptdata3.keyId)) {
- this._decryptdata = key.getDecryptData(this.sn, levelkeys);
- }
- }
- }
- abortRequests() {
- var _this$loader, _this$keyLoader;
- (_this$loader = this.loader) == null || _this$loader.abort();
- (_this$keyLoader = this.keyLoader) == null || _this$keyLoader.abort();
- }
- setElementaryStreamInfo(type, startPTS, endPTS, startDTS, endDTS, partial = false) {
- const {
- elementaryStreams
- } = this;
- const info = elementaryStreams[type];
- if (!info) {
- elementaryStreams[type] = {
- startPTS,
- endPTS,
- startDTS,
- endDTS,
- partial
- };
- return;
- }
- info.startPTS = Math.min(info.startPTS, startPTS);
- info.endPTS = Math.max(info.endPTS, endPTS);
- info.startDTS = Math.min(info.startDTS, startDTS);
- info.endDTS = Math.max(info.endDTS, endDTS);
- }
- };
- var Part = class extends BaseSegment {
- constructor(partAttrs, frag, base, index, previous) {
- super(base);
- this.fragOffset = 0;
- this.duration = 0;
- this.gap = false;
- this.independent = false;
- this.relurl = void 0;
- this.fragment = void 0;
- this.index = void 0;
- this.duration = partAttrs.decimalFloatingPoint("DURATION");
- this.gap = partAttrs.bool("GAP");
- this.independent = partAttrs.bool("INDEPENDENT");
- this.relurl = partAttrs.enumeratedString("URI");
- this.fragment = frag;
- this.index = index;
- const byteRange = partAttrs.enumeratedString("BYTERANGE");
- if (byteRange) {
- this.setByteRange(byteRange, previous);
- }
- if (previous) {
- this.fragOffset = previous.fragOffset + previous.duration;
- }
- }
- get start() {
- return this.fragment.start + this.fragOffset;
- }
- get end() {
- return this.start + this.duration;
- }
- get loaded() {
- const {
- elementaryStreams
- } = this;
- return !!(elementaryStreams.audio || elementaryStreams.video || elementaryStreams.audiovideo);
- }
- };
- function getOwnPropertyDescriptorFromPrototypeChain(object, property) {
- const prototype = Object.getPrototypeOf(object);
- if (prototype) {
- const propertyDescriptor = Object.getOwnPropertyDescriptor(prototype, property);
- if (propertyDescriptor) {
- return propertyDescriptor;
- }
- return getOwnPropertyDescriptorFromPrototypeChain(prototype, property);
- }
- }
- function makeEnumerable(object, property) {
- const d = getOwnPropertyDescriptorFromPrototypeChain(object, property);
- if (d) {
- d.enumerable = true;
- Object.defineProperty(object, property, d);
- }
- }
- var UINT32_MAX$1 = Math.pow(2, 32) - 1;
- var push = [].push;
- var RemuxerTrackIdConfig = {
- video: 1,
- audio: 2,
- id3: 3,
- text: 4
- };
- function bin2str(data) {
- return String.fromCharCode.apply(null, data);
- }
- function readUint16(buffer, offset) {
- const val = buffer[offset] << 8 | buffer[offset + 1];
- return val < 0 ? 65536 + val : val;
- }
- function readUint32(buffer, offset) {
- const val = readSint32(buffer, offset);
- return val < 0 ? 4294967296 + val : val;
- }
- function readUint64(buffer, offset) {
- let result = readUint32(buffer, offset);
- result *= Math.pow(2, 32);
- result += readUint32(buffer, offset + 4);
- return result;
- }
- function readSint32(buffer, offset) {
- return buffer[offset] << 24 | buffer[offset + 1] << 16 | buffer[offset + 2] << 8 | buffer[offset + 3];
- }
- function hasMoofData(data) {
- const end = data.byteLength;
- for (let i = 0; i < end; ) {
- const size = readUint32(data, i);
- if (size > 8 && data[i + 4] === 109 && data[i + 5] === 111 && data[i + 6] === 111 && data[i + 7] === 102) {
- return true;
- }
- i = size > 1 ? i + size : end;
- }
- return false;
- }
- function findBox(data, path) {
- const results = [];
- if (!path.length) {
- return results;
- }
- const end = data.byteLength;
- for (let i = 0; i < end; ) {
- const size = readUint32(data, i);
- const type = bin2str(data.subarray(i + 4, i + 8));
- const endbox = size > 1 ? i + size : end;
- if (type === path[0]) {
- if (path.length === 1) {
- results.push(data.subarray(i + 8, endbox));
- } else {
- const subresults = findBox(data.subarray(i + 8, endbox), path.slice(1));
- if (subresults.length) {
- push.apply(results, subresults);
- }
- }
- }
- i = endbox;
- }
- return results;
- }
- function parseSegmentIndex(sidx) {
- const references = [];
- const version2 = sidx[0];
- let index = 8;
- const timescale = readUint32(sidx, index);
- index += 4;
- let earliestPresentationTime = 0;
- let firstOffset = 0;
- if (version2 === 0) {
- earliestPresentationTime = readUint32(sidx, index);
- firstOffset = readUint32(sidx, index + 4);
- index += 8;
- } else {
- earliestPresentationTime = readUint64(sidx, index);
- firstOffset = readUint64(sidx, index + 8);
- index += 16;
- }
- index += 2;
- let startByte = sidx.length + firstOffset;
- const referencesCount = readUint16(sidx, index);
- index += 2;
- for (let i = 0; i < referencesCount; i++) {
- let referenceIndex = index;
- const referenceInfo = readUint32(sidx, referenceIndex);
- referenceIndex += 4;
- const referenceSize = referenceInfo & 2147483647;
- const referenceType = (referenceInfo & 2147483648) >>> 31;
- if (referenceType === 1) {
- logger.warn("SIDX has hierarchical references (not supported)");
- return null;
- }
- const subsegmentDuration = readUint32(sidx, referenceIndex);
- referenceIndex += 4;
- references.push({
- referenceSize,
- subsegmentDuration,
- // unscaled
- info: {
- duration: subsegmentDuration / timescale,
- start: startByte,
- end: startByte + referenceSize - 1
- }
- });
- startByte += referenceSize;
- referenceIndex += 4;
- index = referenceIndex;
- }
- return {
- earliestPresentationTime,
- timescale,
- version: version2,
- referencesCount,
- references
- };
- }
- function parseInitSegment(initSegment) {
- const result = [];
- const traks = findBox(initSegment, ["moov", "trak"]);
- for (let i = 0; i < traks.length; i++) {
- const trak = traks[i];
- const tkhd = findBox(trak, ["tkhd"])[0];
- if (tkhd) {
- let version2 = tkhd[0];
- const trackId = readUint32(tkhd, version2 === 0 ? 12 : 20);
- const mdhd = findBox(trak, ["mdia", "mdhd"])[0];
- if (mdhd) {
- version2 = mdhd[0];
- const timescale = readUint32(mdhd, version2 === 0 ? 12 : 20);
- const hdlr = findBox(trak, ["mdia", "hdlr"])[0];
- if (hdlr) {
- const hdlrType = bin2str(hdlr.subarray(8, 12));
- const type = {
- soun: ElementaryStreamTypes.AUDIO,
- vide: ElementaryStreamTypes.VIDEO
- }[hdlrType];
- const stsdBox = findBox(trak, ["mdia", "minf", "stbl", "stsd"])[0];
- const stsd = parseStsd(stsdBox);
- if (type) {
- result[trackId] = {
- timescale,
- type,
- stsd
- };
- result[type] = _objectSpread2({
- timescale,
- id: trackId
- }, stsd);
- } else {
- result[trackId] = {
- timescale,
- type: hdlrType,
- stsd
- };
- }
- }
- }
- }
- }
- const trex = findBox(initSegment, ["moov", "mvex", "trex"]);
- trex.forEach((trex2) => {
- const trackId = readUint32(trex2, 4);
- const track = result[trackId];
- if (track) {
- track.default = {
- duration: readUint32(trex2, 12),
- flags: readUint32(trex2, 20)
- };
- }
- });
- return result;
- }
- function parseStsd(stsd) {
- const sampleEntries = stsd.subarray(8);
- const sampleEntriesEnd = sampleEntries.subarray(8 + 78);
- const fourCC = bin2str(sampleEntries.subarray(4, 8));
- let codec = fourCC;
- let supplemental;
- const encrypted = fourCC === "enca" || fourCC === "encv";
- if (encrypted) {
- const encBox = findBox(sampleEntries, [fourCC])[0];
- const encBoxChildren = encBox.subarray(fourCC === "enca" ? 28 : 78);
- const sinfs = findBox(encBoxChildren, ["sinf"]);
- sinfs.forEach((sinf) => {
- const schm = findBox(sinf, ["schm"])[0];
- if (schm) {
- const scheme = bin2str(schm.subarray(4, 8));
- if (scheme === "cbcs" || scheme === "cenc") {
- const frma = findBox(sinf, ["frma"])[0];
- if (frma) {
- codec = bin2str(frma);
- }
- }
- }
- });
- }
- const codecFourCC = codec;
- switch (codec) {
- case "avc1":
- case "avc2":
- case "avc3":
- case "avc4": {
- const avcCBox = findBox(sampleEntriesEnd, ["avcC"])[0];
- if (avcCBox && avcCBox.length > 3) {
- codec += "." + toHex(avcCBox[1]) + toHex(avcCBox[2]) + toHex(avcCBox[3]);
- supplemental = parseSupplementalDoViCodec(codecFourCC === "avc1" ? "dva1" : "dvav", sampleEntriesEnd);
- }
- break;
- }
- case "mp4a": {
- const codecBox = findBox(sampleEntries, [fourCC])[0];
- const esdsBox = findBox(codecBox.subarray(28), ["esds"])[0];
- if (esdsBox && esdsBox.length > 7) {
- let i = 4;
- if (esdsBox[i++] !== 3) {
- break;
- }
- i = skipBERInteger(esdsBox, i);
- i += 2;
- const flags = esdsBox[i++];
- if (flags & 128) {
- i += 2;
- }
- if (flags & 64) {
- i += esdsBox[i++];
- }
- if (esdsBox[i++] !== 4) {
- break;
- }
- i = skipBERInteger(esdsBox, i);
- const objectType = esdsBox[i++];
- if (objectType === 64) {
- codec += "." + toHex(objectType);
- } else {
- break;
- }
- i += 12;
- if (esdsBox[i++] !== 5) {
- break;
- }
- i = skipBERInteger(esdsBox, i);
- const firstByte = esdsBox[i++];
- let audioObjectType = (firstByte & 248) >> 3;
- if (audioObjectType === 31) {
- audioObjectType += 1 + ((firstByte & 7) << 3) + ((esdsBox[i] & 224) >> 5);
- }
- codec += "." + audioObjectType;
- }
- break;
- }
- case "hvc1":
- case "hev1": {
- const hvcCBox = findBox(sampleEntriesEnd, ["hvcC"])[0];
- if (hvcCBox && hvcCBox.length > 12) {
- const profileByte = hvcCBox[1];
- const profileSpace = ["", "A", "B", "C"][profileByte >> 6];
- const generalProfileIdc = profileByte & 31;
- const profileCompat = readUint32(hvcCBox, 2);
- const tierFlag = (profileByte & 32) >> 5 ? "H" : "L";
- const levelIDC = hvcCBox[12];
- const constraintIndicator = hvcCBox.subarray(6, 12);
- codec += "." + profileSpace + generalProfileIdc;
- codec += "." + reverse32BitInt(profileCompat).toString(16).toUpperCase();
- codec += "." + tierFlag + levelIDC;
- let constraintString = "";
- for (let i = constraintIndicator.length; i--; ) {
- const byte = constraintIndicator[i];
- if (byte || constraintString) {
- const encodedByte = byte.toString(16).toUpperCase();
- constraintString = "." + encodedByte + constraintString;
- }
- }
- codec += constraintString;
- }
- supplemental = parseSupplementalDoViCodec(codecFourCC == "hev1" ? "dvhe" : "dvh1", sampleEntriesEnd);
- break;
- }
- case "dvh1":
- case "dvhe":
- case "dvav":
- case "dva1":
- case "dav1": {
- codec = parseSupplementalDoViCodec(codec, sampleEntriesEnd) || codec;
- break;
- }
- case "vp09": {
- const vpcCBox = findBox(sampleEntriesEnd, ["vpcC"])[0];
- if (vpcCBox && vpcCBox.length > 6) {
- const profile = vpcCBox[4];
- const level = vpcCBox[5];
- const bitDepth = vpcCBox[6] >> 4 & 15;
- codec += "." + addLeadingZero(profile) + "." + addLeadingZero(level) + "." + addLeadingZero(bitDepth);
- }
- break;
- }
- case "av01": {
- const av1CBox = findBox(sampleEntriesEnd, ["av1C"])[0];
- if (av1CBox && av1CBox.length > 2) {
- const profile = av1CBox[1] >>> 5;
- const level = av1CBox[1] & 31;
- const tierFlag = av1CBox[2] >>> 7 ? "H" : "M";
- const highBitDepth = (av1CBox[2] & 64) >> 6;
- const twelveBit = (av1CBox[2] & 32) >> 5;
- const bitDepth = profile === 2 && highBitDepth ? twelveBit ? 12 : 10 : highBitDepth ? 10 : 8;
- const monochrome = (av1CBox[2] & 16) >> 4;
- const chromaSubsamplingX = (av1CBox[2] & 8) >> 3;
- const chromaSubsamplingY = (av1CBox[2] & 4) >> 2;
- const chromaSamplePosition = av1CBox[2] & 3;
- const colorPrimaries = 1;
- const transferCharacteristics = 1;
- const matrixCoefficients = 1;
- const videoFullRangeFlag = 0;
- codec += "." + profile + "." + addLeadingZero(level) + tierFlag + "." + addLeadingZero(bitDepth) + "." + monochrome + "." + chromaSubsamplingX + chromaSubsamplingY + chromaSamplePosition + "." + addLeadingZero(colorPrimaries) + "." + addLeadingZero(transferCharacteristics) + "." + addLeadingZero(matrixCoefficients) + "." + videoFullRangeFlag;
- supplemental = parseSupplementalDoViCodec("dav1", sampleEntriesEnd);
- }
- break;
- }
- }
- return {
- codec,
- encrypted,
- supplemental
- };
- }
- function parseSupplementalDoViCodec(fourCC, sampleEntriesEnd) {
- const dvvCResult = findBox(sampleEntriesEnd, ["dvvC"]);
- const dvXCBox = dvvCResult.length ? dvvCResult[0] : findBox(sampleEntriesEnd, ["dvcC"])[0];
- if (dvXCBox) {
- const doViProfile = dvXCBox[2] >> 1 & 127;
- const doViLevel = dvXCBox[2] << 5 & 32 | dvXCBox[3] >> 3 & 31;
- return fourCC + "." + addLeadingZero(doViProfile) + "." + addLeadingZero(doViLevel);
- }
- }
- function reverse32BitInt(val) {
- let result = 0;
- for (let i = 0; i < 32; i++) {
- result |= (val >> i & 1) << 32 - 1 - i;
- }
- return result >>> 0;
- }
- function skipBERInteger(bytes, i) {
- const limit = i + 5;
- while (bytes[i++] & 128 && i < limit) {
- }
- return i;
- }
- function toHex(x) {
- return ("0" + x.toString(16).toUpperCase()).slice(-2);
- }
- function addLeadingZero(num) {
- return (num < 10 ? "0" : "") + num;
- }
- function patchEncyptionData(initSegment, decryptdata) {
- if (!initSegment || !decryptdata) {
- return;
- }
- const keyId = decryptdata.keyId;
- if (keyId && decryptdata.isCommonEncryption) {
- applyToTencBoxes(initSegment, (tenc, isAudio) => {
- const tencKeyId = tenc.subarray(8, 24);
- if (!tencKeyId.some((b) => b !== 0)) {
- logger.log(`[eme] Patching keyId in 'enc${isAudio ? "a" : "v"}>sinf>>tenc' box: ${arrayToHex(tencKeyId)} -> ${arrayToHex(keyId)}`);
- tenc.set(keyId, 8);
- }
- });
- }
- }
- function parseKeyIdsFromTenc(initSegment) {
- const keyIds = [];
- applyToTencBoxes(initSegment, (tenc) => keyIds.push(tenc.subarray(8, 24)));
- return keyIds;
- }
- function applyToTencBoxes(initSegment, predicate) {
- const traks = findBox(initSegment, ["moov", "trak"]);
- traks.forEach((trak) => {
- const stsd = findBox(trak, ["mdia", "minf", "stbl", "stsd"])[0];
- if (!stsd)
- return;
- const sampleEntries = stsd.subarray(8);
- let encBoxes = findBox(sampleEntries, ["enca"]);
- const isAudio = encBoxes.length > 0;
- if (!isAudio) {
- encBoxes = findBox(sampleEntries, ["encv"]);
- }
- encBoxes.forEach((enc) => {
- const encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78);
- const sinfBoxes = findBox(encBoxChildren, ["sinf"]);
- sinfBoxes.forEach((sinf) => {
- const tenc = parseSinf(sinf);
- if (tenc) {
- predicate(tenc, isAudio);
- }
- });
- });
- });
- }
- function parseSinf(sinf) {
- const schm = findBox(sinf, ["schm"])[0];
- if (schm) {
- const scheme = bin2str(schm.subarray(4, 8));
- if (scheme === "cbcs" || scheme === "cenc") {
- const tenc = findBox(sinf, ["schi", "tenc"])[0];
- if (tenc) {
- return tenc;
- }
- }
- }
- }
- function getSampleData(data, initData, logger2) {
- const tracks = {};
- const trafs = findBox(data, ["moof", "traf"]);
- for (let i = 0; i < trafs.length; i++) {
- const traf = trafs[i];
- const tfhd = findBox(traf, ["tfhd"])[0];
- const id = readUint32(tfhd, 4);
- const track = initData[id];
- if (!track) {
- continue;
- }
- tracks[id] || (tracks[id] = {
- start: NaN,
- duration: 0,
- sampleCount: 0,
- timescale: track.timescale,
- type: track.type
- });
- const trackTimes = tracks[id];
- const tfdt = findBox(traf, ["tfdt"])[0];
- if (tfdt) {
- const version2 = tfdt[0];
- let baseTime = readUint32(tfdt, 4);
- if (version2 === 1) {
- if (baseTime === UINT32_MAX$1) {
- logger2.warn(`[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time`);
- } else {
- baseTime *= UINT32_MAX$1 + 1;
- baseTime += readUint32(tfdt, 8);
- }
- }
- if (isFiniteNumber(baseTime) && (!isFiniteNumber(trackTimes.start) || baseTime < trackTimes.start)) {
- trackTimes.start = baseTime;
- }
- }
- const trackDefault = track.default;
- const tfhdFlags = readUint32(tfhd, 0) | (trackDefault == null ? void 0 : trackDefault.flags);
- let defaultSampleDuration = (trackDefault == null ? void 0 : trackDefault.duration) || 0;
- if (tfhdFlags & 8) {
- if (tfhdFlags & 2) {
- defaultSampleDuration = readUint32(tfhd, 12);
- } else {
- defaultSampleDuration = readUint32(tfhd, 8);
- }
- }
- const truns = findBox(traf, ["trun"]);
- let sampleDTS = trackTimes.start || 0;
- let rawDuration = 0;
- let sampleDuration = defaultSampleDuration;
- for (let j = 0; j < truns.length; j++) {
- const trun = truns[j];
- const sampleCount = readUint32(trun, 4);
- const sampleIndex = trackTimes.sampleCount;
- trackTimes.sampleCount += sampleCount;
- const dataOffsetPresent = trun[3] & 1;
- const firstSampleFlagsPresent = trun[3] & 4;
- const sampleDurationPresent = trun[2] & 1;
- const sampleSizePresent = trun[2] & 2;
- const sampleFlagsPresent = trun[2] & 4;
- const sampleCompositionTimeOffsetPresent = trun[2] & 8;
- let offset = 8;
- let remaining = sampleCount;
- if (dataOffsetPresent) {
- offset += 4;
- }
- if (firstSampleFlagsPresent && sampleCount) {
- const isNonSyncSample = trun[offset + 1] & 1;
- if (!isNonSyncSample && trackTimes.keyFrameIndex === void 0) {
- trackTimes.keyFrameIndex = sampleIndex;
- }
- offset += 4;
- if (sampleDurationPresent) {
- sampleDuration = readUint32(trun, offset);
- offset += 4;
- } else {
- sampleDuration = defaultSampleDuration;
- }
- if (sampleSizePresent) {
- offset += 4;
- }
- if (sampleCompositionTimeOffsetPresent) {
- offset += 4;
- }
- sampleDTS += sampleDuration;
- rawDuration += sampleDuration;
- remaining--;
- }
- while (remaining--) {
- if (sampleDurationPresent) {
- sampleDuration = readUint32(trun, offset);
- offset += 4;
- } else {
- sampleDuration = defaultSampleDuration;
- }
- if (sampleSizePresent) {
- offset += 4;
- }
- if (sampleFlagsPresent) {
- const isNonSyncSample = trun[offset + 1] & 1;
- if (!isNonSyncSample) {
- if (trackTimes.keyFrameIndex === void 0) {
- trackTimes.keyFrameIndex = trackTimes.sampleCount - (remaining + 1);
- trackTimes.keyFrameStart = sampleDTS;
- }
- }
- offset += 4;
- }
- if (sampleCompositionTimeOffsetPresent) {
- offset += 4;
- }
- sampleDTS += sampleDuration;
- rawDuration += sampleDuration;
- }
- if (!rawDuration && defaultSampleDuration) {
- rawDuration += defaultSampleDuration * sampleCount;
- }
- }
- trackTimes.duration += rawDuration;
- }
- if (!Object.keys(tracks).some((trackId) => tracks[trackId].duration)) {
- let sidxMinStart = Infinity;
- let sidxMaxEnd = 0;
- const sidxs = findBox(data, ["sidx"]);
- for (let i = 0; i < sidxs.length; i++) {
- const sidx = parseSegmentIndex(sidxs[i]);
- if (sidx != null && sidx.references) {
- sidxMinStart = Math.min(sidxMinStart, sidx.earliestPresentationTime / sidx.timescale);
- const subSegmentDuration = sidx.references.reduce((dur, ref) => dur + ref.info.duration || 0, 0);
- sidxMaxEnd = Math.max(sidxMaxEnd, subSegmentDuration + sidx.earliestPresentationTime / sidx.timescale);
- }
- }
- if (sidxMaxEnd && isFiniteNumber(sidxMaxEnd)) {
- Object.keys(tracks).forEach((trackId) => {
- if (!tracks[trackId].duration) {
- tracks[trackId].duration = sidxMaxEnd * tracks[trackId].timescale - tracks[trackId].start;
- }
- });
- }
- }
- return tracks;
- }
- function segmentValidRange(data) {
- const segmentedRange = {
- valid: null,
- remainder: null
- };
- const moofs = findBox(data, ["moof"]);
- if (moofs.length < 2) {
- segmentedRange.remainder = data;
- return segmentedRange;
- }
- const last = moofs[moofs.length - 1];
- segmentedRange.valid = data.slice(0, last.byteOffset - 8);
- segmentedRange.remainder = data.slice(last.byteOffset - 8);
- return segmentedRange;
- }
- function appendUint8Array(data1, data2) {
- const temp = new Uint8Array(data1.length + data2.length);
- temp.set(data1);
- temp.set(data2, data1.length);
- return temp;
- }
- function parseSamples(timeOffset, track) {
- const seiSamples = [];
- const videoData = track.samples;
- const timescale = track.timescale;
- const trackId = track.id;
- let isHEVCFlavor = false;
- const moofs = findBox(videoData, ["moof"]);
- moofs.map((moof) => {
- const moofOffset = moof.byteOffset - 8;
- const trafs = findBox(moof, ["traf"]);
- trafs.map((traf) => {
- const baseTime = findBox(traf, ["tfdt"]).map((tfdt) => {
- const version2 = tfdt[0];
- let result = readUint32(tfdt, 4);
- if (version2 === 1) {
- result *= Math.pow(2, 32);
- result += readUint32(tfdt, 8);
- }
- return result / timescale;
- })[0];
- if (baseTime !== void 0) {
- timeOffset = baseTime;
- }
- return findBox(traf, ["tfhd"]).map((tfhd) => {
- const id = readUint32(tfhd, 4);
- const tfhdFlags = readUint32(tfhd, 0) & 16777215;
- const baseDataOffsetPresent = (tfhdFlags & 1) !== 0;
- const sampleDescriptionIndexPresent = (tfhdFlags & 2) !== 0;
- const defaultSampleDurationPresent = (tfhdFlags & 8) !== 0;
- let defaultSampleDuration = 0;
- const defaultSampleSizePresent = (tfhdFlags & 16) !== 0;
- let defaultSampleSize = 0;
- const defaultSampleFlagsPresent = (tfhdFlags & 32) !== 0;
- let tfhdOffset = 8;
- if (id === trackId) {
- if (baseDataOffsetPresent) {
- tfhdOffset += 8;
- }
- if (sampleDescriptionIndexPresent) {
- tfhdOffset += 4;
- }
- if (defaultSampleDurationPresent) {
- defaultSampleDuration = readUint32(tfhd, tfhdOffset);
- tfhdOffset += 4;
- }
- if (defaultSampleSizePresent) {
- defaultSampleSize = readUint32(tfhd, tfhdOffset);
- tfhdOffset += 4;
- }
- if (defaultSampleFlagsPresent) {
- tfhdOffset += 4;
- }
- if (track.type === "video") {
- isHEVCFlavor = isHEVC(track.codec);
- }
- findBox(traf, ["trun"]).map((trun) => {
- const version2 = trun[0];
- const flags = readUint32(trun, 0) & 16777215;
- const dataOffsetPresent = (flags & 1) !== 0;
- let dataOffset = 0;
- const firstSampleFlagsPresent = (flags & 4) !== 0;
- const sampleDurationPresent = (flags & 256) !== 0;
- let sampleDuration = 0;
- const sampleSizePresent = (flags & 512) !== 0;
- let sampleSize = 0;
- const sampleFlagsPresent = (flags & 1024) !== 0;
- const sampleCompositionOffsetsPresent = (flags & 2048) !== 0;
- let compositionOffset = 0;
- const sampleCount = readUint32(trun, 4);
- let trunOffset = 8;
- if (dataOffsetPresent) {
- dataOffset = readUint32(trun, trunOffset);
- trunOffset += 4;
- }
- if (firstSampleFlagsPresent) {
- trunOffset += 4;
- }
- let sampleOffset = dataOffset + moofOffset;
- for (let ix = 0; ix < sampleCount; ix++) {
- if (sampleDurationPresent) {
- sampleDuration = readUint32(trun, trunOffset);
- trunOffset += 4;
- } else {
- sampleDuration = defaultSampleDuration;
- }
- if (sampleSizePresent) {
- sampleSize = readUint32(trun, trunOffset);
- trunOffset += 4;
- } else {
- sampleSize = defaultSampleSize;
- }
- if (sampleFlagsPresent) {
- trunOffset += 4;
- }
- if (sampleCompositionOffsetsPresent) {
- if (version2 === 0) {
- compositionOffset = readUint32(trun, trunOffset);
- } else {
- compositionOffset = readSint32(trun, trunOffset);
- }
- trunOffset += 4;
- }
- if (track.type === ElementaryStreamTypes.VIDEO) {
- let naluTotalSize = 0;
- while (naluTotalSize < sampleSize) {
- const naluSize = readUint32(videoData, sampleOffset);
- sampleOffset += 4;
- if (isSEIMessage(isHEVCFlavor, videoData[sampleOffset])) {
- const data = videoData.subarray(sampleOffset, sampleOffset + naluSize);
- parseSEIMessageFromNALu(data, isHEVCFlavor ? 2 : 1, timeOffset + compositionOffset / timescale, seiSamples);
- }
- sampleOffset += naluSize;
- naluTotalSize += naluSize + 4;
- }
- }
- timeOffset += sampleDuration / timescale;
- }
- });
- }
- });
- });
- });
- return seiSamples;
- }
- function isHEVC(codec) {
- if (!codec) {
- return false;
- }
- const baseCodec = codec.substring(0, 4);
- return baseCodec === "hvc1" || baseCodec === "hev1" || // Dolby Vision
- baseCodec === "dvh1" || baseCodec === "dvhe";
- }
- function isSEIMessage(isHEVCFlavor, naluHeader) {
- if (isHEVCFlavor) {
- const naluType = naluHeader >> 1 & 63;
- return naluType === 39 || naluType === 40;
- } else {
- const naluType = naluHeader & 31;
- return naluType === 6;
- }
- }
- function parseSEIMessageFromNALu(unescapedData, headerSize, pts, samples) {
- const data = discardEPB(unescapedData);
- let seiPtr = 0;
- seiPtr += headerSize;
- let payloadType = 0;
- let payloadSize = 0;
- let b = 0;
- while (seiPtr < data.length) {
- payloadType = 0;
- do {
- if (seiPtr >= data.length) {
- break;
- }
- b = data[seiPtr++];
- payloadType += b;
- } while (b === 255);
- payloadSize = 0;
- do {
- if (seiPtr >= data.length) {
- break;
- }
- b = data[seiPtr++];
- payloadSize += b;
- } while (b === 255);
- const leftOver = data.length - seiPtr;
- let payPtr = seiPtr;
- if (payloadSize < leftOver) {
- seiPtr += payloadSize;
- } else if (payloadSize > leftOver) {
- logger.error(`Malformed SEI payload. ${payloadSize} is too small, only ${leftOver} bytes left to parse.`);
- break;
- }
- if (payloadType === 4) {
- const countryCode = data[payPtr++];
- if (countryCode === 181) {
- const providerCode = readUint16(data, payPtr);
- payPtr += 2;
- if (providerCode === 49) {
- const userStructure = readUint32(data, payPtr);
- payPtr += 4;
- if (userStructure === 1195456820) {
- const userDataType = data[payPtr++];
- if (userDataType === 3) {
- const firstByte = data[payPtr++];
- const totalCCs = 31 & firstByte;
- const enabled = 64 & firstByte;
- const totalBytes = enabled ? 2 + totalCCs * 3 : 0;
- const byteArray = new Uint8Array(totalBytes);
- if (enabled) {
- byteArray[0] = firstByte;
- for (let i = 1; i < totalBytes; i++) {
- byteArray[i] = data[payPtr++];
- }
- }
- samples.push({
- type: userDataType,
- payloadType,
- pts,
- bytes: byteArray
- });
- }
- }
- }
- }
- } else if (payloadType === 5) {
- if (payloadSize > 16) {
- const uuidStrArray = [];
- for (let i = 0; i < 16; i++) {
- const _b = data[payPtr++].toString(16);
- uuidStrArray.push(_b.length == 1 ? "0" + _b : _b);
- if (i === 3 || i === 5 || i === 7 || i === 9) {
- uuidStrArray.push("-");
- }
- }
- const length = payloadSize - 16;
- const userDataBytes = new Uint8Array(length);
- for (let i = 0; i < length; i++) {
- userDataBytes[i] = data[payPtr++];
- }
- samples.push({
- payloadType,
- pts,
- uuid: uuidStrArray.join(""),
- userData: utf8ArrayToStr(userDataBytes),
- userDataBytes
- });
- }
- }
- }
- }
- function discardEPB(data) {
- const length = data.byteLength;
- const EPBPositions = [];
- let i = 1;
- while (i < length - 2) {
- if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 3) {
- EPBPositions.push(i + 2);
- i += 2;
- } else {
- i++;
- }
- }
- if (EPBPositions.length === 0) {
- return data;
- }
- const newLength = length - EPBPositions.length;
- const newData = new Uint8Array(newLength);
- let sourceIndex = 0;
- for (i = 0; i < newLength; sourceIndex++, i++) {
- if (sourceIndex === EPBPositions[0]) {
- sourceIndex++;
- EPBPositions.shift();
- }
- newData[i] = data[sourceIndex];
- }
- return newData;
- }
- function parseEmsg(data) {
- const version2 = data[0];
- let schemeIdUri = "";
- let value = "";
- let timeScale = 0;
- let presentationTimeDelta = 0;
- let presentationTime = 0;
- let eventDuration = 0;
- let id = 0;
- let offset = 0;
- if (version2 === 0) {
- while (bin2str(data.subarray(offset, offset + 1)) !== "\0") {
- schemeIdUri += bin2str(data.subarray(offset, offset + 1));
- offset += 1;
- }
- schemeIdUri += bin2str(data.subarray(offset, offset + 1));
- offset += 1;
- while (bin2str(data.subarray(offset, offset + 1)) !== "\0") {
- value += bin2str(data.subarray(offset, offset + 1));
- offset += 1;
- }
- value += bin2str(data.subarray(offset, offset + 1));
- offset += 1;
- timeScale = readUint32(data, 12);
- presentationTimeDelta = readUint32(data, 16);
- eventDuration = readUint32(data, 20);
- id = readUint32(data, 24);
- offset = 28;
- } else if (version2 === 1) {
- offset += 4;
- timeScale = readUint32(data, offset);
- offset += 4;
- const leftPresentationTime = readUint32(data, offset);
- offset += 4;
- const rightPresentationTime = readUint32(data, offset);
- offset += 4;
- presentationTime = 2 ** 32 * leftPresentationTime + rightPresentationTime;
- if (!isSafeInteger(presentationTime)) {
- presentationTime = Number.MAX_SAFE_INTEGER;
- logger.warn("Presentation time exceeds safe integer limit and wrapped to max safe integer in parsing emsg box");
- }
- eventDuration = readUint32(data, offset);
- offset += 4;
- id = readUint32(data, offset);
- offset += 4;
- while (bin2str(data.subarray(offset, offset + 1)) !== "\0") {
- schemeIdUri += bin2str(data.subarray(offset, offset + 1));
- offset += 1;
- }
- schemeIdUri += bin2str(data.subarray(offset, offset + 1));
- offset += 1;
- while (bin2str(data.subarray(offset, offset + 1)) !== "\0") {
- value += bin2str(data.subarray(offset, offset + 1));
- offset += 1;
- }
- value += bin2str(data.subarray(offset, offset + 1));
- offset += 1;
- }
- const payload = data.subarray(offset, data.byteLength);
- return {
- schemeIdUri,
- value,
- timeScale,
- presentationTime,
- presentationTimeDelta,
- eventDuration,
- id,
- payload
- };
- }
- function mp4Box(type, ...payload) {
- const len = payload.length;
- let size = 8;
- let i = len;
- while (i--) {
- size += payload[i].byteLength;
- }
- const result = new Uint8Array(size);
- result[0] = size >> 24 & 255;
- result[1] = size >> 16 & 255;
- result[2] = size >> 8 & 255;
- result[3] = size & 255;
- result.set(type, 4);
- for (i = 0, size = 8; i < len; i++) {
- result.set(payload[i], size);
- size += payload[i].byteLength;
- }
- return result;
- }
- function mp4pssh(systemId, keyids, data) {
- if (systemId.byteLength !== 16) {
- throw new RangeError("Invalid system id");
- }
- let version2;
- let kids;
- {
- version2 = 0;
- kids = new Uint8Array();
- }
- let kidCount;
- if (version2 > 0) {
- kidCount = new Uint8Array(4);
- if (keyids.length > 0) {
- new DataView(kidCount.buffer).setUint32(0, keyids.length, false);
- }
- } else {
- kidCount = new Uint8Array();
- }
- const dataSize = new Uint8Array(4);
- if (data.byteLength > 0) {
- new DataView(dataSize.buffer).setUint32(0, data.byteLength, false);
- }
- return mp4Box(
- [112, 115, 115, 104],
- new Uint8Array([
- version2,
- 0,
- 0,
- 0
- // Flags
- ]),
- systemId,
- // 16 bytes
- kidCount,
- kids,
- dataSize,
- data
- );
- }
- function parseMultiPssh(initData) {
- const results = [];
- if (initData instanceof ArrayBuffer) {
- const length = initData.byteLength;
- let offset = 0;
- while (offset + 32 < length) {
- const view2 = new DataView(initData, offset);
- const pssh = parsePssh(view2);
- results.push(pssh);
- offset += pssh.size;
- }
- }
- return results;
- }
- function parsePssh(view2) {
- const size = view2.getUint32(0);
- const offset = view2.byteOffset;
- const length = view2.byteLength;
- if (length < size) {
- return {
- offset,
- size: length
- };
- }
- const type = view2.getUint32(4);
- if (type !== 1886614376) {
- return {
- offset,
- size
- };
- }
- const version2 = view2.getUint32(8) >>> 24;
- if (version2 !== 0 && version2 !== 1) {
- return {
- offset,
- size
- };
- }
- const buffer = view2.buffer;
- const systemId = arrayToHex(new Uint8Array(buffer, offset + 12, 16));
- let kids = null;
- let data = null;
- let dataSizeOffset = 0;
- if (version2 === 0) {
- dataSizeOffset = 28;
- } else {
- const kidCounts = view2.getUint32(28);
- if (!kidCounts || length < 32 + kidCounts * 16) {
- return {
- offset,
- size
- };
- }
- kids = [];
- for (let i = 0; i < kidCounts; i++) {
- kids.push(new Uint8Array(buffer, offset + 32 + i * 16, 16));
- }
- dataSizeOffset = 32 + kidCounts * 16;
- }
- if (!dataSizeOffset) {
- return {
- offset,
- size
- };
- }
- const dataSizeOrKidCount = view2.getUint32(dataSizeOffset);
- if (size - 32 < dataSizeOrKidCount) {
- return {
- offset,
- size
- };
- }
- data = new Uint8Array(buffer, offset + dataSizeOffset + 4, dataSizeOrKidCount);
- return {
- version: version2,
- systemId,
- kids,
- data,
- offset,
- size
- };
- }
- var userAgentHevcSupportIsInaccurate = () => {
- return /\(Windows.+Firefox\//i.test(navigator.userAgent);
- };
- var sampleEntryCodesISO = {
- audio: {
- a3ds: 1,
- "ac-3": 0.95,
- "ac-4": 1,
- alac: 0.9,
- alaw: 1,
- dra1: 1,
- "dts+": 1,
- "dts-": 1,
- dtsc: 1,
- dtse: 1,
- dtsh: 1,
- "ec-3": 0.9,
- enca: 1,
- fLaC: 0.9,
- // MP4-RA listed codec entry for FLAC
- flac: 0.9,
- // legacy browser codec name for FLAC
- FLAC: 0.9,
- // some manifests may list "FLAC" with Apple's tools
- g719: 1,
- g726: 1,
- m4ae: 1,
- mha1: 1,
- mha2: 1,
- mhm1: 1,
- mhm2: 1,
- mlpa: 1,
- mp4a: 1,
- "raw ": 1,
- Opus: 1,
- opus: 1,
- // browsers expect this to be lowercase despite MP4RA says 'Opus'
- samr: 1,
- sawb: 1,
- sawp: 1,
- sevc: 1,
- sqcp: 1,
- ssmv: 1,
- twos: 1,
- ulaw: 1
- },
- video: {
- avc1: 1,
- avc2: 1,
- avc3: 1,
- avc4: 1,
- avcp: 1,
- av01: 0.8,
- dav1: 0.8,
- drac: 1,
- dva1: 1,
- dvav: 1,
- dvh1: 0.7,
- dvhe: 0.7,
- encv: 1,
- hev1: 0.75,
- hvc1: 0.75,
- mjp2: 1,
- mp4v: 1,
- mvc1: 1,
- mvc2: 1,
- mvc3: 1,
- mvc4: 1,
- resv: 1,
- rv60: 1,
- s263: 1,
- svc1: 1,
- svc2: 1,
- "vc-1": 1,
- vp08: 1,
- vp09: 0.9
- },
- text: {
- stpp: 1,
- wvtt: 1
- }
- };
- function isCodecType(codec, type) {
- const typeCodes = sampleEntryCodesISO[type];
- return !!typeCodes && !!typeCodes[codec.slice(0, 4)];
- }
- function areCodecsMediaSourceSupported(codecs, type, preferManagedMediaSource = true) {
- return !codecs.split(",").some((codec) => !isCodecMediaSourceSupported(codec, type, preferManagedMediaSource));
- }
- function isCodecMediaSourceSupported(codec, type, preferManagedMediaSource = true) {
- var _MediaSource$isTypeSu;
- const MediaSource = getMediaSource(preferManagedMediaSource);
- return (_MediaSource$isTypeSu = MediaSource == null ? void 0 : MediaSource.isTypeSupported(mimeTypeForCodec(codec, type))) != null ? _MediaSource$isTypeSu : false;
- }
- function mimeTypeForCodec(codec, type) {
- return `${type}/mp4;codecs=${codec}`;
- }
- function videoCodecPreferenceValue(videoCodec) {
- if (videoCodec) {
- const fourCC = videoCodec.substring(0, 4);
- return sampleEntryCodesISO.video[fourCC];
- }
- return 2;
- }
- function codecsSetSelectionPreferenceValue(codecSet) {
- const limitedHevcSupport = userAgentHevcSupportIsInaccurate();
- return codecSet.split(",").reduce((num, fourCC) => {
- const lowerPriority = limitedHevcSupport && isHEVC(fourCC);
- const preferenceValue = lowerPriority ? 9 : sampleEntryCodesISO.video[fourCC];
- if (preferenceValue) {
- return (preferenceValue * 2 + num) / (num ? 3 : 2);
- }
- return (sampleEntryCodesISO.audio[fourCC] + num) / (num ? 2 : 1);
- }, 0);
- }
- var CODEC_COMPATIBLE_NAMES = {};
- function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource = true) {
- if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
- return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
- }
- const codecsToCheck = {
- // Idealy fLaC and Opus would be first (spec-compliant) but
- // some browsers will report that fLaC is supported then fail.
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
- flac: ["flac", "fLaC", "FLAC"],
- opus: ["opus", "Opus"],
- // Replace audio codec info if browser does not support mp4a.40.34,
- // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
- "mp4a.40.34": ["mp3"]
- }[lowerCaseCodec];
- for (let i = 0; i < codecsToCheck.length; i++) {
- var _getMediaSource;
- if (isCodecMediaSourceSupported(codecsToCheck[i], "audio", preferManagedMediaSource)) {
- CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
- return codecsToCheck[i];
- } else if (codecsToCheck[i] === "mp3" && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported("audio/mpeg")) {
- return "";
- }
- }
- return lowerCaseCodec;
- }
- var AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
- function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
- return codec.replace(AUDIO_CODEC_REGEXP, (m) => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
- }
- function replaceVideoCodec(originalCodecs, newVideoCodec) {
- const codecs = [];
- if (originalCodecs) {
- const allCodecs = originalCodecs.split(",");
- for (let i = 0; i < allCodecs.length; i++) {
- if (!isCodecType(allCodecs[i], "video")) {
- codecs.push(allCodecs[i]);
- }
- }
- }
- if (newVideoCodec) {
- codecs.push(newVideoCodec);
- }
- return codecs.join(",");
- }
- function pickMostCompleteCodecName(parsedCodec, levelCodec) {
- if (parsedCodec && (parsedCodec.length > 4 || ["ac-3", "ec-3", "alac", "fLaC", "Opus"].indexOf(parsedCodec) !== -1)) {
- if (isCodecSupportedAsType(parsedCodec, "audio") || isCodecSupportedAsType(parsedCodec, "video")) {
- return parsedCodec;
- }
- }
- if (levelCodec) {
- const levelCodecs = levelCodec.split(",");
- if (levelCodecs.length > 1) {
- if (parsedCodec) {
- for (let i = levelCodecs.length; i--; ) {
- if (levelCodecs[i].substring(0, 4) === parsedCodec.substring(0, 4)) {
- return levelCodecs[i];
- }
- }
- }
- return levelCodecs[0];
- }
- }
- return levelCodec || parsedCodec;
- }
- function isCodecSupportedAsType(codec, type) {
- return isCodecType(codec, type) && isCodecMediaSourceSupported(codec, type);
- }
- function convertAVC1ToAVCOTI(videoCodecs) {
- const codecs = videoCodecs.split(",");
- for (let i = 0; i < codecs.length; i++) {
- const avcdata = codecs[i].split(".");
- if (avcdata.length > 2 && avcdata[0] === "avc1") {
- codecs[i] = `avc1.${parseInt(avcdata[1]).toString(16)}${("000" + parseInt(avcdata[2]).toString(16)).slice(-4)}`;
- }
- }
- return codecs.join(",");
- }
- function fillInMissingAV01Params(videoCodec) {
- if (videoCodec.startsWith("av01.")) {
- const av1params = videoCodec.split(".");
- const placeholders = ["0", "111", "01", "01", "01", "0"];
- for (let i = av1params.length; i > 4 && i < 10; i++) {
- av1params[i] = placeholders[i - 4];
- }
- return av1params.join(".");
- }
- return videoCodec;
- }
- function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
- const MediaSource = getMediaSource(preferManagedMediaSource) || {
- isTypeSupported: () => false
- };
- return {
- mpeg: MediaSource.isTypeSupported("audio/mpeg"),
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
- ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
- };
- }
- function getCodecsForMimeType(mimeType) {
- return mimeType.replace(/^.+codecs=["']?([^"']+).*$/, "$1");
- }
- var supportedResult = {
- supported: true,
- powerEfficient: true,
- smooth: true
- // keySystemAccess: null,
- };
- var unsupportedResult = {
- supported: false,
- smooth: false,
- powerEfficient: false
- // keySystemAccess: null,
- };
- var SUPPORTED_INFO_DEFAULT = {
- supported: true,
- configurations: [],
- decodingInfoResults: [supportedResult]
- };
- function getUnsupportedResult(error, configurations) {
- return {
- supported: false,
- configurations,
- decodingInfoResults: [unsupportedResult],
- error
- };
- }
- function requiresMediaCapabilitiesDecodingInfo(level, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference) {
- const videoCodecs = level.videoCodec;
- const audioGroups = level.audioCodec ? level.audioGroups : null;
- const audioCodecPreference = audioPreference == null ? void 0 : audioPreference.audioCodec;
- const channelsPreference = audioPreference == null ? void 0 : audioPreference.channels;
- const maxChannels = channelsPreference ? parseInt(channelsPreference) : audioCodecPreference ? Infinity : 2;
- let audioChannels = null;
- if (audioGroups != null && audioGroups.length) {
- try {
- if (audioGroups.length === 1 && audioGroups[0]) {
- audioChannels = audioTracksByGroup.groups[audioGroups[0]].channels;
- } else {
- audioChannels = audioGroups.reduce((acc, groupId) => {
- if (groupId) {
- const audioTrackGroup = audioTracksByGroup.groups[groupId];
- if (!audioTrackGroup) {
- throw new Error(`Audio track group ${groupId} not found`);
- }
- Object.keys(audioTrackGroup.channels).forEach((key) => {
- acc[key] = (acc[key] || 0) + audioTrackGroup.channels[key];
- });
- }
- return acc;
- }, {
- 2: 0
- });
- }
- } catch (error) {
- return true;
- }
- }
- return videoCodecs !== void 0 && // Force media capabilities check for HEVC to avoid failure on Windows
- (videoCodecs.split(",").some((videoCodec) => isHEVC(videoCodec)) || level.width > 1920 && level.height > 1088 || level.height > 1920 && level.width > 1088 || level.frameRate > Math.max(currentFrameRate, 30) || level.videoRange !== "SDR" && level.videoRange !== currentVideoRange || level.bitrate > Math.max(currentBw, 8e6)) || !!audioChannels && isFiniteNumber(maxChannels) && Object.keys(audioChannels).some((channels) => parseInt(channels) > maxChannels);
- }
- function getMediaDecodingInfoPromise(level, audioTracksByGroup, mediaCapabilities, cache = {}) {
- const videoCodecs = level.videoCodec;
- if (!videoCodecs && !level.audioCodec || !mediaCapabilities) {
- return Promise.resolve(SUPPORTED_INFO_DEFAULT);
- }
- const configurations = [];
- const videoDecodeList = makeVideoConfigurations(level);
- const videoCount = videoDecodeList.length;
- const audioDecodeList = makeAudioConfigurations(level, audioTracksByGroup, videoCount > 0);
- const audioCount = audioDecodeList.length;
- for (let i = videoCount || 1 * audioCount || 1; i--; ) {
- const configuration = {
- type: "media-source"
- };
- if (videoCount) {
- configuration.video = videoDecodeList[i % videoCount];
- }
- if (audioCount) {
- configuration.audio = audioDecodeList[i % audioCount];
- const audioBitrate = configuration.audio.bitrate;
- if (configuration.video && audioBitrate) {
- configuration.video.bitrate -= audioBitrate;
- }
- }
- configurations.push(configuration);
- }
- if (videoCodecs) {
- const ua = navigator.userAgent;
- if (videoCodecs.split(",").some((videoCodec) => isHEVC(videoCodec)) && userAgentHevcSupportIsInaccurate()) {
- return Promise.resolve(getUnsupportedResult(new Error(`Overriding Windows Firefox HEVC MediaCapabilities result based on user-agent string: (${ua})`), configurations));
- }
- }
- return Promise.all(configurations.map((configuration) => {
- const decodingInfoKey = getMediaDecodingInfoKey(configuration);
- return cache[decodingInfoKey] || (cache[decodingInfoKey] = mediaCapabilities.decodingInfo(configuration));
- })).then((decodingInfoResults) => ({
- supported: !decodingInfoResults.some((info) => !info.supported),
- configurations,
- decodingInfoResults
- })).catch((error) => ({
- supported: false,
- configurations,
- decodingInfoResults: [],
- error
- }));
- }
- function makeVideoConfigurations(level) {
- var _level$videoCodec;
- const videoCodecs = (_level$videoCodec = level.videoCodec) == null ? void 0 : _level$videoCodec.split(",");
- const bitrate = getVariantDecodingBitrate(level);
- const width = level.width || 640;
- const height = level.height || 480;
- const framerate = level.frameRate || 30;
- const videoRange = level.videoRange.toLowerCase();
- return videoCodecs ? videoCodecs.map((videoCodec) => {
- const videoConfiguration = {
- contentType: mimeTypeForCodec(fillInMissingAV01Params(videoCodec), "video"),
- width,
- height,
- bitrate,
- framerate
- };
- if (videoRange !== "sdr") {
- videoConfiguration.transferFunction = videoRange;
- }
- return videoConfiguration;
- }) : [];
- }
- function makeAudioConfigurations(level, audioTracksByGroup, hasVideo) {
- var _level$audioCodec;
- const audioCodecs = (_level$audioCodec = level.audioCodec) == null ? void 0 : _level$audioCodec.split(",");
- const combinedBitrate = getVariantDecodingBitrate(level);
- if (audioCodecs && level.audioGroups) {
- return level.audioGroups.reduce((configurations, audioGroupId) => {
- var _audioTracksByGroup$g;
- const tracks = audioGroupId ? (_audioTracksByGroup$g = audioTracksByGroup.groups[audioGroupId]) == null ? void 0 : _audioTracksByGroup$g.tracks : null;
- if (tracks) {
- return tracks.reduce((configs, audioTrack) => {
- if (audioTrack.groupId === audioGroupId) {
- const channelsNumber = parseFloat(audioTrack.channels || "");
- audioCodecs.forEach((audioCodec) => {
- const audioConfiguration = {
- contentType: mimeTypeForCodec(audioCodec, "audio"),
- bitrate: hasVideo ? estimatedAudioBitrate(audioCodec, combinedBitrate) : combinedBitrate
- };
- if (channelsNumber) {
- audioConfiguration.channels = "" + channelsNumber;
- }
- configs.push(audioConfiguration);
- });
- }
- return configs;
- }, configurations);
- }
- return configurations;
- }, []);
- }
- return [];
- }
- function estimatedAudioBitrate(audioCodec, levelBitrate) {
- if (levelBitrate <= 1) {
- return 1;
- }
- let audioBitrate = 128e3;
- if (audioCodec === "ec-3") {
- audioBitrate = 768e3;
- } else if (audioCodec === "ac-3") {
- audioBitrate = 64e4;
- }
- return Math.min(levelBitrate / 2, audioBitrate);
- }
- function getVariantDecodingBitrate(level) {
- return Math.ceil(Math.max(level.bitrate * 0.9, level.averageBitrate) / 1e3) * 1e3 || 1;
- }
- function getMediaDecodingInfoKey(config) {
- let key = "";
- const {
- audio,
- video
- } = config;
- if (video) {
- const codec = getCodecsForMimeType(video.contentType);
- key += `${codec}_r${video.height}x${video.width}f${Math.ceil(video.framerate)}${video.transferFunction || "sd"}_${Math.ceil(video.bitrate / 1e5)}`;
- }
- if (audio) {
- const codec = getCodecsForMimeType(audio.contentType);
- key += `${video ? "_" : ""}${codec}_c${audio.channels}`;
- }
- return key;
- }
- var HdcpLevels = ["NONE", "TYPE-0", "TYPE-1", null];
- function isHdcpLevel(value) {
- return HdcpLevels.indexOf(value) > -1;
- }
- var VideoRangeValues = ["SDR", "PQ", "HLG"];
- function isVideoRange(value) {
- return !!value && VideoRangeValues.indexOf(value) > -1;
- }
- var HlsSkip = {
- No: "",
- Yes: "YES",
- v2: "v2"
- };
- function getSkipValue(details) {
- const {
- canSkipUntil,
- canSkipDateRanges,
- age
- } = details;
- const playlistRecentEnough = age < canSkipUntil / 2;
- if (canSkipUntil && playlistRecentEnough) {
- if (canSkipDateRanges) {
- return HlsSkip.v2;
- }
- return HlsSkip.Yes;
- }
- return HlsSkip.No;
- }
- var HlsUrlParameters = class {
- constructor(msn, part, skip) {
- this.msn = void 0;
- this.part = void 0;
- this.skip = void 0;
- this.msn = msn;
- this.part = part;
- this.skip = skip;
- }
- addDirectives(uri) {
- const url = new self.URL(uri);
- if (this.msn !== void 0) {
- url.searchParams.set("_HLS_msn", this.msn.toString());
- }
- if (this.part !== void 0) {
- url.searchParams.set("_HLS_part", this.part.toString());
- }
- if (this.skip) {
- url.searchParams.set("_HLS_skip", this.skip);
- }
- return url.href;
- }
- };
- var Level = class {
- constructor(data) {
- this._attrs = void 0;
- this.audioCodec = void 0;
- this.bitrate = void 0;
- this.codecSet = void 0;
- this.url = void 0;
- this.frameRate = void 0;
- this.height = void 0;
- this.id = void 0;
- this.name = void 0;
- this.supplemental = void 0;
- this.videoCodec = void 0;
- this.width = void 0;
- this.details = void 0;
- this.fragmentError = 0;
- this.loadError = 0;
- this.loaded = void 0;
- this.realBitrate = 0;
- this.supportedPromise = void 0;
- this.supportedResult = void 0;
- this._avgBitrate = 0;
- this._audioGroups = void 0;
- this._subtitleGroups = void 0;
- this._urlId = 0;
- this.url = [data.url];
- this._attrs = [data.attrs];
- this.bitrate = data.bitrate;
- if (data.details) {
- this.details = data.details;
- }
- this.id = data.id || 0;
- this.name = data.name;
- this.width = data.width || 0;
- this.height = data.height || 0;
- this.frameRate = data.attrs.optionalFloat("FRAME-RATE", 0);
- this._avgBitrate = data.attrs.decimalInteger("AVERAGE-BANDWIDTH");
- this.audioCodec = data.audioCodec;
- this.videoCodec = data.videoCodec;
- this.codecSet = [data.videoCodec, data.audioCodec].filter((c) => !!c).map((s) => s.substring(0, 4)).join(",");
- if ("supplemental" in data) {
- var _data$supplemental;
- this.supplemental = data.supplemental;
- const supplementalVideo = (_data$supplemental = data.supplemental) == null ? void 0 : _data$supplemental.videoCodec;
- if (supplementalVideo && supplementalVideo !== data.videoCodec) {
- this.codecSet += `,${supplementalVideo.substring(0, 4)}`;
- }
- }
- this.addGroupId("audio", data.attrs.AUDIO);
- this.addGroupId("text", data.attrs.SUBTITLES);
- }
- get maxBitrate() {
- return Math.max(this.realBitrate, this.bitrate);
- }
- get averageBitrate() {
- return this._avgBitrate || this.realBitrate || this.bitrate;
- }
- get attrs() {
- return this._attrs[0];
- }
- get codecs() {
- return this.attrs.CODECS || "";
- }
- get pathwayId() {
- return this.attrs["PATHWAY-ID"] || ".";
- }
- get videoRange() {
- return this.attrs["VIDEO-RANGE"] || "SDR";
- }
- get score() {
- return this.attrs.optionalFloat("SCORE", 0);
- }
- get uri() {
- return this.url[0] || "";
- }
- hasAudioGroup(groupId) {
- return hasGroup(this._audioGroups, groupId);
- }
- hasSubtitleGroup(groupId) {
- return hasGroup(this._subtitleGroups, groupId);
- }
- get audioGroups() {
- return this._audioGroups;
- }
- get subtitleGroups() {
- return this._subtitleGroups;
- }
- addGroupId(type, groupId) {
- if (!groupId) {
- return;
- }
- if (type === "audio") {
- let audioGroups = this._audioGroups;
- if (!audioGroups) {
- audioGroups = this._audioGroups = [];
- }
- if (audioGroups.indexOf(groupId) === -1) {
- audioGroups.push(groupId);
- }
- } else if (type === "text") {
- let subtitleGroups = this._subtitleGroups;
- if (!subtitleGroups) {
- subtitleGroups = this._subtitleGroups = [];
- }
- if (subtitleGroups.indexOf(groupId) === -1) {
- subtitleGroups.push(groupId);
- }
- }
- }
- // Deprecated methods (retained for backwards compatibility)
- get urlId() {
- return 0;
- }
- set urlId(value) {
- }
- get audioGroupIds() {
- return this.audioGroups ? [this.audioGroupId] : void 0;
- }
- get textGroupIds() {
- return this.subtitleGroups ? [this.textGroupId] : void 0;
- }
- get audioGroupId() {
- var _this$audioGroups;
- return (_this$audioGroups = this.audioGroups) == null ? void 0 : _this$audioGroups[0];
- }
- get textGroupId() {
- var _this$subtitleGroups;
- return (_this$subtitleGroups = this.subtitleGroups) == null ? void 0 : _this$subtitleGroups[0];
- }
- addFallback() {
- }
- };
- function hasGroup(groups, groupId) {
- if (!groupId || !groups) {
- return false;
- }
- return groups.indexOf(groupId) !== -1;
- }
- function isHdrSupported() {
- if (typeof matchMedia === "function") {
- const mediaQueryList = matchMedia("(dynamic-range: high)");
- const badQuery = matchMedia("bad query");
- if (mediaQueryList.media !== badQuery.media) {
- return mediaQueryList.matches === true;
- }
- }
- return false;
- }
- function getVideoSelectionOptions(currentVideoRange, videoPreference) {
- let preferHDR = false;
- let allowedVideoRanges = [];
- if (currentVideoRange) {
- preferHDR = currentVideoRange !== "SDR";
- allowedVideoRanges = [currentVideoRange];
- }
- if (videoPreference) {
- allowedVideoRanges = videoPreference.allowedVideoRanges || VideoRangeValues.slice(0);
- const allowAutoPreferHDR = allowedVideoRanges.join("") !== "SDR" && !videoPreference.videoCodec;
- preferHDR = videoPreference.preferHDR !== void 0 ? videoPreference.preferHDR : allowAutoPreferHDR && isHdrSupported();
- if (!preferHDR) {
- allowedVideoRanges = ["SDR"];
- }
- }
- return {
- preferHDR,
- allowedVideoRanges
- };
- }
- var omitCircularRefsReplacer = (replacer) => {
- const known = /* @__PURE__ */ new WeakSet();
- return (_, value) => {
- if (replacer) {
- value = replacer(_, value);
- }
- if (typeof value === "object" && value !== null) {
- if (known.has(value)) {
- return;
- }
- known.add(value);
- }
- return value;
- };
- };
- var stringify = (object, replacer) => JSON.stringify(object, omitCircularRefsReplacer(replacer));
- function getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference) {
- const codecSets = Object.keys(codecTiers);
- const channelsPreference = audioPreference == null ? void 0 : audioPreference.channels;
- const audioCodecPreference = audioPreference == null ? void 0 : audioPreference.audioCodec;
- const videoCodecPreference = videoPreference == null ? void 0 : videoPreference.videoCodec;
- const preferStereo = channelsPreference && parseInt(channelsPreference) === 2;
- let hasStereo = false;
- let hasCurrentVideoRange = false;
- let minHeight = Infinity;
- let minFramerate = Infinity;
- let minBitrate = Infinity;
- let minIndex = Infinity;
- let selectedScore = 0;
- let videoRanges = [];
- const {
- preferHDR,
- allowedVideoRanges
- } = getVideoSelectionOptions(currentVideoRange, videoPreference);
- for (let i = codecSets.length; i--; ) {
- const tier = codecTiers[codecSets[i]];
- hasStereo || (hasStereo = tier.channels[2] > 0);
- minHeight = Math.min(minHeight, tier.minHeight);
- minFramerate = Math.min(minFramerate, tier.minFramerate);
- minBitrate = Math.min(minBitrate, tier.minBitrate);
- const matchingVideoRanges = allowedVideoRanges.filter((range) => tier.videoRanges[range] > 0);
- if (matchingVideoRanges.length > 0) {
- hasCurrentVideoRange = true;
- }
- }
- minHeight = isFiniteNumber(minHeight) ? minHeight : 0;
- minFramerate = isFiniteNumber(minFramerate) ? minFramerate : 0;
- const maxHeight = Math.max(1080, minHeight);
- const maxFramerate = Math.max(30, minFramerate);
- minBitrate = isFiniteNumber(minBitrate) ? minBitrate : currentBw;
- currentBw = Math.max(minBitrate, currentBw);
- if (!hasCurrentVideoRange) {
- currentVideoRange = void 0;
- }
- const hasMultipleSets = codecSets.length > 1;
- const codecSet = codecSets.reduce((selected, candidate) => {
- const candidateTier = codecTiers[candidate];
- if (candidate === selected) {
- return selected;
- }
- videoRanges = hasCurrentVideoRange ? allowedVideoRanges.filter((range) => candidateTier.videoRanges[range] > 0) : [];
- if (hasMultipleSets) {
- if (candidateTier.minBitrate > currentBw) {
- logStartCodecCandidateIgnored(candidate, `min bitrate of ${candidateTier.minBitrate} > current estimate of ${currentBw}`);
- return selected;
- }
- if (!candidateTier.hasDefaultAudio) {
- logStartCodecCandidateIgnored(candidate, `no renditions with default or auto-select sound found`);
- return selected;
- }
- if (audioCodecPreference && candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5 !== 0) {
- logStartCodecCandidateIgnored(candidate, `audio codec preference "${audioCodecPreference}" not found`);
- return selected;
- }
- if (channelsPreference && !preferStereo) {
- if (!candidateTier.channels[channelsPreference]) {
- logStartCodecCandidateIgnored(candidate, `no renditions with ${channelsPreference} channel sound found (channels options: ${Object.keys(candidateTier.channels)})`);
- return selected;
- }
- } else if ((!audioCodecPreference || preferStereo) && hasStereo && candidateTier.channels["2"] === 0) {
- logStartCodecCandidateIgnored(candidate, `no renditions with stereo sound found`);
- return selected;
- }
- if (candidateTier.minHeight > maxHeight) {
- logStartCodecCandidateIgnored(candidate, `min resolution of ${candidateTier.minHeight} > maximum of ${maxHeight}`);
- return selected;
- }
- if (candidateTier.minFramerate > maxFramerate) {
- logStartCodecCandidateIgnored(candidate, `min framerate of ${candidateTier.minFramerate} > maximum of ${maxFramerate}`);
- return selected;
- }
- if (!videoRanges.some((range) => candidateTier.videoRanges[range] > 0)) {
- logStartCodecCandidateIgnored(candidate, `no variants with VIDEO-RANGE of ${stringify(videoRanges)} found`);
- return selected;
- }
- if (videoCodecPreference && candidate.indexOf(videoCodecPreference.substring(0, 4)) % 5 !== 0) {
- logStartCodecCandidateIgnored(candidate, `video codec preference "${videoCodecPreference}" not found`);
- return selected;
- }
- if (candidateTier.maxScore < selectedScore) {
- logStartCodecCandidateIgnored(candidate, `max score of ${candidateTier.maxScore} < selected max of ${selectedScore}`);
- return selected;
- }
- }
- if (selected && (codecsSetSelectionPreferenceValue(candidate) >= codecsSetSelectionPreferenceValue(selected) || candidateTier.fragmentError > codecTiers[selected].fragmentError)) {
- return selected;
- }
- minIndex = candidateTier.minIndex;
- selectedScore = candidateTier.maxScore;
- return candidate;
- }, void 0);
- return {
- codecSet,
- videoRanges,
- preferHDR,
- minFramerate,
- minBitrate,
- minIndex
- };
- }
- function logStartCodecCandidateIgnored(codeSet, reason) {
- logger.log(`[abr] start candidates with "${codeSet}" ignored because ${reason}`);
- }
- function getAudioTracksByGroup(allAudioTracks) {
- return allAudioTracks.reduce((audioTracksByGroup, track) => {
- let trackGroup = audioTracksByGroup.groups[track.groupId];
- if (!trackGroup) {
- trackGroup = audioTracksByGroup.groups[track.groupId] = {
- tracks: [],
- channels: {
- 2: 0
- },
- hasDefault: false,
- hasAutoSelect: false
- };
- }
- trackGroup.tracks.push(track);
- const channelsKey = track.channels || "2";
- trackGroup.channels[channelsKey] = (trackGroup.channels[channelsKey] || 0) + 1;
- trackGroup.hasDefault = trackGroup.hasDefault || track.default;
- trackGroup.hasAutoSelect = trackGroup.hasAutoSelect || track.autoselect;
- if (trackGroup.hasDefault) {
- audioTracksByGroup.hasDefaultAudio = true;
- }
- if (trackGroup.hasAutoSelect) {
- audioTracksByGroup.hasAutoSelectAudio = true;
- }
- return audioTracksByGroup;
- }, {
- hasDefaultAudio: false,
- hasAutoSelectAudio: false,
- groups: {}
- });
- }
- function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
- return levels.slice(minAutoLevel, maxAutoLevel + 1).reduce((tiers, level, index) => {
- if (!level.codecSet) {
- return tiers;
- }
- const audioGroups = level.audioGroups;
- let tier = tiers[level.codecSet];
- if (!tier) {
- tiers[level.codecSet] = tier = {
- minBitrate: Infinity,
- minHeight: Infinity,
- minFramerate: Infinity,
- minIndex: index,
- maxScore: 0,
- videoRanges: {
- SDR: 0
- },
- channels: {
- "2": 0
- },
- hasDefaultAudio: !audioGroups,
- fragmentError: 0
- };
- }
- tier.minBitrate = Math.min(tier.minBitrate, level.bitrate);
- const lesserWidthOrHeight = Math.min(level.height, level.width);
- tier.minHeight = Math.min(tier.minHeight, lesserWidthOrHeight);
- tier.minFramerate = Math.min(tier.minFramerate, level.frameRate);
- tier.minIndex = Math.min(tier.minIndex, index);
- tier.maxScore = Math.max(tier.maxScore, level.score);
- tier.fragmentError += level.fragmentError;
- tier.videoRanges[level.videoRange] = (tier.videoRanges[level.videoRange] || 0) + 1;
- if (audioGroups) {
- audioGroups.forEach((audioGroupId) => {
- if (!audioGroupId) {
- return;
- }
- const audioGroup = audioTracksByGroup.groups[audioGroupId];
- if (!audioGroup) {
- return;
- }
- tier.hasDefaultAudio = tier.hasDefaultAudio || audioTracksByGroup.hasDefaultAudio ? audioGroup.hasDefault : audioGroup.hasAutoSelect || !audioTracksByGroup.hasDefaultAudio && !audioTracksByGroup.hasAutoSelectAudio;
- Object.keys(audioGroup.channels).forEach((channels) => {
- tier.channels[channels] = (tier.channels[channels] || 0) + audioGroup.channels[channels];
- });
- });
- }
- return tiers;
- }, {});
- }
- function getBasicSelectionOption(option) {
- if (!option) {
- return option;
- }
- const {
- lang,
- assocLang,
- characteristics,
- channels,
- audioCodec
- } = option;
- return {
- lang,
- assocLang,
- characteristics,
- channels,
- audioCodec
- };
- }
- function findMatchingOption(option, tracks, matchPredicate) {
- if ("attrs" in option) {
- const index = tracks.indexOf(option);
- if (index !== -1) {
- return index;
- }
- }
- for (let i = 0; i < tracks.length; i++) {
- const track = tracks[i];
- if (matchesOption(option, track, matchPredicate)) {
- return i;
- }
- }
- return -1;
- }
- function matchesOption(option, track, matchPredicate) {
- const {
- groupId,
- name,
- lang,
- assocLang,
- default: isDefault
- } = option;
- const forced = option.forced;
- return (groupId === void 0 || track.groupId === groupId) && (name === void 0 || track.name === name) && (lang === void 0 || languagesMatch(lang, track.lang)) && (lang === void 0 || track.assocLang === assocLang) && (isDefault === void 0 || track.default === isDefault) && (forced === void 0 || track.forced === forced) && (!("characteristics" in option) || characteristicsMatch(option.characteristics || "", track.characteristics)) && (matchPredicate === void 0 || matchPredicate(option, track));
- }
- function languagesMatch(languageA, languageB = "--") {
- if (languageA.length === languageB.length) {
- return languageA === languageB;
- }
- return languageA.startsWith(languageB) || languageB.startsWith(languageA);
- }
- function characteristicsMatch(characteristicsA, characteristicsB = "") {
- const arrA = characteristicsA.split(",");
- const arrB = characteristicsB.split(",");
- return arrA.length === arrB.length && !arrA.some((el) => arrB.indexOf(el) === -1);
- }
- function audioMatchPredicate(option, track) {
- const {
- audioCodec,
- channels
- } = option;
- return (audioCodec === void 0 || (track.audioCodec || "").substring(0, 4) === audioCodec.substring(0, 4)) && (channels === void 0 || channels === (track.channels || "2"));
- }
- function findClosestLevelWithAudioGroup(option, levels, allAudioTracks, searchIndex, matchPredicate) {
- const currentLevel = levels[searchIndex];
- const variants = levels.reduce((variantMap, level, index) => {
- const uri = level.uri;
- const renditions2 = variantMap[uri] || (variantMap[uri] = []);
- renditions2.push(index);
- return variantMap;
- }, {});
- const renditions = variants[currentLevel.uri];
- if (renditions.length > 1) {
- searchIndex = Math.max.apply(Math, renditions);
- }
- const currentVideoRange = currentLevel.videoRange;
- const currentFrameRate = currentLevel.frameRate;
- const currentVideoCodec = currentLevel.codecSet.substring(0, 4);
- const matchingVideo = searchDownAndUpList(levels, searchIndex, (level) => {
- if (level.videoRange !== currentVideoRange || level.frameRate !== currentFrameRate || level.codecSet.substring(0, 4) !== currentVideoCodec) {
- return false;
- }
- const audioGroups = level.audioGroups;
- const tracks = allAudioTracks.filter((track) => !audioGroups || audioGroups.indexOf(track.groupId) !== -1);
- return findMatchingOption(option, tracks, matchPredicate) > -1;
- });
- if (matchingVideo > -1) {
- return matchingVideo;
- }
- return searchDownAndUpList(levels, searchIndex, (level) => {
- const audioGroups = level.audioGroups;
- const tracks = allAudioTracks.filter((track) => !audioGroups || audioGroups.indexOf(track.groupId) !== -1);
- return findMatchingOption(option, tracks, matchPredicate) > -1;
- });
- }
- function searchDownAndUpList(arr, searchIndex, predicate) {
- for (let i = searchIndex; i > -1; i--) {
- if (predicate(arr[i])) {
- return i;
- }
- }
- for (let i = searchIndex + 1; i < arr.length; i++) {
- if (predicate(arr[i])) {
- return i;
- }
- }
- return -1;
- }
- function useAlternateAudio(audioTrackUrl, hls) {
- var _hls$loadLevelObj;
- return !!audioTrackUrl && audioTrackUrl !== ((_hls$loadLevelObj = hls.loadLevelObj) == null ? void 0 : _hls$loadLevelObj.uri);
- }
- var AbrController = class extends Logger {
- constructor(_hls) {
- super("abr", _hls.logger);
- this.hls = void 0;
- this.lastLevelLoadSec = 0;
- this.lastLoadedFragLevel = -1;
- this.firstSelection = -1;
- this._nextAutoLevel = -1;
- this.nextAutoLevelKey = "";
- this.audioTracksByGroup = null;
- this.codecTiers = null;
- this.timer = -1;
- this.fragCurrent = null;
- this.partCurrent = null;
- this.bitrateTestDelay = 0;
- this.rebufferNotice = -1;
- this.supportedCache = {};
- this.bwEstimator = void 0;
- this._abandonRulesCheck = (levelLoaded) => {
- var _ref;
- const {
- fragCurrent: frag,
- partCurrent: part,
- hls
- } = this;
- const {
- autoLevelEnabled,
- media
- } = hls;
- if (!frag || !media) {
- return;
- }
- const now2 = performance.now();
- const stats = part ? part.stats : frag.stats;
- const duration = part ? part.duration : frag.duration;
- const timeLoading = now2 - stats.loading.start;
- const minAutoLevel = hls.minAutoLevel;
- const loadingFragForLevel = frag.level;
- const currentAutoLevel = this._nextAutoLevel;
- if (stats.aborted || stats.loaded && stats.loaded === stats.total || loadingFragForLevel <= minAutoLevel) {
- this.clearTimer();
- this._nextAutoLevel = -1;
- return;
- }
- if (!autoLevelEnabled) {
- return;
- }
- const fragBlockingSwitch = currentAutoLevel > -1 && currentAutoLevel !== loadingFragForLevel;
- const levelChange = !!levelLoaded || fragBlockingSwitch;
- if (!levelChange && (media.paused || !media.playbackRate || !media.readyState)) {
- return;
- }
- const bufferInfo = hls.mainForwardBufferInfo;
- if (!levelChange && bufferInfo === null) {
- return;
- }
- const ttfbEstimate = this.bwEstimator.getEstimateTTFB();
- const playbackRate = Math.abs(media.playbackRate);
- if (timeLoading <= Math.max(ttfbEstimate, 1e3 * (duration / (playbackRate * 2)))) {
- return;
- }
- const bufferStarvationDelay = bufferInfo ? bufferInfo.len / playbackRate : 0;
- const ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1;
- const loadedFirstByte = stats.loaded && ttfb > -1;
- const bwEstimate = this.getBwEstimate();
- const levels = hls.levels;
- const level = levels[loadingFragForLevel];
- const expectedLen = Math.max(stats.loaded, Math.round(duration * (frag.bitrate || level.averageBitrate) / 8));
- let timeStreaming = loadedFirstByte ? timeLoading - ttfb : timeLoading;
- if (timeStreaming < 1 && loadedFirstByte) {
- timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate);
- }
- const loadRate = loadedFirstByte ? stats.loaded * 1e3 / timeStreaming : 0;
- const ttfbSeconds = ttfbEstimate / 1e3;
- const fragLoadedDelay = loadRate ? (expectedLen - stats.loaded) / loadRate : expectedLen * 8 / bwEstimate + ttfbSeconds;
- if (fragLoadedDelay <= bufferStarvationDelay) {
- return;
- }
- const bwe = loadRate ? loadRate * 8 : bwEstimate;
- const live = ((_ref = (levelLoaded == null ? void 0 : levelLoaded.details) || this.hls.latestLevelDetails) == null ? void 0 : _ref.live) === true;
- const abrBandWidthUpFactor = this.hls.config.abrBandWidthUpFactor;
- let fragLevelNextLoadedDelay = Number.POSITIVE_INFINITY;
- let nextLoadLevel;
- for (nextLoadLevel = loadingFragForLevel - 1; nextLoadLevel > minAutoLevel; nextLoadLevel--) {
- const levelNextBitrate = levels[nextLoadLevel].maxBitrate;
- const requiresLevelLoad = !levels[nextLoadLevel].details || live;
- fragLevelNextLoadedDelay = this.getTimeToLoadFrag(ttfbSeconds, bwe, duration * levelNextBitrate, requiresLevelLoad);
- if (fragLevelNextLoadedDelay < Math.min(bufferStarvationDelay, duration + ttfbSeconds)) {
- break;
- }
- }
- if (fragLevelNextLoadedDelay >= fragLoadedDelay) {
- return;
- }
- if (fragLevelNextLoadedDelay > duration * 10) {
- return;
- }
- if (loadedFirstByte) {
- this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded);
- } else {
- this.bwEstimator.sampleTTFB(timeLoading);
- }
- const nextLoadLevelBitrate = levels[nextLoadLevel].maxBitrate;
- if (this.getBwEstimate() * abrBandWidthUpFactor > nextLoadLevelBitrate) {
- this.resetEstimator(nextLoadLevelBitrate);
- }
- const bestSwitchLevel = this.findBestLevel(nextLoadLevelBitrate, minAutoLevel, nextLoadLevel, 0, bufferStarvationDelay, 1, 1);
- if (bestSwitchLevel > -1) {
- nextLoadLevel = bestSwitchLevel;
- }
- this.warn(`Fragment ${frag.sn}${part ? " part " + part.index : ""} of level ${loadingFragForLevel} is loading too slowly;
- Fragment duration: ${frag.duration.toFixed(3)}
- Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
- Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
- Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
- TTFB estimate: ${ttfb | 0} ms
- Current BW estimate: ${isFiniteNumber(bwEstimate) ? bwEstimate | 0 : "Unknown"} bps
- New BW estimate: ${this.getBwEstimate() | 0} bps
- Switching to level ${nextLoadLevel} @ ${nextLoadLevelBitrate | 0} bps`);
- hls.nextLoadLevel = hls.nextAutoLevel = nextLoadLevel;
- this.clearTimer();
- const abortAndSwitch = () => {
- this.clearTimer();
- if (this.fragCurrent === frag && this.hls.loadLevel === nextLoadLevel && nextLoadLevel > 0) {
- const bufferStarvationDelay2 = this.getStarvationDelay();
- this.warn(`Aborting inflight request ${nextLoadLevel > 0 ? "and switching down" : ""}
- Fragment duration: ${frag.duration.toFixed(3)} s
- Time to underbuffer: ${bufferStarvationDelay2.toFixed(3)} s`);
- frag.abortRequests();
- this.fragCurrent = this.partCurrent = null;
- if (nextLoadLevel > minAutoLevel) {
- let lowestSwitchLevel = this.findBestLevel(this.hls.levels[minAutoLevel].bitrate, minAutoLevel, nextLoadLevel, 0, bufferStarvationDelay2, 1, 1);
- if (lowestSwitchLevel === -1) {
- lowestSwitchLevel = minAutoLevel;
- }
- this.hls.nextLoadLevel = this.hls.nextAutoLevel = lowestSwitchLevel;
- this.resetEstimator(this.hls.levels[lowestSwitchLevel].bitrate);
- }
- }
- };
- if (fragBlockingSwitch || fragLoadedDelay > fragLevelNextLoadedDelay * 2) {
- abortAndSwitch();
- } else {
- this.timer = self.setInterval(abortAndSwitch, fragLevelNextLoadedDelay * 1e3);
- }
- hls.trigger(Events.FRAG_LOAD_EMERGENCY_ABORTED, {
- frag,
- part,
- stats
- });
- };
- this.hls = _hls;
- this.bwEstimator = this.initEstimator();
- this.registerListeners();
- }
- resetEstimator(abrEwmaDefaultEstimate) {
- if (abrEwmaDefaultEstimate) {
- this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
- this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
- }
- this.firstSelection = -1;
- this.bwEstimator = this.initEstimator();
- }
- initEstimator() {
- const config = this.hls.config;
- return new EwmaBandWidthEstimator(config.abrEwmaSlowVoD, config.abrEwmaFastVoD, config.abrEwmaDefaultEstimate);
- }
- registerListeners() {
- const {
- hls
- } = this;
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
- hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
- hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
- hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
- hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
- hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
- hls.on(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this);
- hls.on(Events.ERROR, this.onError, this);
- }
- unregisterListeners() {
- const {
- hls
- } = this;
- if (!hls) {
- return;
- }
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.FRAG_LOADING, this.onFragLoading, this);
- hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
- hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
- hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
- hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
- hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
- hls.off(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this);
- hls.off(Events.ERROR, this.onError, this);
- }
- destroy() {
- this.unregisterListeners();
- this.clearTimer();
- this.hls = this._abandonRulesCheck = this.supportedCache = null;
- this.fragCurrent = this.partCurrent = null;
- }
- onManifestLoading(event, data) {
- this.lastLoadedFragLevel = -1;
- this.firstSelection = -1;
- this.lastLevelLoadSec = 0;
- this.supportedCache = {};
- this.fragCurrent = this.partCurrent = null;
- this.onLevelsUpdated();
- this.clearTimer();
- }
- onLevelsUpdated() {
- if (this.lastLoadedFragLevel > -1 && this.fragCurrent) {
- this.lastLoadedFragLevel = this.fragCurrent.level;
- }
- this._nextAutoLevel = -1;
- this.onMaxAutoLevelUpdated();
- this.codecTiers = null;
- this.audioTracksByGroup = null;
- }
- onMaxAutoLevelUpdated() {
- this.firstSelection = -1;
- this.nextAutoLevelKey = "";
- }
- onFragLoading(event, data) {
- const frag = data.frag;
- if (this.ignoreFragment(frag)) {
- return;
- }
- if (!frag.bitrateTest) {
- var _data$part;
- this.fragCurrent = frag;
- this.partCurrent = (_data$part = data.part) != null ? _data$part : null;
- }
- this.clearTimer();
- this.timer = self.setInterval(this._abandonRulesCheck, 100);
- }
- onLevelSwitching(event, data) {
- this.clearTimer();
- }
- onError(event, data) {
- if (data.fatal) {
- return;
- }
- switch (data.details) {
- case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
- case ErrorDetails.BUFFER_APPEND_ERROR:
- this.lastLoadedFragLevel = -1;
- this.firstSelection = -1;
- break;
- case ErrorDetails.FRAG_LOAD_TIMEOUT: {
- const frag = data.frag;
- const {
- fragCurrent,
- partCurrent: part
- } = this;
- if (frag && fragCurrent && frag.sn === fragCurrent.sn && frag.level === fragCurrent.level) {
- const now2 = performance.now();
- const stats = part ? part.stats : frag.stats;
- const timeLoading = now2 - stats.loading.start;
- const ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1;
- const loadedFirstByte = stats.loaded && ttfb > -1;
- if (loadedFirstByte) {
- const ttfbEstimate = this.bwEstimator.getEstimateTTFB();
- this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded);
- } else {
- this.bwEstimator.sampleTTFB(timeLoading);
- }
- }
- break;
- }
- }
- }
- getTimeToLoadFrag(timeToFirstByteSec, bandwidth, fragSizeBits, isSwitch) {
- const fragLoadSec = timeToFirstByteSec + fragSizeBits / bandwidth;
- const playlistLoadSec = isSwitch ? timeToFirstByteSec + this.lastLevelLoadSec : 0;
- return fragLoadSec + playlistLoadSec;
- }
- onLevelLoaded(event, data) {
- const config = this.hls.config;
- const {
- loading
- } = data.stats;
- const timeLoadingMs = loading.end - loading.first;
- if (isFiniteNumber(timeLoadingMs)) {
- this.lastLevelLoadSec = timeLoadingMs / 1e3;
- }
- if (data.details.live) {
- this.bwEstimator.update(config.abrEwmaSlowLive, config.abrEwmaFastLive);
- } else {
- this.bwEstimator.update(config.abrEwmaSlowVoD, config.abrEwmaFastVoD);
- }
- if (this.timer > -1) {
- this._abandonRulesCheck(data.levelInfo);
- }
- }
- onFragLoaded(event, {
- frag,
- part
- }) {
- const stats = part ? part.stats : frag.stats;
- if (frag.type === PlaylistLevelType.MAIN) {
- this.bwEstimator.sampleTTFB(stats.loading.first - stats.loading.start);
- }
- if (this.ignoreFragment(frag)) {
- return;
- }
- this.clearTimer();
- if (frag.level === this._nextAutoLevel) {
- this._nextAutoLevel = -1;
- }
- this.firstSelection = -1;
- if (this.hls.config.abrMaxWithRealBitrate) {
- const duration = part ? part.duration : frag.duration;
- const level = this.hls.levels[frag.level];
- const loadedBytes = (level.loaded ? level.loaded.bytes : 0) + stats.loaded;
- const loadedDuration = (level.loaded ? level.loaded.duration : 0) + duration;
- level.loaded = {
- bytes: loadedBytes,
- duration: loadedDuration
- };
- level.realBitrate = Math.round(8 * loadedBytes / loadedDuration);
- }
- if (frag.bitrateTest) {
- const fragBufferedData = {
- stats,
- frag,
- part,
- id: frag.type
- };
- this.onFragBuffered(Events.FRAG_BUFFERED, fragBufferedData);
- frag.bitrateTest = false;
- } else {
- this.lastLoadedFragLevel = frag.level;
- }
- }
- onFragBuffered(event, data) {
- const {
- frag,
- part
- } = data;
- const stats = part != null && part.stats.loaded ? part.stats : frag.stats;
- if (stats.aborted) {
- return;
- }
- if (this.ignoreFragment(frag)) {
- return;
- }
- const processingMs = stats.parsing.end - stats.loading.start - Math.min(stats.loading.first - stats.loading.start, this.bwEstimator.getEstimateTTFB());
- this.bwEstimator.sample(processingMs, stats.loaded);
- stats.bwEstimate = this.getBwEstimate();
- if (frag.bitrateTest) {
- this.bitrateTestDelay = processingMs / 1e3;
- } else {
- this.bitrateTestDelay = 0;
- }
- }
- ignoreFragment(frag) {
- return frag.type !== PlaylistLevelType.MAIN || frag.sn === "initSegment";
- }
- clearTimer() {
- if (this.timer > -1) {
- self.clearInterval(this.timer);
- this.timer = -1;
- }
- }
- get firstAutoLevel() {
- const {
- maxAutoLevel,
- minAutoLevel
- } = this.hls;
- const bwEstimate = this.getBwEstimate();
- const maxStartDelay = this.hls.config.maxStarvationDelay;
- const abrAutoLevel = this.findBestLevel(bwEstimate, minAutoLevel, maxAutoLevel, 0, maxStartDelay, 1, 1);
- if (abrAutoLevel > -1) {
- return abrAutoLevel;
- }
- const firstLevel = this.hls.firstLevel;
- const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
- this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
- return clamped;
- }
- get forcedAutoLevel() {
- if (this.nextAutoLevelKey) {
- return -1;
- }
- return this._nextAutoLevel;
- }
- // return next auto level
- get nextAutoLevel() {
- const forcedAutoLevel = this.forcedAutoLevel;
- const bwEstimator = this.bwEstimator;
- const useEstimate = bwEstimator.canEstimate();
- const loadedFirstFrag = this.lastLoadedFragLevel > -1;
- if (forcedAutoLevel !== -1 && (!useEstimate || !loadedFirstFrag || this.nextAutoLevelKey === this.getAutoLevelKey())) {
- return forcedAutoLevel;
- }
- const nextABRAutoLevel = useEstimate && loadedFirstFrag ? this.getNextABRAutoLevel() : this.firstAutoLevel;
- if (forcedAutoLevel !== -1) {
- const levels = this.hls.levels;
- if (levels.length > Math.max(forcedAutoLevel, nextABRAutoLevel) && levels[forcedAutoLevel].loadError <= levels[nextABRAutoLevel].loadError) {
- return forcedAutoLevel;
- }
- }
- this._nextAutoLevel = nextABRAutoLevel;
- this.nextAutoLevelKey = this.getAutoLevelKey();
- return nextABRAutoLevel;
- }
- getAutoLevelKey() {
- return `${this.getBwEstimate()}_${this.getStarvationDelay().toFixed(2)}`;
- }
- getNextABRAutoLevel() {
- const {
- fragCurrent,
- partCurrent,
- hls
- } = this;
- if (hls.levels.length <= 1) {
- return hls.loadLevel;
- }
- const {
- maxAutoLevel,
- config,
- minAutoLevel
- } = hls;
- const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
- const avgbw = this.getBwEstimate();
- const bufferStarvationDelay = this.getStarvationDelay();
- let bwFactor = config.abrBandWidthFactor;
- let bwUpFactor = config.abrBandWidthUpFactor;
- if (bufferStarvationDelay) {
- const _bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, 0, bwFactor, bwUpFactor);
- if (_bestLevel >= 0) {
- this.rebufferNotice = -1;
- return _bestLevel;
- }
- }
- let maxStarvationDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxStarvationDelay) : config.maxStarvationDelay;
- if (!bufferStarvationDelay) {
- const bitrateTestDelay = this.bitrateTestDelay;
- if (bitrateTestDelay) {
- const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
- maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
- this.info(`bitrate test took ${Math.round(1e3 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1e3 * maxStarvationDelay)} ms`);
- bwFactor = bwUpFactor = 1;
- }
- }
- const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
- if (this.rebufferNotice !== bestLevel) {
- this.rebufferNotice = bestLevel;
- this.info(`${bufferStarvationDelay ? "rebuffering expected" : "buffer is empty"}, optimal quality level ${bestLevel}`);
- }
- if (bestLevel > -1) {
- return bestLevel;
- }
- const minLevel = hls.levels[minAutoLevel];
- const autoLevel = hls.loadLevelObj;
- if (autoLevel && (minLevel == null ? void 0 : minLevel.bitrate) < autoLevel.bitrate) {
- return minAutoLevel;
- }
- return hls.loadLevel;
- }
- getStarvationDelay() {
- const hls = this.hls;
- const media = hls.media;
- if (!media) {
- return Infinity;
- }
- const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1;
- const bufferInfo = hls.mainForwardBufferInfo;
- return (bufferInfo ? bufferInfo.len : 0) / playbackRate;
- }
- getBwEstimate() {
- return this.bwEstimator.canEstimate() ? this.bwEstimator.getEstimate() : this.hls.config.abrEwmaDefaultEstimate;
- }
- findBestLevel(currentBw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor) {
- var _this$hls$latestLevel;
- const maxFetchDuration = bufferStarvationDelay + maxStarvationDelay;
- const lastLoadedFragLevel = this.lastLoadedFragLevel;
- const selectionBaseLevel = lastLoadedFragLevel === -1 ? this.hls.firstLevel : lastLoadedFragLevel;
- const {
- fragCurrent,
- partCurrent
- } = this;
- const {
- levels,
- allAudioTracks,
- loadLevel,
- config
- } = this.hls;
- if (levels.length === 1) {
- return 0;
- }
- const level = levels[selectionBaseLevel];
- const live = !!((_this$hls$latestLevel = this.hls.latestLevelDetails) != null && _this$hls$latestLevel.live);
- const firstSelection = loadLevel === -1 || lastLoadedFragLevel === -1;
- let currentCodecSet;
- let currentVideoRange = "SDR";
- let currentFrameRate = (level == null ? void 0 : level.frameRate) || 0;
- const {
- audioPreference,
- videoPreference
- } = config;
- const audioTracksByGroup = this.audioTracksByGroup || (this.audioTracksByGroup = getAudioTracksByGroup(allAudioTracks));
- let minStartIndex = -1;
- if (firstSelection) {
- if (this.firstSelection !== -1) {
- return this.firstSelection;
- }
- const codecTiers = this.codecTiers || (this.codecTiers = getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel));
- const startTier = getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference);
- const {
- codecSet,
- videoRanges,
- minFramerate,
- minBitrate,
- minIndex,
- preferHDR
- } = startTier;
- minStartIndex = minIndex;
- currentCodecSet = codecSet;
- currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
- currentFrameRate = minFramerate;
- currentBw = Math.max(currentBw, minBitrate);
- this.log(`picked start tier ${stringify(startTier)}`);
- } else {
- currentCodecSet = level == null ? void 0 : level.codecSet;
- currentVideoRange = level == null ? void 0 : level.videoRange;
- }
- const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
- const ttfbEstimateSec = this.bwEstimator.getEstimateTTFB() / 1e3;
- const levelsSkipped = [];
- for (let i = maxAutoLevel; i >= minAutoLevel; i--) {
- var _levelInfo$supportedR;
- const levelInfo = levels[i];
- const upSwitch = i > selectionBaseLevel;
- if (!levelInfo) {
- continue;
- }
- if (config.useMediaCapabilities && !levelInfo.supportedResult && !levelInfo.supportedPromise) {
- const mediaCapabilities = navigator.mediaCapabilities;
- if (typeof (mediaCapabilities == null ? void 0 : mediaCapabilities.decodingInfo) === "function" && requiresMediaCapabilitiesDecodingInfo(levelInfo, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference)) {
- levelInfo.supportedPromise = getMediaDecodingInfoPromise(levelInfo, audioTracksByGroup, mediaCapabilities, this.supportedCache);
- levelInfo.supportedPromise.then((decodingInfo) => {
- if (!this.hls) {
- return;
- }
- levelInfo.supportedResult = decodingInfo;
- const levels2 = this.hls.levels;
- const index = levels2.indexOf(levelInfo);
- if (decodingInfo.error) {
- this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${stringify(decodingInfo)}`);
- } else if (!decodingInfo.supported) {
- this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${stringify(decodingInfo)}`);
- if (index > -1 && levels2.length > 1) {
- this.log(`Removing unsupported level ${index}`);
- this.hls.removeLevel(index);
- if (this.hls.loadLevel === -1) {
- this.hls.nextLoadLevel = 0;
- }
- }
- } else if (decodingInfo.decodingInfoResults.some((info) => info.smooth === false || info.powerEfficient === false)) {
- this.log(`MediaCapabilities decodingInfo for level ${index} not smooth or powerEfficient: ${stringify(decodingInfo)}`);
- }
- }).catch((error) => {
- this.warn(`Error handling MediaCapabilities decodingInfo: ${error}`);
- });
- } else {
- levelInfo.supportedResult = SUPPORTED_INFO_DEFAULT;
- }
- }
- if (currentCodecSet && levelInfo.codecSet !== currentCodecSet || currentVideoRange && levelInfo.videoRange !== currentVideoRange || upSwitch && currentFrameRate > levelInfo.frameRate || !upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate || (_levelInfo$supportedR = levelInfo.supportedResult) != null && (_levelInfo$supportedR = _levelInfo$supportedR.decodingInfoResults) != null && _levelInfo$supportedR.some((info) => info.smooth === false)) {
- if (!firstSelection || i !== minStartIndex) {
- levelsSkipped.push(i);
- continue;
- }
- }
- const levelDetails = levelInfo.details;
- const avgDuration = (partCurrent ? levelDetails == null ? void 0 : levelDetails.partTarget : levelDetails == null ? void 0 : levelDetails.averagetargetduration) || currentFragDuration;
- let adjustedbw;
- if (!upSwitch) {
- adjustedbw = bwFactor * currentBw;
- } else {
- adjustedbw = bwUpFactor * currentBw;
- }
- const bitrate = currentFragDuration && bufferStarvationDelay >= currentFragDuration * 2 && maxStarvationDelay === 0 ? levelInfo.averageBitrate : levelInfo.maxBitrate;
- const fetchDuration = this.getTimeToLoadFrag(ttfbEstimateSec, adjustedbw, bitrate * avgDuration, levelDetails === void 0);
- const canSwitchWithinTolerance = (
- // if adjusted bw is greater than level bitrate AND
- adjustedbw >= bitrate && // no level change, or new level has no error history
- (i === lastLoadedFragLevel || levelInfo.loadError === 0 && levelInfo.fragmentError === 0) && // fragment fetchDuration unknown OR live stream OR fragment fetchDuration less than max allowed fetch duration, then this level matches
- // we don't account for max Fetch Duration for live streams, this is to avoid switching down when near the edge of live sliding window ...
- // special case to support startLevel = -1 (bitrateTest) on live streams : in that case we should not exit loop so that findBestLevel will return -1
- (fetchDuration <= ttfbEstimateSec || !isFiniteNumber(fetchDuration) || live && !this.bitrateTestDelay || fetchDuration < maxFetchDuration)
- );
- if (canSwitchWithinTolerance) {
- const forcedAutoLevel = this.forcedAutoLevel;
- if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
- if (levelsSkipped.length) {
- this.trace(`Skipped level(s) ${levelsSkipped.join(",")} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${currentCodecSet}" ${currentVideoRange}`);
- }
- this.info(`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${levelInfo.codecSet} videoRange:${levelInfo.videoRange} hls.loadLevel:${loadLevel}`);
- }
- if (firstSelection) {
- this.firstSelection = i;
- }
- return i;
- }
- }
- return -1;
- }
- set nextAutoLevel(nextLevel) {
- const value = this.deriveNextAutoLevel(nextLevel);
- if (this._nextAutoLevel !== value) {
- this.nextAutoLevelKey = "";
- this._nextAutoLevel = value;
- }
- }
- deriveNextAutoLevel(nextLevel) {
- const {
- maxAutoLevel,
- minAutoLevel
- } = this.hls;
- return Math.min(Math.max(nextLevel, minAutoLevel), maxAutoLevel);
- }
- };
- var BinarySearch = {
- /**
- * Searches for an item in an array which matches a certain condition.
- * This requires the condition to only match one item in the array,
- * and for the array to be ordered.
- *
- * @param list The array to search.
- * @param comparisonFn
- * Called and provided a candidate item as the first argument.
- * Should return:
- * > -1 if the item should be located at a lower index than the provided item.
- * > 1 if the item should be located at a higher index than the provided item.
- * > 0 if the item is the item you're looking for.
- *
- * @returns the object if found, otherwise returns null
- */
- search: function(list, comparisonFn) {
- let minIndex = 0;
- let maxIndex = list.length - 1;
- let currentIndex = null;
- let currentElement = null;
- while (minIndex <= maxIndex) {
- currentIndex = (minIndex + maxIndex) / 2 | 0;
- currentElement = list[currentIndex];
- const comparisonResult = comparisonFn(currentElement);
- if (comparisonResult > 0) {
- minIndex = currentIndex + 1;
- } else if (comparisonResult < 0) {
- maxIndex = currentIndex - 1;
- } else {
- return currentElement;
- }
- }
- return null;
- }
- };
- function findFragmentByPDT(fragments, PDTValue, maxFragLookUpTolerance) {
- if (PDTValue === null || !Array.isArray(fragments) || !fragments.length || !isFiniteNumber(PDTValue)) {
- return null;
- }
- const startPDT = fragments[0].programDateTime;
- if (PDTValue < (startPDT || 0)) {
- return null;
- }
- const endPDT = fragments[fragments.length - 1].endProgramDateTime;
- if (PDTValue >= (endPDT || 0)) {
- return null;
- }
- for (let seg = 0; seg < fragments.length; ++seg) {
- const frag = fragments[seg];
- if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) {
- return frag;
- }
- }
- return null;
- }
- function findFragmentByPTS(fragPrevious, fragments, bufferEnd = 0, maxFragLookUpTolerance = 0, nextFragLookupTolerance = 5e-3) {
- let fragNext = null;
- if (fragPrevious) {
- fragNext = fragments[1 + fragPrevious.sn - fragments[0].sn] || null;
- const bufferEdgeError = fragPrevious.endDTS - bufferEnd;
- if (bufferEdgeError > 0 && bufferEdgeError < 15e-7) {
- bufferEnd += 15e-7;
- }
- if (fragNext && fragPrevious.level !== fragNext.level && fragNext.end <= fragPrevious.end) {
- fragNext = fragments[2 + fragPrevious.sn - fragments[0].sn] || null;
- }
- } else if (bufferEnd === 0 && fragments[0].start === 0) {
- fragNext = fragments[0];
- }
- if (fragNext && ((!fragPrevious || fragPrevious.level === fragNext.level) && fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext) === 0 || fragmentWithinFastStartSwitch(fragNext, fragPrevious, Math.min(nextFragLookupTolerance, maxFragLookUpTolerance)))) {
- return fragNext;
- }
- const foundFragment = BinarySearch.search(fragments, fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance));
- if (foundFragment && (foundFragment !== fragPrevious || !fragNext)) {
- return foundFragment;
- }
- return fragNext;
- }
- function fragmentWithinFastStartSwitch(fragNext, fragPrevious, nextFragLookupTolerance) {
- if (fragPrevious && fragPrevious.start === 0 && fragPrevious.level < fragNext.level && (fragPrevious.endPTS || 0) > 0) {
- const firstDuration = fragPrevious.tagList.reduce((duration, tag) => {
- if (tag[0] === "INF") {
- duration += parseFloat(tag[1]);
- }
- return duration;
- }, nextFragLookupTolerance);
- return fragNext.start <= firstDuration;
- }
- return false;
- }
- function fragmentWithinToleranceTest(bufferEnd = 0, maxFragLookUpTolerance = 0, candidate) {
- if (candidate.start <= bufferEnd && candidate.start + candidate.duration > bufferEnd) {
- return 0;
- }
- const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0));
- if (candidate.start + candidate.duration - candidateLookupTolerance <= bufferEnd) {
- return 1;
- } else if (candidate.start - candidateLookupTolerance > bufferEnd && candidate.start) {
- return -1;
- }
- return 0;
- }
- function pdtWithinToleranceTest(pdtBufferEnd, maxFragLookUpTolerance, candidate) {
- const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)) * 1e3;
- const endProgramDateTime = candidate.endProgramDateTime || 0;
- return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd;
- }
- function findNearestWithCC(details, cc, pos) {
- if (details) {
- if (details.startCC <= cc && details.endCC >= cc) {
- let fragments = details.fragments;
- const {
- fragmentHint
- } = details;
- if (fragmentHint) {
- fragments = fragments.concat(fragmentHint);
- }
- let closest;
- BinarySearch.search(fragments, (candidate) => {
- if (candidate.cc < cc) {
- return 1;
- }
- if (candidate.cc > cc) {
- return -1;
- }
- closest = candidate;
- if (candidate.end <= pos) {
- return 1;
- }
- if (candidate.start > pos) {
- return -1;
- }
- return 0;
- });
- return closest || null;
- }
- }
- return null;
- }
- function isTimeoutError(error) {
- switch (error.details) {
- case ErrorDetails.FRAG_LOAD_TIMEOUT:
- case ErrorDetails.KEY_LOAD_TIMEOUT:
- case ErrorDetails.LEVEL_LOAD_TIMEOUT:
- case ErrorDetails.MANIFEST_LOAD_TIMEOUT:
- return true;
- }
- return false;
- }
- function isKeyError(error) {
- return error.details.startsWith("key");
- }
- function isUnusableKeyError(error) {
- return isKeyError(error) && !!error.frag && !error.frag.decryptdata;
- }
- function getRetryConfig(loadPolicy, error) {
- const isTimeout = isTimeoutError(error);
- return loadPolicy.default[`${isTimeout ? "timeout" : "error"}Retry`];
- }
- function getRetryDelay(retryConfig, retryCount) {
- const backoffFactor = retryConfig.backoff === "linear" ? 1 : Math.pow(2, retryCount);
- return Math.min(backoffFactor * retryConfig.retryDelayMs, retryConfig.maxRetryDelayMs);
- }
- function getLoaderConfigWithoutReties(loderConfig) {
- return _objectSpread2(_objectSpread2({}, loderConfig), {
- errorRetry: null,
- timeoutRetry: null
- });
- }
- function shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse) {
- if (!retryConfig) {
- return false;
- }
- const httpStatus = loaderResponse == null ? void 0 : loaderResponse.code;
- const retry = retryCount < retryConfig.maxNumRetry && (retryForHttpStatus(httpStatus) || !!isTimeout);
- return retryConfig.shouldRetry ? retryConfig.shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse, retry) : retry;
- }
- function retryForHttpStatus(httpStatus) {
- return offlineHttpStatus(httpStatus) || !!httpStatus && (httpStatus < 400 || httpStatus > 499);
- }
- function offlineHttpStatus(httpStatus) {
- return httpStatus === 0 && navigator.onLine === false;
- }
- var NetworkErrorAction = {
- DoNothing: 0,
- SendEndCallback: 1,
- SendAlternateToPenaltyBox: 2,
- RemoveAlternatePermanently: 3,
- InsertDiscontinuity: 4,
- RetryRequest: 5
- };
- var ErrorActionFlags = {
- None: 0,
- MoveAllAlternatesMatchingHost: 1,
- MoveAllAlternatesMatchingHDCP: 2,
- MoveAllAlternatesMatchingKey: 4,
- SwitchToSDR: 8
- };
- var ErrorController = class extends Logger {
- constructor(hls) {
- super("error-controller", hls.logger);
- this.hls = void 0;
- this.playlistError = 0;
- this.hls = hls;
- this.registerListeners();
- }
- registerListeners() {
- const hls = this.hls;
- hls.on(Events.ERROR, this.onError, this);
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
- }
- unregisterListeners() {
- const hls = this.hls;
- if (!hls) {
- return;
- }
- hls.off(Events.ERROR, this.onError, this);
- hls.off(Events.ERROR, this.onErrorOut, this);
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
- }
- destroy() {
- this.unregisterListeners();
- this.hls = null;
- }
- startLoad(startPosition) {
- }
- stopLoad() {
- this.playlistError = 0;
- }
- getVariantLevelIndex(frag) {
- if ((frag == null ? void 0 : frag.type) === PlaylistLevelType.MAIN) {
- return frag.level;
- }
- return this.getVariantIndex();
- }
- getVariantIndex() {
- var _hls$loadLevelObj;
- const hls = this.hls;
- const currentLevel = hls.currentLevel;
- if ((_hls$loadLevelObj = hls.loadLevelObj) != null && _hls$loadLevelObj.details || currentLevel === -1) {
- return hls.loadLevel;
- }
- return currentLevel;
- }
- variantHasKey(level, keyInError) {
- if (level) {
- var _level$details;
- if ((_level$details = level.details) != null && _level$details.hasKey(keyInError)) {
- return true;
- }
- const audioGroupsIds = level.audioGroups;
- if (audioGroupsIds) {
- const audioTracks = this.hls.allAudioTracks.filter((track) => audioGroupsIds.indexOf(track.groupId) >= 0);
- return audioTracks.some((track) => {
- var _track$details;
- return (_track$details = track.details) == null ? void 0 : _track$details.hasKey(keyInError);
- });
- }
- }
- return false;
- }
- onManifestLoading() {
- this.playlistError = 0;
- }
- onLevelUpdated() {
- this.playlistError = 0;
- }
- onError(event, data) {
- var _data$frag;
- if (data.fatal) {
- return;
- }
- const hls = this.hls;
- const context = data.context;
- switch (data.details) {
- case ErrorDetails.FRAG_LOAD_ERROR:
- case ErrorDetails.FRAG_LOAD_TIMEOUT:
- case ErrorDetails.KEY_LOAD_ERROR:
- case ErrorDetails.KEY_LOAD_TIMEOUT:
- data.errorAction = this.getFragRetryOrSwitchAction(data);
- return;
- case ErrorDetails.FRAG_PARSING_ERROR:
- if ((_data$frag = data.frag) != null && _data$frag.gap) {
- data.errorAction = createDoNothingErrorAction();
- return;
- }
- case ErrorDetails.FRAG_GAP:
- case ErrorDetails.FRAG_DECRYPT_ERROR: {
- data.errorAction = this.getFragRetryOrSwitchAction(data);
- data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox;
- return;
- }
- case ErrorDetails.LEVEL_EMPTY_ERROR:
- case ErrorDetails.LEVEL_PARSING_ERROR:
- {
- var _data$context;
- const levelIndex = data.parent === PlaylistLevelType.MAIN ? data.level : hls.loadLevel;
- if (data.details === ErrorDetails.LEVEL_EMPTY_ERROR && !!((_data$context = data.context) != null && (_data$context = _data$context.levelDetails) != null && _data$context.live)) {
- data.errorAction = this.getPlaylistRetryOrSwitchAction(data, levelIndex);
- } else {
- data.levelRetry = false;
- data.errorAction = this.getLevelSwitchAction(data, levelIndex);
- }
- }
- return;
- case ErrorDetails.LEVEL_LOAD_ERROR:
- case ErrorDetails.LEVEL_LOAD_TIMEOUT:
- if (typeof (context == null ? void 0 : context.level) === "number") {
- data.errorAction = this.getPlaylistRetryOrSwitchAction(data, context.level);
- }
- return;
- case ErrorDetails.AUDIO_TRACK_LOAD_ERROR:
- case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:
- case ErrorDetails.SUBTITLE_LOAD_ERROR:
- case ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT:
- if (context) {
- const level = hls.loadLevelObj;
- if (level && (context.type === PlaylistContextType.AUDIO_TRACK && level.hasAudioGroup(context.groupId) || context.type === PlaylistContextType.SUBTITLE_TRACK && level.hasSubtitleGroup(context.groupId))) {
- data.errorAction = this.getPlaylistRetryOrSwitchAction(data, hls.loadLevel);
- data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox;
- data.errorAction.flags = ErrorActionFlags.MoveAllAlternatesMatchingHost;
- return;
- }
- }
- return;
- case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED:
- {
- data.errorAction = {
- action: NetworkErrorAction.SendAlternateToPenaltyBox,
- flags: ErrorActionFlags.MoveAllAlternatesMatchingHDCP
- };
- }
- return;
- case ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED:
- case ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR:
- case ErrorDetails.KEY_SYSTEM_NO_SESSION:
- {
- data.errorAction = {
- action: NetworkErrorAction.SendAlternateToPenaltyBox,
- flags: ErrorActionFlags.MoveAllAlternatesMatchingKey
- };
- }
- return;
- case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
- case ErrorDetails.REMUX_ALLOC_ERROR:
- case ErrorDetails.BUFFER_APPEND_ERROR:
- if (!data.errorAction) {
- var _data$level;
- data.errorAction = this.getLevelSwitchAction(data, (_data$level = data.level) != null ? _data$level : hls.loadLevel);
- }
- return;
- case ErrorDetails.INTERNAL_EXCEPTION:
- case ErrorDetails.BUFFER_APPENDING_ERROR:
- case ErrorDetails.BUFFER_FULL_ERROR:
- case ErrorDetails.LEVEL_SWITCH_ERROR:
- case ErrorDetails.BUFFER_STALLED_ERROR:
- case ErrorDetails.BUFFER_SEEK_OVER_HOLE:
- case ErrorDetails.BUFFER_NUDGE_ON_STALL:
- data.errorAction = createDoNothingErrorAction();
- return;
- }
- if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) {
- data.levelRetry = false;
- data.errorAction = createDoNothingErrorAction();
- }
- }
- getPlaylistRetryOrSwitchAction(data, levelIndex) {
- const hls = this.hls;
- const retryConfig = getRetryConfig(hls.config.playlistLoadPolicy, data);
- const retryCount = this.playlistError++;
- const retry = shouldRetry(retryConfig, retryCount, isTimeoutError(data), data.response);
- if (retry) {
- return {
- action: NetworkErrorAction.RetryRequest,
- flags: ErrorActionFlags.None,
- retryConfig,
- retryCount
- };
- }
- const errorAction = this.getLevelSwitchAction(data, levelIndex);
- if (retryConfig) {
- errorAction.retryConfig = retryConfig;
- errorAction.retryCount = retryCount;
- }
- return errorAction;
- }
- getFragRetryOrSwitchAction(data) {
- const hls = this.hls;
- const variantLevelIndex = this.getVariantLevelIndex(data.frag);
- const level = hls.levels[variantLevelIndex];
- const {
- fragLoadPolicy,
- keyLoadPolicy
- } = hls.config;
- const retryConfig = getRetryConfig(isKeyError(data) ? keyLoadPolicy : fragLoadPolicy, data);
- const fragmentErrors = hls.levels.reduce((acc, level2) => acc + level2.fragmentError, 0);
- if (level) {
- if (data.details !== ErrorDetails.FRAG_GAP) {
- level.fragmentError++;
- }
- if (!isUnusableKeyError(data)) {
- const retry = shouldRetry(retryConfig, fragmentErrors, isTimeoutError(data), data.response);
- if (retry) {
- return {
- action: NetworkErrorAction.RetryRequest,
- flags: ErrorActionFlags.None,
- retryConfig,
- retryCount: fragmentErrors
- };
- }
- }
- }
- const errorAction = this.getLevelSwitchAction(data, variantLevelIndex);
- if (retryConfig) {
- errorAction.retryConfig = retryConfig;
- errorAction.retryCount = fragmentErrors;
- }
- return errorAction;
- }
- getLevelSwitchAction(data, levelIndex) {
- const hls = this.hls;
- if (levelIndex === null || levelIndex === void 0) {
- levelIndex = hls.loadLevel;
- }
- const level = this.hls.levels[levelIndex];
- if (level) {
- var _data$frag2, _data$context2;
- const errorDetails = data.details;
- level.loadError++;
- if (errorDetails === ErrorDetails.BUFFER_APPEND_ERROR) {
- level.fragmentError++;
- }
- let nextLevel = -1;
- const {
- levels,
- loadLevel,
- minAutoLevel,
- maxAutoLevel
- } = hls;
- if (!hls.autoLevelEnabled && !hls.config.preserveManualLevelOnError) {
- hls.loadLevel = -1;
- }
- const fragErrorType = (_data$frag2 = data.frag) == null ? void 0 : _data$frag2.type;
- const isAudioCodecError = fragErrorType === PlaylistLevelType.AUDIO && errorDetails === ErrorDetails.FRAG_PARSING_ERROR || data.sourceBufferName === "audio" && (errorDetails === ErrorDetails.BUFFER_ADD_CODEC_ERROR || errorDetails === ErrorDetails.BUFFER_APPEND_ERROR);
- const findAudioCodecAlternate = isAudioCodecError && levels.some(({
- audioCodec
- }) => level.audioCodec !== audioCodec);
- const isVideoCodecError = data.sourceBufferName === "video" && (errorDetails === ErrorDetails.BUFFER_ADD_CODEC_ERROR || errorDetails === ErrorDetails.BUFFER_APPEND_ERROR);
- const findVideoCodecAlternate = isVideoCodecError && levels.some(({
- codecSet,
- audioCodec
- }) => level.codecSet !== codecSet && level.audioCodec === audioCodec);
- const {
- type: playlistErrorType,
- groupId: playlistErrorGroupId
- } = (_data$context2 = data.context) != null ? _data$context2 : {};
- for (let i = levels.length; i--; ) {
- const candidate = (i + loadLevel) % levels.length;
- if (candidate !== loadLevel && candidate >= minAutoLevel && candidate <= maxAutoLevel && levels[candidate].loadError === 0) {
- var _level$audioGroups, _level$subtitleGroups;
- const levelCandidate = levels[candidate];
- if (errorDetails === ErrorDetails.FRAG_GAP && fragErrorType === PlaylistLevelType.MAIN && data.frag) {
- const levelDetails = levels[candidate].details;
- if (levelDetails) {
- const fragCandidate = findFragmentByPTS(data.frag, levelDetails.fragments, data.frag.start);
- if (fragCandidate != null && fragCandidate.gap) {
- continue;
- }
- }
- } else if (playlistErrorType === PlaylistContextType.AUDIO_TRACK && levelCandidate.hasAudioGroup(playlistErrorGroupId) || playlistErrorType === PlaylistContextType.SUBTITLE_TRACK && levelCandidate.hasSubtitleGroup(playlistErrorGroupId)) {
- continue;
- } else if (fragErrorType === PlaylistLevelType.AUDIO && (_level$audioGroups = level.audioGroups) != null && _level$audioGroups.some((groupId) => levelCandidate.hasAudioGroup(groupId)) || fragErrorType === PlaylistLevelType.SUBTITLE && (_level$subtitleGroups = level.subtitleGroups) != null && _level$subtitleGroups.some((groupId) => levelCandidate.hasSubtitleGroup(groupId)) || findAudioCodecAlternate && level.audioCodec === levelCandidate.audioCodec || findVideoCodecAlternate && level.codecSet === levelCandidate.codecSet || !findAudioCodecAlternate && level.codecSet !== levelCandidate.codecSet) {
- continue;
- }
- nextLevel = candidate;
- break;
- }
- }
- if (nextLevel > -1 && hls.loadLevel !== nextLevel) {
- data.levelRetry = true;
- this.playlistError = 0;
- return {
- action: NetworkErrorAction.SendAlternateToPenaltyBox,
- flags: ErrorActionFlags.None,
- nextAutoLevel: nextLevel
- };
- }
- }
- return {
- action: NetworkErrorAction.SendAlternateToPenaltyBox,
- flags: ErrorActionFlags.MoveAllAlternatesMatchingHost
- };
- }
- onErrorOut(event, data) {
- var _data$errorAction;
- switch ((_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.action) {
- case NetworkErrorAction.DoNothing:
- break;
- case NetworkErrorAction.SendAlternateToPenaltyBox:
- this.sendAlternateToPenaltyBox(data);
- if (!data.errorAction.resolved && data.details !== ErrorDetails.FRAG_GAP) {
- data.fatal = true;
- } else if (/MediaSource readyState: ended/.test(data.error.message)) {
- this.warn(`MediaSource ended after "${data.sourceBufferName}" sourceBuffer append error. Attempting to recover from media error.`);
- this.hls.recoverMediaError();
- }
- break;
- case NetworkErrorAction.RetryRequest:
- break;
- }
- if (data.fatal) {
- this.hls.stopLoad();
- return;
- }
- }
- sendAlternateToPenaltyBox(data) {
- const hls = this.hls;
- const errorAction = data.errorAction;
- if (!errorAction) {
- return;
- }
- const {
- flags
- } = errorAction;
- const nextAutoLevel = errorAction.nextAutoLevel;
- switch (flags) {
- case ErrorActionFlags.None:
- this.switchLevel(data, nextAutoLevel);
- break;
- case ErrorActionFlags.MoveAllAlternatesMatchingHDCP: {
- const levelIndex = this.getVariantLevelIndex(data.frag);
- const level = hls.levels[levelIndex];
- const restrictedHdcpLevel = level == null ? void 0 : level.attrs["HDCP-LEVEL"];
- errorAction.hdcpLevel = restrictedHdcpLevel;
- if (restrictedHdcpLevel === "NONE") {
- this.warn(`HDCP policy resticted output with HDCP-LEVEL=NONE`);
- } else if (restrictedHdcpLevel) {
- hls.maxHdcpLevel = HdcpLevels[HdcpLevels.indexOf(restrictedHdcpLevel) - 1];
- errorAction.resolved = true;
- this.warn(`Restricting playback to HDCP-LEVEL of "${hls.maxHdcpLevel}" or lower`);
- break;
- }
- }
- case ErrorActionFlags.MoveAllAlternatesMatchingKey: {
- const levelKey = data.decryptdata;
- if (levelKey) {
- const levels = this.hls.levels;
- const levelCountWithError = levels.length;
- for (let i = levelCountWithError; i--; ) {
- if (this.variantHasKey(levels[i], levelKey)) {
- var _levels$i$audioGroups, _data$frag3;
- this.log(`Banned key found in level ${i} (${levels[i].bitrate}bps) or audio group "${(_levels$i$audioGroups = levels[i].audioGroups) == null ? void 0 : _levels$i$audioGroups.join(",")}" (${(_data$frag3 = data.frag) == null ? void 0 : _data$frag3.type} fragment) ${arrayToHex(levelKey.keyId || [])}`);
- levels[i].fragmentError++;
- levels[i].loadError++;
- this.log(`Removing level ${i} with key error (${data.error})`);
- this.hls.removeLevel(i);
- }
- }
- const frag = data.frag;
- if (this.hls.levels.length < levelCountWithError) {
- errorAction.resolved = true;
- } else if (frag && frag.type !== PlaylistLevelType.MAIN) {
- const fragLevelKey = frag.decryptdata;
- if (fragLevelKey && !levelKey.matches(fragLevelKey)) {
- errorAction.resolved = true;
- }
- }
- }
- break;
- }
- }
- if (!errorAction.resolved) {
- this.switchLevel(data, nextAutoLevel);
- }
- }
- switchLevel(data, levelIndex) {
- if (levelIndex !== void 0 && data.errorAction) {
- this.warn(`switching to level ${levelIndex} after ${data.details}`);
- this.hls.nextAutoLevel = levelIndex;
- data.errorAction.resolved = true;
- this.hls.nextLoadLevel = this.hls.nextAutoLevel;
- if (data.details === ErrorDetails.BUFFER_ADD_CODEC_ERROR && data.mimeType && data.sourceBufferName !== "audiovideo") {
- const codec = getCodecsForMimeType(data.mimeType);
- const levels = this.hls.levels;
- for (let i = levels.length; i--; ) {
- if (levels[i][`${data.sourceBufferName}Codec`] === codec) {
- this.log(`Removing level ${i} for ${data.details} ("${codec}" not supported)`);
- this.hls.removeLevel(i);
- }
- }
- }
- }
- }
- };
- function createDoNothingErrorAction(resolved) {
- const errorAction = {
- action: NetworkErrorAction.DoNothing,
- flags: ErrorActionFlags.None
- };
- if (resolved) {
- errorAction.resolved = true;
- }
- return errorAction;
- }
- var FragmentState = {
- NOT_LOADED: "NOT_LOADED",
- APPENDING: "APPENDING",
- PARTIAL: "PARTIAL",
- OK: "OK"
- };
- var FragmentTracker = class {
- constructor(hls) {
- this.activePartLists = /* @__PURE__ */ Object.create(null);
- this.endListFragments = /* @__PURE__ */ Object.create(null);
- this.fragments = /* @__PURE__ */ Object.create(null);
- this.timeRanges = /* @__PURE__ */ Object.create(null);
- this.bufferPadding = 0.2;
- this.hls = void 0;
- this.hasGaps = false;
- this.hls = hls;
- this._registerListeners();
- }
- _registerListeners() {
- const {
- hls
- } = this;
- if (hls) {
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
- hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
- hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
- }
- }
- _unregisterListeners() {
- const {
- hls
- } = this;
- if (hls) {
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
- hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
- hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
- }
- }
- destroy() {
- this._unregisterListeners();
- this.hls = // @ts-ignore
- this.fragments = // @ts-ignore
- this.activePartLists = // @ts-ignore
- this.endListFragments = this.timeRanges = null;
- }
- /**
- * Return a Fragment or Part with an appended range that matches the position and levelType
- * Otherwise, return null
- */
- getAppendedFrag(position, levelType) {
- const activeParts = this.activePartLists[levelType];
- if (activeParts) {
- for (let i = activeParts.length; i--; ) {
- const activePart = activeParts[i];
- if (!activePart) {
- break;
- }
- if (activePart.start <= position && position <= activePart.end && activePart.loaded) {
- return activePart;
- }
- }
- }
- return this.getBufferedFrag(position, levelType);
- }
- /**
- * Return a buffered Fragment that matches the position and levelType.
- * A buffered Fragment is one whose loading, parsing and appending is done (completed or "partial" meaning aborted).
- * If not found any Fragment, return null
- */
- getBufferedFrag(position, levelType) {
- return this.getFragAtPos(position, levelType, true);
- }
- getFragAtPos(position, levelType, buffered) {
- const {
- fragments
- } = this;
- const keys = Object.keys(fragments);
- for (let i = keys.length; i--; ) {
- const fragmentEntity = fragments[keys[i]];
- if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && (!buffered || fragmentEntity.buffered)) {
- const frag = fragmentEntity.body;
- if (frag.start <= position && position <= frag.end) {
- return frag;
- }
- }
- }
- return null;
- }
- /**
- * Partial fragments effected by coded frame eviction will be removed
- * The browser will unload parts of the buffer to free up memory for new buffer data
- * Fragments will need to be reloaded when the buffer is freed up, removing partial fragments will allow them to reload(since there might be parts that are still playable)
- */
- detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedPart, removeAppending) {
- if (this.timeRanges) {
- this.timeRanges[elementaryStream] = timeRange;
- }
- const appendedPartSn = (appendedPart == null ? void 0 : appendedPart.fragment.sn) || -1;
- Object.keys(this.fragments).forEach((key) => {
- const fragmentEntity = this.fragments[key];
- if (!fragmentEntity) {
- return;
- }
- if (appendedPartSn >= fragmentEntity.body.sn) {
- return;
- }
- if (!fragmentEntity.buffered && (!fragmentEntity.loaded || removeAppending)) {
- if (fragmentEntity.body.type === playlistType) {
- this.removeFragment(fragmentEntity.body);
- }
- return;
- }
- const esData = fragmentEntity.range[elementaryStream];
- if (!esData) {
- return;
- }
- if (esData.time.length === 0) {
- this.removeFragment(fragmentEntity.body);
- return;
- }
- esData.time.some((time) => {
- const isNotBuffered = !this.isTimeBuffered(time.startPTS, time.endPTS, timeRange);
- if (isNotBuffered) {
- this.removeFragment(fragmentEntity.body);
- }
- return isNotBuffered;
- });
- });
- }
- /**
- * Checks if the fragment passed in is loaded in the buffer properly
- * Partially loaded fragments will be registered as a partial fragment
- */
- detectPartialFragments(data) {
- const timeRanges = this.timeRanges;
- if (!timeRanges || data.frag.sn === "initSegment") {
- return;
- }
- const frag = data.frag;
- const fragKey = getFragmentKey(frag);
- const fragmentEntity = this.fragments[fragKey];
- if (!fragmentEntity || fragmentEntity.buffered && frag.gap) {
- return;
- }
- const isFragHint = !frag.relurl;
- Object.keys(timeRanges).forEach((elementaryStream) => {
- const streamInfo = frag.elementaryStreams[elementaryStream];
- if (!streamInfo) {
- return;
- }
- const timeRange = timeRanges[elementaryStream];
- const partial = isFragHint || streamInfo.partial === true;
- fragmentEntity.range[elementaryStream] = this.getBufferedTimes(frag, data.part, partial, timeRange);
- });
- fragmentEntity.loaded = null;
- if (Object.keys(fragmentEntity.range).length) {
- fragmentEntity.buffered = true;
- const endList = fragmentEntity.body.endList = frag.endList || fragmentEntity.body.endList;
- if (endList) {
- this.endListFragments[fragmentEntity.body.type] = fragmentEntity;
- }
- if (!isPartial(fragmentEntity)) {
- this.removeParts(frag.sn - 1, frag.type);
- }
- } else {
- this.removeFragment(fragmentEntity.body);
- }
- }
- removeParts(snToKeep, levelType) {
- const activeParts = this.activePartLists[levelType];
- if (!activeParts) {
- return;
- }
- this.activePartLists[levelType] = filterParts(activeParts, (part) => part.fragment.sn >= snToKeep);
- }
- fragBuffered(frag, force) {
- const fragKey = getFragmentKey(frag);
- let fragmentEntity = this.fragments[fragKey];
- if (!fragmentEntity && force) {
- fragmentEntity = this.fragments[fragKey] = {
- body: frag,
- appendedPTS: null,
- loaded: null,
- buffered: false,
- range: /* @__PURE__ */ Object.create(null)
- };
- if (frag.gap) {
- this.hasGaps = true;
- }
- }
- if (fragmentEntity) {
- fragmentEntity.loaded = null;
- fragmentEntity.buffered = true;
- }
- }
- getBufferedTimes(fragment, part, partial, timeRange) {
- const buffered = {
- time: [],
- partial
- };
- const startPTS = fragment.start;
- const endPTS = fragment.end;
- const minEndPTS = fragment.minEndPTS || endPTS;
- const maxStartPTS = fragment.maxStartPTS || startPTS;
- for (let i = 0; i < timeRange.length; i++) {
- const startTime = timeRange.start(i) - this.bufferPadding;
- const endTime = timeRange.end(i) + this.bufferPadding;
- if (maxStartPTS >= startTime && minEndPTS <= endTime) {
- buffered.time.push({
- startPTS: Math.max(startPTS, timeRange.start(i)),
- endPTS: Math.min(endPTS, timeRange.end(i))
- });
- break;
- } else if (startPTS < endTime && endPTS > startTime) {
- const start = Math.max(startPTS, timeRange.start(i));
- const end = Math.min(endPTS, timeRange.end(i));
- if (end > start) {
- buffered.partial = true;
- buffered.time.push({
- startPTS: start,
- endPTS: end
- });
- }
- } else if (endPTS <= startTime) {
- break;
- }
- }
- return buffered;
- }
- /**
- * Gets the partial fragment for a certain time
- */
- getPartialFragment(time) {
- let bestFragment = null;
- let timePadding;
- let startTime;
- let endTime;
- let bestOverlap = 0;
- const {
- bufferPadding,
- fragments
- } = this;
- Object.keys(fragments).forEach((key) => {
- const fragmentEntity = fragments[key];
- if (!fragmentEntity) {
- return;
- }
- if (isPartial(fragmentEntity)) {
- startTime = fragmentEntity.body.start - bufferPadding;
- endTime = fragmentEntity.body.end + bufferPadding;
- if (time >= startTime && time <= endTime) {
- timePadding = Math.min(time - startTime, endTime - time);
- if (bestOverlap <= timePadding) {
- bestFragment = fragmentEntity.body;
- bestOverlap = timePadding;
- }
- }
- }
- });
- return bestFragment;
- }
- isEndListAppended(type) {
- const lastFragmentEntity = this.endListFragments[type];
- return lastFragmentEntity !== void 0 && (lastFragmentEntity.buffered || isPartial(lastFragmentEntity));
- }
- getState(fragment) {
- const fragKey = getFragmentKey(fragment);
- const fragmentEntity = this.fragments[fragKey];
- if (fragmentEntity) {
- if (!fragmentEntity.buffered) {
- return FragmentState.APPENDING;
- } else if (isPartial(fragmentEntity)) {
- return FragmentState.PARTIAL;
- } else {
- return FragmentState.OK;
- }
- }
- return FragmentState.NOT_LOADED;
- }
- isTimeBuffered(startPTS, endPTS, timeRange) {
- let startTime;
- let endTime;
- for (let i = 0; i < timeRange.length; i++) {
- startTime = timeRange.start(i) - this.bufferPadding;
- endTime = timeRange.end(i) + this.bufferPadding;
- if (startPTS >= startTime && endPTS <= endTime) {
- return true;
- }
- if (endPTS <= startTime) {
- return false;
- }
- }
- return false;
- }
- onManifestLoading() {
- this.removeAllFragments();
- }
- onFragLoaded(event, data) {
- if (data.frag.sn === "initSegment" || data.frag.bitrateTest) {
- return;
- }
- const frag = data.frag;
- const loaded = data.part ? null : data;
- const fragKey = getFragmentKey(frag);
- this.fragments[fragKey] = {
- body: frag,
- appendedPTS: null,
- loaded,
- buffered: false,
- range: /* @__PURE__ */ Object.create(null)
- };
- }
- onBufferAppended(event, data) {
- const {
- frag,
- part,
- timeRanges,
- type
- } = data;
- if (frag.sn === "initSegment") {
- return;
- }
- const playlistType = frag.type;
- if (part) {
- let activeParts = this.activePartLists[playlistType];
- if (!activeParts) {
- this.activePartLists[playlistType] = activeParts = [];
- }
- activeParts.push(part);
- }
- this.timeRanges = timeRanges;
- const timeRange = timeRanges[type];
- this.detectEvictedFragments(type, timeRange, playlistType, part);
- }
- onFragBuffered(event, data) {
- this.detectPartialFragments(data);
- }
- hasFragment(fragment) {
- const fragKey = getFragmentKey(fragment);
- return !!this.fragments[fragKey];
- }
- hasFragments(type) {
- const {
- fragments
- } = this;
- const keys = Object.keys(fragments);
- if (!type) {
- return keys.length > 0;
- }
- for (let i = keys.length; i--; ) {
- const fragmentEntity = fragments[keys[i]];
- if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === type) {
- return true;
- }
- }
- return false;
- }
- hasParts(type) {
- var _this$activePartLists;
- return !!((_this$activePartLists = this.activePartLists[type]) != null && _this$activePartLists.length);
- }
- removeFragmentsInRange(start, end, playlistType, withGapOnly, unbufferedOnly) {
- if (withGapOnly && !this.hasGaps) {
- return;
- }
- Object.keys(this.fragments).forEach((key) => {
- const fragmentEntity = this.fragments[key];
- if (!fragmentEntity) {
- return;
- }
- const frag = fragmentEntity.body;
- if (frag.type !== playlistType || withGapOnly && !frag.gap) {
- return;
- }
- if (frag.start < end && frag.end > start && (fragmentEntity.buffered || unbufferedOnly)) {
- this.removeFragment(frag);
- }
- });
- }
- removeFragment(fragment) {
- const fragKey = getFragmentKey(fragment);
- fragment.clearElementaryStreamInfo();
- const activeParts = this.activePartLists[fragment.type];
- if (activeParts) {
- const snToRemove = fragment.sn;
- this.activePartLists[fragment.type] = filterParts(activeParts, (part) => part.fragment.sn !== snToRemove);
- }
- delete this.fragments[fragKey];
- if (fragment.endList) {
- delete this.endListFragments[fragment.type];
- }
- }
- removeAllFragments() {
- var _this$hls;
- this.fragments = /* @__PURE__ */ Object.create(null);
- this.endListFragments = /* @__PURE__ */ Object.create(null);
- this.activePartLists = /* @__PURE__ */ Object.create(null);
- this.hasGaps = false;
- const partlist = (_this$hls = this.hls) == null || (_this$hls = _this$hls.latestLevelDetails) == null ? void 0 : _this$hls.partList;
- if (partlist) {
- partlist.forEach((part) => part.clearElementaryStreamInfo());
- }
- }
- };
- function isPartial(fragmentEntity) {
- var _fragmentEntity$range, _fragmentEntity$range2, _fragmentEntity$range3;
- return fragmentEntity.buffered && !!(fragmentEntity.body.gap || (_fragmentEntity$range = fragmentEntity.range.video) != null && _fragmentEntity$range.partial || (_fragmentEntity$range2 = fragmentEntity.range.audio) != null && _fragmentEntity$range2.partial || (_fragmentEntity$range3 = fragmentEntity.range.audiovideo) != null && _fragmentEntity$range3.partial);
- }
- function getFragmentKey(fragment) {
- return `${fragment.type}_${fragment.level}_${fragment.sn}`;
- }
- function filterParts(partList, predicate) {
- return partList.filter((part) => {
- const keep = predicate(part);
- if (!keep) {
- part.clearElementaryStreamInfo();
- }
- return keep;
- });
- }
- var DecrypterAesMode = {
- cbc: 0,
- ctr: 1
- };
- var AESCrypto = class {
- constructor(subtle, iv, aesMode) {
- this.subtle = void 0;
- this.aesIV = void 0;
- this.aesMode = void 0;
- this.subtle = subtle;
- this.aesIV = iv;
- this.aesMode = aesMode;
- }
- decrypt(data, key) {
- switch (this.aesMode) {
- case DecrypterAesMode.cbc:
- return this.subtle.decrypt({
- name: "AES-CBC",
- iv: this.aesIV
- }, key, data);
- case DecrypterAesMode.ctr:
- return this.subtle.decrypt(
- {
- name: "AES-CTR",
- counter: this.aesIV,
- length: 64
- },
- //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
- key,
- data
- );
- default:
- throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
- }
- }
- };
- function removePadding(array) {
- const outputBytes = array.byteLength;
- const paddingBytes = outputBytes && new DataView(array.buffer).getUint8(outputBytes - 1);
- if (paddingBytes) {
- return array.slice(0, outputBytes - paddingBytes);
- }
- return array;
- }
- var AESDecryptor = class {
- constructor() {
- this.rcon = [0, 1, 2, 4, 8, 16, 32, 64, 128, 27, 54];
- this.subMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];
- this.invSubMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];
- this.sBox = new Uint32Array(256);
- this.invSBox = new Uint32Array(256);
- this.key = new Uint32Array(0);
- this.ksRows = 0;
- this.keySize = 0;
- this.keySchedule = void 0;
- this.invKeySchedule = void 0;
- this.initTable();
- }
- // Using view.getUint32() also swaps the byte order.
- uint8ArrayToUint32Array_(arrayBuffer) {
- const view2 = new DataView(arrayBuffer);
- const newArray = new Uint32Array(4);
- for (let i = 0; i < 4; i++) {
- newArray[i] = view2.getUint32(i * 4);
- }
- return newArray;
- }
- initTable() {
- const sBox = this.sBox;
- const invSBox = this.invSBox;
- const subMix = this.subMix;
- const subMix0 = subMix[0];
- const subMix1 = subMix[1];
- const subMix2 = subMix[2];
- const subMix3 = subMix[3];
- const invSubMix = this.invSubMix;
- const invSubMix0 = invSubMix[0];
- const invSubMix1 = invSubMix[1];
- const invSubMix2 = invSubMix[2];
- const invSubMix3 = invSubMix[3];
- const d = new Uint32Array(256);
- let x = 0;
- let xi = 0;
- let i = 0;
- for (i = 0; i < 256; i++) {
- if (i < 128) {
- d[i] = i << 1;
- } else {
- d[i] = i << 1 ^ 283;
- }
- }
- for (i = 0; i < 256; i++) {
- let sx = xi ^ xi << 1 ^ xi << 2 ^ xi << 3 ^ xi << 4;
- sx = sx >>> 8 ^ sx & 255 ^ 99;
- sBox[x] = sx;
- invSBox[sx] = x;
- const x2 = d[x];
- const x4 = d[x2];
- const x8 = d[x4];
- let t = d[sx] * 257 ^ sx * 16843008;
- subMix0[x] = t << 24 | t >>> 8;
- subMix1[x] = t << 16 | t >>> 16;
- subMix2[x] = t << 8 | t >>> 24;
- subMix3[x] = t;
- t = x8 * 16843009 ^ x4 * 65537 ^ x2 * 257 ^ x * 16843008;
- invSubMix0[sx] = t << 24 | t >>> 8;
- invSubMix1[sx] = t << 16 | t >>> 16;
- invSubMix2[sx] = t << 8 | t >>> 24;
- invSubMix3[sx] = t;
- if (!x) {
- x = xi = 1;
- } else {
- x = x2 ^ d[d[d[x8 ^ x2]]];
- xi ^= d[d[xi]];
- }
- }
- }
- expandKey(keyBuffer) {
- const key = this.uint8ArrayToUint32Array_(keyBuffer);
- let sameKey = true;
- let offset = 0;
- while (offset < key.length && sameKey) {
- sameKey = key[offset] === this.key[offset];
- offset++;
- }
- if (sameKey) {
- return;
- }
- this.key = key;
- const keySize = this.keySize = key.length;
- if (keySize !== 4 && keySize !== 6 && keySize !== 8) {
- throw new Error("Invalid aes key size=" + keySize);
- }
- const ksRows = this.ksRows = (keySize + 6 + 1) * 4;
- let ksRow;
- let invKsRow;
- const keySchedule = this.keySchedule = new Uint32Array(ksRows);
- const invKeySchedule = this.invKeySchedule = new Uint32Array(ksRows);
- const sbox = this.sBox;
- const rcon = this.rcon;
- const invSubMix = this.invSubMix;
- const invSubMix0 = invSubMix[0];
- const invSubMix1 = invSubMix[1];
- const invSubMix2 = invSubMix[2];
- const invSubMix3 = invSubMix[3];
- let prev;
- let t;
- for (ksRow = 0; ksRow < ksRows; ksRow++) {
- if (ksRow < keySize) {
- prev = keySchedule[ksRow] = key[ksRow];
- continue;
- }
- t = prev;
- if (ksRow % keySize === 0) {
- t = t << 8 | t >>> 24;
- t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 255] << 16 | sbox[t >>> 8 & 255] << 8 | sbox[t & 255];
- t ^= rcon[ksRow / keySize | 0] << 24;
- } else if (keySize > 6 && ksRow % keySize === 4) {
- t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 255] << 16 | sbox[t >>> 8 & 255] << 8 | sbox[t & 255];
- }
- keySchedule[ksRow] = prev = (keySchedule[ksRow - keySize] ^ t) >>> 0;
- }
- for (invKsRow = 0; invKsRow < ksRows; invKsRow++) {
- ksRow = ksRows - invKsRow;
- if (invKsRow & 3) {
- t = keySchedule[ksRow];
- } else {
- t = keySchedule[ksRow - 4];
- }
- if (invKsRow < 4 || ksRow <= 4) {
- invKeySchedule[invKsRow] = t;
- } else {
- invKeySchedule[invKsRow] = invSubMix0[sbox[t >>> 24]] ^ invSubMix1[sbox[t >>> 16 & 255]] ^ invSubMix2[sbox[t >>> 8 & 255]] ^ invSubMix3[sbox[t & 255]];
- }
- invKeySchedule[invKsRow] = invKeySchedule[invKsRow] >>> 0;
- }
- }
- // Adding this as a method greatly improves performance.
- networkToHostOrderSwap(word) {
- return word << 24 | (word & 65280) << 8 | (word & 16711680) >> 8 | word >>> 24;
- }
- decrypt(inputArrayBuffer, offset, aesIV) {
- const nRounds = this.keySize + 6;
- const invKeySchedule = this.invKeySchedule;
- const invSBOX = this.invSBox;
- const invSubMix = this.invSubMix;
- const invSubMix0 = invSubMix[0];
- const invSubMix1 = invSubMix[1];
- const invSubMix2 = invSubMix[2];
- const invSubMix3 = invSubMix[3];
- const initVector = this.uint8ArrayToUint32Array_(aesIV);
- let initVector0 = initVector[0];
- let initVector1 = initVector[1];
- let initVector2 = initVector[2];
- let initVector3 = initVector[3];
- const inputInt32 = new Int32Array(inputArrayBuffer);
- const outputInt32 = new Int32Array(inputInt32.length);
- let t0, t1, t2, t3;
- let s0, s1, s2, s3;
- let inputWords0, inputWords1, inputWords2, inputWords3;
- let ksRow, i;
- const swapWord = this.networkToHostOrderSwap;
- while (offset < inputInt32.length) {
- inputWords0 = swapWord(inputInt32[offset]);
- inputWords1 = swapWord(inputInt32[offset + 1]);
- inputWords2 = swapWord(inputInt32[offset + 2]);
- inputWords3 = swapWord(inputInt32[offset + 3]);
- s0 = inputWords0 ^ invKeySchedule[0];
- s1 = inputWords3 ^ invKeySchedule[1];
- s2 = inputWords2 ^ invKeySchedule[2];
- s3 = inputWords1 ^ invKeySchedule[3];
- ksRow = 4;
- for (i = 1; i < nRounds; i++) {
- t0 = invSubMix0[s0 >>> 24] ^ invSubMix1[s1 >> 16 & 255] ^ invSubMix2[s2 >> 8 & 255] ^ invSubMix3[s3 & 255] ^ invKeySchedule[ksRow];
- t1 = invSubMix0[s1 >>> 24] ^ invSubMix1[s2 >> 16 & 255] ^ invSubMix2[s3 >> 8 & 255] ^ invSubMix3[s0 & 255] ^ invKeySchedule[ksRow + 1];
- t2 = invSubMix0[s2 >>> 24] ^ invSubMix1[s3 >> 16 & 255] ^ invSubMix2[s0 >> 8 & 255] ^ invSubMix3[s1 & 255] ^ invKeySchedule[ksRow + 2];
- t3 = invSubMix0[s3 >>> 24] ^ invSubMix1[s0 >> 16 & 255] ^ invSubMix2[s1 >> 8 & 255] ^ invSubMix3[s2 & 255] ^ invKeySchedule[ksRow + 3];
- s0 = t0;
- s1 = t1;
- s2 = t2;
- s3 = t3;
- ksRow = ksRow + 4;
- }
- t0 = invSBOX[s0 >>> 24] << 24 ^ invSBOX[s1 >> 16 & 255] << 16 ^ invSBOX[s2 >> 8 & 255] << 8 ^ invSBOX[s3 & 255] ^ invKeySchedule[ksRow];
- t1 = invSBOX[s1 >>> 24] << 24 ^ invSBOX[s2 >> 16 & 255] << 16 ^ invSBOX[s3 >> 8 & 255] << 8 ^ invSBOX[s0 & 255] ^ invKeySchedule[ksRow + 1];
- t2 = invSBOX[s2 >>> 24] << 24 ^ invSBOX[s3 >> 16 & 255] << 16 ^ invSBOX[s0 >> 8 & 255] << 8 ^ invSBOX[s1 & 255] ^ invKeySchedule[ksRow + 2];
- t3 = invSBOX[s3 >>> 24] << 24 ^ invSBOX[s0 >> 16 & 255] << 16 ^ invSBOX[s1 >> 8 & 255] << 8 ^ invSBOX[s2 & 255] ^ invKeySchedule[ksRow + 3];
- outputInt32[offset] = swapWord(t0 ^ initVector0);
- outputInt32[offset + 1] = swapWord(t3 ^ initVector1);
- outputInt32[offset + 2] = swapWord(t2 ^ initVector2);
- outputInt32[offset + 3] = swapWord(t1 ^ initVector3);
- initVector0 = inputWords0;
- initVector1 = inputWords1;
- initVector2 = inputWords2;
- initVector3 = inputWords3;
- offset = offset + 4;
- }
- return outputInt32.buffer;
- }
- };
- var FastAESKey = class {
- constructor(subtle, key, aesMode) {
- this.subtle = void 0;
- this.key = void 0;
- this.aesMode = void 0;
- this.subtle = subtle;
- this.key = key;
- this.aesMode = aesMode;
- }
- expandKey() {
- const subtleAlgoName = getSubtleAlgoName(this.aesMode);
- return this.subtle.importKey("raw", this.key, {
- name: subtleAlgoName
- }, false, ["encrypt", "decrypt"]);
- }
- };
- function getSubtleAlgoName(aesMode) {
- switch (aesMode) {
- case DecrypterAesMode.cbc:
- return "AES-CBC";
- case DecrypterAesMode.ctr:
- return "AES-CTR";
- default:
- throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
- }
- }
- var CHUNK_SIZE = 16;
- var Decrypter = class {
- constructor(config, {
- removePKCS7Padding = true
- } = {}) {
- this.logEnabled = true;
- this.removePKCS7Padding = void 0;
- this.subtle = null;
- this.softwareDecrypter = null;
- this.key = null;
- this.fastAesKey = null;
- this.remainderData = null;
- this.currentIV = null;
- this.currentResult = null;
- this.useSoftware = void 0;
- this.enableSoftwareAES = void 0;
- this.enableSoftwareAES = config.enableSoftwareAES;
- this.removePKCS7Padding = removePKCS7Padding;
- if (removePKCS7Padding) {
- try {
- const browserCrypto = self.crypto;
- if (browserCrypto) {
- this.subtle = browserCrypto.subtle || browserCrypto.webkitSubtle;
- }
- } catch (e) {
- }
- }
- this.useSoftware = !this.subtle;
- }
- destroy() {
- this.subtle = null;
- this.softwareDecrypter = null;
- this.key = null;
- this.fastAesKey = null;
- this.remainderData = null;
- this.currentIV = null;
- this.currentResult = null;
- }
- isSync() {
- return this.useSoftware;
- }
- flush() {
- const {
- currentResult,
- remainderData
- } = this;
- if (!currentResult || remainderData) {
- this.reset();
- return null;
- }
- const data = new Uint8Array(currentResult);
- this.reset();
- if (this.removePKCS7Padding) {
- return removePadding(data);
- }
- return data;
- }
- reset() {
- this.currentResult = null;
- this.currentIV = null;
- this.remainderData = null;
- if (this.softwareDecrypter) {
- this.softwareDecrypter = null;
- }
- }
- decrypt(data, key, iv, aesMode) {
- if (this.useSoftware) {
- return new Promise((resolve, reject) => {
- const dataView = ArrayBuffer.isView(data) ? data : new Uint8Array(data);
- this.softwareDecrypt(dataView, key, iv, aesMode);
- const decryptResult = this.flush();
- if (decryptResult) {
- resolve(decryptResult.buffer);
- } else {
- reject(new Error("[softwareDecrypt] Failed to decrypt data"));
- }
- });
- }
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
- }
- // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
- // data is handled in the flush() call
- softwareDecrypt(data, key, iv, aesMode) {
- const {
- currentIV,
- currentResult,
- remainderData
- } = this;
- if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
- logger.warn("SoftwareDecrypt: can only handle AES-128-CBC");
- return null;
- }
- this.logOnce("JS AES decrypt");
- if (remainderData) {
- data = appendUint8Array(remainderData, data);
- this.remainderData = null;
- }
- const currentChunk = this.getValidChunk(data);
- if (!currentChunk.length) {
- return null;
- }
- if (currentIV) {
- iv = currentIV;
- }
- let softwareDecrypter = this.softwareDecrypter;
- if (!softwareDecrypter) {
- softwareDecrypter = this.softwareDecrypter = new AESDecryptor();
- }
- softwareDecrypter.expandKey(key);
- const result = currentResult;
- this.currentResult = softwareDecrypter.decrypt(currentChunk.buffer, 0, iv);
- this.currentIV = currentChunk.slice(-16).buffer;
- if (!result) {
- return null;
- }
- return result;
- }
- webCryptoDecrypt(data, key, iv, aesMode) {
- if (this.key !== key || !this.fastAesKey) {
- if (!this.subtle) {
- return Promise.resolve(this.onWebCryptoError(data, key, iv, aesMode));
- }
- this.key = key;
- this.fastAesKey = new FastAESKey(this.subtle, key, aesMode);
- }
- return this.fastAesKey.expandKey().then((aesKey) => {
- if (!this.subtle) {
- return Promise.reject(new Error("web crypto not initialized"));
- }
- this.logOnce("WebCrypto AES decrypt");
- const crypto2 = new AESCrypto(this.subtle, new Uint8Array(iv), aesMode);
- return crypto2.decrypt(data.buffer, aesKey);
- }).catch((err) => {
- logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
- return this.onWebCryptoError(data, key, iv, aesMode);
- });
- }
- onWebCryptoError(data, key, iv, aesMode) {
- const enableSoftwareAES = this.enableSoftwareAES;
- if (enableSoftwareAES) {
- this.useSoftware = true;
- this.logEnabled = true;
- this.softwareDecrypt(data, key, iv, aesMode);
- const decryptResult = this.flush();
- if (decryptResult) {
- return decryptResult.buffer;
- }
- }
- throw new Error("WebCrypto" + (enableSoftwareAES ? " and softwareDecrypt" : "") + ": failed to decrypt data");
- }
- getValidChunk(data) {
- let currentChunk = data;
- const splitPoint = data.length - data.length % CHUNK_SIZE;
- if (splitPoint !== data.length) {
- currentChunk = data.slice(0, splitPoint);
- this.remainderData = data.slice(splitPoint);
- }
- return currentChunk;
- }
- logOnce(msg) {
- if (!this.logEnabled) {
- return;
- }
- logger.log(`[decrypter]: ${msg}`);
- this.logEnabled = false;
- }
- };
- var MIN_CHUNK_SIZE = Math.pow(2, 17);
- var FragmentLoader = class {
- constructor(config) {
- this.config = void 0;
- this.loader = null;
- this.partLoadTimeout = -1;
- this.config = config;
- }
- destroy() {
- if (this.loader) {
- this.loader.destroy();
- this.loader = null;
- }
- }
- abort() {
- if (this.loader) {
- this.loader.abort();
- }
- }
- load(frag, onProgress) {
- const url = frag.url;
- if (!url) {
- return Promise.reject(new LoadError({
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.FRAG_LOAD_ERROR,
- fatal: false,
- frag,
- error: new Error(`Fragment does not have a ${url ? "part list" : "url"}`),
- networkDetails: null
- }));
- }
- this.abort();
- const config = this.config;
- const FragmentILoader = config.fLoader;
- const DefaultILoader = config.loader;
- return new Promise((resolve, reject) => {
- if (this.loader) {
- this.loader.destroy();
- }
- if (frag.gap) {
- if (frag.tagList.some((tags) => tags[0] === "GAP")) {
- reject(createGapLoadError(frag));
- return;
- } else {
- frag.gap = false;
- }
- }
- const loader = this.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
- const loaderContext = createLoaderContext(frag);
- frag.loader = loader;
- const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
- const loaderConfig = {
- loadPolicy,
- timeout: loadPolicy.maxLoadTimeMs,
- maxRetry: 0,
- retryDelay: 0,
- maxRetryDelay: 0,
- highWaterMark: frag.sn === "initSegment" ? Infinity : MIN_CHUNK_SIZE
- };
- frag.stats = loader.stats;
- const callbacks = {
- onSuccess: (response, stats, context, networkDetails) => {
- this.resetLoader(frag, loader);
- let payload = response.data;
- if (context.resetIV && frag.decryptdata) {
- frag.decryptdata.iv = new Uint8Array(payload.slice(0, 16));
- payload = payload.slice(16);
- }
- resolve({
- frag,
- part: null,
- payload,
- networkDetails
- });
- },
- onError: (response, context, networkDetails, stats) => {
- this.resetLoader(frag, loader);
- reject(new LoadError({
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.FRAG_LOAD_ERROR,
- fatal: false,
- frag,
- response: _objectSpread2({
- url,
- data: void 0
- }, response),
- error: new Error(`HTTP Error ${response.code} ${response.text}`),
- networkDetails,
- stats
- }));
- },
- onAbort: (stats, context, networkDetails) => {
- this.resetLoader(frag, loader);
- reject(new LoadError({
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.INTERNAL_ABORTED,
- fatal: false,
- frag,
- error: new Error("Aborted"),
- networkDetails,
- stats
- }));
- },
- onTimeout: (stats, context, networkDetails) => {
- this.resetLoader(frag, loader);
- reject(new LoadError({
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.FRAG_LOAD_TIMEOUT,
- fatal: false,
- frag,
- error: new Error(`Timeout after ${loaderConfig.timeout}ms`),
- networkDetails,
- stats
- }));
- }
- };
- if (onProgress) {
- callbacks.onProgress = (stats, context, data, networkDetails) => onProgress({
- frag,
- part: null,
- payload: data,
- networkDetails
- });
- }
- loader.load(loaderContext, loaderConfig, callbacks);
- });
- }
- loadPart(frag, part, onProgress) {
- this.abort();
- const config = this.config;
- const FragmentILoader = config.fLoader;
- const DefaultILoader = config.loader;
- return new Promise((resolve, reject) => {
- if (this.loader) {
- this.loader.destroy();
- }
- if (frag.gap || part.gap) {
- reject(createGapLoadError(frag, part));
- return;
- }
- const loader = this.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
- const loaderContext = createLoaderContext(frag, part);
- frag.loader = loader;
- const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
- const loaderConfig = {
- loadPolicy,
- timeout: loadPolicy.maxLoadTimeMs,
- maxRetry: 0,
- retryDelay: 0,
- maxRetryDelay: 0,
- highWaterMark: MIN_CHUNK_SIZE
- };
- part.stats = loader.stats;
- loader.load(loaderContext, loaderConfig, {
- onSuccess: (response, stats, context, networkDetails) => {
- this.resetLoader(frag, loader);
- this.updateStatsFromPart(frag, part);
- const partLoadedData = {
- frag,
- part,
- payload: response.data,
- networkDetails
- };
- onProgress(partLoadedData);
- resolve(partLoadedData);
- },
- onError: (response, context, networkDetails, stats) => {
- this.resetLoader(frag, loader);
- reject(new LoadError({
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.FRAG_LOAD_ERROR,
- fatal: false,
- frag,
- part,
- response: _objectSpread2({
- url: loaderContext.url,
- data: void 0
- }, response),
- error: new Error(`HTTP Error ${response.code} ${response.text}`),
- networkDetails,
- stats
- }));
- },
- onAbort: (stats, context, networkDetails) => {
- frag.stats.aborted = part.stats.aborted;
- this.resetLoader(frag, loader);
- reject(new LoadError({
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.INTERNAL_ABORTED,
- fatal: false,
- frag,
- part,
- error: new Error("Aborted"),
- networkDetails,
- stats
- }));
- },
- onTimeout: (stats, context, networkDetails) => {
- this.resetLoader(frag, loader);
- reject(new LoadError({
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.FRAG_LOAD_TIMEOUT,
- fatal: false,
- frag,
- part,
- error: new Error(`Timeout after ${loaderConfig.timeout}ms`),
- networkDetails,
- stats
- }));
- }
- });
- });
- }
- updateStatsFromPart(frag, part) {
- const fragStats = frag.stats;
- const partStats = part.stats;
- const partTotal = partStats.total;
- fragStats.loaded += partStats.loaded;
- if (partTotal) {
- const estTotalParts = Math.round(frag.duration / part.duration);
- const estLoadedParts = Math.min(Math.round(fragStats.loaded / partTotal), estTotalParts);
- const estRemainingParts = estTotalParts - estLoadedParts;
- const estRemainingBytes = estRemainingParts * Math.round(fragStats.loaded / estLoadedParts);
- fragStats.total = fragStats.loaded + estRemainingBytes;
- } else {
- fragStats.total = Math.max(fragStats.loaded, fragStats.total);
- }
- const fragLoading = fragStats.loading;
- const partLoading = partStats.loading;
- if (fragLoading.start) {
- fragLoading.first += partLoading.first - partLoading.start;
- } else {
- fragLoading.start = partLoading.start;
- fragLoading.first = partLoading.first;
- }
- fragLoading.end = partLoading.end;
- }
- resetLoader(frag, loader) {
- frag.loader = null;
- if (this.loader === loader) {
- self.clearTimeout(this.partLoadTimeout);
- this.loader = null;
- }
- loader.destroy();
- }
- };
- function createLoaderContext(frag, part = null) {
- const segment = part || frag;
- const loaderContext = {
- frag,
- part,
- responseType: "arraybuffer",
- url: segment.url,
- headers: {},
- rangeStart: 0,
- rangeEnd: 0
- };
- const start = segment.byteRangeStartOffset;
- const end = segment.byteRangeEndOffset;
- if (isFiniteNumber(start) && isFiniteNumber(end)) {
- var _frag$decryptdata;
- let byteRangeStart = start;
- let byteRangeEnd = end;
- if (frag.sn === "initSegment" && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
- const fragmentLen = end - start;
- if (fragmentLen % 16) {
- byteRangeEnd = end + (16 - fragmentLen % 16);
- }
- if (start !== 0) {
- loaderContext.resetIV = true;
- byteRangeStart = start - 16;
- }
- }
- loaderContext.rangeStart = byteRangeStart;
- loaderContext.rangeEnd = byteRangeEnd;
- }
- return loaderContext;
- }
- function createGapLoadError(frag, part) {
- const error = new Error(`GAP ${frag.gap ? "tag" : "attribute"} found`);
- const errorData = {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.FRAG_GAP,
- fatal: false,
- frag,
- error,
- networkDetails: null
- };
- if (part) {
- errorData.part = part;
- }
- (part ? part : frag).stats.aborted = true;
- return new LoadError(errorData);
- }
- function isMethodFullSegmentAesCbc(method) {
- return method === "AES-128" || method === "AES-256";
- }
- var LoadError = class extends Error {
- constructor(data) {
- super(data.error.message);
- this.data = void 0;
- this.data = data;
- }
- };
- var TaskLoop = class extends Logger {
- constructor(label, logger2) {
- super(label, logger2);
- this._boundTick = void 0;
- this._tickTimer = null;
- this._tickInterval = null;
- this._tickCallCount = 0;
- this._boundTick = this.tick.bind(this);
- }
- destroy() {
- this.onHandlerDestroying();
- this.onHandlerDestroyed();
- }
- onHandlerDestroying() {
- this.clearNextTick();
- this.clearInterval();
- }
- onHandlerDestroyed() {
- }
- hasInterval() {
- return !!this._tickInterval;
- }
- hasNextTick() {
- return !!this._tickTimer;
- }
- /**
- * @param millis - Interval time (ms)
- * @eturns True when interval has been scheduled, false when already scheduled (no effect)
- */
- setInterval(millis) {
- if (!this._tickInterval) {
- this._tickCallCount = 0;
- this._tickInterval = self.setInterval(this._boundTick, millis);
- return true;
- }
- return false;
- }
- /**
- * @returns True when interval was cleared, false when none was set (no effect)
- */
- clearInterval() {
- if (this._tickInterval) {
- self.clearInterval(this._tickInterval);
- this._tickInterval = null;
- return true;
- }
- return false;
- }
- /**
- * @returns True when timeout was cleared, false when none was set (no effect)
- */
- clearNextTick() {
- if (this._tickTimer) {
- self.clearTimeout(this._tickTimer);
- this._tickTimer = null;
- return true;
- }
- return false;
- }
- /**
- * Will call the subclass doTick implementation in this main loop tick
- * or in the next one (via setTimeout(,0)) in case it has already been called
- * in this tick (in case this is a re-entrant call).
- */
- tick() {
- this._tickCallCount++;
- if (this._tickCallCount === 1) {
- this.doTick();
- if (this._tickCallCount > 1) {
- this.tickImmediate();
- }
- this._tickCallCount = 0;
- }
- }
- tickImmediate() {
- this.clearNextTick();
- this._tickTimer = self.setTimeout(this._boundTick, 0);
- }
- /**
- * For subclass to implement task logic
- * @abstract
- */
- doTick() {
- }
- };
- var ChunkMetadata = class {
- constructor(level, sn, id, size = 0, part = -1, partial = false) {
- this.level = void 0;
- this.sn = void 0;
- this.part = void 0;
- this.id = void 0;
- this.size = void 0;
- this.partial = void 0;
- this.transmuxing = getNewPerformanceTiming();
- this.buffering = {
- audio: getNewPerformanceTiming(),
- video: getNewPerformanceTiming(),
- audiovideo: getNewPerformanceTiming()
- };
- this.level = level;
- this.sn = sn;
- this.id = id;
- this.size = size;
- this.part = part;
- this.partial = partial;
- }
- };
- function getNewPerformanceTiming() {
- return {
- start: 0,
- executeStart: 0,
- executeEnd: 0,
- end: 0
- };
- }
- var noopBuffered = {
- length: 0,
- start: () => 0,
- end: () => 0
- };
- var BufferHelper = class _BufferHelper {
- /**
- * Return true if `media`'s buffered include `position`
- */
- static isBuffered(media, position) {
- if (media) {
- const buffered = _BufferHelper.getBuffered(media);
- for (let i = buffered.length; i--; ) {
- if (position >= buffered.start(i) && position <= buffered.end(i)) {
- return true;
- }
- }
- }
- return false;
- }
- static bufferedRanges(media) {
- if (media) {
- const timeRanges = _BufferHelper.getBuffered(media);
- return _BufferHelper.timeRangesToArray(timeRanges);
- }
- return [];
- }
- static timeRangesToArray(timeRanges) {
- const buffered = [];
- for (let i = 0; i < timeRanges.length; i++) {
- buffered.push({
- start: timeRanges.start(i),
- end: timeRanges.end(i)
- });
- }
- return buffered;
- }
- static bufferInfo(media, pos, maxHoleDuration) {
- if (media) {
- const buffered = _BufferHelper.bufferedRanges(media);
- if (buffered.length) {
- return _BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
- }
- }
- return {
- len: 0,
- start: pos,
- end: pos,
- bufferedIndex: -1
- };
- }
- static bufferedInfo(buffered, pos, maxHoleDuration) {
- pos = Math.max(0, pos);
- if (buffered.length > 1) {
- buffered.sort((a, b) => a.start - b.start || b.end - a.end);
- }
- let bufferedIndex = -1;
- let buffered2 = [];
- if (maxHoleDuration) {
- for (let i = 0; i < buffered.length; i++) {
- if (pos >= buffered[i].start && pos <= buffered[i].end) {
- bufferedIndex = i;
- }
- const buf2len = buffered2.length;
- if (buf2len) {
- const buf2end = buffered2[buf2len - 1].end;
- if (buffered[i].start - buf2end < maxHoleDuration) {
- if (buffered[i].end > buf2end) {
- buffered2[buf2len - 1].end = buffered[i].end;
- }
- } else {
- buffered2.push(buffered[i]);
- }
- } else {
- buffered2.push(buffered[i]);
- }
- }
- } else {
- buffered2 = buffered;
- }
- let bufferLen = 0;
- let nextStart;
- let bufferStart = pos;
- let bufferEnd = pos;
- for (let i = 0; i < buffered2.length; i++) {
- const start = buffered2[i].start;
- const end = buffered2[i].end;
- if (bufferedIndex === -1 && pos >= start && pos <= end) {
- bufferedIndex = i;
- }
- if (pos + maxHoleDuration >= start && pos < end) {
- bufferStart = start;
- bufferEnd = end;
- bufferLen = bufferEnd - pos;
- } else if (pos + maxHoleDuration < start) {
- nextStart = start;
- break;
- }
- }
- return {
- len: bufferLen,
- start: bufferStart || 0,
- end: bufferEnd || 0,
- nextStart,
- buffered,
- bufferedIndex
- };
- }
- /**
- * Safe method to get buffered property.
- * SourceBuffer.buffered may throw if SourceBuffer is removed from it's MediaSource
- */
- static getBuffered(media) {
- try {
- return media.buffered || noopBuffered;
- } catch (e) {
- logger.log("failed to get media.buffered", e);
- return noopBuffered;
- }
- }
- };
- var VARIABLE_REPLACEMENT_REGEX = /\{\$([a-zA-Z0-9-_]+)\}/g;
- function hasVariableReferences(str) {
- return VARIABLE_REPLACEMENT_REGEX.test(str);
- }
- function substituteVariables(parsed, value) {
- if (parsed.variableList !== null || parsed.hasVariableRefs) {
- const variableList = parsed.variableList;
- return value.replace(VARIABLE_REPLACEMENT_REGEX, (variableReference) => {
- const variableName = variableReference.substring(2, variableReference.length - 1);
- const variableValue = variableList == null ? void 0 : variableList[variableName];
- if (variableValue === void 0) {
- parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`Missing preceding EXT-X-DEFINE tag for Variable Reference: "${variableName}"`));
- return variableReference;
- }
- return variableValue;
- });
- }
- return value;
- }
- function addVariableDefinition(parsed, attr, parentUrl) {
- let variableList = parsed.variableList;
- if (!variableList) {
- parsed.variableList = variableList = {};
- }
- let NAME;
- let VALUE;
- if ("QUERYPARAM" in attr) {
- NAME = attr.QUERYPARAM;
- try {
- const searchParams = new self.URL(parentUrl).searchParams;
- if (searchParams.has(NAME)) {
- VALUE = searchParams.get(NAME);
- } else {
- throw new Error(`"${NAME}" does not match any query parameter in URI: "${parentUrl}"`);
- }
- } catch (error) {
- parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE QUERYPARAM: ${error.message}`));
- }
- } else {
- NAME = attr.NAME;
- VALUE = attr.VALUE;
- }
- if (NAME in variableList) {
- parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE duplicate Variable Name declarations: "${NAME}"`));
- } else {
- variableList[NAME] = VALUE || "";
- }
- }
- function importVariableDefinition(parsed, attr, sourceVariableList) {
- const IMPORT = attr.IMPORT;
- if (sourceVariableList && IMPORT in sourceVariableList) {
- let variableList = parsed.variableList;
- if (!variableList) {
- parsed.variableList = variableList = {};
- }
- variableList[IMPORT] = sourceVariableList[IMPORT];
- } else {
- parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: "${IMPORT}"`));
- }
- }
- var DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
- var ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
- var AttrList = class _AttrList {
- constructor(attrs, parsed) {
- if (typeof attrs === "string") {
- attrs = _AttrList.parseAttrList(attrs, parsed);
- }
- _extends(this, attrs);
- }
- get clientAttrs() {
- return Object.keys(this).filter((attr) => attr.substring(0, 2) === "X-");
- }
- decimalInteger(attrName) {
- const intValue = parseInt(this[attrName], 10);
- if (intValue > Number.MAX_SAFE_INTEGER) {
- return Infinity;
- }
- return intValue;
- }
- hexadecimalInteger(attrName) {
- if (this[attrName]) {
- let stringValue = (this[attrName] || "0x").slice(2);
- stringValue = (stringValue.length & 1 ? "0" : "") + stringValue;
- const value = new Uint8Array(stringValue.length / 2);
- for (let i = 0; i < stringValue.length / 2; i++) {
- value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16);
- }
- return value;
- }
- return null;
- }
- hexadecimalIntegerAsNumber(attrName) {
- const intValue = parseInt(this[attrName], 16);
- if (intValue > Number.MAX_SAFE_INTEGER) {
- return Infinity;
- }
- return intValue;
- }
- decimalFloatingPoint(attrName) {
- return parseFloat(this[attrName]);
- }
- optionalFloat(attrName, defaultValue) {
- const value = this[attrName];
- return value ? parseFloat(value) : defaultValue;
- }
- enumeratedString(attrName) {
- return this[attrName];
- }
- enumeratedStringList(attrName, dict) {
- const attrValue = this[attrName];
- return (attrValue ? attrValue.split(/[ ,]+/) : []).reduce((result, identifier) => {
- result[identifier.toLowerCase()] = true;
- return result;
- }, dict);
- }
- bool(attrName) {
- return this[attrName] === "YES";
- }
- decimalResolution(attrName) {
- const res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]);
- if (res === null) {
- return void 0;
- }
- return {
- width: parseInt(res[1], 10),
- height: parseInt(res[2], 10)
- };
- }
- static parseAttrList(input, parsed) {
- let match;
- const attrs = {};
- const quote = '"';
- ATTR_LIST_REGEX.lastIndex = 0;
- while ((match = ATTR_LIST_REGEX.exec(input)) !== null) {
- const name = match[1].trim();
- let value = match[2];
- const quotedString = value.indexOf(quote) === 0 && value.lastIndexOf(quote) === value.length - 1;
- let hexadecimalSequence = false;
- if (quotedString) {
- value = value.slice(1, -1);
- } else {
- switch (name) {
- case "IV":
- case "SCTE35-CMD":
- case "SCTE35-IN":
- case "SCTE35-OUT":
- hexadecimalSequence = true;
- }
- }
- if (parsed && (quotedString || hexadecimalSequence)) {
- {
- value = substituteVariables(parsed, value);
- }
- } else if (!hexadecimalSequence && !quotedString) {
- switch (name) {
- case "CLOSED-CAPTIONS":
- if (value === "NONE") {
- break;
- }
- case "ALLOWED-CPC":
- case "CLASS":
- case "ASSOC-LANGUAGE":
- case "AUDIO":
- case "BYTERANGE":
- case "CHANNELS":
- case "CHARACTERISTICS":
- case "CODECS":
- case "DATA-ID":
- case "END-DATE":
- case "GROUP-ID":
- case "ID":
- case "IMPORT":
- case "INSTREAM-ID":
- case "KEYFORMAT":
- case "KEYFORMATVERSIONS":
- case "LANGUAGE":
- case "NAME":
- case "PATHWAY-ID":
- case "QUERYPARAM":
- case "RECENTLY-REMOVED-DATERANGES":
- case "SERVER-URI":
- case "STABLE-RENDITION-ID":
- case "STABLE-VARIANT-ID":
- case "START-DATE":
- case "SUBTITLES":
- case "SUPPLEMENTAL-CODECS":
- case "URI":
- case "VALUE":
- case "VIDEO":
- case "X-ASSET-LIST":
- case "X-ASSET-URI":
- logger.warn(`${input}: attribute ${name} is missing quotes`);
- }
- }
- attrs[name] = value;
- }
- return attrs;
- }
- };
- var CLASS_INTERSTITIAL = "com.apple.hls.interstitial";
- function isDateRangeCueAttribute(attrName) {
- return attrName !== "ID" && attrName !== "CLASS" && attrName !== "CUE" && attrName !== "START-DATE" && attrName !== "DURATION" && attrName !== "END-DATE" && attrName !== "END-ON-NEXT";
- }
- function isSCTE35Attribute(attrName) {
- return attrName === "SCTE35-OUT" || attrName === "SCTE35-IN" || attrName === "SCTE35-CMD";
- }
- var DateRange = class {
- constructor(dateRangeAttr, dateRangeWithSameId, tagCount = 0) {
- var _dateRangeWithSameId$;
- this.attr = void 0;
- this.tagAnchor = void 0;
- this.tagOrder = void 0;
- this._startDate = void 0;
- this._endDate = void 0;
- this._dateAtEnd = void 0;
- this._cue = void 0;
- this._badValueForSameId = void 0;
- this.tagAnchor = (dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.tagAnchor) || null;
- this.tagOrder = (_dateRangeWithSameId$ = dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.tagOrder) != null ? _dateRangeWithSameId$ : tagCount;
- if (dateRangeWithSameId) {
- const previousAttr = dateRangeWithSameId.attr;
- for (const key in previousAttr) {
- if (Object.prototype.hasOwnProperty.call(dateRangeAttr, key) && dateRangeAttr[key] !== previousAttr[key]) {
- logger.warn(`DATERANGE tag attribute: "${key}" does not match for tags with ID: "${dateRangeAttr.ID}"`);
- this._badValueForSameId = key;
- break;
- }
- }
- dateRangeAttr = _extends(new AttrList({}), previousAttr, dateRangeAttr);
- }
- this.attr = dateRangeAttr;
- if (dateRangeWithSameId) {
- this._startDate = dateRangeWithSameId._startDate;
- this._cue = dateRangeWithSameId._cue;
- this._endDate = dateRangeWithSameId._endDate;
- this._dateAtEnd = dateRangeWithSameId._dateAtEnd;
- } else {
- this._startDate = new Date(dateRangeAttr["START-DATE"]);
- }
- if ("END-DATE" in this.attr) {
- const endDate = (dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.endDate) || new Date(this.attr["END-DATE"]);
- if (isFiniteNumber(endDate.getTime())) {
- this._endDate = endDate;
- }
- }
- }
- get id() {
- return this.attr.ID;
- }
- get class() {
- return this.attr.CLASS;
- }
- get cue() {
- const _cue = this._cue;
- if (_cue === void 0) {
- return this._cue = this.attr.enumeratedStringList(this.attr.CUE ? "CUE" : "X-CUE", {
- pre: false,
- post: false,
- once: false
- });
- }
- return _cue;
- }
- get startTime() {
- const {
- tagAnchor
- } = this;
- if (tagAnchor === null || tagAnchor.programDateTime === null) {
- logger.warn(`Expected tagAnchor Fragment with PDT set for DateRange "${this.id}": ${tagAnchor}`);
- return NaN;
- }
- return tagAnchor.start + (this.startDate.getTime() - tagAnchor.programDateTime) / 1e3;
- }
- get startDate() {
- return this._startDate;
- }
- get endDate() {
- const dateAtEnd = this._endDate || this._dateAtEnd;
- if (dateAtEnd) {
- return dateAtEnd;
- }
- const duration = this.duration;
- if (duration !== null) {
- return this._dateAtEnd = new Date(this._startDate.getTime() + duration * 1e3);
- }
- return null;
- }
- get duration() {
- if ("DURATION" in this.attr) {
- const duration = this.attr.decimalFloatingPoint("DURATION");
- if (isFiniteNumber(duration)) {
- return duration;
- }
- } else if (this._endDate) {
- return (this._endDate.getTime() - this._startDate.getTime()) / 1e3;
- }
- return null;
- }
- get plannedDuration() {
- if ("PLANNED-DURATION" in this.attr) {
- return this.attr.decimalFloatingPoint("PLANNED-DURATION");
- }
- return null;
- }
- get endOnNext() {
- return this.attr.bool("END-ON-NEXT");
- }
- get isInterstitial() {
- return this.class === CLASS_INTERSTITIAL;
- }
- get isValid() {
- return !!this.id && !this._badValueForSameId && isFiniteNumber(this.startDate.getTime()) && (this.duration === null || this.duration >= 0) && (!this.endOnNext || !!this.class) && (!this.attr.CUE || !this.cue.pre && !this.cue.post || this.cue.pre !== this.cue.post) && (!this.isInterstitial || "X-ASSET-URI" in this.attr || "X-ASSET-LIST" in this.attr);
- }
- };
- var DEFAULT_TARGET_DURATION = 10;
- var LevelDetails = class {
- constructor(baseUrl) {
- this.PTSKnown = false;
- this.alignedSliding = false;
- this.averagetargetduration = void 0;
- this.endCC = 0;
- this.endSN = 0;
- this.fragments = void 0;
- this.fragmentHint = void 0;
- this.partList = null;
- this.dateRanges = void 0;
- this.dateRangeTagCount = 0;
- this.live = true;
- this.requestScheduled = -1;
- this.ageHeader = 0;
- this.advancedDateTime = void 0;
- this.updated = true;
- this.advanced = true;
- this.misses = 0;
- this.startCC = 0;
- this.startSN = 0;
- this.startTimeOffset = null;
- this.targetduration = 0;
- this.totalduration = 0;
- this.type = null;
- this.url = void 0;
- this.m3u8 = "";
- this.version = null;
- this.canBlockReload = false;
- this.canSkipUntil = 0;
- this.canSkipDateRanges = false;
- this.skippedSegments = 0;
- this.recentlyRemovedDateranges = void 0;
- this.partHoldBack = 0;
- this.holdBack = 0;
- this.partTarget = 0;
- this.preloadHint = void 0;
- this.renditionReports = void 0;
- this.tuneInGoal = 0;
- this.deltaUpdateFailed = void 0;
- this.driftStartTime = 0;
- this.driftEndTime = 0;
- this.driftStart = 0;
- this.driftEnd = 0;
- this.encryptedFragments = void 0;
- this.playlistParsingError = null;
- this.variableList = null;
- this.hasVariableRefs = false;
- this.appliedTimelineOffset = void 0;
- this.fragments = [];
- this.encryptedFragments = [];
- this.dateRanges = {};
- this.url = baseUrl;
- }
- reloaded(previous) {
- if (!previous) {
- this.advanced = true;
- this.updated = true;
- return;
- }
- const partSnDiff = this.lastPartSn - previous.lastPartSn;
- const partIndexDiff = this.lastPartIndex - previous.lastPartIndex;
- this.updated = this.endSN !== previous.endSN || !!partIndexDiff || !!partSnDiff || !this.live;
- this.advanced = this.endSN > previous.endSN || partSnDiff > 0 || partSnDiff === 0 && partIndexDiff > 0;
- if (this.updated || this.advanced) {
- this.misses = Math.floor(previous.misses * 0.6);
- } else {
- this.misses = previous.misses + 1;
- }
- }
- hasKey(levelKey) {
- return this.encryptedFragments.some((frag) => {
- let decryptdata = frag.decryptdata;
- if (!decryptdata) {
- frag.setKeyFormat(levelKey.keyFormat);
- decryptdata = frag.decryptdata;
- }
- return !!decryptdata && levelKey.matches(decryptdata);
- });
- }
- get hasProgramDateTime() {
- if (this.fragments.length) {
- return isFiniteNumber(this.fragments[this.fragments.length - 1].programDateTime);
- }
- return false;
- }
- get levelTargetDuration() {
- return this.averagetargetduration || this.targetduration || DEFAULT_TARGET_DURATION;
- }
- get drift() {
- const runTime = this.driftEndTime - this.driftStartTime;
- if (runTime > 0) {
- const runDuration = this.driftEnd - this.driftStart;
- return runDuration * 1e3 / runTime;
- }
- return 1;
- }
- get edge() {
- return this.partEnd || this.fragmentEnd;
- }
- get partEnd() {
- var _this$partList;
- if ((_this$partList = this.partList) != null && _this$partList.length) {
- return this.partList[this.partList.length - 1].end;
- }
- return this.fragmentEnd;
- }
- get fragmentEnd() {
- if (this.fragments.length) {
- return this.fragments[this.fragments.length - 1].end;
- }
- return 0;
- }
- get fragmentStart() {
- if (this.fragments.length) {
- return this.fragments[0].start;
- }
- return 0;
- }
- get age() {
- if (this.advancedDateTime) {
- return Math.max(Date.now() - this.advancedDateTime, 0) / 1e3;
- }
- return 0;
- }
- get lastPartIndex() {
- var _this$partList2;
- if ((_this$partList2 = this.partList) != null && _this$partList2.length) {
- return this.partList[this.partList.length - 1].index;
- }
- return -1;
- }
- get maxPartIndex() {
- const partList = this.partList;
- if (partList) {
- const lastIndex = this.lastPartIndex;
- if (lastIndex !== -1) {
- for (let i = partList.length; i--; ) {
- if (partList[i].index > lastIndex) {
- return partList[i].index;
- }
- }
- return lastIndex;
- }
- }
- return 0;
- }
- get lastPartSn() {
- var _this$partList3;
- if ((_this$partList3 = this.partList) != null && _this$partList3.length) {
- return this.partList[this.partList.length - 1].fragment.sn;
- }
- return this.endSN;
- }
- get expired() {
- if (this.live && this.age && this.misses < 3) {
- const playlistWindowDuration = this.partEnd - this.fragmentStart;
- return this.age > Math.max(playlistWindowDuration, this.totalduration) + this.levelTargetDuration;
- }
- return false;
- }
- };
- function arrayValuesMatch(a, b) {
- if (a.length === b.length) {
- return !a.some((value, i) => value !== b[i]);
- }
- return false;
- }
- function optionalArrayValuesMatch(a, b) {
- if (!a && !b) {
- return true;
- }
- if (!a || !b) {
- return false;
- }
- return arrayValuesMatch(a, b);
- }
- function isFullSegmentEncryption(method) {
- return method === "AES-128" || method === "AES-256" || method === "AES-256-CTR";
- }
- function getAesModeFromFullSegmentMethod(method) {
- switch (method) {
- case "AES-128":
- case "AES-256":
- return DecrypterAesMode.cbc;
- case "AES-256-CTR":
- return DecrypterAesMode.ctr;
- default:
- throw new Error(`invalid full segment method ${method}`);
- }
- }
- function base64Decode(base64encodedStr) {
- return Uint8Array.from(atob(base64encodedStr), (c) => c.charCodeAt(0));
- }
- function strToUtf8array(str) {
- return Uint8Array.from(unescape(encodeURIComponent(str)), (c) => c.charCodeAt(0));
- }
- function getKeyIdBytes(str) {
- const keyIdbytes = strToUtf8array(str).subarray(0, 16);
- const paddedkeyIdbytes = new Uint8Array(16);
- paddedkeyIdbytes.set(keyIdbytes, 16 - keyIdbytes.length);
- return paddedkeyIdbytes;
- }
- function changeEndianness(keyId) {
- const swap = function swap2(array, from, to) {
- const cur = array[from];
- array[from] = array[to];
- array[to] = cur;
- };
- swap(keyId, 0, 3);
- swap(keyId, 1, 2);
- swap(keyId, 4, 5);
- swap(keyId, 6, 7);
- }
- function convertDataUriToArrayBytes(uri) {
- const colonsplit = uri.split(":");
- let keydata = null;
- if (colonsplit[0] === "data" && colonsplit.length === 2) {
- const semicolonsplit = colonsplit[1].split(";");
- const commasplit = semicolonsplit[semicolonsplit.length - 1].split(",");
- if (commasplit.length === 2) {
- const isbase64 = commasplit[0] === "base64";
- const data = commasplit[1];
- if (isbase64) {
- semicolonsplit.splice(-1, 1);
- keydata = base64Decode(data);
- } else {
- keydata = getKeyIdBytes(data);
- }
- }
- }
- return keydata;
- }
- var optionalSelf = typeof self !== "undefined" ? self : void 0;
- var KeySystems = {
- CLEARKEY: "org.w3.clearkey",
- FAIRPLAY: "com.apple.fps",
- PLAYREADY: "com.microsoft.playready",
- WIDEVINE: "com.widevine.alpha"
- };
- var KeySystemFormats = {
- CLEARKEY: "org.w3.clearkey",
- FAIRPLAY: "com.apple.streamingkeydelivery",
- PLAYREADY: "com.microsoft.playready",
- WIDEVINE: "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
- };
- function keySystemFormatToKeySystemDomain(format2) {
- switch (format2) {
- case KeySystemFormats.FAIRPLAY:
- return KeySystems.FAIRPLAY;
- case KeySystemFormats.PLAYREADY:
- return KeySystems.PLAYREADY;
- case KeySystemFormats.WIDEVINE:
- return KeySystems.WIDEVINE;
- case KeySystemFormats.CLEARKEY:
- return KeySystems.CLEARKEY;
- }
- }
- function keySystemDomainToKeySystemFormat(keySystem) {
- switch (keySystem) {
- case KeySystems.FAIRPLAY:
- return KeySystemFormats.FAIRPLAY;
- case KeySystems.PLAYREADY:
- return KeySystemFormats.PLAYREADY;
- case KeySystems.WIDEVINE:
- return KeySystemFormats.WIDEVINE;
- case KeySystems.CLEARKEY:
- return KeySystemFormats.CLEARKEY;
- }
- }
- function getKeySystemsForConfig(config) {
- const {
- drmSystems,
- widevineLicenseUrl
- } = config;
- const keySystemsToAttempt = drmSystems ? [KeySystems.FAIRPLAY, KeySystems.WIDEVINE, KeySystems.PLAYREADY, KeySystems.CLEARKEY].filter((keySystem) => !!drmSystems[keySystem]) : [];
- if (!keySystemsToAttempt[KeySystems.WIDEVINE] && widevineLicenseUrl) {
- keySystemsToAttempt.push(KeySystems.WIDEVINE);
- }
- return keySystemsToAttempt;
- }
- var requestMediaKeySystemAccess = function(_optionalSelf$navigat) {
- if (optionalSelf != null && (_optionalSelf$navigat = optionalSelf.navigator) != null && _optionalSelf$navigat.requestMediaKeySystemAccess) {
- return self.navigator.requestMediaKeySystemAccess.bind(self.navigator);
- } else {
- return null;
- }
- }();
- function getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, drmSystemOptions) {
- let initDataTypes;
- switch (keySystem) {
- case KeySystems.FAIRPLAY:
- initDataTypes = ["cenc", "sinf"];
- break;
- case KeySystems.WIDEVINE:
- case KeySystems.PLAYREADY:
- initDataTypes = ["cenc"];
- break;
- case KeySystems.CLEARKEY:
- initDataTypes = ["cenc", "keyids"];
- break;
- default:
- throw new Error(`Unknown key-system: ${keySystem}`);
- }
- return createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions);
- }
- function createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions) {
- const baseConfig = {
- initDataTypes,
- persistentState: drmSystemOptions.persistentState || "optional",
- distinctiveIdentifier: drmSystemOptions.distinctiveIdentifier || "optional",
- sessionTypes: drmSystemOptions.sessionTypes || [drmSystemOptions.sessionType || "temporary"],
- audioCapabilities: audioCodecs.map((codec) => ({
- contentType: `audio/mp4; codecs=${codec}`,
- robustness: drmSystemOptions.audioRobustness || "",
- encryptionScheme: drmSystemOptions.audioEncryptionScheme || null
- })),
- videoCapabilities: videoCodecs.map((codec) => ({
- contentType: `video/mp4; codecs=${codec}`,
- robustness: drmSystemOptions.videoRobustness || "",
- encryptionScheme: drmSystemOptions.videoEncryptionScheme || null
- }))
- };
- return [baseConfig];
- }
- function isPersistentSessionType(drmSystemOptions) {
- var _drmSystemOptions$ses;
- return !!drmSystemOptions && (drmSystemOptions.sessionType === "persistent-license" || !!((_drmSystemOptions$ses = drmSystemOptions.sessionTypes) != null && _drmSystemOptions$ses.some((type) => type === "persistent-license")));
- }
- function parsePlayReadyWRM(keyBytes) {
- const keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2);
- const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
- const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf("<"), keyByteStr.length);
- const parser = new DOMParser();
- const xmlDoc = parser.parseFromString(xmlKeyBytes, "text/xml");
- const keyData = xmlDoc.getElementsByTagName("KID")[0];
- if (keyData) {
- const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute("VALUE");
- if (keyId) {
- const keyIdArray = base64Decode(keyId).subarray(0, 16);
- changeEndianness(keyIdArray);
- return keyIdArray;
- }
- }
- return null;
- }
- var keyUriToKeyIdMap = {};
- var LevelKey = class _LevelKey {
- static clearKeyUriToKeyIdMap() {
- keyUriToKeyIdMap = {};
- }
- static setKeyIdForUri(uri, keyId) {
- keyUriToKeyIdMap[uri] = keyId;
- }
- constructor(method, uri, format2, formatversions = [1], iv = null, keyId) {
- this.uri = void 0;
- this.method = void 0;
- this.keyFormat = void 0;
- this.keyFormatVersions = void 0;
- this.encrypted = void 0;
- this.isCommonEncryption = void 0;
- this.iv = null;
- this.key = null;
- this.keyId = null;
- this.pssh = null;
- this.method = method;
- this.uri = uri;
- this.keyFormat = format2;
- this.keyFormatVersions = formatversions;
- this.iv = iv;
- this.encrypted = method ? method !== "NONE" : false;
- this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
- if (keyId != null && keyId.startsWith("0x")) {
- this.keyId = new Uint8Array(hexToArrayBuffer(keyId));
- }
- }
- matches(key) {
- return key.uri === this.uri && key.method === this.method && key.encrypted === this.encrypted && key.keyFormat === this.keyFormat && arrayValuesMatch(key.keyFormatVersions, this.keyFormatVersions) && optionalArrayValuesMatch(key.iv, this.iv) && optionalArrayValuesMatch(key.keyId, this.keyId);
- }
- isSupported() {
- if (this.method) {
- if (isFullSegmentEncryption(this.method) || this.method === "NONE") {
- return true;
- }
- if (this.keyFormat === "identity") {
- return this.method === "SAMPLE-AES";
- } else {
- switch (this.keyFormat) {
- case KeySystemFormats.FAIRPLAY:
- case KeySystemFormats.WIDEVINE:
- case KeySystemFormats.PLAYREADY:
- case KeySystemFormats.CLEARKEY:
- return ["SAMPLE-AES", "SAMPLE-AES-CENC", "SAMPLE-AES-CTR"].indexOf(this.method) !== -1;
- }
- }
- }
- return false;
- }
- getDecryptData(sn, levelKeys) {
- if (!this.encrypted || !this.uri) {
- return null;
- }
- if (isFullSegmentEncryption(this.method)) {
- let iv = this.iv;
- if (!iv) {
- if (typeof sn !== "number") {
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
- sn = 0;
- }
- iv = createInitializationVector(sn);
- }
- const decryptdata = new _LevelKey(this.method, this.uri, "identity", this.keyFormatVersions, iv);
- return decryptdata;
- }
- if (this.keyId) {
- const assignedKeyId = keyUriToKeyIdMap[this.uri];
- if (assignedKeyId && !arrayValuesMatch(this.keyId, assignedKeyId)) {
- _LevelKey.setKeyIdForUri(this.uri, this.keyId);
- }
- if (this.pssh) {
- return this;
- }
- }
- const keyBytes = convertDataUriToArrayBytes(this.uri);
- if (keyBytes) {
- switch (this.keyFormat) {
- case KeySystemFormats.WIDEVINE:
- this.pssh = keyBytes;
- if (!this.keyId) {
- const results = parseMultiPssh(keyBytes.buffer);
- if (results.length) {
- var _psshData$kids;
- const psshData = results[0];
- this.keyId = (_psshData$kids = psshData.kids) != null && _psshData$kids.length ? psshData.kids[0] : null;
- }
- }
- if (!this.keyId) {
- this.keyId = getKeyIdFromPlayReadyKey(levelKeys);
- }
- break;
- case KeySystemFormats.PLAYREADY: {
- const PlayReadyKeySystemUUID = new Uint8Array([154, 4, 240, 121, 152, 64, 66, 134, 171, 146, 230, 91, 224, 136, 95, 149]);
- this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes);
- this.keyId = parsePlayReadyWRM(keyBytes);
- break;
- }
- default: {
- let keydata = keyBytes.subarray(0, 16);
- if (keydata.length !== 16) {
- const padded = new Uint8Array(16);
- padded.set(keydata, 16 - keydata.length);
- keydata = padded;
- }
- this.keyId = keydata;
- break;
- }
- }
- }
- if (!this.keyId || this.keyId.byteLength !== 16) {
- let keyId;
- keyId = getKeyIdFromWidevineKey(levelKeys);
- if (!keyId) {
- keyId = getKeyIdFromPlayReadyKey(levelKeys);
- if (!keyId) {
- keyId = keyUriToKeyIdMap[this.uri];
- }
- }
- if (keyId) {
- this.keyId = keyId;
- _LevelKey.setKeyIdForUri(this.uri, keyId);
- }
- }
- return this;
- }
- };
- function getKeyIdFromWidevineKey(levelKeys) {
- const widevineKey = levelKeys == null ? void 0 : levelKeys[KeySystemFormats.WIDEVINE];
- if (widevineKey) {
- return widevineKey.keyId;
- }
- return null;
- }
- function getKeyIdFromPlayReadyKey(levelKeys) {
- const playReadyKey = levelKeys == null ? void 0 : levelKeys[KeySystemFormats.PLAYREADY];
- if (playReadyKey) {
- const playReadyKeyBytes = convertDataUriToArrayBytes(playReadyKey.uri);
- if (playReadyKeyBytes) {
- return parsePlayReadyWRM(playReadyKeyBytes);
- }
- }
- return null;
- }
- function createInitializationVector(segmentNumber) {
- const uint8View = new Uint8Array(16);
- for (let i = 12; i < 16; i++) {
- uint8View[i] = segmentNumber >> 8 * (15 - i) & 255;
- }
- return uint8View;
- }
- var MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g;
- var MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
- var IS_MEDIA_PLAYLIST = /^#EXT(?:INF|-X-TARGETDURATION):/m;
- var LEVEL_PLAYLIST_REGEX_FAST = new RegExp([
- /#EXTINF:\s*(\d*(?:\.\d+)?)(?:,(.*)\s+)?/.source,
- // duration (#EXTINF:<duration>,<title>), group 1 => duration, group 2 => title
- /(?!#) *(\S[^\r\n]*)/.source,
- // segment URI, group 3 => the URI (note newline is not eaten)
- /#.*/.source
- // All other non-segment oriented tags will match with all groups empty
- ].join("|"), "g");
- var LEVEL_PLAYLIST_REGEX_SLOW = new RegExp([/#EXT-X-(PROGRAM-DATE-TIME|BYTERANGE|DATERANGE|DEFINE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/.source, /#EXT-X-(BITRATE|DISCONTINUITY-SEQUENCE|MEDIA-SEQUENCE|TARGETDURATION|VERSION): *(\d+)/.source, /#EXT-X-(DISCONTINUITY|ENDLIST|GAP|INDEPENDENT-SEGMENTS)/.source, /(#)([^:]*):(.*)/.source, /(#)(.*)(?:.*)\r?\n?/.source].join("|"));
- var M3U8Parser = class _M3U8Parser {
- static findGroup(groups, mediaGroupId) {
- for (let i = 0; i < groups.length; i++) {
- const group = groups[i];
- if (group.id === mediaGroupId) {
- return group;
- }
- }
- }
- static resolve(url, baseUrl) {
- return urlToolkitExports.buildAbsoluteURL(baseUrl, url, {
- alwaysNormalize: true
- });
- }
- static isMediaPlaylist(str) {
- return IS_MEDIA_PLAYLIST.test(str);
- }
- static parseMasterPlaylist(string, baseurl) {
- const hasVariableRefs = hasVariableReferences(string);
- const parsed = {
- contentSteering: null,
- levels: [],
- playlistParsingError: null,
- sessionData: null,
- sessionKeys: null,
- startTimeOffset: null,
- variableList: null,
- hasVariableRefs
- };
- const levelsWithKnownCodecs = [];
- MASTER_PLAYLIST_REGEX.lastIndex = 0;
- if (!string.startsWith("#EXTM3U")) {
- parsed.playlistParsingError = new Error("no EXTM3U delimiter");
- return parsed;
- }
- let result;
- while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) {
- if (result[1]) {
- var _level$unknownCodecs;
- const attrs = new AttrList(result[1], parsed);
- const uri = substituteVariables(parsed, result[2]);
- const level = {
- attrs,
- bitrate: attrs.decimalInteger("BANDWIDTH") || attrs.decimalInteger("AVERAGE-BANDWIDTH"),
- name: attrs.NAME,
- url: _M3U8Parser.resolve(uri, baseurl)
- };
- const resolution = attrs.decimalResolution("RESOLUTION");
- if (resolution) {
- level.width = resolution.width;
- level.height = resolution.height;
- }
- setCodecs(attrs.CODECS, level);
- const supplementalCodecs = attrs["SUPPLEMENTAL-CODECS"];
- if (supplementalCodecs) {
- level.supplemental = {};
- setCodecs(supplementalCodecs, level.supplemental);
- }
- if (!((_level$unknownCodecs = level.unknownCodecs) != null && _level$unknownCodecs.length)) {
- levelsWithKnownCodecs.push(level);
- }
- parsed.levels.push(level);
- } else if (result[3]) {
- const tag = result[3];
- const attributes = result[4];
- switch (tag) {
- case "SESSION-DATA": {
- const sessionAttrs = new AttrList(attributes, parsed);
- const dataId = sessionAttrs["DATA-ID"];
- if (dataId) {
- if (parsed.sessionData === null) {
- parsed.sessionData = {};
- }
- parsed.sessionData[dataId] = sessionAttrs;
- }
- break;
- }
- case "SESSION-KEY": {
- const sessionKey = parseKey(attributes, baseurl, parsed);
- if (sessionKey.encrypted && sessionKey.isSupported()) {
- if (parsed.sessionKeys === null) {
- parsed.sessionKeys = [];
- }
- parsed.sessionKeys.push(sessionKey);
- } else {
- logger.warn(`[Keys] Ignoring invalid EXT-X-SESSION-KEY tag: "${attributes}"`);
- }
- break;
- }
- case "DEFINE": {
- {
- const variableAttributes = new AttrList(attributes, parsed);
- addVariableDefinition(parsed, variableAttributes, baseurl);
- }
- break;
- }
- case "CONTENT-STEERING": {
- const contentSteeringAttributes = new AttrList(attributes, parsed);
- parsed.contentSteering = {
- uri: _M3U8Parser.resolve(contentSteeringAttributes["SERVER-URI"], baseurl),
- pathwayId: contentSteeringAttributes["PATHWAY-ID"] || "."
- };
- break;
- }
- case "START": {
- parsed.startTimeOffset = parseStartTimeOffset(attributes);
- break;
- }
- }
- }
- }
- const stripUnknownCodecLevels = levelsWithKnownCodecs.length > 0 && levelsWithKnownCodecs.length < parsed.levels.length;
- parsed.levels = stripUnknownCodecLevels ? levelsWithKnownCodecs : parsed.levels;
- if (parsed.levels.length === 0) {
- parsed.playlistParsingError = new Error("no levels found in manifest");
- }
- return parsed;
- }
- static parseMasterPlaylistMedia(string, baseurl, parsed) {
- let result;
- const results = {};
- const levels = parsed.levels;
- const groupsByType = {
- AUDIO: levels.map((level) => ({
- id: level.attrs.AUDIO,
- audioCodec: level.audioCodec
- })),
- SUBTITLES: levels.map((level) => ({
- id: level.attrs.SUBTITLES,
- textCodec: level.textCodec
- })),
- "CLOSED-CAPTIONS": []
- };
- let id = 0;
- MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0;
- while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) {
- const attrs = new AttrList(result[1], parsed);
- const type = attrs.TYPE;
- if (type) {
- const groups = groupsByType[type];
- const medias = results[type] || [];
- results[type] = medias;
- const lang = attrs.LANGUAGE;
- const assocLang = attrs["ASSOC-LANGUAGE"];
- const channels = attrs.CHANNELS;
- const characteristics = attrs.CHARACTERISTICS;
- const instreamId = attrs["INSTREAM-ID"];
- const media = {
- attrs,
- bitrate: 0,
- id: id++,
- groupId: attrs["GROUP-ID"] || "",
- name: attrs.NAME || lang || "",
- type,
- default: attrs.bool("DEFAULT"),
- autoselect: attrs.bool("AUTOSELECT"),
- forced: attrs.bool("FORCED"),
- lang,
- url: attrs.URI ? _M3U8Parser.resolve(attrs.URI, baseurl) : ""
- };
- if (assocLang) {
- media.assocLang = assocLang;
- }
- if (channels) {
- media.channels = channels;
- }
- if (characteristics) {
- media.characteristics = characteristics;
- }
- if (instreamId) {
- media.instreamId = instreamId;
- }
- if (groups != null && groups.length) {
- const groupCodec = _M3U8Parser.findGroup(groups, media.groupId) || groups[0];
- assignCodec(media, groupCodec, "audioCodec");
- assignCodec(media, groupCodec, "textCodec");
- }
- medias.push(media);
- }
- }
- return results;
- }
- static parseLevelPlaylist(string, baseurl, id, type, levelUrlId, multivariantVariableList) {
- var _LEVEL_PLAYLIST_REGEX;
- const base = {
- url: baseurl
- };
- const level = new LevelDetails(baseurl);
- const fragments = level.fragments;
- const programDateTimes = [];
- let currentInitSegment = null;
- let currentSN = 0;
- let currentPart = 0;
- let totalduration = 0;
- let discontinuityCounter = 0;
- let currentBitrate = 0;
- let prevFrag = null;
- let frag = new Fragment(type, base);
- let result;
- let i;
- let levelkeys;
- let firstPdtIndex = -1;
- let createNextFrag = false;
- let nextByteRange = null;
- let serverControlAttrs;
- LEVEL_PLAYLIST_REGEX_FAST.lastIndex = 0;
- level.m3u8 = string;
- level.hasVariableRefs = hasVariableReferences(string);
- if (((_LEVEL_PLAYLIST_REGEX = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) == null ? void 0 : _LEVEL_PLAYLIST_REGEX[0]) !== "#EXTM3U") {
- level.playlistParsingError = new Error("Missing format identifier #EXTM3U");
- return level;
- }
- while ((result = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) !== null) {
- if (createNextFrag) {
- createNextFrag = false;
- frag = new Fragment(type, base);
- frag.playlistOffset = totalduration;
- frag.setStart(totalduration);
- frag.sn = currentSN;
- frag.cc = discontinuityCounter;
- if (currentBitrate) {
- frag.bitrate = currentBitrate;
- }
- frag.level = id;
- if (currentInitSegment) {
- frag.initSegment = currentInitSegment;
- if (currentInitSegment.rawProgramDateTime) {
- frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime;
- currentInitSegment.rawProgramDateTime = null;
- }
- if (nextByteRange) {
- frag.setByteRange(nextByteRange);
- nextByteRange = null;
- }
- }
- }
- const duration = result[1];
- if (duration) {
- frag.duration = parseFloat(duration);
- const title = (" " + result[2]).slice(1);
- frag.title = title || null;
- frag.tagList.push(title ? ["INF", duration, title] : ["INF", duration]);
- } else if (result[3]) {
- if (isFiniteNumber(frag.duration)) {
- frag.playlistOffset = totalduration;
- frag.setStart(totalduration);
- if (levelkeys) {
- setFragLevelKeys(frag, levelkeys, level);
- }
- frag.sn = currentSN;
- frag.level = id;
- frag.cc = discontinuityCounter;
- fragments.push(frag);
- const uri = (" " + result[3]).slice(1);
- frag.relurl = substituteVariables(level, uri);
- assignProgramDateTime(frag, prevFrag, programDateTimes);
- prevFrag = frag;
- totalduration += frag.duration;
- currentSN++;
- currentPart = 0;
- createNextFrag = true;
- }
- } else {
- result = result[0].match(LEVEL_PLAYLIST_REGEX_SLOW);
- if (!result) {
- logger.warn("No matches on slow regex match for level playlist!");
- continue;
- }
- for (i = 1; i < result.length; i++) {
- if (result[i] !== void 0) {
- break;
- }
- }
- const tag = (" " + result[i]).slice(1);
- const value1 = (" " + result[i + 1]).slice(1);
- const value2 = result[i + 2] ? (" " + result[i + 2]).slice(1) : null;
- switch (tag) {
- case "BYTERANGE":
- if (prevFrag) {
- frag.setByteRange(value1, prevFrag);
- } else {
- frag.setByteRange(value1);
- }
- break;
- case "PROGRAM-DATE-TIME":
- frag.rawProgramDateTime = value1;
- frag.tagList.push(["PROGRAM-DATE-TIME", value1]);
- if (firstPdtIndex === -1) {
- firstPdtIndex = fragments.length;
- }
- break;
- case "PLAYLIST-TYPE":
- if (level.type) {
- assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
- }
- level.type = value1.toUpperCase();
- break;
- case "MEDIA-SEQUENCE":
- if (level.startSN !== 0) {
- assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
- } else if (fragments.length > 0) {
- assignMustAppearBeforeSegmentsError(level, tag, result);
- }
- currentSN = level.startSN = parseInt(value1);
- break;
- case "SKIP": {
- if (level.skippedSegments) {
- assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
- }
- const skipAttrs = new AttrList(value1, level);
- const skippedSegments = skipAttrs.decimalInteger("SKIPPED-SEGMENTS");
- if (isFiniteNumber(skippedSegments)) {
- level.skippedSegments += skippedSegments;
- for (let _i = skippedSegments; _i--; ) {
- fragments.push(null);
- }
- currentSN += skippedSegments;
- }
- const recentlyRemovedDateranges = skipAttrs.enumeratedString("RECENTLY-REMOVED-DATERANGES");
- if (recentlyRemovedDateranges) {
- level.recentlyRemovedDateranges = (level.recentlyRemovedDateranges || []).concat(recentlyRemovedDateranges.split(" "));
- }
- break;
- }
- case "TARGETDURATION":
- if (level.targetduration !== 0) {
- assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
- }
- level.targetduration = Math.max(parseInt(value1), 1);
- break;
- case "VERSION":
- if (level.version !== null) {
- assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
- }
- level.version = parseInt(value1);
- break;
- case "INDEPENDENT-SEGMENTS":
- break;
- case "ENDLIST":
- if (!level.live) {
- assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
- }
- level.live = false;
- break;
- case "#":
- if (value1 || value2) {
- frag.tagList.push(value2 ? [value1, value2] : [value1]);
- }
- break;
- case "DISCONTINUITY":
- discontinuityCounter++;
- frag.tagList.push(["DIS"]);
- break;
- case "GAP":
- frag.gap = true;
- frag.tagList.push([tag]);
- break;
- case "BITRATE":
- frag.tagList.push([tag, value1]);
- currentBitrate = parseInt(value1) * 1e3;
- if (isFiniteNumber(currentBitrate)) {
- frag.bitrate = currentBitrate;
- } else {
- currentBitrate = 0;
- }
- break;
- case "DATERANGE": {
- const dateRangeAttr = new AttrList(value1, level);
- const dateRange = new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID], level.dateRangeTagCount);
- level.dateRangeTagCount++;
- if (dateRange.isValid || level.skippedSegments) {
- level.dateRanges[dateRange.id] = dateRange;
- } else {
- logger.warn(`Ignoring invalid DATERANGE tag: "${value1}"`);
- }
- frag.tagList.push(["EXT-X-DATERANGE", value1]);
- break;
- }
- case "DEFINE": {
- {
- const variableAttributes = new AttrList(value1, level);
- if ("IMPORT" in variableAttributes) {
- importVariableDefinition(level, variableAttributes, multivariantVariableList);
- } else {
- addVariableDefinition(level, variableAttributes, baseurl);
- }
- }
- break;
- }
- case "DISCONTINUITY-SEQUENCE":
- if (level.startCC !== 0) {
- assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
- } else if (fragments.length > 0) {
- assignMustAppearBeforeSegmentsError(level, tag, result);
- }
- level.startCC = discontinuityCounter = parseInt(value1);
- break;
- case "KEY": {
- const levelKey = parseKey(value1, baseurl, level);
- if (levelKey.isSupported()) {
- if (levelKey.method === "NONE") {
- levelkeys = void 0;
- break;
- }
- if (!levelkeys) {
- levelkeys = {};
- }
- const currentKey = levelkeys[levelKey.keyFormat];
- if (!(currentKey != null && currentKey.matches(levelKey))) {
- if (currentKey) {
- levelkeys = _extends({}, levelkeys);
- }
- levelkeys[levelKey.keyFormat] = levelKey;
- }
- } else {
- logger.warn(`[Keys] Ignoring unsupported EXT-X-KEY tag: "${value1}"${""}`);
- }
- break;
- }
- case "START":
- level.startTimeOffset = parseStartTimeOffset(value1);
- break;
- case "MAP": {
- const mapAttrs = new AttrList(value1, level);
- if (frag.duration) {
- const init = new Fragment(type, base);
- setInitSegment(init, mapAttrs, id, levelkeys);
- currentInitSegment = init;
- frag.initSegment = currentInitSegment;
- if (currentInitSegment.rawProgramDateTime && !frag.rawProgramDateTime) {
- frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime;
- }
- } else {
- const end = frag.byteRangeEndOffset;
- if (end) {
- const start = frag.byteRangeStartOffset;
- nextByteRange = `${end - start}@${start}`;
- } else {
- nextByteRange = null;
- }
- setInitSegment(frag, mapAttrs, id, levelkeys);
- currentInitSegment = frag;
- createNextFrag = true;
- }
- currentInitSegment.cc = discontinuityCounter;
- break;
- }
- case "SERVER-CONTROL": {
- if (serverControlAttrs) {
- assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
- }
- serverControlAttrs = new AttrList(value1);
- level.canBlockReload = serverControlAttrs.bool("CAN-BLOCK-RELOAD");
- level.canSkipUntil = serverControlAttrs.optionalFloat("CAN-SKIP-UNTIL", 0);
- level.canSkipDateRanges = level.canSkipUntil > 0 && serverControlAttrs.bool("CAN-SKIP-DATERANGES");
- level.partHoldBack = serverControlAttrs.optionalFloat("PART-HOLD-BACK", 0);
- level.holdBack = serverControlAttrs.optionalFloat("HOLD-BACK", 0);
- break;
- }
- case "PART-INF": {
- if (level.partTarget) {
- assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
- }
- const partInfAttrs = new AttrList(value1);
- level.partTarget = partInfAttrs.decimalFloatingPoint("PART-TARGET");
- break;
- }
- case "PART": {
- let partList = level.partList;
- if (!partList) {
- partList = level.partList = [];
- }
- const previousFragmentPart = currentPart > 0 ? partList[partList.length - 1] : void 0;
- const index = currentPart++;
- const partAttrs = new AttrList(value1, level);
- const part = new Part(partAttrs, frag, base, index, previousFragmentPart);
- partList.push(part);
- frag.duration += part.duration;
- break;
- }
- case "PRELOAD-HINT": {
- const preloadHintAttrs = new AttrList(value1, level);
- level.preloadHint = preloadHintAttrs;
- break;
- }
- case "RENDITION-REPORT": {
- const renditionReportAttrs = new AttrList(value1, level);
- level.renditionReports = level.renditionReports || [];
- level.renditionReports.push(renditionReportAttrs);
- break;
- }
- default:
- logger.warn(`line parsed but not handled: ${result}`);
- break;
- }
- }
- }
- if (prevFrag && !prevFrag.relurl) {
- fragments.pop();
- totalduration -= prevFrag.duration;
- if (level.partList) {
- level.fragmentHint = prevFrag;
- }
- } else if (level.partList) {
- assignProgramDateTime(frag, prevFrag, programDateTimes);
- frag.cc = discontinuityCounter;
- level.fragmentHint = frag;
- if (levelkeys) {
- setFragLevelKeys(frag, levelkeys, level);
- }
- }
- if (!level.targetduration) {
- level.playlistParsingError = new Error(`Missing Target Duration`);
- }
- const fragmentLength = fragments.length;
- const firstFragment = fragments[0];
- const lastFragment = fragments[fragmentLength - 1];
- totalduration += level.skippedSegments * level.targetduration;
- if (totalduration > 0 && fragmentLength && lastFragment) {
- level.averagetargetduration = totalduration / fragmentLength;
- const lastSn = lastFragment.sn;
- level.endSN = lastSn !== "initSegment" ? lastSn : 0;
- if (!level.live) {
- lastFragment.endList = true;
- }
- if (firstPdtIndex > 0) {
- backfillProgramDateTimes(fragments, firstPdtIndex);
- if (firstFragment) {
- programDateTimes.unshift(firstFragment);
- }
- }
- }
- if (level.fragmentHint) {
- totalduration += level.fragmentHint.duration;
- }
- level.totalduration = totalduration;
- if (programDateTimes.length && level.dateRangeTagCount && firstFragment) {
- mapDateRanges(programDateTimes, level);
- }
- level.endCC = discontinuityCounter;
- return level;
- }
- };
- function mapDateRanges(programDateTimes, details) {
- let programDateTimeCount = programDateTimes.length;
- if (!programDateTimeCount) {
- if (details.hasProgramDateTime) {
- const lastFragment = details.fragments[details.fragments.length - 1];
- programDateTimes.push(lastFragment);
- programDateTimeCount++;
- } else {
- return;
- }
- }
- const lastProgramDateTime = programDateTimes[programDateTimeCount - 1];
- const playlistEnd = details.live ? Infinity : details.totalduration;
- const dateRangeIds = Object.keys(details.dateRanges);
- for (let i = dateRangeIds.length; i--; ) {
- const dateRange = details.dateRanges[dateRangeIds[i]];
- const startDateTime = dateRange.startDate.getTime();
- dateRange.tagAnchor = lastProgramDateTime.ref;
- for (let j = programDateTimeCount; j--; ) {
- var _programDateTimes$j;
- if (((_programDateTimes$j = programDateTimes[j]) == null ? void 0 : _programDateTimes$j.sn) < details.startSN) {
- break;
- }
- const fragIndex = findFragmentWithStartDate(details, startDateTime, programDateTimes, j, playlistEnd);
- if (fragIndex !== -1) {
- dateRange.tagAnchor = details.fragments[fragIndex].ref;
- break;
- }
- }
- }
- }
- function findFragmentWithStartDate(details, startDateTime, programDateTimes, index, endTime) {
- const pdtFragment = programDateTimes[index];
- if (pdtFragment) {
- const pdtStart = pdtFragment.programDateTime;
- if (startDateTime >= pdtStart || index === 0) {
- var _programDateTimes;
- const durationBetweenPdt = (((_programDateTimes = programDateTimes[index + 1]) == null ? void 0 : _programDateTimes.start) || endTime) - pdtFragment.start;
- if (startDateTime <= pdtStart + durationBetweenPdt * 1e3) {
- const startIndex = programDateTimes[index].sn - details.startSN;
- if (startIndex < 0) {
- return -1;
- }
- const fragments = details.fragments;
- if (fragments.length > programDateTimes.length) {
- const endSegment = programDateTimes[index + 1] || fragments[fragments.length - 1];
- const endIndex = endSegment.sn - details.startSN;
- for (let i = endIndex; i > startIndex; i--) {
- const fragStartDateTime = fragments[i].programDateTime;
- if (startDateTime >= fragStartDateTime && startDateTime < fragStartDateTime + fragments[i].duration * 1e3) {
- return i;
- }
- }
- }
- return startIndex;
- }
- }
- }
- return -1;
- }
- function parseKey(keyTagAttributes, baseurl, parsed) {
- var _keyAttrs$METHOD, _keyAttrs$KEYFORMAT;
- const keyAttrs = new AttrList(keyTagAttributes, parsed);
- const decryptmethod = (_keyAttrs$METHOD = keyAttrs.METHOD) != null ? _keyAttrs$METHOD : "";
- const decrypturi = keyAttrs.URI;
- const decryptiv = keyAttrs.hexadecimalInteger("IV");
- const decryptkeyformatversions = keyAttrs.KEYFORMATVERSIONS;
- const decryptkeyformat = (_keyAttrs$KEYFORMAT = keyAttrs.KEYFORMAT) != null ? _keyAttrs$KEYFORMAT : "identity";
- if (decrypturi && keyAttrs.IV && !decryptiv) {
- logger.error(`Invalid IV: ${keyAttrs.IV}`);
- }
- const resolvedUri = decrypturi ? M3U8Parser.resolve(decrypturi, baseurl) : "";
- const keyFormatVersions = (decryptkeyformatversions ? decryptkeyformatversions : "1").split("/").map(Number).filter(Number.isFinite);
- return new LevelKey(decryptmethod, resolvedUri, decryptkeyformat, keyFormatVersions, decryptiv, keyAttrs.KEYID);
- }
- function parseStartTimeOffset(startAttributes) {
- const startAttrs = new AttrList(startAttributes);
- const startTimeOffset = startAttrs.decimalFloatingPoint("TIME-OFFSET");
- if (isFiniteNumber(startTimeOffset)) {
- return startTimeOffset;
- }
- return null;
- }
- function setCodecs(codecsAttributeValue, level) {
- let codecs = (codecsAttributeValue || "").split(/[ ,]+/).filter((c) => c);
- ["video", "audio", "text"].forEach((type) => {
- const filtered = codecs.filter((codec) => isCodecType(codec, type));
- if (filtered.length) {
- level[`${type}Codec`] = filtered.map((c) => c.split("/")[0]).join(",");
- codecs = codecs.filter((codec) => filtered.indexOf(codec) === -1);
- }
- });
- level.unknownCodecs = codecs;
- }
- function assignCodec(media, groupItem, codecProperty) {
- const codecValue = groupItem[codecProperty];
- if (codecValue) {
- media[codecProperty] = codecValue;
- }
- }
- function backfillProgramDateTimes(fragments, firstPdtIndex) {
- let fragPrev = fragments[firstPdtIndex];
- for (let i = firstPdtIndex; i--; ) {
- const frag = fragments[i];
- if (!frag) {
- return;
- }
- frag.programDateTime = fragPrev.programDateTime - frag.duration * 1e3;
- fragPrev = frag;
- }
- }
- function assignProgramDateTime(frag, prevFrag, programDateTimes) {
- if (frag.rawProgramDateTime) {
- programDateTimes.push(frag);
- } else if (prevFrag != null && prevFrag.programDateTime) {
- frag.programDateTime = prevFrag.endProgramDateTime;
- }
- }
- function setInitSegment(frag, mapAttrs, id, levelkeys) {
- frag.relurl = mapAttrs.URI;
- if (mapAttrs.BYTERANGE) {
- frag.setByteRange(mapAttrs.BYTERANGE);
- }
- frag.level = id;
- frag.sn = "initSegment";
- if (levelkeys) {
- frag.levelkeys = levelkeys;
- }
- frag.initSegment = null;
- }
- function setFragLevelKeys(frag, levelkeys, level) {
- frag.levelkeys = levelkeys;
- const {
- encryptedFragments
- } = level;
- if ((!encryptedFragments.length || encryptedFragments[encryptedFragments.length - 1].levelkeys !== levelkeys) && Object.keys(levelkeys).some((format2) => levelkeys[format2].isCommonEncryption)) {
- encryptedFragments.push(frag);
- }
- }
- function assignMultipleMediaPlaylistTagOccuranceError(level, tag, result) {
- level.playlistParsingError = new Error(`#EXT-X-${tag} must not appear more than once (${result[0]})`);
- }
- function assignMustAppearBeforeSegmentsError(level, tag, result) {
- level.playlistParsingError = new Error(`#EXT-X-${tag} must appear before the first Media Segment (${result[0]})`);
- }
- function updateFromToPTS(fragFrom, fragTo) {
- const fragToPTS = fragTo.startPTS;
- if (isFiniteNumber(fragToPTS)) {
- let duration = 0;
- let frag;
- if (fragTo.sn > fragFrom.sn) {
- duration = fragToPTS - fragFrom.start;
- frag = fragFrom;
- } else {
- duration = fragFrom.start - fragToPTS;
- frag = fragTo;
- }
- if (frag.duration !== duration) {
- frag.setDuration(duration);
- }
- } else if (fragTo.sn > fragFrom.sn) {
- const contiguous = fragFrom.cc === fragTo.cc;
- if (contiguous && fragFrom.minEndPTS) {
- fragTo.setStart(fragFrom.start + (fragFrom.minEndPTS - fragFrom.start));
- } else {
- fragTo.setStart(fragFrom.start + fragFrom.duration);
- }
- } else {
- fragTo.setStart(Math.max(fragFrom.start - fragTo.duration, 0));
- }
- }
- function updateFragPTSDTS(details, frag, startPTS, endPTS, startDTS, endDTS, logger2) {
- const parsedMediaDuration = endPTS - startPTS;
- if (parsedMediaDuration <= 0) {
- logger2.warn("Fragment should have a positive duration", frag);
- endPTS = startPTS + frag.duration;
- endDTS = startDTS + frag.duration;
- }
- let maxStartPTS = startPTS;
- let minEndPTS = endPTS;
- const fragStartPts = frag.startPTS;
- const fragEndPts = frag.endPTS;
- if (isFiniteNumber(fragStartPts)) {
- const deltaPTS = Math.abs(fragStartPts - startPTS);
- if (details && deltaPTS > details.totalduration) {
- logger2.warn(`media timestamps and playlist times differ by ${deltaPTS}s for level ${frag.level} ${details.url}`);
- } else if (!isFiniteNumber(frag.deltaPTS)) {
- frag.deltaPTS = deltaPTS;
- } else {
- frag.deltaPTS = Math.max(deltaPTS, frag.deltaPTS);
- }
- maxStartPTS = Math.max(startPTS, fragStartPts);
- startPTS = Math.min(startPTS, fragStartPts);
- startDTS = frag.startDTS !== void 0 ? Math.min(startDTS, frag.startDTS) : startDTS;
- minEndPTS = Math.min(endPTS, fragEndPts);
- endPTS = Math.max(endPTS, fragEndPts);
- endDTS = frag.endDTS !== void 0 ? Math.max(endDTS, frag.endDTS) : endDTS;
- }
- const drift = startPTS - frag.start;
- if (frag.start !== 0) {
- frag.setStart(startPTS);
- }
- frag.setDuration(endPTS - frag.start);
- frag.startPTS = startPTS;
- frag.maxStartPTS = maxStartPTS;
- frag.startDTS = startDTS;
- frag.endPTS = endPTS;
- frag.minEndPTS = minEndPTS;
- frag.endDTS = endDTS;
- const sn = frag.sn;
- if (!details || sn < details.startSN || sn > details.endSN) {
- return 0;
- }
- let i;
- const fragIdx = sn - details.startSN;
- const fragments = details.fragments;
- fragments[fragIdx] = frag;
- for (i = fragIdx; i > 0; i--) {
- updateFromToPTS(fragments[i], fragments[i - 1]);
- }
- for (i = fragIdx; i < fragments.length - 1; i++) {
- updateFromToPTS(fragments[i], fragments[i + 1]);
- }
- if (details.fragmentHint) {
- updateFromToPTS(fragments[fragments.length - 1], details.fragmentHint);
- }
- details.PTSKnown = details.alignedSliding = true;
- return drift;
- }
- function mergeDetails(oldDetails, newDetails, logger2) {
- if (oldDetails === newDetails) {
- return;
- }
- let currentInitSegment = null;
- const oldFragments = oldDetails.fragments;
- for (let i = oldFragments.length - 1; i >= 0; i--) {
- const oldInit = oldFragments[i].initSegment;
- if (oldInit) {
- currentInitSegment = oldInit;
- break;
- }
- }
- if (oldDetails.fragmentHint) {
- delete oldDetails.fragmentHint.endPTS;
- }
- let PTSFrag;
- mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag, newFragIndex, newFragments2) => {
- if ((!newDetails.startCC || newDetails.skippedSegments) && newFrag.cc !== oldFrag.cc) {
- const ccOffset = oldFrag.cc - newFrag.cc;
- for (let i = newFragIndex; i < newFragments2.length; i++) {
- newFragments2[i].cc += ccOffset;
- }
- newDetails.endCC = newFragments2[newFragments2.length - 1].cc;
- }
- if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) {
- newFrag.setStart(newFrag.startPTS = oldFrag.startPTS);
- newFrag.startDTS = oldFrag.startDTS;
- newFrag.maxStartPTS = oldFrag.maxStartPTS;
- newFrag.endPTS = oldFrag.endPTS;
- newFrag.endDTS = oldFrag.endDTS;
- newFrag.minEndPTS = oldFrag.minEndPTS;
- newFrag.setDuration(oldFrag.endPTS - oldFrag.startPTS);
- if (newFrag.duration) {
- PTSFrag = newFrag;
- }
- newDetails.PTSKnown = newDetails.alignedSliding = true;
- }
- if (oldFrag.hasStreams) {
- newFrag.elementaryStreams = oldFrag.elementaryStreams;
- }
- newFrag.loader = oldFrag.loader;
- if (oldFrag.hasStats) {
- newFrag.stats = oldFrag.stats;
- }
- if (oldFrag.initSegment) {
- newFrag.initSegment = oldFrag.initSegment;
- currentInitSegment = oldFrag.initSegment;
- }
- });
- const newFragments = newDetails.fragments;
- const fragmentsToCheck = newDetails.fragmentHint ? newFragments.concat(newDetails.fragmentHint) : newFragments;
- if (currentInitSegment) {
- fragmentsToCheck.forEach((frag) => {
- var _currentInitSegment;
- if (frag && (!frag.initSegment || frag.initSegment.relurl === ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.relurl))) {
- frag.initSegment = currentInitSegment;
- }
- });
- }
- if (newDetails.skippedSegments) {
- newDetails.deltaUpdateFailed = newFragments.some((frag) => !frag);
- if (newDetails.deltaUpdateFailed) {
- logger2.warn("[level-helper] Previous playlist missing segments skipped in delta playlist");
- for (let i = newDetails.skippedSegments; i--; ) {
- newFragments.shift();
- }
- newDetails.startSN = newFragments[0].sn;
- } else {
- if (newDetails.canSkipDateRanges) {
- newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails, logger2);
- }
- const programDateTimes = oldDetails.fragments.filter((frag) => frag.rawProgramDateTime);
- if (oldDetails.hasProgramDateTime && !newDetails.hasProgramDateTime) {
- for (let i = 1; i < fragmentsToCheck.length; i++) {
- if (fragmentsToCheck[i].programDateTime === null) {
- assignProgramDateTime(fragmentsToCheck[i], fragmentsToCheck[i - 1], programDateTimes);
- }
- }
- }
- mapDateRanges(programDateTimes, newDetails);
- }
- newDetails.endCC = newFragments[newFragments.length - 1].cc;
- }
- if (!newDetails.startCC) {
- var _fragPriorToNewStart$;
- const fragPriorToNewStart = getFragmentWithSN(oldDetails, newDetails.startSN - 1);
- newDetails.startCC = (_fragPriorToNewStart$ = fragPriorToNewStart == null ? void 0 : fragPriorToNewStart.cc) != null ? _fragPriorToNewStart$ : newFragments[0].cc;
- }
- mapPartIntersection(oldDetails.partList, newDetails.partList, (oldPart, newPart) => {
- newPart.elementaryStreams = oldPart.elementaryStreams;
- newPart.stats = oldPart.stats;
- });
- if (PTSFrag) {
- updateFragPTSDTS(newDetails, PTSFrag, PTSFrag.startPTS, PTSFrag.endPTS, PTSFrag.startDTS, PTSFrag.endDTS, logger2);
- } else {
- adjustSliding(oldDetails, newDetails);
- }
- if (newFragments.length) {
- newDetails.totalduration = newDetails.edge - newFragments[0].start;
- }
- newDetails.driftStartTime = oldDetails.driftStartTime;
- newDetails.driftStart = oldDetails.driftStart;
- const advancedDateTime = newDetails.advancedDateTime;
- if (newDetails.advanced && advancedDateTime) {
- const edge = newDetails.edge;
- if (!newDetails.driftStart) {
- newDetails.driftStartTime = advancedDateTime;
- newDetails.driftStart = edge;
- }
- newDetails.driftEndTime = advancedDateTime;
- newDetails.driftEnd = edge;
- } else {
- newDetails.driftEndTime = oldDetails.driftEndTime;
- newDetails.driftEnd = oldDetails.driftEnd;
- newDetails.advancedDateTime = oldDetails.advancedDateTime;
- }
- if (newDetails.requestScheduled === -1) {
- newDetails.requestScheduled = oldDetails.requestScheduled;
- }
- }
- function mergeDateRanges(oldDateRanges, newDetails, logger2) {
- const {
- dateRanges: deltaDateRanges,
- recentlyRemovedDateranges
- } = newDetails;
- const dateRanges = _extends({}, oldDateRanges);
- if (recentlyRemovedDateranges) {
- recentlyRemovedDateranges.forEach((id) => {
- delete dateRanges[id];
- });
- }
- const mergeIds = Object.keys(dateRanges);
- const mergeCount = mergeIds.length;
- if (!mergeCount) {
- return deltaDateRanges;
- }
- Object.keys(deltaDateRanges).forEach((id) => {
- const mergedDateRange = dateRanges[id];
- const dateRange = new DateRange(deltaDateRanges[id].attr, mergedDateRange);
- if (dateRange.isValid) {
- dateRanges[id] = dateRange;
- if (!mergedDateRange) {
- dateRange.tagOrder += mergeCount;
- }
- } else {
- logger2.warn(`Ignoring invalid Playlist Delta Update DATERANGE tag: "${stringify(deltaDateRanges[id].attr)}"`);
- }
- });
- return dateRanges;
- }
- function mapPartIntersection(oldParts, newParts, intersectionFn) {
- if (oldParts && newParts) {
- let delta = 0;
- for (let i = 0, len = oldParts.length; i <= len; i++) {
- const oldPart = oldParts[i];
- const newPart = newParts[i + delta];
- if (oldPart && newPart && oldPart.index === newPart.index && oldPart.fragment.sn === newPart.fragment.sn) {
- intersectionFn(oldPart, newPart);
- } else {
- delta--;
- }
- }
- }
- }
- function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) {
- const skippedSegments = newDetails.skippedSegments;
- const start = Math.max(oldDetails.startSN, newDetails.startSN) - newDetails.startSN;
- const end = (oldDetails.fragmentHint ? 1 : 0) + (skippedSegments ? newDetails.endSN : Math.min(oldDetails.endSN, newDetails.endSN)) - newDetails.startSN;
- const delta = newDetails.startSN - oldDetails.startSN;
- const newFrags = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
- const oldFrags = oldDetails.fragmentHint ? oldDetails.fragments.concat(oldDetails.fragmentHint) : oldDetails.fragments;
- for (let i = start; i <= end; i++) {
- const oldFrag = oldFrags[delta + i];
- let newFrag = newFrags[i];
- if (skippedSegments && !newFrag && oldFrag) {
- newFrag = newDetails.fragments[i] = oldFrag;
- }
- if (oldFrag && newFrag) {
- intersectionFn(oldFrag, newFrag, i, newFrags);
- const uriBefore = oldFrag.relurl;
- const uriAfter = newFrag.relurl;
- if (uriBefore && notEqualAfterStrippingQueries(uriBefore, uriAfter)) {
- newDetails.playlistParsingError = getSequenceError(`media sequence mismatch ${newFrag.sn}:`, oldDetails, newDetails, oldFrag, newFrag);
- return;
- } else if (oldFrag.cc !== newFrag.cc) {
- newDetails.playlistParsingError = getSequenceError(`discontinuity sequence mismatch (${oldFrag.cc}!=${newFrag.cc})`, oldDetails, newDetails, oldFrag, newFrag);
- return;
- }
- }
- }
- }
- function getSequenceError(message, oldDetails, newDetails, oldFrag, newFrag) {
- return new Error(`${message} ${newFrag.url}
- Playlist starting @${oldDetails.startSN}
- ${oldDetails.m3u8}
- Playlist starting @${newDetails.startSN}
- ${newDetails.m3u8}`);
- }
- function adjustSliding(oldDetails, newDetails, matchingStableVariantOrRendition = true) {
- const delta = newDetails.startSN + newDetails.skippedSegments - oldDetails.startSN;
- const oldFragments = oldDetails.fragments;
- const advancedOrStable = delta >= 0;
- let sliding = 0;
- if (advancedOrStable && delta < oldFragments.length) {
- sliding = oldFragments[delta].start;
- } else if (advancedOrStable && newDetails.startSN === oldDetails.endSN + 1) {
- sliding = oldDetails.fragmentEnd;
- } else if (advancedOrStable && matchingStableVariantOrRendition) {
- sliding = oldDetails.fragmentStart + delta * newDetails.levelTargetDuration;
- } else if (!newDetails.skippedSegments && newDetails.fragmentStart === 0) {
- sliding = oldDetails.fragmentStart;
- } else {
- return;
- }
- addSliding(newDetails, sliding);
- }
- function addSliding(details, sliding) {
- if (sliding) {
- const fragments = details.fragments;
- for (let i = details.skippedSegments; i < fragments.length; i++) {
- fragments[i].addStart(sliding);
- }
- if (details.fragmentHint) {
- details.fragmentHint.addStart(sliding);
- }
- }
- }
- function computeReloadInterval(newDetails, distanceToLiveEdgeMs = Infinity) {
- let reloadInterval = 1e3 * newDetails.targetduration;
- if (newDetails.updated) {
- const fragments = newDetails.fragments;
- const liveEdgeMaxTargetDurations = 4;
- if (fragments.length && reloadInterval * liveEdgeMaxTargetDurations > distanceToLiveEdgeMs) {
- const lastSegmentDuration = fragments[fragments.length - 1].duration * 1e3;
- if (lastSegmentDuration < reloadInterval) {
- reloadInterval = lastSegmentDuration;
- }
- }
- } else {
- reloadInterval /= 2;
- }
- return Math.round(reloadInterval);
- }
- function getFragmentWithSN(details, sn, fragCurrent) {
- if (!details) {
- return null;
- }
- let fragment = details.fragments[sn - details.startSN];
- if (fragment) {
- return fragment;
- }
- fragment = details.fragmentHint;
- if (fragment && fragment.sn === sn) {
- return fragment;
- }
- if (sn < details.startSN && fragCurrent && fragCurrent.sn === sn) {
- return fragCurrent;
- }
- return null;
- }
- function getPartWith(details, sn, partIndex) {
- if (!details) {
- return null;
- }
- return findPart(details.partList, sn, partIndex);
- }
- function findPart(partList, sn, partIndex) {
- if (partList) {
- for (let i = partList.length; i--; ) {
- const part = partList[i];
- if (part.index === partIndex && part.fragment.sn === sn) {
- return part;
- }
- }
- }
- return null;
- }
- function reassignFragmentLevelIndexes(levels) {
- levels.forEach((level, index) => {
- var _level$details;
- (_level$details = level.details) == null || _level$details.fragments.forEach((fragment) => {
- fragment.level = index;
- if (fragment.initSegment) {
- fragment.initSegment.level = index;
- }
- });
- });
- }
- function notEqualAfterStrippingQueries(uriBefore, uriAfter) {
- if (uriBefore !== uriAfter && uriAfter) {
- return stripQuery(uriBefore) !== stripQuery(uriAfter);
- }
- return false;
- }
- function stripQuery(uri) {
- return uri.replace(/\?[^?]*$/, "");
- }
- function findFirstFragWithCC(fragments, cc) {
- for (let i = 0, len = fragments.length; i < len; i++) {
- var _fragments$i;
- if (((_fragments$i = fragments[i]) == null ? void 0 : _fragments$i.cc) === cc) {
- return fragments[i];
- }
- }
- return null;
- }
- function shouldAlignOnDiscontinuities(refDetails, details) {
- if (refDetails) {
- if (details.startCC < refDetails.endCC && details.endCC > refDetails.startCC) {
- return true;
- }
- }
- return false;
- }
- function adjustFragmentStart(frag, sliding) {
- const start = frag.start + sliding;
- frag.startPTS = start;
- frag.setStart(start);
- frag.endPTS = start + frag.duration;
- }
- function adjustSlidingStart(sliding, details) {
- const fragments = details.fragments;
- for (let i = 0, len = fragments.length; i < len; i++) {
- adjustFragmentStart(fragments[i], sliding);
- }
- if (details.fragmentHint) {
- adjustFragmentStart(details.fragmentHint, sliding);
- }
- details.alignedSliding = true;
- }
- function alignStream(switchDetails, details) {
- if (!switchDetails) {
- return;
- }
- alignDiscontinuities(details, switchDetails);
- if (!details.alignedSliding) {
- alignMediaPlaylistByPDT(details, switchDetails);
- }
- if (!details.alignedSliding && !details.skippedSegments) {
- adjustSliding(switchDetails, details, false);
- }
- }
- function alignDiscontinuities(details, refDetails) {
- if (!shouldAlignOnDiscontinuities(refDetails, details)) {
- return;
- }
- const targetCC = Math.min(refDetails.endCC, details.endCC);
- const refFrag = findFirstFragWithCC(refDetails.fragments, targetCC);
- const frag = findFirstFragWithCC(details.fragments, targetCC);
- if (!refFrag || !frag) {
- return;
- }
- logger.log(`Aligning playlist at start of dicontinuity sequence ${targetCC}`);
- const delta = refFrag.start - frag.start;
- adjustSlidingStart(delta, details);
- }
- function alignMediaPlaylistByPDT(details, refDetails) {
- if (!details.hasProgramDateTime || !refDetails.hasProgramDateTime) {
- return;
- }
- const fragments = details.fragments;
- const refFragments = refDetails.fragments;
- if (!fragments.length || !refFragments.length) {
- return;
- }
- let refFrag;
- let frag;
- const targetCC = Math.min(refDetails.endCC, details.endCC);
- if (refDetails.startCC < targetCC && details.startCC < targetCC) {
- refFrag = findFirstFragWithCC(refFragments, targetCC);
- frag = findFirstFragWithCC(fragments, targetCC);
- }
- if (!refFrag || !frag) {
- refFrag = refFragments[Math.floor(refFragments.length / 2)];
- frag = findFirstFragWithCC(fragments, refFrag.cc) || fragments[Math.floor(fragments.length / 2)];
- }
- const refPDT = refFrag.programDateTime;
- const targetPDT = frag.programDateTime;
- if (!refPDT || !targetPDT) {
- return;
- }
- const delta = (targetPDT - refPDT) / 1e3 - (frag.start - refFrag.start);
- adjustSlidingStart(delta, details);
- }
- function addEventListener(el, type, listener) {
- removeEventListener(el, type, listener);
- el.addEventListener(type, listener);
- }
- function removeEventListener(el, type, listener) {
- el.removeEventListener(type, listener);
- }
- var TimeRanges = {
- toString: function(r) {
- let log = "";
- const len = r.length;
- for (let i = 0; i < len; i++) {
- log += `[${r.start(i).toFixed(3)}-${r.end(i).toFixed(3)}]`;
- }
- return log;
- }
- };
- var State = {
- STOPPED: "STOPPED",
- IDLE: "IDLE",
- KEY_LOADING: "KEY_LOADING",
- FRAG_LOADING: "FRAG_LOADING",
- FRAG_LOADING_WAITING_RETRY: "FRAG_LOADING_WAITING_RETRY",
- WAITING_TRACK: "WAITING_TRACK",
- PARSING: "PARSING",
- PARSED: "PARSED",
- ENDED: "ENDED",
- ERROR: "ERROR",
- WAITING_INIT_PTS: "WAITING_INIT_PTS",
- WAITING_LEVEL: "WAITING_LEVEL"
- };
- var BaseStreamController = class extends TaskLoop {
- constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
- super(logPrefix, hls.logger);
- this.hls = void 0;
- this.fragPrevious = null;
- this.fragCurrent = null;
- this.fragmentTracker = void 0;
- this.transmuxer = null;
- this._state = State.STOPPED;
- this.playlistType = void 0;
- this.media = null;
- this.mediaBuffer = null;
- this.config = void 0;
- this.bitrateTest = false;
- this.lastCurrentTime = 0;
- this.nextLoadPosition = 0;
- this.startPosition = 0;
- this.startTimeOffset = null;
- this.retryDate = 0;
- this.levels = null;
- this.fragmentLoader = void 0;
- this.keyLoader = void 0;
- this.levelLastLoaded = null;
- this.startFragRequested = false;
- this.decrypter = void 0;
- this.initPTS = [];
- this.buffering = true;
- this.loadingParts = false;
- this.loopSn = void 0;
- this.onMediaSeeking = () => {
- const {
- config,
- fragCurrent,
- media,
- mediaBuffer,
- state
- } = this;
- const currentTime = media ? media.currentTime : 0;
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
- const noFowardBuffer = !bufferInfo.len;
- this.log(`Media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}, ${noFowardBuffer ? "out of" : "in"} buffer`);
- if (this.state === State.ENDED) {
- this.resetLoadingState();
- } else if (fragCurrent) {
- const tolerance = config.maxFragLookUpTolerance;
- const fragStartOffset = fragCurrent.start - tolerance;
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
- if (noFowardBuffer || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
- const pastFragment = currentTime > fragEndOffset;
- if (currentTime < fragStartOffset || pastFragment) {
- if (pastFragment && fragCurrent.loader) {
- this.log(`Cancelling fragment load for seek (sn: ${fragCurrent.sn})`);
- fragCurrent.abortRequests();
- this.resetLoadingState();
- }
- this.fragPrevious = null;
- }
- }
- }
- if (media) {
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
- const lastCurrentTime = this.lastCurrentTime;
- if (currentTime > lastCurrentTime) {
- this.lastCurrentTime = currentTime;
- }
- if (!this.loadingParts) {
- const bufferEnd = Math.max(bufferInfo.end, currentTime);
- const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
- if (shouldLoadParts) {
- this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
- this.loadingParts = shouldLoadParts;
- }
- }
- }
- if (!this.hls.hasEnoughToStart) {
- this.log(`Setting ${noFowardBuffer ? "startPosition" : "nextLoadPosition"} to ${currentTime} for seek without enough to start`);
- this.nextLoadPosition = currentTime;
- if (noFowardBuffer) {
- this.startPosition = currentTime;
- }
- }
- if (noFowardBuffer && this.state === State.IDLE) {
- this.tickImmediate();
- }
- };
- this.onMediaEnded = () => {
- this.log(`setting startPosition to 0 because media ended`);
- this.startPosition = this.lastCurrentTime = 0;
- };
- this.playlistType = playlistType;
- this.hls = hls;
- this.fragmentLoader = new FragmentLoader(hls.config);
- this.keyLoader = keyLoader;
- this.fragmentTracker = fragmentTracker;
- this.config = hls.config;
- this.decrypter = new Decrypter(hls.config);
- }
- registerListeners() {
- const {
- hls
- } = this;
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
- hls.on(Events.ERROR, this.onError, this);
- }
- unregisterListeners() {
- const {
- hls
- } = this;
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
- hls.off(Events.ERROR, this.onError, this);
- }
- doTick() {
- this.onTickEnd();
- }
- onTickEnd() {
- }
- startLoad(startPosition) {
- }
- stopLoad() {
- if (this.state === State.STOPPED) {
- return;
- }
- this.fragmentLoader.abort();
- this.keyLoader.abort(this.playlistType);
- const frag = this.fragCurrent;
- if (frag != null && frag.loader) {
- frag.abortRequests();
- this.fragmentTracker.removeFragment(frag);
- }
- this.resetTransmuxer();
- this.fragCurrent = null;
- this.fragPrevious = null;
- this.clearInterval();
- this.clearNextTick();
- this.state = State.STOPPED;
- }
- get startPositionValue() {
- const {
- nextLoadPosition,
- startPosition
- } = this;
- if (startPosition === -1 && nextLoadPosition) {
- return nextLoadPosition;
- }
- return startPosition;
- }
- get bufferingEnabled() {
- return this.buffering;
- }
- pauseBuffering() {
- this.buffering = false;
- }
- resumeBuffering() {
- this.buffering = true;
- }
- get inFlightFrag() {
- return {
- frag: this.fragCurrent,
- state: this.state
- };
- }
- _streamEnded(bufferInfo, levelDetails) {
- if (levelDetails.live || !this.media) {
- return false;
- }
- const bufferEnd = bufferInfo.end || 0;
- const timelineStart = this.config.timelineOffset || 0;
- if (bufferEnd <= timelineStart) {
- return false;
- }
- const bufferedRanges = bufferInfo.buffered;
- if (this.config.maxBufferHole && bufferedRanges && bufferedRanges.length > 1) {
- bufferInfo = BufferHelper.bufferedInfo(bufferedRanges, bufferInfo.start, 0);
- }
- const nextStart = bufferInfo.nextStart;
- const hasSecondBufferedRange = nextStart && nextStart > timelineStart && nextStart < levelDetails.edge;
- if (hasSecondBufferedRange) {
- return false;
- }
- if (this.media.currentTime < bufferInfo.start) {
- return false;
- }
- const partList = levelDetails.partList;
- if (partList != null && partList.length) {
- const lastPart = partList[partList.length - 1];
- const lastPartBuffered = BufferHelper.isBuffered(this.media, lastPart.start + lastPart.duration / 2);
- return lastPartBuffered;
- }
- const playlistType = levelDetails.fragments[levelDetails.fragments.length - 1].type;
- return this.fragmentTracker.isEndListAppended(playlistType);
- }
- getLevelDetails() {
- if (this.levels && this.levelLastLoaded !== null) {
- return this.levelLastLoaded.details;
- }
- }
- get timelineOffset() {
- const configuredTimelineOffset = this.config.timelineOffset;
- if (configuredTimelineOffset) {
- var _this$getLevelDetails;
- return ((_this$getLevelDetails = this.getLevelDetails()) == null ? void 0 : _this$getLevelDetails.appliedTimelineOffset) || configuredTimelineOffset;
- }
- return 0;
- }
- onMediaAttached(event, data) {
- const media = this.media = this.mediaBuffer = data.media;
- addEventListener(media, "seeking", this.onMediaSeeking);
- addEventListener(media, "ended", this.onMediaEnded);
- const config = this.config;
- if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
- this.startLoad(config.startPosition);
- }
- }
- onMediaDetaching(event, data) {
- const transferringMedia = !!data.transferMedia;
- const media = this.media;
- if (media === null) {
- return;
- }
- if (media.ended) {
- this.log("MSE detaching and video ended, reset startPosition");
- this.startPosition = this.lastCurrentTime = 0;
- }
- removeEventListener(media, "seeking", this.onMediaSeeking);
- removeEventListener(media, "ended", this.onMediaEnded);
- if (this.keyLoader && !transferringMedia) {
- this.keyLoader.detach();
- }
- this.media = this.mediaBuffer = null;
- this.loopSn = void 0;
- if (transferringMedia) {
- this.resetLoadingState();
- this.resetTransmuxer();
- return;
- }
- this.loadingParts = false;
- this.fragmentTracker.removeAllFragments();
- this.stopLoad();
- }
- onManifestLoading() {
- this.initPTS = [];
- this.levels = this.levelLastLoaded = this.fragCurrent = null;
- this.lastCurrentTime = this.startPosition = 0;
- this.startFragRequested = false;
- }
- onError(event, data) {
- }
- onManifestLoaded(event, data) {
- this.startTimeOffset = data.startTimeOffset;
- }
- onHandlerDestroying() {
- this.stopLoad();
- if (this.transmuxer) {
- this.transmuxer.destroy();
- this.transmuxer = null;
- }
- super.onHandlerDestroying();
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
- }
- onHandlerDestroyed() {
- this.state = State.STOPPED;
- if (this.fragmentLoader) {
- this.fragmentLoader.destroy();
- }
- if (this.keyLoader) {
- this.keyLoader.destroy();
- }
- if (this.decrypter) {
- this.decrypter.destroy();
- }
- this.hls = this.log = this.warn = this.decrypter = this.keyLoader = this.fragmentLoader = this.fragmentTracker = null;
- super.onHandlerDestroyed();
- }
- loadFragment(frag, level, targetBufferTime) {
- this.startFragRequested = true;
- this._loadFragForPlayback(frag, level, targetBufferTime);
- }
- _loadFragForPlayback(fragment, level, targetBufferTime) {
- const progressCallback = (data) => {
- const frag = data.frag;
- if (this.fragContextChanged(frag)) {
- this.warn(`${frag.type} sn: ${frag.sn}${data.part ? " part: " + data.part.index : ""} of ${this.fragInfo(frag, false, data.part)}) was dropped during download.`);
- this.fragmentTracker.removeFragment(frag);
- return;
- }
- frag.stats.chunkCount++;
- this._handleFragmentLoadProgress(data);
- };
- this._doFragLoad(fragment, level, targetBufferTime, progressCallback).then((data) => {
- if (!data) {
- return;
- }
- const state = this.state;
- const frag = data.frag;
- if (this.fragContextChanged(frag)) {
- if (state === State.FRAG_LOADING || !this.fragCurrent && state === State.PARSING) {
- this.fragmentTracker.removeFragment(frag);
- this.state = State.IDLE;
- }
- return;
- }
- if ("payload" in data) {
- this.log(`Loaded ${frag.type} sn: ${frag.sn} of ${this.playlistLabel()} ${frag.level}`);
- this.hls.trigger(Events.FRAG_LOADED, data);
- }
- this._handleFragmentLoadComplete(data);
- }).catch((reason) => {
- if (this.state === State.STOPPED || this.state === State.ERROR) {
- return;
- }
- this.warn(`Frag error: ${(reason == null ? void 0 : reason.message) || reason}`);
- this.resetFragmentLoading(fragment);
- });
- }
- clearTrackerIfNeeded(frag) {
- var _this$mediaBuffer;
- const {
- fragmentTracker
- } = this;
- const fragState = fragmentTracker.getState(frag);
- if (fragState === FragmentState.APPENDING) {
- const playlistType = frag.type;
- const bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType);
- const minForwardBufferLength = Math.max(frag.duration, bufferedInfo ? bufferedInfo.len : this.config.maxBufferLength);
- const backtrackFragment = this.backtrackFragment;
- const backtracked = backtrackFragment ? frag.sn - backtrackFragment.sn : 0;
- if (backtracked === 1 || this.reduceMaxBufferLength(minForwardBufferLength, frag.duration)) {
- fragmentTracker.removeFragment(frag);
- }
- } else if (((_this$mediaBuffer = this.mediaBuffer) == null ? void 0 : _this$mediaBuffer.buffered.length) === 0) {
- fragmentTracker.removeAllFragments();
- } else if (fragmentTracker.hasParts(frag.type)) {
- fragmentTracker.detectPartialFragments({
- frag,
- part: null,
- stats: frag.stats,
- id: frag.type
- });
- if (fragmentTracker.getState(frag) === FragmentState.PARTIAL) {
- fragmentTracker.removeFragment(frag);
- }
- }
- }
- checkLiveUpdate(details) {
- if (details.updated && !details.live) {
- const lastFragment = details.fragments[details.fragments.length - 1];
- this.fragmentTracker.detectPartialFragments({
- frag: lastFragment,
- part: null,
- stats: lastFragment.stats,
- id: lastFragment.type
- });
- }
- if (!details.fragments[0]) {
- details.deltaUpdateFailed = true;
- }
- }
- waitForLive(levelInfo) {
- const details = levelInfo.details;
- return (details == null ? void 0 : details.live) && details.type !== "EVENT" && (this.levelLastLoaded !== levelInfo || details.expired);
- }
- flushMainBuffer(startOffset, endOffset, type = null) {
- if (!(startOffset - endOffset)) {
- return;
- }
- const flushScope = {
- startOffset,
- endOffset,
- type
- };
- this.hls.trigger(Events.BUFFER_FLUSHING, flushScope);
- }
- _loadInitSegment(fragment, level) {
- this._doFragLoad(fragment, level).then((data) => {
- const frag = data == null ? void 0 : data.frag;
- if (!frag || this.fragContextChanged(frag) || !this.levels) {
- throw new Error("init load aborted");
- }
- return data;
- }).then((data) => {
- const {
- hls
- } = this;
- const {
- frag,
- payload
- } = data;
- const decryptData = frag.decryptdata;
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
- const startTime = self.performance.now();
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch((err) => {
- hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.FRAG_DECRYPT_ERROR,
- fatal: false,
- error: err,
- reason: err.message,
- frag
- });
- throw err;
- }).then((decryptedData) => {
- const endTime = self.performance.now();
- hls.trigger(Events.FRAG_DECRYPTED, {
- frag,
- payload: decryptedData,
- stats: {
- tstart: startTime,
- tdecrypt: endTime
- }
- });
- data.payload = decryptedData;
- return this.completeInitSegmentLoad(data);
- });
- }
- return this.completeInitSegmentLoad(data);
- }).catch((reason) => {
- if (this.state === State.STOPPED || this.state === State.ERROR) {
- return;
- }
- this.warn(reason);
- this.resetFragmentLoading(fragment);
- });
- }
- completeInitSegmentLoad(data) {
- const {
- levels
- } = this;
- if (!levels) {
- throw new Error("init load aborted, missing levels");
- }
- const stats = data.frag.stats;
- if (this.state !== State.STOPPED) {
- this.state = State.IDLE;
- }
- data.frag.data = new Uint8Array(data.payload);
- stats.parsing.start = stats.buffering.start = self.performance.now();
- stats.parsing.end = stats.buffering.end = self.performance.now();
- this.tick();
- }
- unhandledEncryptionError(initSegment, frag) {
- var _tracks$audio, _tracks$video;
- const tracks = initSegment.tracks;
- if (tracks && !frag.encrypted && ((_tracks$audio = tracks.audio) != null && _tracks$audio.encrypted || (_tracks$video = tracks.video) != null && _tracks$video.encrypted) && (!this.config.emeEnabled || !this.keyLoader.emeController)) {
- const media = this.media;
- const error = new Error(`Encrypted track with no key in ${this.fragInfo(frag)} (media ${media ? "attached mediaKeys: " + media.mediaKeys : "detached"})`);
- this.warn(error.message);
- if (!media || media.mediaKeys) {
- return false;
- }
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.KEY_SYSTEM_ERROR,
- details: ErrorDetails.KEY_SYSTEM_NO_KEYS,
- fatal: false,
- error,
- frag
- });
- this.resetTransmuxer();
- return true;
- }
- return false;
- }
- fragContextChanged(frag) {
- const {
- fragCurrent
- } = this;
- return !frag || !fragCurrent || frag.sn !== fragCurrent.sn || frag.level !== fragCurrent.level;
- }
- fragBufferedComplete(frag, part) {
- const media = this.mediaBuffer ? this.mediaBuffer : this.media;
- this.log(`Buffered ${frag.type} sn: ${frag.sn}${part ? " part: " + part.index : ""} of ${this.fragInfo(frag, false, part)} > buffer:${media ? TimeRanges.toString(BufferHelper.getBuffered(media)) : "(detached)"})`);
- if (isMediaFragment(frag)) {
- var _this$levels;
- if (frag.type !== PlaylistLevelType.SUBTITLE) {
- const el = frag.elementaryStreams;
- if (!Object.keys(el).some((type) => !!el[type])) {
- this.state = State.IDLE;
- return;
- }
- }
- const level = (_this$levels = this.levels) == null ? void 0 : _this$levels[frag.level];
- if (level != null && level.fragmentError) {
- this.log(`Resetting level fragment error count of ${level.fragmentError} on frag buffered`);
- level.fragmentError = 0;
- }
- }
- this.state = State.IDLE;
- }
- _handleFragmentLoadComplete(fragLoadedEndData) {
- const {
- transmuxer
- } = this;
- if (!transmuxer) {
- return;
- }
- const {
- frag,
- part,
- partsLoaded
- } = fragLoadedEndData;
- const complete = !partsLoaded || partsLoaded.length === 0 || partsLoaded.some((fragLoaded) => !fragLoaded);
- const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount + 1, 0, part ? part.index : -1, !complete);
- transmuxer.flush(chunkMeta);
- }
- _handleFragmentLoadProgress(frag) {
- }
- _doFragLoad(frag, level, targetBufferTime = null, progressCallback) {
- var _frag$decryptdata;
- this.fragCurrent = frag;
- const details = level.details;
- if (!this.levels || !details) {
- throw new Error(`frag load aborted, missing level${details ? "" : " detail"}s`);
- }
- let keyLoadingPromise = null;
- if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistLabel()} ${frag.level}`);
- this.state = State.KEY_LOADING;
- this.fragCurrent = frag;
- keyLoadingPromise = this.keyLoader.load(frag).then((keyLoadedData) => {
- if (!this.fragContextChanged(keyLoadedData.frag)) {
- this.hls.trigger(Events.KEY_LOADED, keyLoadedData);
- if (this.state === State.KEY_LOADING) {
- this.state = State.IDLE;
- }
- return keyLoadedData;
- }
- });
- this.hls.trigger(Events.KEY_LOADING, {
- frag
- });
- if (this.fragCurrent === null) {
- this.log(`context changed in KEY_LOADING`);
- return Promise.resolve(null);
- }
- } else if (!frag.encrypted) {
- keyLoadingPromise = this.keyLoader.loadClear(frag, details.encryptedFragments, this.startFragRequested);
- if (keyLoadingPromise) {
- this.log(`[eme] blocking frag load until media-keys acquired`);
- }
- }
- const fragPrevious = this.fragPrevious;
- if (isMediaFragment(frag) && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
- const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
- if (shouldLoadParts !== this.loadingParts) {
- this.log(`LL-Part loading ${shouldLoadParts ? "ON" : "OFF"} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
- this.loadingParts = shouldLoadParts;
- }
- }
- targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
- if (this.loadingParts && isMediaFragment(frag)) {
- const partList = details.partList;
- if (partList && progressCallback) {
- if (targetBufferTime > details.fragmentEnd && details.fragmentHint) {
- frag = details.fragmentHint;
- }
- const partIndex = this.getNextPart(partList, frag, targetBufferTime);
- if (partIndex > -1) {
- const part = partList[partIndex];
- frag = this.fragCurrent = part.fragment;
- this.log(`Loading ${frag.type} sn: ${frag.sn} part: ${part.index} (${partIndex}/${partList.length - 1}) of ${this.fragInfo(frag, false, part)}) cc: ${frag.cc} [${details.startSN}-${details.endSN}], target: ${parseFloat(targetBufferTime.toFixed(3))}`);
- this.nextLoadPosition = part.start + part.duration;
- this.state = State.FRAG_LOADING;
- let _result;
- if (keyLoadingPromise) {
- _result = keyLoadingPromise.then((keyLoadedData) => {
- if (!keyLoadedData || this.fragContextChanged(keyLoadedData.frag)) {
- return null;
- }
- return this.doFragPartsLoad(frag, part, level, progressCallback);
- }).catch((error) => this.handleFragLoadError(error));
- } else {
- _result = this.doFragPartsLoad(frag, part, level, progressCallback).catch((error) => this.handleFragLoadError(error));
- }
- this.hls.trigger(Events.FRAG_LOADING, {
- frag,
- part,
- targetBufferTime
- });
- if (this.fragCurrent === null) {
- return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING parts`));
- }
- return _result;
- } else if (!frag.url || this.loadedEndOfParts(partList, targetBufferTime)) {
- return Promise.resolve(null);
- }
- }
- }
- if (isMediaFragment(frag) && this.loadingParts) {
- var _details$partList;
- this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)} Check buffer at sn: ${frag.sn} loaded parts: ${(_details$partList = details.partList) == null ? void 0 : _details$partList.filter((p) => p.loaded).map((p) => `[${p.start}-${p.end}]`)}`);
- this.loadingParts = false;
- } else if (!frag.url) {
- return Promise.resolve(null);
- }
- this.log(`Loading ${frag.type} sn: ${frag.sn} of ${this.fragInfo(frag, false)}) cc: ${frag.cc} ${"[" + details.startSN + "-" + details.endSN + "]"}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
- if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
- this.nextLoadPosition = frag.start + frag.duration;
- }
- this.state = State.FRAG_LOADING;
- const dataOnProgress = this.config.progressive;
- let result;
- if (dataOnProgress && keyLoadingPromise) {
- result = keyLoadingPromise.then((keyLoadedData) => {
- if (!keyLoadedData || this.fragContextChanged(keyLoadedData.frag)) {
- return null;
- }
- return this.fragmentLoader.load(frag, progressCallback);
- }).catch((error) => this.handleFragLoadError(error));
- } else {
- result = Promise.all([this.fragmentLoader.load(frag, dataOnProgress ? progressCallback : void 0), keyLoadingPromise]).then(([fragLoadedData]) => {
- if (!dataOnProgress && progressCallback) {
- progressCallback(fragLoadedData);
- }
- return fragLoadedData;
- }).catch((error) => this.handleFragLoadError(error));
- }
- this.hls.trigger(Events.FRAG_LOADING, {
- frag,
- targetBufferTime
- });
- if (this.fragCurrent === null) {
- return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING`));
- }
- return result;
- }
- doFragPartsLoad(frag, fromPart, level, progressCallback) {
- return new Promise((resolve, reject) => {
- var _level$details;
- const partsLoaded = [];
- const initialPartList = (_level$details = level.details) == null ? void 0 : _level$details.partList;
- const loadPart = (part) => {
- this.fragmentLoader.loadPart(frag, part, progressCallback).then((partLoadedData) => {
- partsLoaded[part.index] = partLoadedData;
- const loadedPart = partLoadedData.part;
- this.hls.trigger(Events.FRAG_LOADED, partLoadedData);
- const nextPart = getPartWith(level.details, frag.sn, part.index + 1) || findPart(initialPartList, frag.sn, part.index + 1);
- if (nextPart) {
- loadPart(nextPart);
- } else {
- return resolve({
- frag,
- part: loadedPart,
- partsLoaded
- });
- }
- }).catch(reject);
- };
- loadPart(fromPart);
- });
- }
- handleFragLoadError(error) {
- if ("data" in error) {
- const data = error.data;
- if (data.frag && data.details === ErrorDetails.INTERNAL_ABORTED) {
- this.handleFragLoadAborted(data.frag, data.part);
- } else if (data.frag && data.type === ErrorTypes.KEY_SYSTEM_ERROR) {
- data.frag.abortRequests();
- this.resetStartWhenNotLoaded();
- this.resetFragmentLoading(data.frag);
- } else {
- this.hls.trigger(Events.ERROR, data);
- }
- } else {
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.INTERNAL_EXCEPTION,
- err: error,
- error,
- fatal: true
- });
- }
- return null;
- }
- _handleTransmuxerFlush(chunkMeta) {
- const context = this.getCurrentContext(chunkMeta);
- if (!context || this.state !== State.PARSING) {
- if (!this.fragCurrent && this.state !== State.STOPPED && this.state !== State.ERROR) {
- this.state = State.IDLE;
- }
- return;
- }
- const {
- frag,
- part,
- level
- } = context;
- const now2 = self.performance.now();
- frag.stats.parsing.end = now2;
- if (part) {
- part.stats.parsing.end = now2;
- }
- const levelDetails = this.getLevelDetails();
- const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
- const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
- if (shouldLoadParts !== this.loadingParts) {
- this.log(`LL-Part loading ${shouldLoadParts ? "ON" : "OFF"} after parsing segment ending @${frag.end.toFixed(2)}`);
- this.loadingParts = shouldLoadParts;
- }
- this.updateLevelTiming(frag, part, level, chunkMeta.partial);
- }
- shouldLoadParts(details, bufferEnd) {
- if (this.config.lowLatencyMode) {
- if (!details) {
- return this.loadingParts;
- }
- if (details.partList) {
- var _details$fragmentHint;
- const firstPart = details.partList[0];
- if (firstPart.fragment.type === PlaylistLevelType.SUBTITLE) {
- return false;
- }
- const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
- if (bufferEnd >= safePartStart) {
- var _this$media;
- const playhead = this.hls.hasEnoughToStart ? ((_this$media = this.media) == null ? void 0 : _this$media.currentTime) || this.lastCurrentTime : this.getLoadPosition();
- if (playhead > firstPart.start - firstPart.fragment.duration) {
- return true;
- }
- }
- }
- }
- return false;
- }
- getCurrentContext(chunkMeta) {
- const {
- levels,
- fragCurrent
- } = this;
- const {
- level: levelIndex,
- sn,
- part: partIndex
- } = chunkMeta;
- if (!(levels != null && levels[levelIndex])) {
- this.warn(`Levels object was unset while buffering fragment ${sn} of ${this.playlistLabel()} ${levelIndex}. The current chunk will not be buffered.`);
- return null;
- }
- const level = levels[levelIndex];
- const levelDetails = level.details;
- const part = partIndex > -1 ? getPartWith(levelDetails, sn, partIndex) : null;
- const frag = part ? part.fragment : getFragmentWithSN(levelDetails, sn, fragCurrent);
- if (!frag) {
- return null;
- }
- if (fragCurrent && fragCurrent !== frag) {
- frag.stats = fragCurrent.stats;
- }
- return {
- frag,
- part,
- level
- };
- }
- bufferFragmentData(data, frag, part, chunkMeta, noBacktracking) {
- if (this.state !== State.PARSING) {
- return;
- }
- const {
- data1,
- data2
- } = data;
- let buffer = data1;
- if (data2) {
- buffer = appendUint8Array(data1, data2);
- }
- if (!buffer.length) {
- return;
- }
- const offsetTimestamp = this.initPTS[frag.cc];
- const offset = offsetTimestamp ? -offsetTimestamp.baseTime / offsetTimestamp.timescale : void 0;
- const segment = {
- type: data.type,
- frag,
- part,
- chunkMeta,
- offset,
- parent: frag.type,
- data: buffer
- };
- this.hls.trigger(Events.BUFFER_APPENDING, segment);
- if (data.dropped && data.independent && !part) {
- if (noBacktracking) {
- return;
- }
- this.flushBufferGap(frag);
- }
- }
- flushBufferGap(frag) {
- const media = this.media;
- if (!media) {
- return;
- }
- if (!BufferHelper.isBuffered(media, media.currentTime)) {
- this.flushMainBuffer(0, frag.start);
- return;
- }
- const currentTime = media.currentTime;
- const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
- const fragDuration = frag.duration;
- const segmentFraction = Math.min(this.config.maxFragLookUpTolerance * 2, fragDuration * 0.25);
- const start = Math.max(Math.min(frag.start - segmentFraction, bufferInfo.end - segmentFraction), currentTime + segmentFraction);
- if (frag.start - start > segmentFraction) {
- this.flushMainBuffer(start, frag.start);
- }
- }
- getFwdBufferInfo(bufferable, type) {
- var _this$media2;
- const pos = this.getLoadPosition();
- if (!isFiniteNumber(pos)) {
- return null;
- }
- const backwardSeek = this.lastCurrentTime > pos;
- const maxBufferHole = backwardSeek || (_this$media2 = this.media) != null && _this$media2.paused ? 0 : this.config.maxBufferHole;
- return this.getFwdBufferInfoAtPos(bufferable, pos, type, maxBufferHole);
- }
- getFwdBufferInfoAtPos(bufferable, pos, type, maxBufferHole) {
- const bufferInfo = BufferHelper.bufferInfo(bufferable, pos, maxBufferHole);
- if (bufferInfo.len === 0 && bufferInfo.nextStart !== void 0) {
- const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
- if (bufferedFragAtPos && (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)) {
- const gapDuration = Math.max(Math.min(bufferInfo.nextStart, bufferedFragAtPos.end) - pos, maxBufferHole);
- return BufferHelper.bufferInfo(bufferable, pos, gapDuration);
- }
- }
- return bufferInfo;
- }
- getMaxBufferLength(levelBitrate) {
- const {
- config
- } = this;
- let maxBufLen;
- if (levelBitrate) {
- maxBufLen = Math.max(8 * config.maxBufferSize / levelBitrate, config.maxBufferLength);
- } else {
- maxBufLen = config.maxBufferLength;
- }
- return Math.min(maxBufLen, config.maxMaxBufferLength);
- }
- reduceMaxBufferLength(threshold, fragDuration) {
- const config = this.config;
- const minLength = Math.max(Math.min(threshold - fragDuration, config.maxBufferLength), fragDuration);
- const reducedLength = Math.max(threshold - fragDuration * 3, config.maxMaxBufferLength / 2, minLength);
- if (reducedLength >= minLength) {
- config.maxMaxBufferLength = reducedLength;
- this.warn(`Reduce max buffer length to ${reducedLength}s`);
- return true;
- }
- return false;
- }
- getAppendedFrag(position, playlistType = PlaylistLevelType.MAIN) {
- const fragOrPart = this.fragmentTracker ? this.fragmentTracker.getAppendedFrag(position, playlistType) : null;
- if (fragOrPart && "fragment" in fragOrPart) {
- return fragOrPart.fragment;
- }
- return fragOrPart;
- }
- getNextFragment(pos, levelDetails) {
- const fragments = levelDetails.fragments;
- const fragLen = fragments.length;
- if (!fragLen) {
- return null;
- }
- const {
- config
- } = this;
- const start = fragments[0].start;
- const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
- let frag = null;
- if (levelDetails.live) {
- const initialLiveManifestSize = config.initialLiveManifestSize;
- if (fragLen < initialLiveManifestSize) {
- this.warn(`Not enough fragments to start playback (have: ${fragLen}, need: ${initialLiveManifestSize})`);
- return null;
- }
- if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
- var _frag;
- if (canLoadParts && !this.loadingParts) {
- this.log(`LL-Part loading ON for initial live fragment`);
- this.loadingParts = true;
- }
- frag = this.getInitialLiveFragment(levelDetails);
- const mainStart = this.hls.startPosition;
- const liveSyncPosition = this.hls.liveSyncPosition;
- const startPosition = frag ? (mainStart !== -1 && mainStart >= start ? mainStart : liveSyncPosition) || frag.start : pos;
- this.log(`Setting startPosition to ${startPosition} to match start frag at live edge. mainStart: ${mainStart} liveSyncPosition: ${liveSyncPosition} frag.start: ${(_frag = frag) == null ? void 0 : _frag.start}`);
- this.startPosition = this.nextLoadPosition = startPosition;
- }
- } else if (pos <= start) {
- frag = fragments[0];
- }
- if (!frag) {
- const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
- frag = this.getFragmentAtPosition(pos, end, levelDetails);
- }
- let programFrag = this.filterReplacedPrimary(frag, levelDetails);
- if (!programFrag && frag) {
- const curSNIdx = frag.sn - levelDetails.startSN;
- programFrag = this.filterReplacedPrimary(fragments[curSNIdx + 1] || null, levelDetails);
- }
- return this.mapToInitFragWhenRequired(programFrag);
- }
- isLoopLoading(frag, targetBufferTime) {
- const trackerState = this.fragmentTracker.getState(frag);
- return (trackerState === FragmentState.OK || trackerState === FragmentState.PARTIAL && !!frag.gap) && this.nextLoadPosition > targetBufferTime;
- }
- getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, playlistType, maxBufLen) {
- let nextFragment = null;
- if (frag.gap) {
- nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
- if (nextFragment && !nextFragment.gap && bufferInfo.nextStart) {
- const nextbufferInfo = this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer : this.media, bufferInfo.nextStart, playlistType, 0);
- if (nextbufferInfo !== null && bufferInfo.len + nextbufferInfo.len >= maxBufLen) {
- const sn = nextFragment.sn;
- if (this.loopSn !== sn) {
- this.log(`buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`);
- this.loopSn = sn;
- }
- return null;
- }
- }
- }
- this.loopSn = void 0;
- return nextFragment;
- }
- get primaryPrefetch() {
- if (interstitialsEnabled(this.config)) {
- var _this$hls$interstitia;
- const playingInterstitial = (_this$hls$interstitia = this.hls.interstitialsManager) == null || (_this$hls$interstitia = _this$hls$interstitia.playingItem) == null ? void 0 : _this$hls$interstitia.event;
- if (playingInterstitial) {
- return true;
- }
- }
- return false;
- }
- filterReplacedPrimary(frag, details) {
- if (!frag) {
- return frag;
- }
- if (interstitialsEnabled(this.config) && frag.type !== PlaylistLevelType.SUBTITLE) {
- const interstitials = this.hls.interstitialsManager;
- const bufferingItem = interstitials == null ? void 0 : interstitials.bufferingItem;
- if (bufferingItem) {
- const bufferingInterstitial = bufferingItem.event;
- if (bufferingInterstitial) {
- if (bufferingInterstitial.appendInPlace || Math.abs(frag.start - bufferingItem.start) > 1 || bufferingItem.start === 0) {
- return null;
- }
- } else {
- if (frag.end <= bufferingItem.start && (details == null ? void 0 : details.live) === false) {
- return null;
- }
- if (frag.start > bufferingItem.end && bufferingItem.nextEvent) {
- if (bufferingItem.nextEvent.appendInPlace || frag.start - bufferingItem.end > 1) {
- return null;
- }
- }
- }
- }
- const playerQueue = interstitials == null ? void 0 : interstitials.playerQueue;
- if (playerQueue) {
- for (let i = playerQueue.length; i--; ) {
- const interstitial = playerQueue[i].interstitial;
- if (interstitial.appendInPlace && frag.start >= interstitial.startTime && frag.end <= interstitial.resumeTime) {
- return null;
- }
- }
- }
- }
- return frag;
- }
- mapToInitFragWhenRequired(frag) {
- if (frag != null && frag.initSegment && !frag.initSegment.data && !this.bitrateTest) {
- return frag.initSegment;
- }
- return frag;
- }
- getNextPart(partList, frag, targetBufferTime) {
- let nextPart = -1;
- let contiguous = false;
- let independentAttrOmitted = true;
- for (let i = 0, len = partList.length; i < len; i++) {
- const part = partList[i];
- independentAttrOmitted = independentAttrOmitted && !part.independent;
- if (nextPart > -1 && targetBufferTime < part.start) {
- break;
- }
- const loaded = part.loaded;
- if (loaded) {
- nextPart = -1;
- } else if (contiguous || (part.independent || independentAttrOmitted) && part.fragment === frag) {
- if (part.fragment !== frag) {
- this.warn(`Need buffer at ${targetBufferTime} but next unloaded part starts at ${part.start}`);
- }
- nextPart = i;
- }
- contiguous = loaded;
- }
- return nextPart;
- }
- loadedEndOfParts(partList, targetBufferTime) {
- let part;
- for (let i = partList.length; i--; ) {
- part = partList[i];
- if (!part.loaded) {
- return false;
- }
- if (targetBufferTime > part.start) {
- return true;
- }
- }
- return false;
- }
- /*
- This method is used find the best matching first fragment for a live playlist. This fragment is used to calculate the
- "sliding" of the playlist, which is its offset from the start of playback. After sliding we can compute the real
- start and end times for each fragment in the playlist (after which this method will not need to be called).
- */
- getInitialLiveFragment(levelDetails) {
- const fragments = levelDetails.fragments;
- const fragPrevious = this.fragPrevious;
- let frag = null;
- if (fragPrevious) {
- if (levelDetails.hasProgramDateTime) {
- this.log(`Live playlist, switching playlist, load frag with same PDT: ${fragPrevious.programDateTime}`);
- frag = findFragmentByPDT(fragments, fragPrevious.endProgramDateTime, this.config.maxFragLookUpTolerance);
- }
- if (!frag) {
- const targetSN = fragPrevious.sn + 1;
- if (targetSN >= levelDetails.startSN && targetSN <= levelDetails.endSN) {
- const fragNext = fragments[targetSN - levelDetails.startSN];
- if (fragPrevious.cc === fragNext.cc) {
- frag = fragNext;
- this.log(`Live playlist, switching playlist, load frag with next SN: ${frag.sn}`);
- }
- }
- if (!frag) {
- frag = findNearestWithCC(levelDetails, fragPrevious.cc, fragPrevious.end);
- if (frag) {
- this.log(`Live playlist, switching playlist, load frag with same CC: ${frag.sn}`);
- }
- }
- }
- } else {
- const liveStart = this.hls.liveSyncPosition;
- if (liveStart !== null) {
- frag = this.getFragmentAtPosition(liveStart, this.bitrateTest ? levelDetails.fragmentEnd : levelDetails.edge, levelDetails);
- }
- }
- return frag;
- }
- /*
- This method finds the best matching fragment given the provided position.
- */
- getFragmentAtPosition(bufferEnd, end, levelDetails) {
- const {
- config
- } = this;
- let {
- fragPrevious
- } = this;
- let {
- fragments,
- endSN
- } = levelDetails;
- const {
- fragmentHint
- } = levelDetails;
- const {
- maxFragLookUpTolerance
- } = config;
- const partList = levelDetails.partList;
- const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
- if (loadingParts && !this.bitrateTest && partList[partList.length - 1].fragment.sn === fragmentHint.sn) {
- fragments = fragments.concat(fragmentHint);
- endSN = fragmentHint.sn;
- }
- let frag;
- if (bufferEnd < end) {
- var _this$media3;
- const backwardSeek = bufferEnd < this.lastCurrentTime;
- const lookupTolerance = backwardSeek || bufferEnd > end - maxFragLookUpTolerance || (_this$media3 = this.media) != null && _this$media3.paused || !this.startFragRequested ? 0 : maxFragLookUpTolerance;
- frag = findFragmentByPTS(fragPrevious, fragments, bufferEnd, lookupTolerance);
- } else {
- frag = fragments[fragments.length - 1];
- }
- if (frag) {
- const curSNIdx = frag.sn - levelDetails.startSN;
- const fragState = this.fragmentTracker.getState(frag);
- if (fragState === FragmentState.OK || fragState === FragmentState.PARTIAL && frag.gap) {
- fragPrevious = frag;
- }
- if (fragPrevious && frag.sn === fragPrevious.sn && (!loadingParts || partList[0].fragment.sn > frag.sn || !levelDetails.live)) {
- const sameLevel = frag.level === fragPrevious.level;
- if (sameLevel) {
- const nextFrag = fragments[curSNIdx + 1];
- if (frag.sn < endSN && this.fragmentTracker.getState(nextFrag) !== FragmentState.OK) {
- frag = nextFrag;
- } else {
- frag = null;
- }
- }
- }
- }
- return frag;
- }
- alignPlaylists(details, previousDetails, switchDetails) {
- const length = details.fragments.length;
- if (!length) {
- this.warn(`No fragments in live playlist`);
- return 0;
- }
- const slidingStart = details.fragmentStart;
- const firstLevelLoad = !previousDetails;
- const aligned = details.alignedSliding && isFiniteNumber(slidingStart);
- if (firstLevelLoad || !aligned && !slidingStart) {
- alignStream(switchDetails, details);
- const alignedSlidingStart = details.fragmentStart;
- this.log(`Live playlist sliding: ${alignedSlidingStart.toFixed(2)} start-sn: ${previousDetails ? previousDetails.startSN : "na"}->${details.startSN} fragments: ${length}`);
- return alignedSlidingStart;
- }
- return slidingStart;
- }
- waitForCdnTuneIn(details) {
- const advancePartLimit = 3;
- return details.live && details.canBlockReload && details.partTarget && details.tuneInGoal > Math.max(details.partHoldBack, details.partTarget * advancePartLimit);
- }
- setStartPosition(details, sliding) {
- let startPosition = this.startPosition;
- if (startPosition < sliding) {
- startPosition = -1;
- }
- const timelineOffset = this.timelineOffset;
- if (startPosition === -1) {
- const offsetInMultivariantPlaylist = this.startTimeOffset !== null;
- const startTimeOffset = offsetInMultivariantPlaylist ? this.startTimeOffset : details.startTimeOffset;
- if (startTimeOffset !== null && isFiniteNumber(startTimeOffset)) {
- startPosition = sliding + startTimeOffset;
- if (startTimeOffset < 0) {
- startPosition += details.edge;
- }
- startPosition = Math.min(Math.max(sliding, startPosition), sliding + details.totalduration);
- this.log(`Setting startPosition to ${startPosition} for start time offset ${startTimeOffset} found in ${offsetInMultivariantPlaylist ? "multivariant" : "media"} playlist`);
- this.startPosition = startPosition;
- } else if (details.live) {
- startPosition = this.hls.liveSyncPosition || sliding;
- this.log(`Setting startPosition to -1 to start at live edge ${startPosition}`);
- this.startPosition = -1;
- } else {
- this.log(`setting startPosition to 0 by default`);
- this.startPosition = startPosition = 0;
- }
- this.lastCurrentTime = startPosition + timelineOffset;
- }
- this.nextLoadPosition = startPosition + timelineOffset;
- }
- getLoadPosition() {
- var _this$hls;
- const {
- media
- } = this;
- let pos = 0;
- if ((_this$hls = this.hls) != null && _this$hls.hasEnoughToStart && media) {
- pos = media.currentTime;
- } else if (this.nextLoadPosition >= 0) {
- pos = this.nextLoadPosition;
- }
- return pos;
- }
- handleFragLoadAborted(frag, part) {
- if (this.transmuxer && frag.type === this.playlistType && isMediaFragment(frag) && frag.stats.aborted) {
- this.log(`Fragment ${frag.sn}${part ? " part " + part.index : ""} of ${this.playlistLabel()} ${frag.level} was aborted`);
- this.resetFragmentLoading(frag);
- }
- }
- resetFragmentLoading(frag) {
- if (!this.fragCurrent || !this.fragContextChanged(frag) && this.state !== State.FRAG_LOADING_WAITING_RETRY) {
- this.state = State.IDLE;
- }
- }
- onFragmentOrKeyLoadError(filterType, data) {
- var _this$hls$latestLevel;
- if (data.chunkMeta && !data.frag) {
- const context = this.getCurrentContext(data.chunkMeta);
- if (context) {
- data.frag = context.frag;
- }
- }
- const frag = data.frag;
- if (!frag || frag.type !== filterType || !this.levels) {
- return;
- }
- if (this.fragContextChanged(frag)) {
- var _this$fragCurrent;
- this.warn(`Frag load error must match current frag to retry ${frag.url} > ${(_this$fragCurrent = this.fragCurrent) == null ? void 0 : _this$fragCurrent.url}`);
- return;
- }
- const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
- if (gapTagEncountered) {
- this.fragmentTracker.fragBuffered(frag, true);
- }
- const errorAction = data.errorAction;
- if (!errorAction) {
- this.state = State.ERROR;
- return;
- }
- const {
- action,
- flags,
- retryCount = 0,
- retryConfig
- } = errorAction;
- const couldRetry = !!retryConfig;
- const retry = couldRetry && action === NetworkErrorAction.RetryRequest;
- const noAlternate = couldRetry && !errorAction.resolved && flags === ErrorActionFlags.MoveAllAlternatesMatchingHost;
- const live = (_this$hls$latestLevel = this.hls.latestLevelDetails) == null ? void 0 : _this$hls$latestLevel.live;
- if (!retry && noAlternate && isMediaFragment(frag) && !frag.endList && live && !isUnusableKeyError(data)) {
- this.resetFragmentErrors(filterType);
- this.treatAsGap(frag);
- errorAction.resolved = true;
- } else if ((retry || noAlternate) && retryCount < retryConfig.maxNumRetry) {
- var _data$response;
- const offlineStatus = offlineHttpStatus((_data$response = data.response) == null ? void 0 : _data$response.code);
- const delay = getRetryDelay(retryConfig, retryCount);
- this.resetStartWhenNotLoaded();
- this.retryDate = self.performance.now() + delay;
- this.state = State.FRAG_LOADING_WAITING_RETRY;
- errorAction.resolved = true;
- if (offlineStatus) {
- this.log(`Waiting for connection (offline)`);
- this.retryDate = Infinity;
- data.reason = "offline";
- return;
- }
- this.warn(`Fragment ${frag.sn} of ${filterType} ${frag.level} errored with ${data.details}, retrying loading ${retryCount + 1}/${retryConfig.maxNumRetry} in ${delay}ms`);
- } else if (retryConfig) {
- this.resetFragmentErrors(filterType);
- if (retryCount < retryConfig.maxNumRetry) {
- if (!gapTagEncountered && action !== NetworkErrorAction.RemoveAlternatePermanently) {
- errorAction.resolved = true;
- }
- } else {
- this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
- return;
- }
- } else if (action === NetworkErrorAction.SendAlternateToPenaltyBox) {
- this.state = State.WAITING_LEVEL;
- } else {
- this.state = State.ERROR;
- }
- this.tickImmediate();
- }
- checkRetryDate() {
- const now2 = self.performance.now();
- const retryDate = this.retryDate;
- const waitingForConnection = retryDate === Infinity;
- if (!retryDate || now2 >= retryDate || waitingForConnection && !offlineHttpStatus(0)) {
- if (waitingForConnection) {
- this.log(`Connection restored (online)`);
- }
- this.resetStartWhenNotLoaded();
- this.state = State.IDLE;
- }
- }
- reduceLengthAndFlushBuffer(data) {
- if (this.state === State.PARSING || this.state === State.PARSED) {
- const frag = data.frag;
- const playlistType = data.parent;
- const bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType);
- const buffered = bufferedInfo && bufferedInfo.len > 0.5;
- if (buffered) {
- this.reduceMaxBufferLength(bufferedInfo.len, (frag == null ? void 0 : frag.duration) || 10);
- }
- const flushBuffer = !buffered;
- if (flushBuffer) {
- this.warn(`Buffer full error while media.currentTime (${this.getLoadPosition()}) is not buffered, flush ${playlistType} buffer`);
- }
- if (frag) {
- this.fragmentTracker.removeFragment(frag);
- this.nextLoadPosition = frag.start;
- }
- this.resetLoadingState();
- return flushBuffer;
- }
- return false;
- }
- resetFragmentErrors(filterType) {
- if (filterType === PlaylistLevelType.AUDIO) {
- this.fragCurrent = null;
- }
- if (!this.hls.hasEnoughToStart) {
- this.startFragRequested = false;
- }
- if (this.state !== State.STOPPED) {
- this.state = State.IDLE;
- }
- }
- afterBufferFlushed(media, bufferType, playlistType) {
- if (!media) {
- return;
- }
- const bufferedTimeRanges = BufferHelper.getBuffered(media);
- this.fragmentTracker.detectEvictedFragments(bufferType, bufferedTimeRanges, playlistType);
- if (this.state === State.ENDED) {
- this.resetLoadingState();
- }
- }
- resetLoadingState() {
- this.log("Reset loading state");
- this.fragCurrent = null;
- this.fragPrevious = null;
- if (this.state !== State.STOPPED) {
- this.state = State.IDLE;
- }
- }
- resetStartWhenNotLoaded() {
- if (!this.hls.hasEnoughToStart) {
- this.startFragRequested = false;
- const level = this.levelLastLoaded;
- const details = level ? level.details : null;
- if (details != null && details.live) {
- this.log(`resetting startPosition for live start`);
- this.startPosition = -1;
- this.setStartPosition(details, details.fragmentStart);
- this.resetLoadingState();
- } else {
- this.nextLoadPosition = this.startPosition;
- }
- }
- }
- resetWhenMissingContext(chunkMeta) {
- this.log(`Loading context changed while buffering sn ${chunkMeta.sn} of ${this.playlistLabel()} ${chunkMeta.level === -1 ? "<removed>" : chunkMeta.level}. This chunk will not be buffered.`);
- this.removeUnbufferedFrags();
- this.resetStartWhenNotLoaded();
- this.resetLoadingState();
- }
- removeUnbufferedFrags(start = 0) {
- this.fragmentTracker.removeFragmentsInRange(start, Infinity, this.playlistType, false, true);
- }
- updateLevelTiming(frag, part, level, partial) {
- const details = level.details;
- if (!details) {
- this.warn("level.details undefined");
- return;
- }
- const parsed = Object.keys(frag.elementaryStreams).reduce((result, type) => {
- const info = frag.elementaryStreams[type];
- if (info) {
- const parsedDuration = info.endPTS - info.startPTS;
- if (parsedDuration <= 0) {
- this.warn(`Could not parse fragment ${frag.sn} ${type} duration reliably (${parsedDuration})`);
- return result || false;
- }
- const drift = partial ? 0 : updateFragPTSDTS(details, frag, info.startPTS, info.endPTS, info.startDTS, info.endDTS, this);
- this.hls.trigger(Events.LEVEL_PTS_UPDATED, {
- details,
- level,
- drift,
- type,
- frag,
- start: info.startPTS,
- end: info.endPTS
- });
- return true;
- }
- return result;
- }, false);
- if (!parsed) {
- var _this$transmuxer;
- if (level.fragmentError === 0) {
- this.treatAsGap(frag, level);
- }
- if (((_this$transmuxer = this.transmuxer) == null ? void 0 : _this$transmuxer.error) === null) {
- const error = new Error(`Found no media in fragment ${frag.sn} of ${this.playlistLabel()} ${frag.level} resetting transmuxer to fallback to playlist timing`);
- this.warn(error.message);
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.FRAG_PARSING_ERROR,
- fatal: false,
- error,
- frag,
- reason: `Found no media in msn ${frag.sn} of ${this.playlistLabel()} "${level.url}"`
- });
- if (!this.hls) {
- return;
- }
- this.resetTransmuxer();
- }
- }
- this.state = State.PARSED;
- this.log(`Parsed ${frag.type} sn: ${frag.sn}${part ? " part: " + part.index : ""} of ${this.fragInfo(frag, false, part)})`);
- this.hls.trigger(Events.FRAG_PARSED, {
- frag,
- part
- });
- }
- playlistLabel() {
- return this.playlistType === PlaylistLevelType.MAIN ? "level" : "track";
- }
- fragInfo(frag, pts = true, part) {
- var _ref, _ref2;
- return `${this.playlistLabel()} ${frag.level} (${part ? "part" : "frag"}:[${((_ref = pts && !part ? frag.startPTS : (part || frag).start) != null ? _ref : NaN).toFixed(3)}-${((_ref2 = pts && !part ? frag.endPTS : (part || frag).end) != null ? _ref2 : NaN).toFixed(3)}]${part && frag.type === "main" ? "INDEPENDENT=" + (part.independent ? "YES" : "NO") : ""}`;
- }
- treatAsGap(frag, level) {
- if (level) {
- level.fragmentError++;
- }
- frag.gap = true;
- this.fragmentTracker.removeFragment(frag);
- this.fragmentTracker.fragBuffered(frag, true);
- }
- resetTransmuxer() {
- var _this$transmuxer2;
- (_this$transmuxer2 = this.transmuxer) == null || _this$transmuxer2.reset();
- }
- recoverWorkerError(data) {
- if (data.event === "demuxerWorker") {
- this.fragmentTracker.removeAllFragments();
- if (this.transmuxer) {
- this.transmuxer.destroy();
- this.transmuxer = null;
- }
- this.resetStartWhenNotLoaded();
- this.resetLoadingState();
- }
- }
- set state(nextState) {
- const previousState = this._state;
- if (previousState !== nextState) {
- this._state = nextState;
- this.log(`${previousState}->${nextState}`);
- }
- }
- get state() {
- return this._state;
- }
- };
- function interstitialsEnabled(config) {
- return !!config.interstitialsController && config.enableInterstitialPlayback !== false;
- }
- var ChunkCache = class {
- constructor() {
- this.chunks = [];
- this.dataLength = 0;
- }
- push(chunk) {
- this.chunks.push(chunk);
- this.dataLength += chunk.length;
- }
- flush() {
- const {
- chunks,
- dataLength
- } = this;
- let result;
- if (!chunks.length) {
- return new Uint8Array(0);
- } else if (chunks.length === 1) {
- result = chunks[0];
- } else {
- result = concatUint8Arrays(chunks, dataLength);
- }
- this.reset();
- return result;
- }
- reset() {
- this.chunks.length = 0;
- this.dataLength = 0;
- }
- };
- function concatUint8Arrays(chunks, dataLength) {
- const result = new Uint8Array(dataLength);
- let offset = 0;
- for (let i = 0; i < chunks.length; i++) {
- const chunk = chunks[i];
- result.set(chunk, offset);
- offset += chunk.length;
- }
- return result;
- }
- var eventemitter3 = { exports: {} };
- var hasRequiredEventemitter3;
- function requireEventemitter3() {
- if (hasRequiredEventemitter3)
- return eventemitter3.exports;
- hasRequiredEventemitter3 = 1;
- (function(module) {
- var has = Object.prototype.hasOwnProperty, prefix = "~";
- function Events2() {
- }
- if (Object.create) {
- Events2.prototype = /* @__PURE__ */ Object.create(null);
- if (!new Events2().__proto__)
- prefix = false;
- }
- function EE(fn, context, once) {
- this.fn = fn;
- this.context = context;
- this.once = once || false;
- }
- function addListener(emitter, event, fn, context, once) {
- if (typeof fn !== "function") {
- throw new TypeError("The listener must be a function");
- }
- var listener = new EE(fn, context || emitter, once), evt = prefix ? prefix + event : event;
- if (!emitter._events[evt])
- emitter._events[evt] = listener, emitter._eventsCount++;
- else if (!emitter._events[evt].fn)
- emitter._events[evt].push(listener);
- else
- emitter._events[evt] = [emitter._events[evt], listener];
- return emitter;
- }
- function clearEvent(emitter, evt) {
- if (--emitter._eventsCount === 0)
- emitter._events = new Events2();
- else
- delete emitter._events[evt];
- }
- function EventEmitter2() {
- this._events = new Events2();
- this._eventsCount = 0;
- }
- EventEmitter2.prototype.eventNames = function eventNames() {
- var names = [], events, name;
- if (this._eventsCount === 0)
- return names;
- for (name in events = this._events) {
- if (has.call(events, name))
- names.push(prefix ? name.slice(1) : name);
- }
- if (Object.getOwnPropertySymbols) {
- return names.concat(Object.getOwnPropertySymbols(events));
- }
- return names;
- };
- EventEmitter2.prototype.listeners = function listeners(event) {
- var evt = prefix ? prefix + event : event, handlers = this._events[evt];
- if (!handlers)
- return [];
- if (handlers.fn)
- return [handlers.fn];
- for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
- ee[i] = handlers[i].fn;
- }
- return ee;
- };
- EventEmitter2.prototype.listenerCount = function listenerCount(event) {
- var evt = prefix ? prefix + event : event, listeners = this._events[evt];
- if (!listeners)
- return 0;
- if (listeners.fn)
- return 1;
- return listeners.length;
- };
- EventEmitter2.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
- var evt = prefix ? prefix + event : event;
- if (!this._events[evt])
- return false;
- var listeners = this._events[evt], len = arguments.length, args, i;
- if (listeners.fn) {
- if (listeners.once)
- this.removeListener(event, listeners.fn, void 0, true);
- switch (len) {
- case 1:
- return listeners.fn.call(listeners.context), true;
- case 2:
- return listeners.fn.call(listeners.context, a1), true;
- case 3:
- return listeners.fn.call(listeners.context, a1, a2), true;
- case 4:
- return listeners.fn.call(listeners.context, a1, a2, a3), true;
- case 5:
- return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
- case 6:
- return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
- }
- for (i = 1, args = new Array(len - 1); i < len; i++) {
- args[i - 1] = arguments[i];
- }
- listeners.fn.apply(listeners.context, args);
- } else {
- var length = listeners.length, j;
- for (i = 0; i < length; i++) {
- if (listeners[i].once)
- this.removeListener(event, listeners[i].fn, void 0, true);
- switch (len) {
- case 1:
- listeners[i].fn.call(listeners[i].context);
- break;
- case 2:
- listeners[i].fn.call(listeners[i].context, a1);
- break;
- case 3:
- listeners[i].fn.call(listeners[i].context, a1, a2);
- break;
- case 4:
- listeners[i].fn.call(listeners[i].context, a1, a2, a3);
- break;
- default:
- if (!args)
- for (j = 1, args = new Array(len - 1); j < len; j++) {
- args[j - 1] = arguments[j];
- }
- listeners[i].fn.apply(listeners[i].context, args);
- }
- }
- }
- return true;
- };
- EventEmitter2.prototype.on = function on(event, fn, context) {
- return addListener(this, event, fn, context, false);
- };
- EventEmitter2.prototype.once = function once(event, fn, context) {
- return addListener(this, event, fn, context, true);
- };
- EventEmitter2.prototype.removeListener = function removeListener(event, fn, context, once) {
- var evt = prefix ? prefix + event : event;
- if (!this._events[evt])
- return this;
- if (!fn) {
- clearEvent(this, evt);
- return this;
- }
- var listeners = this._events[evt];
- if (listeners.fn) {
- if (listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context)) {
- clearEvent(this, evt);
- }
- } else {
- for (var i = 0, events = [], length = listeners.length; i < length; i++) {
- if (listeners[i].fn !== fn || once && !listeners[i].once || context && listeners[i].context !== context) {
- events.push(listeners[i]);
- }
- }
- if (events.length)
- this._events[evt] = events.length === 1 ? events[0] : events;
- else
- clearEvent(this, evt);
- }
- return this;
- };
- EventEmitter2.prototype.removeAllListeners = function removeAllListeners(event) {
- var evt;
- if (event) {
- evt = prefix ? prefix + event : event;
- if (this._events[evt])
- clearEvent(this, evt);
- } else {
- this._events = new Events2();
- this._eventsCount = 0;
- }
- return this;
- };
- EventEmitter2.prototype.off = EventEmitter2.prototype.removeListener;
- EventEmitter2.prototype.addListener = EventEmitter2.prototype.on;
- EventEmitter2.prefixed = prefix;
- EventEmitter2.EventEmitter = EventEmitter2;
- {
- module.exports = EventEmitter2;
- }
- })(eventemitter3);
- return eventemitter3.exports;
- }
- var eventemitter3Exports = requireEventemitter3();
- var EventEmitter = getDefaultExportFromCjs(eventemitter3Exports);
- var version = "1.6.13";
- var workerStore = {};
- function hasUMDWorker() {
- return typeof __HLS_WORKER_BUNDLE__ === "function";
- }
- function injectWorker() {
- const workerContext = workerStore[version];
- if (workerContext) {
- workerContext.clientCount++;
- return workerContext;
- }
- const blob = new self.Blob([`var exports={};var module={exports:exports};function define(f){f()};define.amd=true;(${__HLS_WORKER_BUNDLE__.toString()})(true);`], {
- type: "text/javascript"
- });
- const objectURL = self.URL.createObjectURL(blob);
- const worker = new self.Worker(objectURL);
- const result = {
- worker,
- objectURL,
- clientCount: 1
- };
- workerStore[version] = result;
- return result;
- }
- function loadWorker(path) {
- const workerContext = workerStore[path];
- if (workerContext) {
- workerContext.clientCount++;
- return workerContext;
- }
- const scriptURL = new self.URL(path, self.location.href).href;
- const worker = new self.Worker(scriptURL);
- const result = {
- worker,
- scriptURL,
- clientCount: 1
- };
- workerStore[path] = result;
- return result;
- }
- function removeWorkerFromStore(path) {
- const workerContext = workerStore[path || version];
- if (workerContext) {
- const clientCount = workerContext.clientCount--;
- if (clientCount === 1) {
- const {
- worker,
- objectURL
- } = workerContext;
- delete workerStore[path || version];
- if (objectURL) {
- self.URL.revokeObjectURL(objectURL);
- }
- worker.terminate();
- }
- }
- }
- function isId3Footer(data, offset) {
- if (offset + 10 <= data.length) {
- if (data[offset] === 51 && data[offset + 1] === 68 && data[offset + 2] === 73) {
- if (data[offset + 3] < 255 && data[offset + 4] < 255) {
- if (data[offset + 6] < 128 && data[offset + 7] < 128 && data[offset + 8] < 128 && data[offset + 9] < 128) {
- return true;
- }
- }
- }
- }
- return false;
- }
- function isId3Header(data, offset) {
- if (offset + 10 <= data.length) {
- if (data[offset] === 73 && data[offset + 1] === 68 && data[offset + 2] === 51) {
- if (data[offset + 3] < 255 && data[offset + 4] < 255) {
- if (data[offset + 6] < 128 && data[offset + 7] < 128 && data[offset + 8] < 128 && data[offset + 9] < 128) {
- return true;
- }
- }
- }
- }
- return false;
- }
- function readId3Size(data, offset) {
- let size = 0;
- size = (data[offset] & 127) << 21;
- size |= (data[offset + 1] & 127) << 14;
- size |= (data[offset + 2] & 127) << 7;
- size |= data[offset + 3] & 127;
- return size;
- }
- function getId3Data(data, offset) {
- const front = offset;
- let length = 0;
- while (isId3Header(data, offset)) {
- length += 10;
- const size = readId3Size(data, offset + 6);
- length += size;
- if (isId3Footer(data, offset + 10)) {
- length += 10;
- }
- offset += length;
- }
- if (length > 0) {
- return data.subarray(front, front + length);
- }
- return void 0;
- }
- function getAudioConfig(observer, data, offset, manifestCodec) {
- const adtsSamplingRates = [96e3, 88200, 64e3, 48e3, 44100, 32e3, 24e3, 22050, 16e3, 12e3, 11025, 8e3, 7350];
- const byte2 = data[offset + 2];
- const adtsSamplingIndex = byte2 >> 2 & 15;
- if (adtsSamplingIndex > 12) {
- const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
- observer.emit(Events.ERROR, Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.FRAG_PARSING_ERROR,
- fatal: true,
- error,
- reason: error.message
- });
- return;
- }
- const adtsObjectType = (byte2 >> 6 & 3) + 1;
- const channelCount = data[offset + 3] >> 6 & 3 | (byte2 & 1) << 2;
- const codec = "mp4a.40." + adtsObjectType;
- const samplerate = adtsSamplingRates[adtsSamplingIndex];
- let aacSampleIndex = adtsSamplingIndex;
- if (adtsObjectType === 5 || adtsObjectType === 29) {
- aacSampleIndex -= 3;
- }
- const config = [adtsObjectType << 3 | (aacSampleIndex & 14) >> 1, (aacSampleIndex & 1) << 7 | channelCount << 3];
- logger.log(`manifest codec:${manifestCodec}, parsed codec:${codec}, channels:${channelCount}, rate:${samplerate} (ADTS object type:${adtsObjectType} sampling index:${adtsSamplingIndex})`);
- return {
- config,
- samplerate,
- channelCount,
- codec,
- parsedCodec: codec,
- manifestCodec
- };
- }
- function isHeaderPattern$1(data, offset) {
- return data[offset] === 255 && (data[offset + 1] & 246) === 240;
- }
- function getHeaderLength(data, offset) {
- return data[offset + 1] & 1 ? 7 : 9;
- }
- function getFullFrameLength(data, offset) {
- return (data[offset + 3] & 3) << 11 | data[offset + 4] << 3 | (data[offset + 5] & 224) >>> 5;
- }
- function canGetFrameLength(data, offset) {
- return offset + 5 < data.length;
- }
- function isHeader$1(data, offset) {
- return offset + 1 < data.length && isHeaderPattern$1(data, offset);
- }
- function canParse$1(data, offset) {
- return canGetFrameLength(data, offset) && isHeaderPattern$1(data, offset) && getFullFrameLength(data, offset) <= data.length - offset;
- }
- function probe$1(data, offset) {
- if (isHeader$1(data, offset)) {
- const headerLength = getHeaderLength(data, offset);
- if (offset + headerLength >= data.length) {
- return false;
- }
- const frameLength = getFullFrameLength(data, offset);
- if (frameLength <= headerLength) {
- return false;
- }
- const newOffset = offset + frameLength;
- return newOffset === data.length || isHeader$1(data, newOffset);
- }
- return false;
- }
- function initTrackConfig(track, observer, data, offset, audioCodec) {
- if (!track.samplerate) {
- const config = getAudioConfig(observer, data, offset, audioCodec);
- if (!config) {
- return;
- }
- _extends(track, config);
- }
- }
- function getFrameDuration(samplerate) {
- return 1024 * 9e4 / samplerate;
- }
- function parseFrameHeader(data, offset) {
- const headerLength = getHeaderLength(data, offset);
- if (offset + headerLength <= data.length) {
- const frameLength = getFullFrameLength(data, offset) - headerLength;
- if (frameLength > 0) {
- return {
- headerLength,
- frameLength
- };
- }
- }
- }
- function appendFrame$2(track, data, offset, pts, frameIndex) {
- const frameDuration = getFrameDuration(track.samplerate);
- const stamp = pts + frameIndex * frameDuration;
- const header = parseFrameHeader(data, offset);
- let unit;
- if (header) {
- const {
- frameLength,
- headerLength
- } = header;
- const _length = headerLength + frameLength;
- const missing = Math.max(0, offset + _length - data.length);
- if (missing) {
- unit = new Uint8Array(_length - headerLength);
- unit.set(data.subarray(offset + headerLength, data.length), 0);
- } else {
- unit = data.subarray(offset + headerLength, offset + _length);
- }
- const _sample = {
- unit,
- pts: stamp
- };
- if (!missing) {
- track.samples.push(_sample);
- }
- return {
- sample: _sample,
- length: _length,
- missing
- };
- }
- const length = data.length - offset;
- unit = new Uint8Array(length);
- unit.set(data.subarray(offset, data.length), 0);
- const sample = {
- unit,
- pts: stamp
- };
- return {
- sample,
- length,
- missing: -1
- };
- }
- function canParseId3(data, offset) {
- return isId3Header(data, offset) && readId3Size(data, offset + 6) + 10 <= data.length - offset;
- }
- function toArrayBuffer(view2) {
- if (view2 instanceof ArrayBuffer) {
- return view2;
- } else {
- if (view2.byteOffset == 0 && view2.byteLength == view2.buffer.byteLength) {
- return view2.buffer;
- }
- return new Uint8Array(view2).buffer;
- }
- }
- function toUint8(data, offset = 0, length = Infinity) {
- return view(data, offset, length, Uint8Array);
- }
- function view(data, offset, length, Type) {
- const buffer = unsafeGetArrayBuffer(data);
- let bytesPerElement = 1;
- if ("BYTES_PER_ELEMENT" in Type) {
- bytesPerElement = Type.BYTES_PER_ELEMENT;
- }
- const dataOffset = isArrayBufferView(data) ? data.byteOffset : 0;
- const dataEnd = (dataOffset + data.byteLength) / bytesPerElement;
- const rawStart = (dataOffset + offset) / bytesPerElement;
- const start = Math.floor(Math.max(0, Math.min(rawStart, dataEnd)));
- const end = Math.floor(Math.min(start + Math.max(length, 0), dataEnd));
- return new Type(buffer, start, end - start);
- }
- function unsafeGetArrayBuffer(view2) {
- if (view2 instanceof ArrayBuffer) {
- return view2;
- } else {
- return view2.buffer;
- }
- }
- function isArrayBufferView(obj) {
- return obj && obj.buffer instanceof ArrayBuffer && obj.byteLength !== void 0 && obj.byteOffset !== void 0;
- }
- function decodeId3ImageFrame(frame) {
- const metadataFrame = {
- key: frame.type,
- description: "",
- data: "",
- mimeType: null,
- pictureType: null
- };
- const utf8Encoding = 3;
- if (frame.size < 2) {
- return void 0;
- }
- if (frame.data[0] !== utf8Encoding) {
- console.log("Ignore frame with unrecognized character encoding");
- return void 0;
- }
- const mimeTypeEndIndex = frame.data.subarray(1).indexOf(0);
- if (mimeTypeEndIndex === -1) {
- return void 0;
- }
- const mimeType = utf8ArrayToStr(toUint8(frame.data, 1, mimeTypeEndIndex));
- const pictureType = frame.data[2 + mimeTypeEndIndex];
- const descriptionEndIndex = frame.data.subarray(3 + mimeTypeEndIndex).indexOf(0);
- if (descriptionEndIndex === -1) {
- return void 0;
- }
- const description = utf8ArrayToStr(toUint8(frame.data, 3 + mimeTypeEndIndex, descriptionEndIndex));
- let data;
- if (mimeType === "-->") {
- data = utf8ArrayToStr(toUint8(frame.data, 4 + mimeTypeEndIndex + descriptionEndIndex));
- } else {
- data = toArrayBuffer(frame.data.subarray(4 + mimeTypeEndIndex + descriptionEndIndex));
- }
- metadataFrame.mimeType = mimeType;
- metadataFrame.pictureType = pictureType;
- metadataFrame.description = description;
- metadataFrame.data = data;
- return metadataFrame;
- }
- function decodeId3PrivFrame(frame) {
- if (frame.size < 2) {
- return void 0;
- }
- const owner = utf8ArrayToStr(frame.data, true);
- const privateData = new Uint8Array(frame.data.subarray(owner.length + 1));
- return {
- key: frame.type,
- info: owner,
- data: privateData.buffer
- };
- }
- function decodeId3TextFrame(frame) {
- if (frame.size < 2) {
- return void 0;
- }
- if (frame.type === "TXXX") {
- let index = 1;
- const description = utf8ArrayToStr(frame.data.subarray(index), true);
- index += description.length + 1;
- const value = utf8ArrayToStr(frame.data.subarray(index));
- return {
- key: frame.type,
- info: description,
- data: value
- };
- }
- const text = utf8ArrayToStr(frame.data.subarray(1));
- return {
- key: frame.type,
- info: "",
- data: text
- };
- }
- function decodeId3UrlFrame(frame) {
- if (frame.type === "WXXX") {
- if (frame.size < 2) {
- return void 0;
- }
- let index = 1;
- const description = utf8ArrayToStr(frame.data.subarray(index), true);
- index += description.length + 1;
- const value = utf8ArrayToStr(frame.data.subarray(index));
- return {
- key: frame.type,
- info: description,
- data: value
- };
- }
- const url = utf8ArrayToStr(frame.data);
- return {
- key: frame.type,
- info: "",
- data: url
- };
- }
- function decodeId3Frame(frame) {
- if (frame.type === "PRIV") {
- return decodeId3PrivFrame(frame);
- } else if (frame.type[0] === "W") {
- return decodeId3UrlFrame(frame);
- } else if (frame.type === "APIC") {
- return decodeId3ImageFrame(frame);
- }
- return decodeId3TextFrame(frame);
- }
- function getId3FrameData(data) {
- const type = String.fromCharCode(data[0], data[1], data[2], data[3]);
- const size = readId3Size(data, 4);
- const offset = 10;
- return {
- type,
- size,
- data: data.subarray(offset, offset + size)
- };
- }
- var HEADER_FOOTER_SIZE = 10;
- var FRAME_SIZE = 10;
- function getId3Frames(id3Data) {
- let offset = 0;
- const frames = [];
- while (isId3Header(id3Data, offset)) {
- const size = readId3Size(id3Data, offset + 6);
- if (id3Data[offset + 5] >> 6 & 1) {
- offset += HEADER_FOOTER_SIZE;
- }
- offset += HEADER_FOOTER_SIZE;
- const end = offset + size;
- while (offset + FRAME_SIZE < end) {
- const frameData = getId3FrameData(id3Data.subarray(offset));
- const frame = decodeId3Frame(frameData);
- if (frame) {
- frames.push(frame);
- }
- offset += frameData.size + HEADER_FOOTER_SIZE;
- }
- if (isId3Footer(id3Data, offset)) {
- offset += HEADER_FOOTER_SIZE;
- }
- }
- return frames;
- }
- function isId3TimestampFrame(frame) {
- return frame && frame.key === "PRIV" && frame.info === "com.apple.streaming.transportStreamTimestamp";
- }
- function readId3Timestamp(timeStampFrame) {
- if (timeStampFrame.data.byteLength === 8) {
- const data = new Uint8Array(timeStampFrame.data);
- const pts33Bit = data[3] & 1;
- let timestamp = (data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7];
- timestamp /= 45;
- if (pts33Bit) {
- timestamp += 4772185884e-2;
- }
- return Math.round(timestamp);
- }
- return void 0;
- }
- function getId3Timestamp(data) {
- const frames = getId3Frames(data);
- for (let i = 0; i < frames.length; i++) {
- const frame = frames[i];
- if (isId3TimestampFrame(frame)) {
- return readId3Timestamp(frame);
- }
- }
- return void 0;
- }
- var MetadataSchema = function(MetadataSchema2) {
- MetadataSchema2["audioId3"] = "org.id3";
- MetadataSchema2["dateRange"] = "com.apple.quicktime.HLS";
- MetadataSchema2["emsg"] = "https://aomedia.org/emsg/ID3";
- MetadataSchema2["misbklv"] = "urn:misb:KLV:bin:1910.1";
- return MetadataSchema2;
- }({});
- function dummyTrack(type = "", inputTimeScale = 9e4) {
- return {
- type,
- id: -1,
- pid: -1,
- inputTimeScale,
- sequenceNumber: -1,
- samples: [],
- dropped: 0
- };
- }
- var BaseAudioDemuxer = class {
- constructor() {
- this._audioTrack = void 0;
- this._id3Track = void 0;
- this.frameIndex = 0;
- this.cachedData = null;
- this.basePTS = null;
- this.initPTS = null;
- this.lastPTS = null;
- }
- resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
- this._id3Track = {
- type: "id3",
- id: 3,
- pid: -1,
- inputTimeScale: 9e4,
- sequenceNumber: 0,
- samples: [],
- dropped: 0
- };
- }
- resetTimeStamp(deaultTimestamp) {
- this.initPTS = deaultTimestamp;
- this.resetContiguity();
- }
- resetContiguity() {
- this.basePTS = null;
- this.lastPTS = null;
- this.frameIndex = 0;
- }
- canParse(data, offset) {
- return false;
- }
- appendFrame(track, data, offset) {
- }
- // feed incoming data to the front of the parsing pipeline
- demux(data, timeOffset) {
- if (this.cachedData) {
- data = appendUint8Array(this.cachedData, data);
- this.cachedData = null;
- }
- let id3Data = getId3Data(data, 0);
- let offset = id3Data ? id3Data.length : 0;
- let lastDataIndex;
- const track = this._audioTrack;
- const id3Track = this._id3Track;
- const timestamp = id3Data ? getId3Timestamp(id3Data) : void 0;
- const length = data.length;
- if (this.basePTS === null || this.frameIndex === 0 && isFiniteNumber(timestamp)) {
- this.basePTS = initPTSFn(timestamp, timeOffset, this.initPTS);
- this.lastPTS = this.basePTS;
- }
- if (this.lastPTS === null) {
- this.lastPTS = this.basePTS;
- }
- if (id3Data && id3Data.length > 0) {
- id3Track.samples.push({
- pts: this.lastPTS,
- dts: this.lastPTS,
- data: id3Data,
- type: MetadataSchema.audioId3,
- duration: Number.POSITIVE_INFINITY
- });
- }
- while (offset < length) {
- if (this.canParse(data, offset)) {
- const frame = this.appendFrame(track, data, offset);
- if (frame) {
- this.frameIndex++;
- this.lastPTS = frame.sample.pts;
- offset += frame.length;
- lastDataIndex = offset;
- } else {
- offset = length;
- }
- } else if (canParseId3(data, offset)) {
- id3Data = getId3Data(data, offset);
- id3Track.samples.push({
- pts: this.lastPTS,
- dts: this.lastPTS,
- data: id3Data,
- type: MetadataSchema.audioId3,
- duration: Number.POSITIVE_INFINITY
- });
- offset += id3Data.length;
- lastDataIndex = offset;
- } else {
- offset++;
- }
- if (offset === length && lastDataIndex !== length) {
- const partialData = data.slice(lastDataIndex);
- if (this.cachedData) {
- this.cachedData = appendUint8Array(this.cachedData, partialData);
- } else {
- this.cachedData = partialData;
- }
- }
- }
- return {
- audioTrack: track,
- videoTrack: dummyTrack(),
- id3Track,
- textTrack: dummyTrack()
- };
- }
- demuxSampleAes(data, keyData, timeOffset) {
- return Promise.reject(new Error(`[${this}] This demuxer does not support Sample-AES decryption`));
- }
- flush(timeOffset) {
- const cachedData = this.cachedData;
- if (cachedData) {
- this.cachedData = null;
- this.demux(cachedData, 0);
- }
- return {
- audioTrack: this._audioTrack,
- videoTrack: dummyTrack(),
- id3Track: this._id3Track,
- textTrack: dummyTrack()
- };
- }
- destroy() {
- this.cachedData = null;
- this._audioTrack = this._id3Track = void 0;
- }
- };
- var initPTSFn = (timestamp, timeOffset, initPTS) => {
- if (isFiniteNumber(timestamp)) {
- return timestamp * 90;
- }
- const init90kHz = initPTS ? initPTS.baseTime * 9e4 / initPTS.timescale : 0;
- return timeOffset * 9e4 + init90kHz;
- };
- var chromeVersion$1 = null;
- var BitratesMap = [32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160];
- var SamplingRateMap = [44100, 48e3, 32e3, 22050, 24e3, 16e3, 11025, 12e3, 8e3];
- var SamplesCoefficients = [
- // MPEG 2.5
- [
- 0,
- // Reserved
- 72,
- // Layer3
- 144,
- // Layer2
- 12
- // Layer1
- ],
- // Reserved
- [
- 0,
- // Reserved
- 0,
- // Layer3
- 0,
- // Layer2
- 0
- // Layer1
- ],
- // MPEG 2
- [
- 0,
- // Reserved
- 72,
- // Layer3
- 144,
- // Layer2
- 12
- // Layer1
- ],
- // MPEG 1
- [
- 0,
- // Reserved
- 144,
- // Layer3
- 144,
- // Layer2
- 12
- // Layer1
- ]
- ];
- var BytesInSlot = [
- 0,
- // Reserved
- 1,
- // Layer3
- 1,
- // Layer2
- 4
- // Layer1
- ];
- function appendFrame$1(track, data, offset, pts, frameIndex) {
- if (offset + 24 > data.length) {
- return;
- }
- const header = parseHeader(data, offset);
- if (header && offset + header.frameLength <= data.length) {
- const frameDuration = header.samplesPerFrame * 9e4 / header.sampleRate;
- const stamp = pts + frameIndex * frameDuration;
- const sample = {
- unit: data.subarray(offset, offset + header.frameLength),
- pts: stamp,
- dts: stamp
- };
- track.config = [];
- track.channelCount = header.channelCount;
- track.samplerate = header.sampleRate;
- track.samples.push(sample);
- return {
- sample,
- length: header.frameLength,
- missing: 0
- };
- }
- }
- function parseHeader(data, offset) {
- const mpegVersion = data[offset + 1] >> 3 & 3;
- const mpegLayer = data[offset + 1] >> 1 & 3;
- const bitRateIndex = data[offset + 2] >> 4 & 15;
- const sampleRateIndex = data[offset + 2] >> 2 & 3;
- if (mpegVersion !== 1 && bitRateIndex !== 0 && bitRateIndex !== 15 && sampleRateIndex !== 3) {
- const paddingBit = data[offset + 2] >> 1 & 1;
- const channelMode = data[offset + 3] >> 6;
- const columnInBitrates = mpegVersion === 3 ? 3 - mpegLayer : mpegLayer === 3 ? 3 : 4;
- const bitRate = BitratesMap[columnInBitrates * 14 + bitRateIndex - 1] * 1e3;
- const columnInSampleRates = mpegVersion === 3 ? 0 : mpegVersion === 2 ? 1 : 2;
- const sampleRate = SamplingRateMap[columnInSampleRates * 3 + sampleRateIndex];
- const channelCount = channelMode === 3 ? 1 : 2;
- const sampleCoefficient = SamplesCoefficients[mpegVersion][mpegLayer];
- const bytesInSlot = BytesInSlot[mpegLayer];
- const samplesPerFrame = sampleCoefficient * 8 * bytesInSlot;
- const frameLength = Math.floor(sampleCoefficient * bitRate / sampleRate + paddingBit) * bytesInSlot;
- if (chromeVersion$1 === null) {
- const userAgent = navigator.userAgent || "";
- const result = userAgent.match(/Chrome\/(\d+)/i);
- chromeVersion$1 = result ? parseInt(result[1]) : 0;
- }
- const needChromeFix = !!chromeVersion$1 && chromeVersion$1 <= 87;
- if (needChromeFix && mpegLayer === 2 && bitRate >= 224e3 && channelMode === 0) {
- data[offset + 3] = data[offset + 3] | 128;
- }
- return {
- sampleRate,
- channelCount,
- frameLength,
- samplesPerFrame
- };
- }
- }
- function isHeaderPattern(data, offset) {
- return data[offset] === 255 && (data[offset + 1] & 224) === 224 && (data[offset + 1] & 6) !== 0;
- }
- function isHeader(data, offset) {
- return offset + 1 < data.length && isHeaderPattern(data, offset);
- }
- function canParse(data, offset) {
- const headerSize = 4;
- return isHeaderPattern(data, offset) && headerSize <= data.length - offset;
- }
- function probe(data, offset) {
- if (offset + 1 < data.length && isHeaderPattern(data, offset)) {
- const headerLength = 4;
- const header = parseHeader(data, offset);
- let frameLength = headerLength;
- if (header != null && header.frameLength) {
- frameLength = header.frameLength;
- }
- const newOffset = offset + frameLength;
- return newOffset === data.length || isHeader(data, newOffset);
- }
- return false;
- }
- var AACDemuxer = class extends BaseAudioDemuxer {
- constructor(observer, config) {
- super();
- this.observer = void 0;
- this.config = void 0;
- this.observer = observer;
- this.config = config;
- }
- resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
- super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
- this._audioTrack = {
- container: "audio/adts",
- type: "audio",
- id: 2,
- pid: -1,
- sequenceNumber: 0,
- segmentCodec: "aac",
- samples: [],
- manifestCodec: audioCodec,
- duration: trackDuration,
- inputTimeScale: 9e4,
- dropped: 0
- };
- }
- // Source for probe info - https://wiki.multimedia.cx/index.php?title=ADTS
- static probe(data, logger2) {
- if (!data) {
- return false;
- }
- const id3Data = getId3Data(data, 0);
- let offset = (id3Data == null ? void 0 : id3Data.length) || 0;
- if (probe(data, offset)) {
- return false;
- }
- for (let length = data.length; offset < length; offset++) {
- if (probe$1(data, offset)) {
- logger2.log("ADTS sync word found !");
- return true;
- }
- }
- return false;
- }
- canParse(data, offset) {
- return canParse$1(data, offset);
- }
- appendFrame(track, data, offset) {
- initTrackConfig(track, this.observer, data, offset, track.manifestCodec);
- const frame = appendFrame$2(track, data, offset, this.basePTS, this.frameIndex);
- if (frame && frame.missing === 0) {
- return frame;
- }
- }
- };
- var getAudioBSID = (data, offset) => {
- let bsid = 0;
- let numBits = 5;
- offset += numBits;
- const temp = new Uint32Array(1);
- const mask = new Uint32Array(1);
- const byte = new Uint8Array(1);
- while (numBits > 0) {
- byte[0] = data[offset];
- const bits = Math.min(numBits, 8);
- const shift = 8 - bits;
- mask[0] = 4278190080 >>> 24 + shift << shift;
- temp[0] = (byte[0] & mask[0]) >> shift;
- bsid = !bsid ? temp[0] : bsid << bits | temp[0];
- offset += 1;
- numBits -= bits;
- }
- return bsid;
- };
- var AC3Demuxer = class extends BaseAudioDemuxer {
- constructor(observer) {
- super();
- this.observer = void 0;
- this.observer = observer;
- }
- resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
- super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
- this._audioTrack = {
- container: "audio/ac-3",
- type: "audio",
- id: 2,
- pid: -1,
- sequenceNumber: 0,
- segmentCodec: "ac3",
- samples: [],
- manifestCodec: audioCodec,
- duration: trackDuration,
- inputTimeScale: 9e4,
- dropped: 0
- };
- }
- canParse(data, offset) {
- return offset + 64 < data.length;
- }
- appendFrame(track, data, offset) {
- const frameLength = appendFrame(track, data, offset, this.basePTS, this.frameIndex);
- if (frameLength !== -1) {
- const sample = track.samples[track.samples.length - 1];
- return {
- sample,
- length: frameLength,
- missing: 0
- };
- }
- }
- static probe(data) {
- if (!data) {
- return false;
- }
- const id3Data = getId3Data(data, 0);
- if (!id3Data) {
- return false;
- }
- const offset = id3Data.length;
- if (data[offset] === 11 && data[offset + 1] === 119 && getId3Timestamp(id3Data) !== void 0 && // check the bsid to confirm ac-3
- getAudioBSID(data, offset) < 16) {
- return true;
- }
- return false;
- }
- };
- function appendFrame(track, data, start, pts, frameIndex) {
- if (start + 8 > data.length) {
- return -1;
- }
- if (data[start] !== 11 || data[start + 1] !== 119) {
- return -1;
- }
- const samplingRateCode = data[start + 4] >> 6;
- if (samplingRateCode >= 3) {
- return -1;
- }
- const samplingRateMap = [48e3, 44100, 32e3];
- const sampleRate = samplingRateMap[samplingRateCode];
- const frameSizeCode = data[start + 4] & 63;
- const frameSizeMap = [64, 69, 96, 64, 70, 96, 80, 87, 120, 80, 88, 120, 96, 104, 144, 96, 105, 144, 112, 121, 168, 112, 122, 168, 128, 139, 192, 128, 140, 192, 160, 174, 240, 160, 175, 240, 192, 208, 288, 192, 209, 288, 224, 243, 336, 224, 244, 336, 256, 278, 384, 256, 279, 384, 320, 348, 480, 320, 349, 480, 384, 417, 576, 384, 418, 576, 448, 487, 672, 448, 488, 672, 512, 557, 768, 512, 558, 768, 640, 696, 960, 640, 697, 960, 768, 835, 1152, 768, 836, 1152, 896, 975, 1344, 896, 976, 1344, 1024, 1114, 1536, 1024, 1115, 1536, 1152, 1253, 1728, 1152, 1254, 1728, 1280, 1393, 1920, 1280, 1394, 1920];
- const frameLength = frameSizeMap[frameSizeCode * 3 + samplingRateCode] * 2;
- if (start + frameLength > data.length) {
- return -1;
- }
- const channelMode = data[start + 6] >> 5;
- let skipCount = 0;
- if (channelMode === 2) {
- skipCount += 2;
- } else {
- if (channelMode & 1 && channelMode !== 1) {
- skipCount += 2;
- }
- if (channelMode & 4) {
- skipCount += 2;
- }
- }
- const lfeon = (data[start + 6] << 8 | data[start + 7]) >> 12 - skipCount & 1;
- const channelsMap = [2, 1, 2, 3, 3, 4, 4, 5];
- const channelCount = channelsMap[channelMode] + lfeon;
- const bsid = data[start + 5] >> 3;
- const bsmod = data[start + 5] & 7;
- const config = new Uint8Array([samplingRateCode << 6 | bsid << 1 | bsmod >> 2, (bsmod & 3) << 6 | channelMode << 3 | lfeon << 2 | frameSizeCode >> 4, frameSizeCode << 4 & 224]);
- const frameDuration = 1536 / sampleRate * 9e4;
- const stamp = pts + frameIndex * frameDuration;
- const unit = data.subarray(start, start + frameLength);
- track.config = config;
- track.channelCount = channelCount;
- track.samplerate = sampleRate;
- track.samples.push({
- unit,
- pts: stamp
- });
- return frameLength;
- }
- var MP3Demuxer = class extends BaseAudioDemuxer {
- resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
- super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
- this._audioTrack = {
- container: "audio/mpeg",
- type: "audio",
- id: 2,
- pid: -1,
- sequenceNumber: 0,
- segmentCodec: "mp3",
- samples: [],
- manifestCodec: audioCodec,
- duration: trackDuration,
- inputTimeScale: 9e4,
- dropped: 0
- };
- }
- static probe(data) {
- if (!data) {
- return false;
- }
- const id3Data = getId3Data(data, 0);
- let offset = (id3Data == null ? void 0 : id3Data.length) || 0;
- if (id3Data && data[offset] === 11 && data[offset + 1] === 119 && getId3Timestamp(id3Data) !== void 0 && // check the bsid to confirm ac-3 or ec-3 (not mp3)
- getAudioBSID(data, offset) <= 16) {
- return false;
- }
- for (let length = data.length; offset < length; offset++) {
- if (probe(data, offset)) {
- logger.log("MPEG Audio sync word found !");
- return true;
- }
- }
- return false;
- }
- canParse(data, offset) {
- return canParse(data, offset);
- }
- appendFrame(track, data, offset) {
- if (this.basePTS === null) {
- return;
- }
- return appendFrame$1(track, data, offset, this.basePTS, this.frameIndex);
- }
- };
- var emsgSchemePattern = /\/emsg[-/]ID3/i;
- var MP4Demuxer = class {
- constructor(observer, config) {
- this.remainderData = null;
- this.timeOffset = 0;
- this.config = void 0;
- this.videoTrack = void 0;
- this.audioTrack = void 0;
- this.id3Track = void 0;
- this.txtTrack = void 0;
- this.config = config;
- }
- resetTimeStamp() {
- }
- resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
- const videoTrack = this.videoTrack = dummyTrack("video", 1);
- const audioTrack = this.audioTrack = dummyTrack("audio", 1);
- const captionTrack = this.txtTrack = dummyTrack("text", 1);
- this.id3Track = dummyTrack("id3", 1);
- this.timeOffset = 0;
- if (!(initSegment != null && initSegment.byteLength)) {
- return;
- }
- const initData = parseInitSegment(initSegment);
- if (initData.video) {
- const {
- id,
- timescale,
- codec,
- supplemental
- } = initData.video;
- videoTrack.id = id;
- videoTrack.timescale = captionTrack.timescale = timescale;
- videoTrack.codec = codec;
- videoTrack.supplemental = supplemental;
- }
- if (initData.audio) {
- const {
- id,
- timescale,
- codec
- } = initData.audio;
- audioTrack.id = id;
- audioTrack.timescale = timescale;
- audioTrack.codec = codec;
- }
- captionTrack.id = RemuxerTrackIdConfig.text;
- videoTrack.sampleDuration = 0;
- videoTrack.duration = audioTrack.duration = trackDuration;
- }
- resetContiguity() {
- this.remainderData = null;
- }
- static probe(data) {
- return hasMoofData(data);
- }
- demux(data, timeOffset) {
- this.timeOffset = timeOffset;
- let videoSamples = data;
- const videoTrack = this.videoTrack;
- const textTrack = this.txtTrack;
- if (this.config.progressive) {
- if (this.remainderData) {
- videoSamples = appendUint8Array(this.remainderData, data);
- }
- const segmentedData = segmentValidRange(videoSamples);
- this.remainderData = segmentedData.remainder;
- videoTrack.samples = segmentedData.valid || new Uint8Array();
- } else {
- videoTrack.samples = videoSamples;
- }
- const id3Track = this.extractID3Track(videoTrack, timeOffset);
- textTrack.samples = parseSamples(timeOffset, videoTrack);
- return {
- videoTrack,
- audioTrack: this.audioTrack,
- id3Track,
- textTrack: this.txtTrack
- };
- }
- flush() {
- const timeOffset = this.timeOffset;
- const videoTrack = this.videoTrack;
- const textTrack = this.txtTrack;
- videoTrack.samples = this.remainderData || new Uint8Array();
- this.remainderData = null;
- const id3Track = this.extractID3Track(videoTrack, this.timeOffset);
- textTrack.samples = parseSamples(timeOffset, videoTrack);
- return {
- videoTrack,
- audioTrack: dummyTrack(),
- id3Track,
- textTrack: dummyTrack()
- };
- }
- extractID3Track(videoTrack, timeOffset) {
- const id3Track = this.id3Track;
- if (videoTrack.samples.length) {
- const emsgs = findBox(videoTrack.samples, ["emsg"]);
- if (emsgs) {
- emsgs.forEach((data) => {
- const emsgInfo = parseEmsg(data);
- if (emsgSchemePattern.test(emsgInfo.schemeIdUri)) {
- const pts = getEmsgStartTime(emsgInfo, timeOffset);
- let duration = emsgInfo.eventDuration === 4294967295 ? Number.POSITIVE_INFINITY : emsgInfo.eventDuration / emsgInfo.timeScale;
- if (duration <= 1e-3) {
- duration = Number.POSITIVE_INFINITY;
- }
- const payload = emsgInfo.payload;
- id3Track.samples.push({
- data: payload,
- len: payload.byteLength,
- dts: pts,
- pts,
- type: MetadataSchema.emsg,
- duration
- });
- } else if (this.config.enableEmsgKLVMetadata && emsgInfo.schemeIdUri.startsWith("urn:misb:KLV:bin:1910.1")) {
- const pts = getEmsgStartTime(emsgInfo, timeOffset);
- id3Track.samples.push({
- data: emsgInfo.payload,
- len: emsgInfo.payload.byteLength,
- dts: pts,
- pts,
- type: MetadataSchema.misbklv,
- duration: Number.POSITIVE_INFINITY
- });
- }
- });
- }
- }
- return id3Track;
- }
- demuxSampleAes(data, keyData, timeOffset) {
- return Promise.reject(new Error("The MP4 demuxer does not support SAMPLE-AES decryption"));
- }
- destroy() {
- this.config = null;
- this.remainderData = null;
- this.videoTrack = this.audioTrack = this.id3Track = this.txtTrack = void 0;
- }
- };
- function getEmsgStartTime(emsgInfo, timeOffset) {
- return isFiniteNumber(emsgInfo.presentationTime) ? emsgInfo.presentationTime / emsgInfo.timeScale : timeOffset + emsgInfo.presentationTimeDelta / emsgInfo.timeScale;
- }
- var SampleAesDecrypter = class {
- constructor(observer, config, keyData) {
- this.keyData = void 0;
- this.decrypter = void 0;
- this.keyData = keyData;
- this.decrypter = new Decrypter(config, {
- removePKCS7Padding: false
- });
- }
- decryptBuffer(encryptedData) {
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
- }
- // AAC - encrypt all full 16 bytes blocks starting from offset 16
- decryptAacSample(samples, sampleIndex, callback) {
- const curUnit = samples[sampleIndex].unit;
- if (curUnit.length <= 16) {
- return;
- }
- const encryptedData = curUnit.subarray(16, curUnit.length - curUnit.length % 16);
- const encryptedBuffer = encryptedData.buffer.slice(encryptedData.byteOffset, encryptedData.byteOffset + encryptedData.length);
- this.decryptBuffer(encryptedBuffer).then((decryptedBuffer) => {
- const decryptedData = new Uint8Array(decryptedBuffer);
- curUnit.set(decryptedData, 16);
- if (!this.decrypter.isSync()) {
- this.decryptAacSamples(samples, sampleIndex + 1, callback);
- }
- }).catch(callback);
- }
- decryptAacSamples(samples, sampleIndex, callback) {
- for (; ; sampleIndex++) {
- if (sampleIndex >= samples.length) {
- callback();
- return;
- }
- if (samples[sampleIndex].unit.length < 32) {
- continue;
- }
- this.decryptAacSample(samples, sampleIndex, callback);
- if (!this.decrypter.isSync()) {
- return;
- }
- }
- }
- // AVC - encrypt one 16 bytes block out of ten, starting from offset 32
- getAvcEncryptedData(decodedData) {
- const encryptedDataLen = Math.floor((decodedData.length - 48) / 160) * 16 + 16;
- const encryptedData = new Int8Array(encryptedDataLen);
- let outputPos = 0;
- for (let inputPos = 32; inputPos < decodedData.length - 16; inputPos += 160, outputPos += 16) {
- encryptedData.set(decodedData.subarray(inputPos, inputPos + 16), outputPos);
- }
- return encryptedData;
- }
- getAvcDecryptedUnit(decodedData, decryptedData) {
- const uint8DecryptedData = new Uint8Array(decryptedData);
- let inputPos = 0;
- for (let outputPos = 32; outputPos < decodedData.length - 16; outputPos += 160, inputPos += 16) {
- decodedData.set(uint8DecryptedData.subarray(inputPos, inputPos + 16), outputPos);
- }
- return decodedData;
- }
- decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit) {
- const decodedData = discardEPB(curUnit.data);
- const encryptedData = this.getAvcEncryptedData(decodedData);
- this.decryptBuffer(encryptedData.buffer).then((decryptedBuffer) => {
- curUnit.data = this.getAvcDecryptedUnit(decodedData, decryptedBuffer);
- if (!this.decrypter.isSync()) {
- this.decryptAvcSamples(samples, sampleIndex, unitIndex + 1, callback);
- }
- }).catch(callback);
- }
- decryptAvcSamples(samples, sampleIndex, unitIndex, callback) {
- if (samples instanceof Uint8Array) {
- throw new Error("Cannot decrypt samples of type Uint8Array");
- }
- for (; ; sampleIndex++, unitIndex = 0) {
- if (sampleIndex >= samples.length) {
- callback();
- return;
- }
- const curUnits = samples[sampleIndex].units;
- for (; ; unitIndex++) {
- if (unitIndex >= curUnits.length) {
- break;
- }
- const curUnit = curUnits[unitIndex];
- if (curUnit.data.length <= 48 || curUnit.type !== 1 && curUnit.type !== 5) {
- continue;
- }
- this.decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit);
- if (!this.decrypter.isSync()) {
- return;
- }
- }
- }
- }
- };
- var BaseVideoParser = class {
- constructor() {
- this.VideoSample = null;
- }
- createVideoSample(key, pts, dts) {
- return {
- key,
- frame: false,
- pts,
- dts,
- units: [],
- length: 0
- };
- }
- getLastNalUnit(samples) {
- var _VideoSample;
- let VideoSample = this.VideoSample;
- let lastUnit;
- if (!VideoSample || VideoSample.units.length === 0) {
- VideoSample = samples[samples.length - 1];
- }
- if ((_VideoSample = VideoSample) != null && _VideoSample.units) {
- const units = VideoSample.units;
- lastUnit = units[units.length - 1];
- }
- return lastUnit;
- }
- pushAccessUnit(VideoSample, videoTrack) {
- if (VideoSample.units.length && VideoSample.frame) {
- if (VideoSample.pts === void 0) {
- const samples = videoTrack.samples;
- const nbSamples = samples.length;
- if (nbSamples) {
- const lastSample = samples[nbSamples - 1];
- VideoSample.pts = lastSample.pts;
- VideoSample.dts = lastSample.dts;
- } else {
- videoTrack.dropped++;
- return;
- }
- }
- videoTrack.samples.push(VideoSample);
- }
- }
- parseNALu(track, array, endOfSegment) {
- const len = array.byteLength;
- let state = track.naluState || 0;
- const lastState = state;
- const units = [];
- let i = 0;
- let value;
- let overflow;
- let unitType;
- let lastUnitStart = -1;
- let lastUnitType = 0;
- if (state === -1) {
- lastUnitStart = 0;
- lastUnitType = this.getNALuType(array, 0);
- state = 0;
- i = 1;
- }
- while (i < len) {
- value = array[i++];
- if (!state) {
- state = value ? 0 : 1;
- continue;
- }
- if (state === 1) {
- state = value ? 0 : 2;
- continue;
- }
- if (!value) {
- state = 3;
- } else if (value === 1) {
- overflow = i - state - 1;
- if (lastUnitStart >= 0) {
- const unit = {
- data: array.subarray(lastUnitStart, overflow),
- type: lastUnitType
- };
- units.push(unit);
- } else {
- const lastUnit = this.getLastNalUnit(track.samples);
- if (lastUnit) {
- if (lastState && i <= 4 - lastState) {
- if (lastUnit.state) {
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
- }
- }
- if (overflow > 0) {
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
- lastUnit.state = 0;
- }
- }
- }
- if (i < len) {
- unitType = this.getNALuType(array, i);
- lastUnitStart = i;
- lastUnitType = unitType;
- state = 0;
- } else {
- state = -1;
- }
- } else {
- state = 0;
- }
- }
- if (lastUnitStart >= 0 && state >= 0) {
- const unit = {
- data: array.subarray(lastUnitStart, len),
- type: lastUnitType,
- state
- };
- units.push(unit);
- }
- if (units.length === 0) {
- const lastUnit = this.getLastNalUnit(track.samples);
- if (lastUnit) {
- lastUnit.data = appendUint8Array(lastUnit.data, array);
- }
- }
- track.naluState = state;
- return units;
- }
- };
- var ExpGolomb = class {
- constructor(data) {
- this.data = void 0;
- this.bytesAvailable = void 0;
- this.word = void 0;
- this.bitsAvailable = void 0;
- this.data = data;
- this.bytesAvailable = data.byteLength;
- this.word = 0;
- this.bitsAvailable = 0;
- }
- // ():void
- loadWord() {
- const data = this.data;
- const bytesAvailable = this.bytesAvailable;
- const position = data.byteLength - bytesAvailable;
- const workingBytes = new Uint8Array(4);
- const availableBytes = Math.min(4, bytesAvailable);
- if (availableBytes === 0) {
- throw new Error("no bytes available");
- }
- workingBytes.set(data.subarray(position, position + availableBytes));
- this.word = new DataView(workingBytes.buffer).getUint32(0);
- this.bitsAvailable = availableBytes * 8;
- this.bytesAvailable -= availableBytes;
- }
- // (count:int):void
- skipBits(count) {
- let skipBytes;
- count = Math.min(count, this.bytesAvailable * 8 + this.bitsAvailable);
- if (this.bitsAvailable > count) {
- this.word <<= count;
- this.bitsAvailable -= count;
- } else {
- count -= this.bitsAvailable;
- skipBytes = count >> 3;
- count -= skipBytes << 3;
- this.bytesAvailable -= skipBytes;
- this.loadWord();
- this.word <<= count;
- this.bitsAvailable -= count;
- }
- }
- // (size:int):uint
- readBits(size) {
- let bits = Math.min(this.bitsAvailable, size);
- const valu = this.word >>> 32 - bits;
- if (size > 32) {
- logger.error("Cannot read more than 32 bits at a time");
- }
- this.bitsAvailable -= bits;
- if (this.bitsAvailable > 0) {
- this.word <<= bits;
- } else if (this.bytesAvailable > 0) {
- this.loadWord();
- } else {
- throw new Error("no bits available");
- }
- bits = size - bits;
- if (bits > 0 && this.bitsAvailable) {
- return valu << bits | this.readBits(bits);
- } else {
- return valu;
- }
- }
- // ():uint
- skipLZ() {
- let leadingZeroCount;
- for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount) {
- if ((this.word & 2147483648 >>> leadingZeroCount) !== 0) {
- this.word <<= leadingZeroCount;
- this.bitsAvailable -= leadingZeroCount;
- return leadingZeroCount;
- }
- }
- this.loadWord();
- return leadingZeroCount + this.skipLZ();
- }
- // ():void
- skipUEG() {
- this.skipBits(1 + this.skipLZ());
- }
- // ():void
- skipEG() {
- this.skipBits(1 + this.skipLZ());
- }
- // ():uint
- readUEG() {
- const clz = this.skipLZ();
- return this.readBits(clz + 1) - 1;
- }
- // ():int
- readEG() {
- const valu = this.readUEG();
- if (1 & valu) {
- return 1 + valu >>> 1;
- } else {
- return -1 * (valu >>> 1);
- }
- }
- // Some convenience functions
- // :Boolean
- readBoolean() {
- return this.readBits(1) === 1;
- }
- // ():int
- readUByte() {
- return this.readBits(8);
- }
- // ():int
- readUShort() {
- return this.readBits(16);
- }
- // ():int
- readUInt() {
- return this.readBits(32);
- }
- };
- var AvcVideoParser = class extends BaseVideoParser {
- parsePES(track, textTrack, pes, endOfSegment) {
- const units = this.parseNALu(track, pes.data, endOfSegment);
- let VideoSample = this.VideoSample;
- let push2;
- let spsfound = false;
- pes.data = null;
- if (VideoSample && units.length && !track.audFound) {
- this.pushAccessUnit(VideoSample, track);
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts);
- }
- units.forEach((unit) => {
- var _VideoSample2, _VideoSample3;
- switch (unit.type) {
- case 1: {
- let iskey = false;
- push2 = true;
- const data = unit.data;
- if (spsfound && data.length > 4) {
- const sliceType = this.readSliceType(data);
- if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
- iskey = true;
- }
- }
- if (iskey) {
- var _VideoSample;
- if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
- this.pushAccessUnit(VideoSample, track);
- VideoSample = this.VideoSample = null;
- }
- }
- if (!VideoSample) {
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts);
- }
- VideoSample.frame = true;
- VideoSample.key = iskey;
- break;
- }
- case 5:
- push2 = true;
- if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
- this.pushAccessUnit(VideoSample, track);
- VideoSample = this.VideoSample = null;
- }
- if (!VideoSample) {
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts);
- }
- VideoSample.key = true;
- VideoSample.frame = true;
- break;
- case 6: {
- push2 = true;
- parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
- break;
- }
- case 7: {
- var _track$pixelRatio, _track$pixelRatio2;
- push2 = true;
- spsfound = true;
- const sps = unit.data;
- const config = this.readSPS(sps);
- if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
- track.width = config.width;
- track.height = config.height;
- track.pixelRatio = config.pixelRatio;
- track.sps = [sps];
- const codecarray = sps.subarray(1, 4);
- let codecstring = "avc1.";
- for (let i = 0; i < 3; i++) {
- let h = codecarray[i].toString(16);
- if (h.length < 2) {
- h = "0" + h;
- }
- codecstring += h;
- }
- track.codec = codecstring;
- }
- break;
- }
- case 8:
- push2 = true;
- track.pps = [unit.data];
- break;
- case 9:
- push2 = true;
- track.audFound = true;
- if ((_VideoSample3 = VideoSample) != null && _VideoSample3.frame) {
- this.pushAccessUnit(VideoSample, track);
- VideoSample = null;
- }
- if (!VideoSample) {
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts);
- }
- break;
- case 12:
- push2 = true;
- break;
- default:
- push2 = false;
- break;
- }
- if (VideoSample && push2) {
- const units2 = VideoSample.units;
- units2.push(unit);
- }
- });
- if (endOfSegment && VideoSample) {
- this.pushAccessUnit(VideoSample, track);
- this.VideoSample = null;
- }
- }
- getNALuType(data, offset) {
- return data[offset] & 31;
- }
- readSliceType(data) {
- const eg = new ExpGolomb(data);
- eg.readUByte();
- eg.readUEG();
- return eg.readUEG();
- }
- /**
- * The scaling list is optionally transmitted as part of a sequence parameter
- * set and is not relevant to transmuxing.
- * @param count the number of entries in this scaling list
- * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
- */
- skipScalingList(count, reader) {
- let lastScale = 8;
- let nextScale = 8;
- let deltaScale;
- for (let j = 0; j < count; j++) {
- if (nextScale !== 0) {
- deltaScale = reader.readEG();
- nextScale = (lastScale + deltaScale + 256) % 256;
- }
- lastScale = nextScale === 0 ? lastScale : nextScale;
- }
- }
- /**
- * 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.
- * @returns an object with configuration parsed from the
- * sequence parameter set, including the dimensions of the
- * associated video frames.
- */
- readSPS(sps) {
- const eg = new ExpGolomb(sps);
- let frameCropLeftOffset = 0;
- let frameCropRightOffset = 0;
- let frameCropTopOffset = 0;
- let frameCropBottomOffset = 0;
- let numRefFramesInPicOrderCntCycle;
- let scalingListCount;
- let i;
- const readUByte = eg.readUByte.bind(eg);
- const readBits = eg.readBits.bind(eg);
- const readUEG = eg.readUEG.bind(eg);
- const readBoolean = eg.readBoolean.bind(eg);
- const skipBits = eg.skipBits.bind(eg);
- const skipEG = eg.skipEG.bind(eg);
- const skipUEG = eg.skipUEG.bind(eg);
- const skipScalingList = this.skipScalingList.bind(this);
- readUByte();
- const profileIdc = readUByte();
- readBits(5);
- skipBits(3);
- readUByte();
- skipUEG();
- if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
- const chromaFormatIdc = readUEG();
- if (chromaFormatIdc === 3) {
- skipBits(1);
- }
- skipUEG();
- skipUEG();
- skipBits(1);
- if (readBoolean()) {
- scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
- for (i = 0; i < scalingListCount; i++) {
- if (readBoolean()) {
- if (i < 6) {
- skipScalingList(16, eg);
- } else {
- skipScalingList(64, eg);
- }
- }
- }
- }
- }
- skipUEG();
- const picOrderCntType = readUEG();
- if (picOrderCntType === 0) {
- readUEG();
- } else if (picOrderCntType === 1) {
- skipBits(1);
- skipEG();
- skipEG();
- numRefFramesInPicOrderCntCycle = readUEG();
- for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
- skipEG();
- }
- }
- skipUEG();
- skipBits(1);
- const picWidthInMbsMinus1 = readUEG();
- const picHeightInMapUnitsMinus1 = readUEG();
- const frameMbsOnlyFlag = readBits(1);
- if (frameMbsOnlyFlag === 0) {
- skipBits(1);
- }
- skipBits(1);
- if (readBoolean()) {
- frameCropLeftOffset = readUEG();
- frameCropRightOffset = readUEG();
- frameCropTopOffset = readUEG();
- frameCropBottomOffset = readUEG();
- }
- let pixelRatio = [1, 1];
- if (readBoolean()) {
- if (readBoolean()) {
- const aspectRatioIdc = readUByte();
- switch (aspectRatioIdc) {
- case 1:
- pixelRatio = [1, 1];
- break;
- case 2:
- pixelRatio = [12, 11];
- break;
- case 3:
- pixelRatio = [10, 11];
- break;
- case 4:
- pixelRatio = [16, 11];
- break;
- case 5:
- pixelRatio = [40, 33];
- break;
- case 6:
- pixelRatio = [24, 11];
- break;
- case 7:
- pixelRatio = [20, 11];
- break;
- case 8:
- pixelRatio = [32, 11];
- break;
- case 9:
- pixelRatio = [80, 33];
- break;
- case 10:
- pixelRatio = [18, 11];
- break;
- case 11:
- pixelRatio = [15, 11];
- break;
- case 12:
- pixelRatio = [64, 33];
- break;
- case 13:
- pixelRatio = [160, 99];
- break;
- case 14:
- pixelRatio = [4, 3];
- break;
- case 15:
- pixelRatio = [3, 2];
- break;
- case 16:
- pixelRatio = [2, 1];
- break;
- case 255: {
- pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
- break;
- }
- }
- }
- }
- return {
- width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
- height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
- pixelRatio
- };
- }
- };
- var HevcVideoParser = class extends BaseVideoParser {
- constructor(...args) {
- super(...args);
- this.initVPS = null;
- }
- parsePES(track, textTrack, pes, endOfSegment) {
- const units = this.parseNALu(track, pes.data, endOfSegment);
- let VideoSample = this.VideoSample;
- let push2;
- let spsfound = false;
- pes.data = null;
- if (VideoSample && units.length && !track.audFound) {
- this.pushAccessUnit(VideoSample, track);
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts);
- }
- units.forEach((unit) => {
- var _VideoSample2, _VideoSample3;
- switch (unit.type) {
- case 0:
- case 1:
- case 2:
- case 3:
- case 4:
- case 5:
- case 6:
- case 7:
- case 8:
- case 9:
- if (!VideoSample) {
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts);
- }
- VideoSample.frame = true;
- push2 = true;
- break;
- case 16:
- case 17:
- case 18:
- case 21:
- push2 = true;
- if (spsfound) {
- var _VideoSample;
- if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
- this.pushAccessUnit(VideoSample, track);
- VideoSample = this.VideoSample = null;
- }
- }
- if (!VideoSample) {
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts);
- }
- VideoSample.key = true;
- VideoSample.frame = true;
- break;
- case 19:
- case 20:
- push2 = true;
- if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
- this.pushAccessUnit(VideoSample, track);
- VideoSample = this.VideoSample = null;
- }
- if (!VideoSample) {
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts);
- }
- VideoSample.key = true;
- VideoSample.frame = true;
- break;
- case 39:
- push2 = true;
- parseSEIMessageFromNALu(
- unit.data,
- 2,
- // NALu header size
- pes.pts,
- textTrack.samples
- );
- break;
- case 32:
- push2 = true;
- if (!track.vps) {
- if (typeof track.params !== "object") {
- track.params = {};
- }
- track.params = _extends(track.params, this.readVPS(unit.data));
- this.initVPS = unit.data;
- }
- track.vps = [unit.data];
- break;
- case 33:
- push2 = true;
- spsfound = true;
- if (track.vps !== void 0 && track.vps[0] !== this.initVPS && track.sps !== void 0 && !this.matchSPS(track.sps[0], unit.data)) {
- this.initVPS = track.vps[0];
- track.sps = track.pps = void 0;
- }
- if (!track.sps) {
- const config = this.readSPS(unit.data);
- track.width = config.width;
- track.height = config.height;
- track.pixelRatio = config.pixelRatio;
- track.codec = config.codecString;
- track.sps = [];
- if (typeof track.params !== "object") {
- track.params = {};
- }
- for (const prop in config.params) {
- track.params[prop] = config.params[prop];
- }
- }
- this.pushParameterSet(track.sps, unit.data, track.vps);
- if (!VideoSample) {
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts);
- }
- VideoSample.key = true;
- break;
- case 34:
- push2 = true;
- if (typeof track.params === "object") {
- if (!track.pps) {
- track.pps = [];
- const config = this.readPPS(unit.data);
- for (const prop in config) {
- track.params[prop] = config[prop];
- }
- }
- this.pushParameterSet(track.pps, unit.data, track.vps);
- }
- break;
- case 35:
- push2 = true;
- track.audFound = true;
- if ((_VideoSample3 = VideoSample) != null && _VideoSample3.frame) {
- this.pushAccessUnit(VideoSample, track);
- VideoSample = null;
- }
- if (!VideoSample) {
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts);
- }
- break;
- default:
- push2 = false;
- break;
- }
- if (VideoSample && push2) {
- const units2 = VideoSample.units;
- units2.push(unit);
- }
- });
- if (endOfSegment && VideoSample) {
- this.pushAccessUnit(VideoSample, track);
- this.VideoSample = null;
- }
- }
- pushParameterSet(parameterSets, data, vps) {
- if (vps && vps[0] === this.initVPS || !vps && !parameterSets.length) {
- parameterSets.push(data);
- }
- }
- getNALuType(data, offset) {
- return (data[offset] & 126) >>> 1;
- }
- ebsp2rbsp(arr) {
- const dst = new Uint8Array(arr.byteLength);
- let dstIdx = 0;
- for (let i = 0; i < arr.byteLength; i++) {
- if (i >= 2) {
- if (arr[i] === 3 && arr[i - 1] === 0 && arr[i - 2] === 0) {
- continue;
- }
- }
- dst[dstIdx] = arr[i];
- dstIdx++;
- }
- return new Uint8Array(dst.buffer, 0, dstIdx);
- }
- pushAccessUnit(VideoSample, videoTrack) {
- super.pushAccessUnit(VideoSample, videoTrack);
- if (this.initVPS) {
- this.initVPS = null;
- }
- }
- readVPS(vps) {
- const eg = new ExpGolomb(vps);
- eg.readUByte();
- eg.readUByte();
- eg.readBits(4);
- eg.skipBits(2);
- eg.readBits(6);
- const max_sub_layers_minus1 = eg.readBits(3);
- const temporal_id_nesting_flag = eg.readBoolean();
- return {
- numTemporalLayers: max_sub_layers_minus1 + 1,
- temporalIdNested: temporal_id_nesting_flag
- };
- }
- readSPS(sps) {
- const eg = new ExpGolomb(this.ebsp2rbsp(sps));
- eg.readUByte();
- eg.readUByte();
- eg.readBits(4);
- const max_sub_layers_minus1 = eg.readBits(3);
- eg.readBoolean();
- const general_profile_space = eg.readBits(2);
- const general_tier_flag = eg.readBoolean();
- const general_profile_idc = eg.readBits(5);
- const general_profile_compatibility_flags_1 = eg.readUByte();
- const general_profile_compatibility_flags_2 = eg.readUByte();
- const general_profile_compatibility_flags_3 = eg.readUByte();
- const general_profile_compatibility_flags_4 = eg.readUByte();
- const general_constraint_indicator_flags_1 = eg.readUByte();
- const general_constraint_indicator_flags_2 = eg.readUByte();
- const general_constraint_indicator_flags_3 = eg.readUByte();
- const general_constraint_indicator_flags_4 = eg.readUByte();
- const general_constraint_indicator_flags_5 = eg.readUByte();
- const general_constraint_indicator_flags_6 = eg.readUByte();
- const general_level_idc = eg.readUByte();
- const sub_layer_profile_present_flags = [];
- const sub_layer_level_present_flags = [];
- for (let i = 0; i < max_sub_layers_minus1; i++) {
- sub_layer_profile_present_flags.push(eg.readBoolean());
- sub_layer_level_present_flags.push(eg.readBoolean());
- }
- if (max_sub_layers_minus1 > 0) {
- for (let i = max_sub_layers_minus1; i < 8; i++) {
- eg.readBits(2);
- }
- }
- for (let i = 0; i < max_sub_layers_minus1; i++) {
- if (sub_layer_profile_present_flags[i]) {
- eg.readUByte();
- eg.readUByte();
- eg.readUByte();
- eg.readUByte();
- eg.readUByte();
- eg.readUByte();
- eg.readUByte();
- eg.readUByte();
- eg.readUByte();
- eg.readUByte();
- eg.readUByte();
- }
- if (sub_layer_level_present_flags[i]) {
- eg.readUByte();
- }
- }
- eg.readUEG();
- const chroma_format_idc = eg.readUEG();
- if (chroma_format_idc == 3) {
- eg.skipBits(1);
- }
- const pic_width_in_luma_samples = eg.readUEG();
- const pic_height_in_luma_samples = eg.readUEG();
- const conformance_window_flag = eg.readBoolean();
- let pic_left_offset = 0, pic_right_offset = 0, pic_top_offset = 0, pic_bottom_offset = 0;
- if (conformance_window_flag) {
- pic_left_offset += eg.readUEG();
- pic_right_offset += eg.readUEG();
- pic_top_offset += eg.readUEG();
- pic_bottom_offset += eg.readUEG();
- }
- const bit_depth_luma_minus8 = eg.readUEG();
- const bit_depth_chroma_minus8 = eg.readUEG();
- const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
- const sub_layer_ordering_info_present_flag = eg.readBoolean();
- for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
- eg.skipUEG();
- eg.skipUEG();
- eg.skipUEG();
- }
- eg.skipUEG();
- eg.skipUEG();
- eg.skipUEG();
- eg.skipUEG();
- eg.skipUEG();
- eg.skipUEG();
- const scaling_list_enabled_flag = eg.readBoolean();
- if (scaling_list_enabled_flag) {
- const sps_scaling_list_data_present_flag = eg.readBoolean();
- if (sps_scaling_list_data_present_flag) {
- for (let sizeId = 0; sizeId < 4; sizeId++) {
- for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
- const scaling_list_pred_mode_flag = eg.readBoolean();
- if (!scaling_list_pred_mode_flag) {
- eg.readUEG();
- } else {
- const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
- if (sizeId > 1) {
- eg.readEG();
- }
- for (let i = 0; i < coefNum; i++) {
- eg.readEG();
- }
- }
- }
- }
- }
- }
- eg.readBoolean();
- eg.readBoolean();
- const pcm_enabled_flag = eg.readBoolean();
- if (pcm_enabled_flag) {
- eg.readUByte();
- eg.skipUEG();
- eg.skipUEG();
- eg.readBoolean();
- }
- const num_short_term_ref_pic_sets = eg.readUEG();
- let num_delta_pocs = 0;
- for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
- let inter_ref_pic_set_prediction_flag = false;
- if (i !== 0) {
- inter_ref_pic_set_prediction_flag = eg.readBoolean();
- }
- if (inter_ref_pic_set_prediction_flag) {
- if (i === num_short_term_ref_pic_sets) {
- eg.readUEG();
- }
- eg.readBoolean();
- eg.readUEG();
- let next_num_delta_pocs = 0;
- for (let j = 0; j <= num_delta_pocs; j++) {
- const used_by_curr_pic_flag = eg.readBoolean();
- let use_delta_flag = false;
- if (!used_by_curr_pic_flag) {
- use_delta_flag = eg.readBoolean();
- }
- if (used_by_curr_pic_flag || use_delta_flag) {
- next_num_delta_pocs++;
- }
- }
- num_delta_pocs = next_num_delta_pocs;
- } else {
- const num_negative_pics = eg.readUEG();
- const num_positive_pics = eg.readUEG();
- num_delta_pocs = num_negative_pics + num_positive_pics;
- for (let j = 0; j < num_negative_pics; j++) {
- eg.readUEG();
- eg.readBoolean();
- }
- for (let j = 0; j < num_positive_pics; j++) {
- eg.readUEG();
- eg.readBoolean();
- }
- }
- }
- const long_term_ref_pics_present_flag = eg.readBoolean();
- if (long_term_ref_pics_present_flag) {
- const num_long_term_ref_pics_sps = eg.readUEG();
- for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
- for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
- eg.readBits(1);
- }
- eg.readBits(1);
- }
- }
- let min_spatial_segmentation_idc = 0;
- let sar_width = 1, sar_height = 1;
- let fps_fixed = true, fps_den = 1, fps_num = 0;
- eg.readBoolean();
- eg.readBoolean();
- let default_display_window_flag = false;
- const vui_parameters_present_flag = eg.readBoolean();
- if (vui_parameters_present_flag) {
- const aspect_ratio_info_present_flag = eg.readBoolean();
- if (aspect_ratio_info_present_flag) {
- const aspect_ratio_idc = eg.readUByte();
- const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
- const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
- if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
- sar_width = sar_width_table[aspect_ratio_idc - 1];
- sar_height = sar_height_table[aspect_ratio_idc - 1];
- } else if (aspect_ratio_idc === 255) {
- sar_width = eg.readBits(16);
- sar_height = eg.readBits(16);
- }
- }
- const overscan_info_present_flag = eg.readBoolean();
- if (overscan_info_present_flag) {
- eg.readBoolean();
- }
- const video_signal_type_present_flag = eg.readBoolean();
- if (video_signal_type_present_flag) {
- eg.readBits(3);
- eg.readBoolean();
- const colour_description_present_flag = eg.readBoolean();
- if (colour_description_present_flag) {
- eg.readUByte();
- eg.readUByte();
- eg.readUByte();
- }
- }
- const chroma_loc_info_present_flag = eg.readBoolean();
- if (chroma_loc_info_present_flag) {
- eg.readUEG();
- eg.readUEG();
- }
- eg.readBoolean();
- eg.readBoolean();
- eg.readBoolean();
- default_display_window_flag = eg.readBoolean();
- if (default_display_window_flag) {
- eg.skipUEG();
- eg.skipUEG();
- eg.skipUEG();
- eg.skipUEG();
- }
- const vui_timing_info_present_flag = eg.readBoolean();
- if (vui_timing_info_present_flag) {
- fps_den = eg.readBits(32);
- fps_num = eg.readBits(32);
- const vui_poc_proportional_to_timing_flag = eg.readBoolean();
- if (vui_poc_proportional_to_timing_flag) {
- eg.readUEG();
- }
- const vui_hrd_parameters_present_flag = eg.readBoolean();
- if (vui_hrd_parameters_present_flag) {
- const nal_hrd_parameters_present_flag = eg.readBoolean();
- const vcl_hrd_parameters_present_flag = eg.readBoolean();
- let sub_pic_hrd_params_present_flag = false;
- if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
- sub_pic_hrd_params_present_flag = eg.readBoolean();
- if (sub_pic_hrd_params_present_flag) {
- eg.readUByte();
- eg.readBits(5);
- eg.readBoolean();
- eg.readBits(5);
- }
- eg.readBits(4);
- eg.readBits(4);
- if (sub_pic_hrd_params_present_flag) {
- eg.readBits(4);
- }
- eg.readBits(5);
- eg.readBits(5);
- eg.readBits(5);
- }
- for (let i = 0; i <= max_sub_layers_minus1; i++) {
- fps_fixed = eg.readBoolean();
- const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
- let low_delay_hrd_flag = false;
- if (fixed_pic_rate_within_cvs_flag) {
- eg.readEG();
- } else {
- low_delay_hrd_flag = eg.readBoolean();
- }
- const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
- if (nal_hrd_parameters_present_flag) {
- for (let j = 0; j < cpb_cnt; j++) {
- eg.readUEG();
- eg.readUEG();
- if (sub_pic_hrd_params_present_flag) {
- eg.readUEG();
- eg.readUEG();
- }
- eg.skipBits(1);
- }
- }
- if (vcl_hrd_parameters_present_flag) {
- for (let j = 0; j < cpb_cnt; j++) {
- eg.readUEG();
- eg.readUEG();
- if (sub_pic_hrd_params_present_flag) {
- eg.readUEG();
- eg.readUEG();
- }
- eg.skipBits(1);
- }
- }
- }
- }
- }
- const bitstream_restriction_flag = eg.readBoolean();
- if (bitstream_restriction_flag) {
- eg.readBoolean();
- eg.readBoolean();
- eg.readBoolean();
- min_spatial_segmentation_idc = eg.readUEG();
- }
- }
- let width = pic_width_in_luma_samples, height = pic_height_in_luma_samples;
- if (conformance_window_flag) {
- let chroma_scale_w = 1, chroma_scale_h = 1;
- if (chroma_format_idc === 1) {
- chroma_scale_w = chroma_scale_h = 2;
- } else if (chroma_format_idc == 2) {
- chroma_scale_w = 2;
- }
- width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
- height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
- }
- const profile_space_string = general_profile_space ? ["A", "B", "C"][general_profile_space] : "";
- const profile_compatibility_buf = general_profile_compatibility_flags_1 << 24 | general_profile_compatibility_flags_2 << 16 | general_profile_compatibility_flags_3 << 8 | general_profile_compatibility_flags_4;
- let profile_compatibility_rev = 0;
- for (let i = 0; i < 32; i++) {
- profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0;
- }
- let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
- if (general_profile_idc === 1 && profile_compatibility_flags_string === "2") {
- profile_compatibility_flags_string = "6";
- }
- const tier_flag_string = general_tier_flag ? "H" : "L";
- return {
- codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
- params: {
- general_tier_flag,
- general_profile_idc,
- general_profile_space,
- general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
- general_constraint_indicator_flags: [general_constraint_indicator_flags_1, general_constraint_indicator_flags_2, general_constraint_indicator_flags_3, general_constraint_indicator_flags_4, general_constraint_indicator_flags_5, general_constraint_indicator_flags_6],
- general_level_idc,
- bit_depth: bit_depth_luma_minus8 + 8,
- bit_depth_luma_minus8,
- bit_depth_chroma_minus8,
- min_spatial_segmentation_idc,
- chroma_format_idc,
- frame_rate: {
- fixed: fps_fixed,
- fps: fps_num / fps_den
- }
- },
- width,
- height,
- pixelRatio: [sar_width, sar_height]
- };
- }
- readPPS(pps) {
- const eg = new ExpGolomb(this.ebsp2rbsp(pps));
- eg.readUByte();
- eg.readUByte();
- eg.skipUEG();
- eg.skipUEG();
- eg.skipBits(2);
- eg.skipBits(3);
- eg.skipBits(2);
- eg.skipUEG();
- eg.skipUEG();
- eg.skipEG();
- eg.skipBits(2);
- const cu_qp_delta_enabled_flag = eg.readBoolean();
- if (cu_qp_delta_enabled_flag) {
- eg.skipUEG();
- }
- eg.skipEG();
- eg.skipEG();
- eg.skipBits(4);
- const tiles_enabled_flag = eg.readBoolean();
- const entropy_coding_sync_enabled_flag = eg.readBoolean();
- let parallelismType = 1;
- if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
- parallelismType = 0;
- } else if (entropy_coding_sync_enabled_flag) {
- parallelismType = 3;
- } else if (tiles_enabled_flag) {
- parallelismType = 2;
- }
- return {
- parallelismType
- };
- }
- matchSPS(sps1, sps2) {
- return String.fromCharCode.apply(null, sps1).substr(3) === String.fromCharCode.apply(null, sps2).substr(3);
- }
- };
- var PACKET_LENGTH = 188;
- var TSDemuxer = class _TSDemuxer {
- constructor(observer, config, typeSupported, logger2) {
- this.logger = void 0;
- this.observer = void 0;
- this.config = void 0;
- this.typeSupported = void 0;
- this.sampleAes = null;
- this.pmtParsed = false;
- this.audioCodec = void 0;
- this.videoCodec = void 0;
- this._pmtId = -1;
- this._videoTrack = void 0;
- this._audioTrack = void 0;
- this._id3Track = void 0;
- this._txtTrack = void 0;
- this.aacOverFlow = null;
- this.remainderData = null;
- this.videoParser = void 0;
- this.observer = observer;
- this.config = config;
- this.typeSupported = typeSupported;
- this.logger = logger2;
- this.videoParser = null;
- }
- static probe(data, logger2) {
- const syncOffset = _TSDemuxer.syncOffset(data);
- if (syncOffset > 0) {
- logger2.warn(`MPEG2-TS detected but first sync word found @ offset ${syncOffset}`);
- }
- return syncOffset !== -1;
- }
- static syncOffset(data) {
- const length = data.length;
- let scanwindow = Math.min(PACKET_LENGTH * 5, length - PACKET_LENGTH) + 1;
- let i = 0;
- while (i < scanwindow) {
- let foundPat = false;
- let packetStart = -1;
- let tsPackets = 0;
- for (let j = i; j < length; j += PACKET_LENGTH) {
- if (data[j] === 71 && (length - j === PACKET_LENGTH || data[j + PACKET_LENGTH] === 71)) {
- tsPackets++;
- if (packetStart === -1) {
- packetStart = j;
- if (packetStart !== 0) {
- scanwindow = Math.min(packetStart + PACKET_LENGTH * 99, data.length - PACKET_LENGTH) + 1;
- }
- }
- if (!foundPat) {
- foundPat = parsePID(data, j) === 0;
- }
- if (foundPat && tsPackets > 1 && (packetStart === 0 && tsPackets > 2 || j + PACKET_LENGTH > scanwindow)) {
- return packetStart;
- }
- } else if (tsPackets) {
- return -1;
- } else {
- break;
- }
- }
- i++;
- }
- return -1;
- }
- /**
- * Creates a track model internal to demuxer used to drive remuxing input
- */
- static createTrack(type, duration) {
- return {
- container: type === "video" || type === "audio" ? "video/mp2t" : void 0,
- type,
- id: RemuxerTrackIdConfig[type],
- pid: -1,
- inputTimeScale: 9e4,
- sequenceNumber: 0,
- samples: [],
- dropped: 0,
- duration: type === "audio" ? duration : void 0
- };
- }
- /**
- * Initializes a new init segment on the demuxer/remuxer interface. Needed for discontinuities/track-switches (or at stream start)
- * Resets all internal track instances of the demuxer.
- */
- resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
- this.pmtParsed = false;
- this._pmtId = -1;
- this._videoTrack = _TSDemuxer.createTrack("video");
- this._videoTrack.duration = trackDuration;
- this._audioTrack = _TSDemuxer.createTrack("audio", trackDuration);
- this._id3Track = _TSDemuxer.createTrack("id3");
- this._txtTrack = _TSDemuxer.createTrack("text");
- this._audioTrack.segmentCodec = "aac";
- this.videoParser = null;
- this.aacOverFlow = null;
- this.remainderData = null;
- this.audioCodec = audioCodec;
- this.videoCodec = videoCodec;
- }
- resetTimeStamp() {
- }
- resetContiguity() {
- const {
- _audioTrack,
- _videoTrack,
- _id3Track
- } = this;
- if (_audioTrack) {
- _audioTrack.pesData = null;
- }
- if (_videoTrack) {
- _videoTrack.pesData = null;
- }
- if (_id3Track) {
- _id3Track.pesData = null;
- }
- this.aacOverFlow = null;
- this.remainderData = null;
- }
- demux(data, timeOffset, isSampleAes = false, flush = false) {
- if (!isSampleAes) {
- this.sampleAes = null;
- }
- let pes;
- const videoTrack = this._videoTrack;
- const audioTrack = this._audioTrack;
- const id3Track = this._id3Track;
- const textTrack = this._txtTrack;
- let videoPid = videoTrack.pid;
- let videoData = videoTrack.pesData;
- let audioPid = audioTrack.pid;
- let id3Pid = id3Track.pid;
- let audioData = audioTrack.pesData;
- let id3Data = id3Track.pesData;
- let unknownPID = null;
- let pmtParsed = this.pmtParsed;
- let pmtId = this._pmtId;
- let len = data.length;
- if (this.remainderData) {
- data = appendUint8Array(this.remainderData, data);
- len = data.length;
- this.remainderData = null;
- }
- if (len < PACKET_LENGTH && !flush) {
- this.remainderData = data;
- return {
- audioTrack,
- videoTrack,
- id3Track,
- textTrack
- };
- }
- const syncOffset = Math.max(0, _TSDemuxer.syncOffset(data));
- len -= (len - syncOffset) % PACKET_LENGTH;
- if (len < data.byteLength && !flush) {
- this.remainderData = new Uint8Array(data.buffer, len, data.buffer.byteLength - len);
- }
- let tsPacketErrors = 0;
- for (let start = syncOffset; start < len; start += PACKET_LENGTH) {
- if (data[start] === 71) {
- const stt = !!(data[start + 1] & 64);
- const pid = parsePID(data, start);
- const atf = (data[start + 3] & 48) >> 4;
- let offset;
- if (atf > 1) {
- offset = start + 5 + data[start + 4];
- if (offset === start + PACKET_LENGTH) {
- continue;
- }
- } else {
- offset = start + 4;
- }
- switch (pid) {
- case videoPid:
- if (stt) {
- if (videoData && (pes = parsePES(videoData, this.logger))) {
- this.readyVideoParser(videoTrack.segmentCodec);
- if (this.videoParser !== null) {
- this.videoParser.parsePES(videoTrack, textTrack, pes, false);
- }
- }
- videoData = {
- data: [],
- size: 0
- };
- }
- if (videoData) {
- videoData.data.push(data.subarray(offset, start + PACKET_LENGTH));
- videoData.size += start + PACKET_LENGTH - offset;
- }
- break;
- case audioPid:
- if (stt) {
- if (audioData && (pes = parsePES(audioData, this.logger))) {
- switch (audioTrack.segmentCodec) {
- case "aac":
- this.parseAACPES(audioTrack, pes);
- break;
- case "mp3":
- this.parseMPEGPES(audioTrack, pes);
- break;
- case "ac3":
- {
- this.parseAC3PES(audioTrack, pes);
- }
- break;
- }
- }
- audioData = {
- data: [],
- size: 0
- };
- }
- if (audioData) {
- audioData.data.push(data.subarray(offset, start + PACKET_LENGTH));
- audioData.size += start + PACKET_LENGTH - offset;
- }
- break;
- case id3Pid:
- if (stt) {
- if (id3Data && (pes = parsePES(id3Data, this.logger))) {
- this.parseID3PES(id3Track, pes);
- }
- id3Data = {
- data: [],
- size: 0
- };
- }
- if (id3Data) {
- id3Data.data.push(data.subarray(offset, start + PACKET_LENGTH));
- id3Data.size += start + PACKET_LENGTH - offset;
- }
- break;
- case 0:
- if (stt) {
- offset += data[offset] + 1;
- }
- pmtId = this._pmtId = parsePAT(data, offset);
- break;
- case pmtId: {
- if (stt) {
- offset += data[offset] + 1;
- }
- const parsedPIDs = parsePMT(data, offset, this.typeSupported, isSampleAes, this.observer, this.logger);
- videoPid = parsedPIDs.videoPid;
- if (videoPid > 0) {
- videoTrack.pid = videoPid;
- videoTrack.segmentCodec = parsedPIDs.segmentVideoCodec;
- }
- audioPid = parsedPIDs.audioPid;
- if (audioPid > 0) {
- audioTrack.pid = audioPid;
- audioTrack.segmentCodec = parsedPIDs.segmentAudioCodec;
- }
- id3Pid = parsedPIDs.id3Pid;
- if (id3Pid > 0) {
- id3Track.pid = id3Pid;
- }
- if (unknownPID !== null && !pmtParsed) {
- this.logger.warn(`MPEG-TS PMT found at ${start} after unknown PID '${unknownPID}'. Backtracking to sync byte @${syncOffset} to parse all TS packets.`);
- unknownPID = null;
- start = syncOffset - 188;
- }
- pmtParsed = this.pmtParsed = true;
- break;
- }
- case 17:
- case 8191:
- break;
- default:
- unknownPID = pid;
- break;
- }
- } else {
- tsPacketErrors++;
- }
- }
- if (tsPacketErrors > 0) {
- emitParsingError(this.observer, new Error(`Found ${tsPacketErrors} TS packet/s that do not start with 0x47`), void 0, this.logger);
- }
- videoTrack.pesData = videoData;
- audioTrack.pesData = audioData;
- id3Track.pesData = id3Data;
- const demuxResult = {
- audioTrack,
- videoTrack,
- id3Track,
- textTrack
- };
- if (flush) {
- this.extractRemainingSamples(demuxResult);
- }
- return demuxResult;
- }
- flush() {
- const {
- remainderData
- } = this;
- this.remainderData = null;
- let result;
- if (remainderData) {
- result = this.demux(remainderData, -1, false, true);
- } else {
- result = {
- videoTrack: this._videoTrack,
- audioTrack: this._audioTrack,
- id3Track: this._id3Track,
- textTrack: this._txtTrack
- };
- }
- this.extractRemainingSamples(result);
- if (this.sampleAes) {
- return this.decrypt(result, this.sampleAes);
- }
- return result;
- }
- extractRemainingSamples(demuxResult) {
- const {
- audioTrack,
- videoTrack,
- id3Track,
- textTrack
- } = demuxResult;
- const videoData = videoTrack.pesData;
- const audioData = audioTrack.pesData;
- const id3Data = id3Track.pesData;
- let pes;
- if (videoData && (pes = parsePES(videoData, this.logger))) {
- this.readyVideoParser(videoTrack.segmentCodec);
- if (this.videoParser !== null) {
- this.videoParser.parsePES(videoTrack, textTrack, pes, true);
- videoTrack.pesData = null;
- }
- } else {
- videoTrack.pesData = videoData;
- }
- if (audioData && (pes = parsePES(audioData, this.logger))) {
- switch (audioTrack.segmentCodec) {
- case "aac":
- this.parseAACPES(audioTrack, pes);
- break;
- case "mp3":
- this.parseMPEGPES(audioTrack, pes);
- break;
- case "ac3":
- {
- this.parseAC3PES(audioTrack, pes);
- }
- break;
- }
- audioTrack.pesData = null;
- } else {
- if (audioData != null && audioData.size) {
- this.logger.log("last AAC PES packet truncated,might overlap between fragments");
- }
- audioTrack.pesData = audioData;
- }
- if (id3Data && (pes = parsePES(id3Data, this.logger))) {
- this.parseID3PES(id3Track, pes);
- id3Track.pesData = null;
- } else {
- id3Track.pesData = id3Data;
- }
- }
- demuxSampleAes(data, keyData, timeOffset) {
- const demuxResult = this.demux(data, timeOffset, true, !this.config.progressive);
- const sampleAes = this.sampleAes = new SampleAesDecrypter(this.observer, this.config, keyData);
- return this.decrypt(demuxResult, sampleAes);
- }
- readyVideoParser(codec) {
- if (this.videoParser === null) {
- if (codec === "avc") {
- this.videoParser = new AvcVideoParser();
- } else if (codec === "hevc") {
- this.videoParser = new HevcVideoParser();
- }
- }
- }
- decrypt(demuxResult, sampleAes) {
- return new Promise((resolve) => {
- const {
- audioTrack,
- videoTrack
- } = demuxResult;
- if (audioTrack.samples && audioTrack.segmentCodec === "aac") {
- sampleAes.decryptAacSamples(audioTrack.samples, 0, () => {
- if (videoTrack.samples) {
- sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => {
- resolve(demuxResult);
- });
- } else {
- resolve(demuxResult);
- }
- });
- } else if (videoTrack.samples) {
- sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => {
- resolve(demuxResult);
- });
- }
- });
- }
- destroy() {
- if (this.observer) {
- this.observer.removeAllListeners();
- }
- this.config = this.logger = this.observer = null;
- this.aacOverFlow = this.videoParser = this.remainderData = this.sampleAes = null;
- this._videoTrack = this._audioTrack = this._id3Track = this._txtTrack = void 0;
- }
- parseAACPES(track, pes) {
- let startOffset = 0;
- const aacOverFlow = this.aacOverFlow;
- let data = pes.data;
- if (aacOverFlow) {
- this.aacOverFlow = null;
- const frameMissingBytes = aacOverFlow.missing;
- const sampleLength = aacOverFlow.sample.unit.byteLength;
- if (frameMissingBytes === -1) {
- data = appendUint8Array(aacOverFlow.sample.unit, data);
- } else {
- const frameOverflowBytes = sampleLength - frameMissingBytes;
- aacOverFlow.sample.unit.set(data.subarray(0, frameMissingBytes), frameOverflowBytes);
- track.samples.push(aacOverFlow.sample);
- startOffset = aacOverFlow.missing;
- }
- }
- let offset;
- let len;
- for (offset = startOffset, len = data.length; offset < len - 1; offset++) {
- if (isHeader$1(data, offset)) {
- break;
- }
- }
- if (offset !== startOffset) {
- let reason;
- const recoverable = offset < len - 1;
- if (recoverable) {
- reason = `AAC PES did not start with ADTS header,offset:${offset}`;
- } else {
- reason = "No ADTS header found in AAC PES";
- }
- emitParsingError(this.observer, new Error(reason), recoverable, this.logger);
- if (!recoverable) {
- return;
- }
- }
- initTrackConfig(track, this.observer, data, offset, this.audioCodec);
- let pts;
- if (pes.pts !== void 0) {
- pts = pes.pts;
- } else if (aacOverFlow) {
- const frameDuration = getFrameDuration(track.samplerate);
- pts = aacOverFlow.sample.pts + frameDuration;
- } else {
- this.logger.warn("[tsdemuxer]: AAC PES unknown PTS");
- return;
- }
- let frameIndex = 0;
- let frame;
- while (offset < len) {
- frame = appendFrame$2(track, data, offset, pts, frameIndex);
- offset += frame.length;
- if (!frame.missing) {
- frameIndex++;
- for (; offset < len - 1; offset++) {
- if (isHeader$1(data, offset)) {
- break;
- }
- }
- } else {
- this.aacOverFlow = frame;
- break;
- }
- }
- }
- parseMPEGPES(track, pes) {
- const data = pes.data;
- const length = data.length;
- let frameIndex = 0;
- let offset = 0;
- const pts = pes.pts;
- if (pts === void 0) {
- this.logger.warn("[tsdemuxer]: MPEG PES unknown PTS");
- return;
- }
- while (offset < length) {
- if (isHeader(data, offset)) {
- const frame = appendFrame$1(track, data, offset, pts, frameIndex);
- if (frame) {
- offset += frame.length;
- frameIndex++;
- } else {
- break;
- }
- } else {
- offset++;
- }
- }
- }
- parseAC3PES(track, pes) {
- {
- const data = pes.data;
- const pts = pes.pts;
- if (pts === void 0) {
- this.logger.warn("[tsdemuxer]: AC3 PES unknown PTS");
- return;
- }
- const length = data.length;
- let frameIndex = 0;
- let offset = 0;
- let parsed;
- while (offset < length && (parsed = appendFrame(track, data, offset, pts, frameIndex++)) > 0) {
- offset += parsed;
- }
- }
- }
- parseID3PES(id3Track, pes) {
- if (pes.pts === void 0) {
- this.logger.warn("[tsdemuxer]: ID3 PES unknown PTS");
- return;
- }
- const id3Sample = _extends({}, pes, {
- type: this._videoTrack ? MetadataSchema.emsg : MetadataSchema.audioId3,
- duration: Number.POSITIVE_INFINITY
- });
- id3Track.samples.push(id3Sample);
- }
- };
- function parsePID(data, offset) {
- return ((data[offset + 1] & 31) << 8) + data[offset + 2];
- }
- function parsePAT(data, offset) {
- return (data[offset + 10] & 31) << 8 | data[offset + 11];
- }
- function parsePMT(data, offset, typeSupported, isSampleAes, observer, logger2) {
- const result = {
- audioPid: -1,
- videoPid: -1,
- id3Pid: -1,
- segmentVideoCodec: "avc",
- segmentAudioCodec: "aac"
- };
- const sectionLength = (data[offset + 1] & 15) << 8 | data[offset + 2];
- const tableEnd = offset + 3 + sectionLength - 4;
- const programInfoLength = (data[offset + 10] & 15) << 8 | data[offset + 11];
- offset += 12 + programInfoLength;
- while (offset < tableEnd) {
- const pid = parsePID(data, offset);
- const esInfoLength = (data[offset + 3] & 15) << 8 | data[offset + 4];
- switch (data[offset]) {
- case 207:
- if (!isSampleAes) {
- logEncryptedSamplesFoundInUnencryptedStream("ADTS AAC", logger2);
- break;
- }
- case 15:
- if (result.audioPid === -1) {
- result.audioPid = pid;
- }
- break;
- case 21:
- if (result.id3Pid === -1) {
- result.id3Pid = pid;
- }
- break;
- case 219:
- if (!isSampleAes) {
- logEncryptedSamplesFoundInUnencryptedStream("H.264", logger2);
- break;
- }
- case 27:
- if (result.videoPid === -1) {
- result.videoPid = pid;
- }
- break;
- case 3:
- case 4:
- if (!typeSupported.mpeg && !typeSupported.mp3) {
- logger2.log("MPEG audio found, not supported in this browser");
- } else if (result.audioPid === -1) {
- result.audioPid = pid;
- result.segmentAudioCodec = "mp3";
- }
- break;
- case 193:
- if (!isSampleAes) {
- logEncryptedSamplesFoundInUnencryptedStream("AC-3", logger2);
- break;
- }
- case 129:
- {
- if (!typeSupported.ac3) {
- logger2.log("AC-3 audio found, not supported in this browser");
- } else if (result.audioPid === -1) {
- result.audioPid = pid;
- result.segmentAudioCodec = "ac3";
- }
- }
- break;
- case 6:
- if (result.audioPid === -1 && esInfoLength > 0) {
- let parsePos = offset + 5;
- let remaining = esInfoLength;
- while (remaining > 2) {
- const descriptorId = data[parsePos];
- switch (descriptorId) {
- case 106:
- {
- if (typeSupported.ac3 !== true) {
- logger2.log("AC-3 audio found, not supported in this browser for now");
- } else {
- result.audioPid = pid;
- result.segmentAudioCodec = "ac3";
- }
- }
- break;
- }
- const descriptorLen = data[parsePos + 1] + 2;
- parsePos += descriptorLen;
- remaining -= descriptorLen;
- }
- }
- break;
- case 194:
- case 135:
- emitParsingError(observer, new Error("Unsupported EC-3 in M2TS found"), void 0, logger2);
- return result;
- case 36:
- {
- if (result.videoPid === -1) {
- result.videoPid = pid;
- result.segmentVideoCodec = "hevc";
- logger2.log("HEVC in M2TS found");
- }
- }
- break;
- }
- offset += esInfoLength + 5;
- }
- return result;
- }
- function emitParsingError(observer, error, levelRetry, logger2) {
- logger2.warn(`parsing error: ${error.message}`);
- observer.emit(Events.ERROR, Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.FRAG_PARSING_ERROR,
- fatal: false,
- levelRetry,
- error,
- reason: error.message
- });
- }
- function logEncryptedSamplesFoundInUnencryptedStream(type, logger2) {
- logger2.log(`${type} with AES-128-CBC encryption found in unencrypted stream`);
- }
- function parsePES(stream, logger2) {
- let i = 0;
- let frag;
- let pesLen;
- let pesHdrLen;
- let pesPts;
- let pesDts;
- const data = stream.data;
- if (!stream || stream.size === 0) {
- return null;
- }
- while (data[0].length < 19 && data.length > 1) {
- data[0] = appendUint8Array(data[0], data[1]);
- data.splice(1, 1);
- }
- frag = data[0];
- const pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2];
- if (pesPrefix === 1) {
- pesLen = (frag[4] << 8) + frag[5];
- if (pesLen && pesLen > stream.size - 6) {
- return null;
- }
- const pesFlags = frag[7];
- if (pesFlags & 192) {
- pesPts = (frag[9] & 14) * 536870912 + // 1 << 29
- (frag[10] & 255) * 4194304 + // 1 << 22
- (frag[11] & 254) * 16384 + // 1 << 14
- (frag[12] & 255) * 128 + // 1 << 7
- (frag[13] & 254) / 2;
- if (pesFlags & 64) {
- pesDts = (frag[14] & 14) * 536870912 + // 1 << 29
- (frag[15] & 255) * 4194304 + // 1 << 22
- (frag[16] & 254) * 16384 + // 1 << 14
- (frag[17] & 255) * 128 + // 1 << 7
- (frag[18] & 254) / 2;
- if (pesPts - pesDts > 60 * 9e4) {
- logger2.warn(`${Math.round((pesPts - pesDts) / 9e4)}s delta between PTS and DTS, align them`);
- pesPts = pesDts;
- }
- } else {
- pesDts = pesPts;
- }
- }
- pesHdrLen = frag[8];
- let payloadStartOffset = pesHdrLen + 9;
- if (stream.size <= payloadStartOffset) {
- return null;
- }
- stream.size -= payloadStartOffset;
- const pesData = new Uint8Array(stream.size);
- for (let j = 0, dataLen = data.length; j < dataLen; j++) {
- frag = data[j];
- let len = frag.byteLength;
- if (payloadStartOffset) {
- if (payloadStartOffset > len) {
- payloadStartOffset -= len;
- continue;
- } else {
- frag = frag.subarray(payloadStartOffset);
- len -= payloadStartOffset;
- payloadStartOffset = 0;
- }
- }
- pesData.set(frag, i);
- i += len;
- }
- if (pesLen) {
- pesLen -= pesHdrLen + 3;
- }
- return {
- data: pesData,
- pts: pesPts,
- dts: pesDts,
- len: pesLen
- };
- }
- return null;
- }
- var AAC = class {
- static getSilentFrame(codec, channelCount) {
- switch (codec) {
- case "mp4a.40.2":
- if (channelCount === 1) {
- return new Uint8Array([0, 200, 0, 128, 35, 128]);
- } else if (channelCount === 2) {
- return new Uint8Array([33, 0, 73, 144, 2, 25, 0, 35, 128]);
- } else if (channelCount === 3) {
- return new Uint8Array([0, 200, 0, 128, 32, 132, 1, 38, 64, 8, 100, 0, 142]);
- } else if (channelCount === 4) {
- return new Uint8Array([0, 200, 0, 128, 32, 132, 1, 38, 64, 8, 100, 0, 128, 44, 128, 8, 2, 56]);
- } else if (channelCount === 5) {
- return new Uint8Array([0, 200, 0, 128, 32, 132, 1, 38, 64, 8, 100, 0, 130, 48, 4, 153, 0, 33, 144, 2, 56]);
- } else if (channelCount === 6) {
- return new Uint8Array([0, 200, 0, 128, 32, 132, 1, 38, 64, 8, 100, 0, 130, 48, 4, 153, 0, 33, 144, 2, 0, 178, 0, 32, 8, 224]);
- }
- break;
- default:
- if (channelCount === 1) {
- return new Uint8Array([1, 64, 34, 128, 163, 78, 230, 128, 186, 8, 0, 0, 0, 28, 6, 241, 193, 10, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 94]);
- } else if (channelCount === 2) {
- return new Uint8Array([1, 64, 34, 128, 163, 94, 230, 128, 186, 8, 0, 0, 0, 0, 149, 0, 6, 241, 161, 10, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 94]);
- } else if (channelCount === 3) {
- return new Uint8Array([1, 64, 34, 128, 163, 94, 230, 128, 186, 8, 0, 0, 0, 0, 149, 0, 6, 241, 161, 10, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 94]);
- }
- break;
- }
- return void 0;
- }
- };
- var UINT32_MAX = Math.pow(2, 32) - 1;
- var MP4 = class _MP4 {
- static init() {
- _MP4.types = {
- avc1: [],
- // codingname
- avcC: [],
- hvc1: [],
- hvcC: [],
- btrt: [],
- dinf: [],
- dref: [],
- esds: [],
- ftyp: [],
- hdlr: [],
- mdat: [],
- mdhd: [],
- mdia: [],
- mfhd: [],
- minf: [],
- moof: [],
- moov: [],
- mp4a: [],
- ".mp3": [],
- dac3: [],
- "ac-3": [],
- mvex: [],
- mvhd: [],
- pasp: [],
- sdtp: [],
- stbl: [],
- stco: [],
- stsc: [],
- stsd: [],
- stsz: [],
- stts: [],
- tfdt: [],
- tfhd: [],
- traf: [],
- trak: [],
- trun: [],
- trex: [],
- tkhd: [],
- vmhd: [],
- smhd: []
- };
- let i;
- for (i in _MP4.types) {
- if (_MP4.types.hasOwnProperty(i)) {
- _MP4.types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
- }
- }
- const videoHdlr = new Uint8Array([
- 0,
- // version 0
- 0,
- 0,
- 0,
- // flags
- 0,
- 0,
- 0,
- 0,
- // pre_defined
- 118,
- 105,
- 100,
- 101,
- // handler_type: 'vide'
- 0,
- 0,
- 0,
- 0,
- // reserved
- 0,
- 0,
- 0,
- 0,
- // reserved
- 0,
- 0,
- 0,
- 0,
- // reserved
- 86,
- 105,
- 100,
- 101,
- 111,
- 72,
- 97,
- 110,
- 100,
- 108,
- 101,
- 114,
- 0
- // name: 'VideoHandler'
- ]);
- const audioHdlr = new Uint8Array([
- 0,
- // version 0
- 0,
- 0,
- 0,
- // flags
- 0,
- 0,
- 0,
- 0,
- // pre_defined
- 115,
- 111,
- 117,
- 110,
- // handler_type: 'soun'
- 0,
- 0,
- 0,
- 0,
- // reserved
- 0,
- 0,
- 0,
- 0,
- // reserved
- 0,
- 0,
- 0,
- 0,
- // reserved
- 83,
- 111,
- 117,
- 110,
- 100,
- 72,
- 97,
- 110,
- 100,
- 108,
- 101,
- 114,
- 0
- // name: 'SoundHandler'
- ]);
- _MP4.HDLR_TYPES = {
- video: videoHdlr,
- audio: audioHdlr
- };
- const dref = new Uint8Array([
- 0,
- // version 0
- 0,
- 0,
- 0,
- // flags
- 0,
- 0,
- 0,
- 1,
- // entry_count
- 0,
- 0,
- 0,
- 12,
- // entry_size
- 117,
- 114,
- 108,
- 32,
- // 'url' type
- 0,
- // version 0
- 0,
- 0,
- 1
- // entry_flags
- ]);
- const stco = new Uint8Array([
- 0,
- // version
- 0,
- 0,
- 0,
- // flags
- 0,
- 0,
- 0,
- 0
- // entry_count
- ]);
- _MP4.STTS = _MP4.STSC = _MP4.STCO = stco;
- _MP4.STSZ = new Uint8Array([
- 0,
- // version
- 0,
- 0,
- 0,
- // flags
- 0,
- 0,
- 0,
- 0,
- // sample_size
- 0,
- 0,
- 0,
- 0
- // sample_count
- ]);
- _MP4.VMHD = new Uint8Array([
- 0,
- // version
- 0,
- 0,
- 1,
- // flags
- 0,
- 0,
- // graphicsmode
- 0,
- 0,
- 0,
- 0,
- 0,
- 0
- // opcolor
- ]);
- _MP4.SMHD = new Uint8Array([
- 0,
- // version
- 0,
- 0,
- 0,
- // flags
- 0,
- 0,
- // balance
- 0,
- 0
- // reserved
- ]);
- _MP4.STSD = new Uint8Array([
- 0,
- // version 0
- 0,
- 0,
- 0,
- // flags
- 0,
- 0,
- 0,
- 1
- ]);
- const majorBrand = new Uint8Array([105, 115, 111, 109]);
- const avc1Brand = new Uint8Array([97, 118, 99, 49]);
- const minorVersion = new Uint8Array([0, 0, 0, 1]);
- _MP4.FTYP = _MP4.box(_MP4.types.ftyp, majorBrand, minorVersion, majorBrand, avc1Brand);
- _MP4.DINF = _MP4.box(_MP4.types.dinf, _MP4.box(_MP4.types.dref, dref));
- }
- static box(type, ...payload) {
- let size = 8;
- let i = payload.length;
- const len = i;
- while (i--) {
- size += payload[i].byteLength;
- }
- const result = new Uint8Array(size);
- result[0] = size >> 24 & 255;
- result[1] = size >> 16 & 255;
- result[2] = size >> 8 & 255;
- result[3] = size & 255;
- result.set(type, 4);
- for (i = 0, size = 8; i < len; i++) {
- result.set(payload[i], size);
- size += payload[i].byteLength;
- }
- return result;
- }
- static hdlr(type) {
- return _MP4.box(_MP4.types.hdlr, _MP4.HDLR_TYPES[type]);
- }
- static mdat(data) {
- return _MP4.box(_MP4.types.mdat, data);
- }
- static mdhd(timescale, duration) {
- duration *= timescale;
- const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
- const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
- return _MP4.box(_MP4.types.mdhd, new Uint8Array([
- 1,
- // version 1
- 0,
- 0,
- 0,
- // flags
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 2,
- // creation_time
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 3,
- // modification_time
- timescale >> 24 & 255,
- timescale >> 16 & 255,
- timescale >> 8 & 255,
- timescale & 255,
- // timescale
- upperWordDuration >> 24,
- upperWordDuration >> 16 & 255,
- upperWordDuration >> 8 & 255,
- upperWordDuration & 255,
- lowerWordDuration >> 24,
- lowerWordDuration >> 16 & 255,
- lowerWordDuration >> 8 & 255,
- lowerWordDuration & 255,
- 85,
- 196,
- // 'und' language (undetermined)
- 0,
- 0
- ]));
- }
- static mdia(track) {
- return _MP4.box(_MP4.types.mdia, _MP4.mdhd(track.timescale || 0, track.duration || 0), _MP4.hdlr(track.type), _MP4.minf(track));
- }
- static mfhd(sequenceNumber) {
- return _MP4.box(_MP4.types.mfhd, new Uint8Array([
- 0,
- 0,
- 0,
- 0,
- // flags
- sequenceNumber >> 24,
- sequenceNumber >> 16 & 255,
- sequenceNumber >> 8 & 255,
- sequenceNumber & 255
- // sequence_number
- ]));
- }
- static minf(track) {
- if (track.type === "audio") {
- return _MP4.box(_MP4.types.minf, _MP4.box(_MP4.types.smhd, _MP4.SMHD), _MP4.DINF, _MP4.stbl(track));
- } else {
- return _MP4.box(_MP4.types.minf, _MP4.box(_MP4.types.vmhd, _MP4.VMHD), _MP4.DINF, _MP4.stbl(track));
- }
- }
- static moof(sn, baseMediaDecodeTime, track) {
- return _MP4.box(_MP4.types.moof, _MP4.mfhd(sn), _MP4.traf(track, baseMediaDecodeTime));
- }
- static moov(tracks) {
- let i = tracks.length;
- const boxes = [];
- while (i--) {
- boxes[i] = _MP4.trak(tracks[i]);
- }
- return _MP4.box.apply(null, [_MP4.types.moov, _MP4.mvhd(tracks[0].timescale || 0, tracks[0].duration || 0)].concat(boxes).concat(_MP4.mvex(tracks)));
- }
- static mvex(tracks) {
- let i = tracks.length;
- const boxes = [];
- while (i--) {
- boxes[i] = _MP4.trex(tracks[i]);
- }
- return _MP4.box.apply(null, [_MP4.types.mvex, ...boxes]);
- }
- static mvhd(timescale, duration) {
- duration *= timescale;
- const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
- const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
- const bytes = new Uint8Array([
- 1,
- // version 1
- 0,
- 0,
- 0,
- // flags
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 2,
- // creation_time
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 3,
- // modification_time
- timescale >> 24 & 255,
- timescale >> 16 & 255,
- timescale >> 8 & 255,
- timescale & 255,
- // timescale
- upperWordDuration >> 24,
- upperWordDuration >> 16 & 255,
- upperWordDuration >> 8 & 255,
- upperWordDuration & 255,
- lowerWordDuration >> 24,
- lowerWordDuration >> 16 & 255,
- lowerWordDuration >> 8 & 255,
- lowerWordDuration & 255,
- 0,
- 1,
- 0,
- 0,
- // 1.0 rate
- 1,
- 0,
- // 1.0 volume
- 0,
- 0,
- // reserved
- 0,
- 0,
- 0,
- 0,
- // reserved
- 0,
- 0,
- 0,
- 0,
- // reserved
- 0,
- 1,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 1,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 64,
- 0,
- 0,
- 0,
- // transformation: unity matrix
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- // pre_defined
- 255,
- 255,
- 255,
- 255
- // next_track_ID
- ]);
- return _MP4.box(_MP4.types.mvhd, bytes);
- }
- static sdtp(track) {
- const samples = track.samples || [];
- const bytes = new Uint8Array(4 + samples.length);
- let i;
- let flags;
- for (i = 0; i < samples.length; i++) {
- flags = samples[i].flags;
- bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
- }
- return _MP4.box(_MP4.types.sdtp, bytes);
- }
- static stbl(track) {
- return _MP4.box(_MP4.types.stbl, _MP4.stsd(track), _MP4.box(_MP4.types.stts, _MP4.STTS), _MP4.box(_MP4.types.stsc, _MP4.STSC), _MP4.box(_MP4.types.stsz, _MP4.STSZ), _MP4.box(_MP4.types.stco, _MP4.STCO));
- }
- static avc1(track) {
- let sps = [];
- let pps = [];
- let i;
- let data;
- let len;
- for (i = 0; i < track.sps.length; i++) {
- data = track.sps[i];
- len = data.byteLength;
- sps.push(len >>> 8 & 255);
- sps.push(len & 255);
- sps = sps.concat(Array.prototype.slice.call(data));
- }
- for (i = 0; i < track.pps.length; i++) {
- data = track.pps[i];
- len = data.byteLength;
- pps.push(len >>> 8 & 255);
- pps.push(len & 255);
- pps = pps.concat(Array.prototype.slice.call(data));
- }
- const avcc = _MP4.box(_MP4.types.avcC, new Uint8Array([
- 1,
- // version
- sps[3],
- // profile
- sps[4],
- // profile compat
- sps[5],
- // level
- 252 | 3,
- // lengthSizeMinusOne, hard-coded to 4 bytes
- 224 | track.sps.length
- // 3bit reserved (111) + numOfSequenceParameterSets
- ].concat(sps).concat([
- track.pps.length
- // numOfPictureParameterSets
- ]).concat(pps)));
- const width = track.width;
- const height = track.height;
- const hSpacing = track.pixelRatio[0];
- const vSpacing = track.pixelRatio[1];
- return _MP4.box(
- _MP4.types.avc1,
- new Uint8Array([
- 0,
- 0,
- 0,
- // reserved
- 0,
- 0,
- 0,
- // reserved
- 0,
- 1,
- // data_reference_index
- 0,
- 0,
- // pre_defined
- 0,
- 0,
- // reserved
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- // pre_defined
- width >> 8 & 255,
- width & 255,
- // width
- height >> 8 & 255,
- height & 255,
- // height
- 0,
- 72,
- 0,
- 0,
- // horizresolution
- 0,
- 72,
- 0,
- 0,
- // vertresolution
- 0,
- 0,
- 0,
- 0,
- // reserved
- 0,
- 1,
- // frame_count
- 18,
- 100,
- 97,
- 105,
- 108,
- // dailymotion/hls.js
- 121,
- 109,
- 111,
- 116,
- 105,
- 111,
- 110,
- 47,
- 104,
- 108,
- 115,
- 46,
- 106,
- 115,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- // compressorname
- 0,
- 24,
- // depth = 24
- 17,
- 17
- ]),
- // pre_defined = -1
- avcc,
- _MP4.box(_MP4.types.btrt, new Uint8Array([
- 0,
- 28,
- 156,
- 128,
- // bufferSizeDB
- 0,
- 45,
- 198,
- 192,
- // maxBitrate
- 0,
- 45,
- 198,
- 192
- ])),
- // avgBitrate
- _MP4.box(_MP4.types.pasp, new Uint8Array([
- hSpacing >> 24,
- // hSpacing
- hSpacing >> 16 & 255,
- hSpacing >> 8 & 255,
- hSpacing & 255,
- vSpacing >> 24,
- // vSpacing
- vSpacing >> 16 & 255,
- vSpacing >> 8 & 255,
- vSpacing & 255
- ]))
- );
- }
- static esds(track) {
- const config = track.config;
- return new Uint8Array([
- 0,
- // version 0
- 0,
- 0,
- 0,
- // flags
- 3,
- // descriptor_type
- 25,
- // length
- 0,
- 1,
- // es_id
- 0,
- // stream_priority
- 4,
- // descriptor_type
- 17,
- // length
- 64,
- // codec : mpeg4_audio
- 21,
- // stream_type
- 0,
- 0,
- 0,
- // buffer_size
- 0,
- 0,
- 0,
- 0,
- // maxBitrate
- 0,
- 0,
- 0,
- 0,
- // avgBitrate
- 5,
- // descriptor_type
- 2,
- // length
- ...config,
- 6,
- 1,
- 2
- // GASpecificConfig)); // length + audio config descriptor
- ]);
- }
- static audioStsd(track) {
- const samplerate = track.samplerate || 0;
- return new Uint8Array([
- 0,
- 0,
- 0,
- // reserved
- 0,
- 0,
- 0,
- // reserved
- 0,
- 1,
- // data_reference_index
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- // reserved
- 0,
- track.channelCount || 0,
- // channelcount
- 0,
- 16,
- // sampleSize:16bits
- 0,
- 0,
- 0,
- 0,
- // reserved2
- samplerate >> 8 & 255,
- samplerate & 255,
- //
- 0,
- 0
- ]);
- }
- static mp4a(track) {
- return _MP4.box(_MP4.types.mp4a, _MP4.audioStsd(track), _MP4.box(_MP4.types.esds, _MP4.esds(track)));
- }
- static mp3(track) {
- return _MP4.box(_MP4.types[".mp3"], _MP4.audioStsd(track));
- }
- static ac3(track) {
- return _MP4.box(_MP4.types["ac-3"], _MP4.audioStsd(track), _MP4.box(_MP4.types.dac3, track.config));
- }
- static stsd(track) {
- const {
- segmentCodec
- } = track;
- if (track.type === "audio") {
- if (segmentCodec === "aac") {
- return _MP4.box(_MP4.types.stsd, _MP4.STSD, _MP4.mp4a(track));
- }
- if (segmentCodec === "ac3" && track.config) {
- return _MP4.box(_MP4.types.stsd, _MP4.STSD, _MP4.ac3(track));
- }
- if (segmentCodec === "mp3" && track.codec === "mp3") {
- return _MP4.box(_MP4.types.stsd, _MP4.STSD, _MP4.mp3(track));
- }
- } else {
- if (track.pps && track.sps) {
- if (segmentCodec === "avc") {
- return _MP4.box(_MP4.types.stsd, _MP4.STSD, _MP4.avc1(track));
- }
- if (segmentCodec === "hevc" && track.vps) {
- return _MP4.box(_MP4.types.stsd, _MP4.STSD, _MP4.hvc1(track));
- }
- } else {
- throw new Error(`video track missing pps or sps`);
- }
- }
- throw new Error(`unsupported ${track.type} segment codec (${segmentCodec}/${track.codec})`);
- }
- static tkhd(track) {
- const id = track.id;
- const duration = (track.duration || 0) * (track.timescale || 0);
- const width = track.width || 0;
- const height = track.height || 0;
- const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
- const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
- return _MP4.box(_MP4.types.tkhd, new Uint8Array([
- 1,
- // version 1
- 0,
- 0,
- 7,
- // flags
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 2,
- // creation_time
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 3,
- // modification_time
- id >> 24 & 255,
- id >> 16 & 255,
- id >> 8 & 255,
- id & 255,
- // track_ID
- 0,
- 0,
- 0,
- 0,
- // reserved
- upperWordDuration >> 24,
- upperWordDuration >> 16 & 255,
- upperWordDuration >> 8 & 255,
- upperWordDuration & 255,
- lowerWordDuration >> 24,
- lowerWordDuration >> 16 & 255,
- lowerWordDuration >> 8 & 255,
- lowerWordDuration & 255,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- // reserved
- 0,
- 0,
- // layer
- 0,
- 0,
- // alternate_group
- 0,
- 0,
- // non-audio track volume
- 0,
- 0,
- // reserved
- 0,
- 1,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 1,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 64,
- 0,
- 0,
- 0,
- // transformation: unity matrix
- width >> 8 & 255,
- width & 255,
- 0,
- 0,
- // width
- height >> 8 & 255,
- height & 255,
- 0,
- 0
- // height
- ]));
- }
- static traf(track, baseMediaDecodeTime) {
- const sampleDependencyTable = _MP4.sdtp(track);
- const id = track.id;
- const upperWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime / (UINT32_MAX + 1));
- const lowerWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime % (UINT32_MAX + 1));
- return _MP4.box(
- _MP4.types.traf,
- _MP4.box(_MP4.types.tfhd, new Uint8Array([
- 0,
- // version 0
- 0,
- 0,
- 0,
- // flags
- id >> 24,
- id >> 16 & 255,
- id >> 8 & 255,
- id & 255
- // track_ID
- ])),
- _MP4.box(_MP4.types.tfdt, new Uint8Array([
- 1,
- // version 1
- 0,
- 0,
- 0,
- // flags
- upperWordBaseMediaDecodeTime >> 24,
- upperWordBaseMediaDecodeTime >> 16 & 255,
- upperWordBaseMediaDecodeTime >> 8 & 255,
- upperWordBaseMediaDecodeTime & 255,
- lowerWordBaseMediaDecodeTime >> 24,
- lowerWordBaseMediaDecodeTime >> 16 & 255,
- lowerWordBaseMediaDecodeTime >> 8 & 255,
- lowerWordBaseMediaDecodeTime & 255
- ])),
- _MP4.trun(track, sampleDependencyTable.length + 16 + // tfhd
- 20 + // tfdt
- 8 + // traf header
- 16 + // mfhd
- 8 + // moof header
- 8),
- // mdat header
- sampleDependencyTable
- );
- }
- /**
- * Generate a track box.
- * @param track a track definition
- */
- static trak(track) {
- track.duration = track.duration || 4294967295;
- return _MP4.box(_MP4.types.trak, _MP4.tkhd(track), _MP4.mdia(track));
- }
- static trex(track) {
- const id = track.id;
- return _MP4.box(_MP4.types.trex, new Uint8Array([
- 0,
- // version 0
- 0,
- 0,
- 0,
- // flags
- id >> 24,
- id >> 16 & 255,
- id >> 8 & 255,
- id & 255,
- // track_ID
- 0,
- 0,
- 0,
- 1,
- // default_sample_description_index
- 0,
- 0,
- 0,
- 0,
- // default_sample_duration
- 0,
- 0,
- 0,
- 0,
- // default_sample_size
- 0,
- 1,
- 0,
- 1
- // default_sample_flags
- ]));
- }
- static trun(track, offset) {
- const samples = track.samples || [];
- const len = samples.length;
- const arraylen = 12 + 16 * len;
- const array = new Uint8Array(arraylen);
- let i;
- let sample;
- let duration;
- let size;
- let flags;
- let cts;
- offset += 8 + arraylen;
- array.set([
- track.type === "video" ? 1 : 0,
- // version 1 for video with signed-int sample_composition_time_offset
- 0,
- 15,
- 1,
- // flags
- len >>> 24 & 255,
- len >>> 16 & 255,
- len >>> 8 & 255,
- len & 255,
- // sample_count
- offset >>> 24 & 255,
- offset >>> 16 & 255,
- offset >>> 8 & 255,
- offset & 255
- // data_offset
- ], 0);
- for (i = 0; i < len; i++) {
- sample = samples[i];
- duration = sample.duration;
- size = sample.size;
- flags = sample.flags;
- cts = sample.cts;
- array.set([
- duration >>> 24 & 255,
- duration >>> 16 & 255,
- duration >>> 8 & 255,
- duration & 255,
- // sample_duration
- size >>> 24 & 255,
- size >>> 16 & 255,
- size >>> 8 & 255,
- size & 255,
- // sample_size
- flags.isLeading << 2 | flags.dependsOn,
- flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync,
- flags.degradPrio & 240 << 8,
- flags.degradPrio & 15,
- // sample_flags
- cts >>> 24 & 255,
- cts >>> 16 & 255,
- cts >>> 8 & 255,
- cts & 255
- // sample_composition_time_offset
- ], 12 + 16 * i);
- }
- return _MP4.box(_MP4.types.trun, array);
- }
- static initSegment(tracks) {
- if (!_MP4.types) {
- _MP4.init();
- }
- const movie = _MP4.moov(tracks);
- const result = appendUint8Array(_MP4.FTYP, movie);
- return result;
- }
- static hvc1(track) {
- const ps = track.params;
- const units = [track.vps, track.sps, track.pps];
- const NALuLengthSize = 4;
- const config = new Uint8Array([1, ps.general_profile_space << 6 | (ps.general_tier_flag ? 32 : 0) | ps.general_profile_idc, ps.general_profile_compatibility_flags[0], ps.general_profile_compatibility_flags[1], ps.general_profile_compatibility_flags[2], ps.general_profile_compatibility_flags[3], ps.general_constraint_indicator_flags[0], ps.general_constraint_indicator_flags[1], ps.general_constraint_indicator_flags[2], ps.general_constraint_indicator_flags[3], ps.general_constraint_indicator_flags[4], ps.general_constraint_indicator_flags[5], ps.general_level_idc, 240 | ps.min_spatial_segmentation_idc >> 8, 255 & ps.min_spatial_segmentation_idc, 252 | ps.parallelismType, 252 | ps.chroma_format_idc, 248 | ps.bit_depth_luma_minus8, 248 | ps.bit_depth_chroma_minus8, 0, parseInt(ps.frame_rate.fps), NALuLengthSize - 1 | ps.temporal_id_nested << 2 | ps.num_temporal_layers << 3 | (ps.frame_rate.fixed ? 64 : 0), units.length]);
- let length = config.length;
- for (let i = 0; i < units.length; i += 1) {
- length += 3;
- for (let j = 0; j < units[i].length; j += 1) {
- length += 2 + units[i][j].length;
- }
- }
- const hvcC = new Uint8Array(length);
- hvcC.set(config, 0);
- length = config.length;
- const iMax = units.length - 1;
- for (let i = 0; i < units.length; i += 1) {
- hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0, units[i].length]), length);
- length += 3;
- for (let j = 0; j < units[i].length; j += 1) {
- hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
- length += 2;
- hvcC.set(units[i][j], length);
- length += units[i][j].length;
- }
- }
- const hvcc = _MP4.box(_MP4.types.hvcC, hvcC);
- const width = track.width;
- const height = track.height;
- const hSpacing = track.pixelRatio[0];
- const vSpacing = track.pixelRatio[1];
- return _MP4.box(
- _MP4.types.hvc1,
- new Uint8Array([
- 0,
- 0,
- 0,
- // reserved
- 0,
- 0,
- 0,
- // reserved
- 0,
- 1,
- // data_reference_index
- 0,
- 0,
- // pre_defined
- 0,
- 0,
- // reserved
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- // pre_defined
- width >> 8 & 255,
- width & 255,
- // width
- height >> 8 & 255,
- height & 255,
- // height
- 0,
- 72,
- 0,
- 0,
- // horizresolution
- 0,
- 72,
- 0,
- 0,
- // vertresolution
- 0,
- 0,
- 0,
- 0,
- // reserved
- 0,
- 1,
- // frame_count
- 18,
- 100,
- 97,
- 105,
- 108,
- // dailymotion/hls.js
- 121,
- 109,
- 111,
- 116,
- 105,
- 111,
- 110,
- 47,
- 104,
- 108,
- 115,
- 46,
- 106,
- 115,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- // compressorname
- 0,
- 24,
- // depth = 24
- 17,
- 17
- ]),
- // pre_defined = -1
- hvcc,
- _MP4.box(_MP4.types.btrt, new Uint8Array([
- 0,
- 28,
- 156,
- 128,
- // bufferSizeDB
- 0,
- 45,
- 198,
- 192,
- // maxBitrate
- 0,
- 45,
- 198,
- 192
- ])),
- // avgBitrate
- _MP4.box(_MP4.types.pasp, new Uint8Array([
- hSpacing >> 24,
- // hSpacing
- hSpacing >> 16 & 255,
- hSpacing >> 8 & 255,
- hSpacing & 255,
- vSpacing >> 24,
- // vSpacing
- vSpacing >> 16 & 255,
- vSpacing >> 8 & 255,
- vSpacing & 255
- ]))
- );
- }
- };
- MP4.types = void 0;
- MP4.HDLR_TYPES = void 0;
- MP4.STTS = void 0;
- MP4.STSC = void 0;
- MP4.STCO = void 0;
- MP4.STSZ = void 0;
- MP4.VMHD = void 0;
- MP4.SMHD = void 0;
- MP4.STSD = void 0;
- MP4.FTYP = void 0;
- MP4.DINF = void 0;
- var MPEG_TS_CLOCK_FREQ_HZ = 9e4;
- function toTimescaleFromBase(baseTime, destScale, srcBase = 1, round = false) {
- const result = baseTime * destScale * srcBase;
- return round ? Math.round(result) : result;
- }
- function toTimescaleFromScale(baseTime, destScale, srcScale = 1, round = false) {
- return toTimescaleFromBase(baseTime, destScale, 1 / srcScale, round);
- }
- function toMsFromMpegTsClock(baseTime, round = false) {
- return toTimescaleFromBase(baseTime, 1e3, 1 / MPEG_TS_CLOCK_FREQ_HZ, round);
- }
- function toMpegTsClockFromTimescale(baseTime, srcScale = 1) {
- return toTimescaleFromBase(baseTime, MPEG_TS_CLOCK_FREQ_HZ, 1 / srcScale);
- }
- function timestampToString(timestamp) {
- const {
- baseTime,
- timescale,
- trackId
- } = timestamp;
- return `${baseTime / timescale} (${baseTime}/${timescale}) trackId: ${trackId}`;
- }
- var MAX_SILENT_FRAME_DURATION = 10 * 1e3;
- var AAC_SAMPLES_PER_FRAME = 1024;
- var MPEG_AUDIO_SAMPLE_PER_FRAME = 1152;
- var AC3_SAMPLES_PER_FRAME = 1536;
- var chromeVersion = null;
- var safariWebkitVersion = null;
- function createMp4Sample(isKeyframe, duration, size, cts) {
- return {
- duration,
- size,
- cts,
- flags: {
- isLeading: 0,
- isDependedOn: 0,
- hasRedundancy: 0,
- degradPrio: 0,
- dependsOn: isKeyframe ? 2 : 1,
- isNonSync: isKeyframe ? 0 : 1
- }
- };
- }
- var MP4Remuxer = class extends Logger {
- constructor(observer, config, typeSupported, logger2) {
- super("mp4-remuxer", logger2);
- this.observer = void 0;
- this.config = void 0;
- this.typeSupported = void 0;
- this.ISGenerated = false;
- this._initPTS = null;
- this._initDTS = null;
- this.nextVideoTs = null;
- this.nextAudioTs = null;
- this.videoSampleDuration = null;
- this.isAudioContiguous = false;
- this.isVideoContiguous = false;
- this.videoTrackConfig = void 0;
- this.observer = observer;
- this.config = config;
- this.typeSupported = typeSupported;
- this.ISGenerated = false;
- if (chromeVersion === null) {
- const userAgent = navigator.userAgent || "";
- const result = userAgent.match(/Chrome\/(\d+)/i);
- chromeVersion = result ? parseInt(result[1]) : 0;
- }
- if (safariWebkitVersion === null) {
- const result = navigator.userAgent.match(/Safari\/(\d+)/i);
- safariWebkitVersion = result ? parseInt(result[1]) : 0;
- }
- }
- destroy() {
- this.config = this.videoTrackConfig = this._initPTS = this._initDTS = null;
- }
- resetTimeStamp(defaultTimeStamp) {
- const initPTS = this._initPTS;
- if (!initPTS || !defaultTimeStamp || defaultTimeStamp.trackId !== initPTS.trackId || defaultTimeStamp.baseTime !== initPTS.baseTime || defaultTimeStamp.timescale !== initPTS.timescale) {
- this.log(`Reset initPTS: ${initPTS ? timestampToString(initPTS) : initPTS} > ${defaultTimeStamp ? timestampToString(defaultTimeStamp) : defaultTimeStamp}`);
- }
- this._initPTS = this._initDTS = defaultTimeStamp;
- }
- resetNextTimestamp() {
- this.log("reset next timestamp");
- this.isVideoContiguous = false;
- this.isAudioContiguous = false;
- }
- resetInitSegment() {
- this.log("ISGenerated flag reset");
- this.ISGenerated = false;
- this.videoTrackConfig = void 0;
- }
- getVideoStartPts(videoSamples) {
- let rolloverDetected = false;
- const firstPts = videoSamples[0].pts;
- const startPTS = videoSamples.reduce((minPTS, sample) => {
- let pts = sample.pts;
- let delta = pts - minPTS;
- if (delta < -4294967296) {
- rolloverDetected = true;
- pts = normalizePts(pts, firstPts);
- delta = pts - minPTS;
- }
- if (delta > 0) {
- return minPTS;
- }
- return pts;
- }, firstPts);
- if (rolloverDetected) {
- this.debug("PTS rollover detected");
- }
- return startPTS;
- }
- remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, flush, playlistType) {
- let video;
- let audio;
- let initSegment;
- let text;
- let id3;
- let independent;
- let audioTimeOffset = timeOffset;
- let videoTimeOffset = timeOffset;
- const hasAudio = audioTrack.pid > -1;
- const hasVideo = videoTrack.pid > -1;
- const length = videoTrack.samples.length;
- const enoughAudioSamples = audioTrack.samples.length > 0;
- const enoughVideoSamples = flush && length > 0 || length > 1;
- const canRemuxAvc = (!hasAudio || enoughAudioSamples) && (!hasVideo || enoughVideoSamples) || this.ISGenerated || flush;
- if (canRemuxAvc) {
- if (this.ISGenerated) {
- var _videoTrack$pixelRati, _config$pixelRatio, _videoTrack$pixelRati2, _config$pixelRatio2;
- const config = this.videoTrackConfig;
- if (config && (videoTrack.width !== config.width || videoTrack.height !== config.height || ((_videoTrack$pixelRati = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati[0]) !== ((_config$pixelRatio = config.pixelRatio) == null ? void 0 : _config$pixelRatio[0]) || ((_videoTrack$pixelRati2 = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati2[1]) !== ((_config$pixelRatio2 = config.pixelRatio) == null ? void 0 : _config$pixelRatio2[1])) || !config && enoughVideoSamples || this.nextAudioTs === null && enoughAudioSamples) {
- this.resetInitSegment();
- }
- }
- if (!this.ISGenerated) {
- initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
- }
- const isVideoContiguous = this.isVideoContiguous;
- let firstKeyFrameIndex = -1;
- let firstKeyFramePTS;
- if (enoughVideoSamples) {
- firstKeyFrameIndex = findKeyframeIndex(videoTrack.samples);
- if (!isVideoContiguous && this.config.forceKeyFrameOnDiscontinuity) {
- independent = true;
- if (firstKeyFrameIndex > 0) {
- this.warn(`Dropped ${firstKeyFrameIndex} out of ${length} video samples due to a missing keyframe`);
- const startPTS = this.getVideoStartPts(videoTrack.samples);
- videoTrack.samples = videoTrack.samples.slice(firstKeyFrameIndex);
- videoTrack.dropped += firstKeyFrameIndex;
- videoTimeOffset += (videoTrack.samples[0].pts - startPTS) / videoTrack.inputTimeScale;
- firstKeyFramePTS = videoTimeOffset;
- } else if (firstKeyFrameIndex === -1) {
- this.warn(`No keyframe found out of ${length} video samples`);
- independent = false;
- }
- }
- }
- if (this.ISGenerated) {
- if (enoughAudioSamples && enoughVideoSamples) {
- const startPTS = this.getVideoStartPts(videoTrack.samples);
- const tsDelta = normalizePts(audioTrack.samples[0].pts, startPTS) - startPTS;
- const audiovideoTimestampDelta = tsDelta / videoTrack.inputTimeScale;
- audioTimeOffset += Math.max(0, audiovideoTimestampDelta);
- videoTimeOffset += Math.max(0, -audiovideoTimestampDelta);
- }
- if (enoughAudioSamples) {
- if (!audioTrack.samplerate) {
- this.warn("regenerate InitSegment as audio detected");
- initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
- }
- audio = this.remuxAudio(audioTrack, audioTimeOffset, this.isAudioContiguous, accurateTimeOffset, hasVideo || enoughVideoSamples || playlistType === PlaylistLevelType.AUDIO ? videoTimeOffset : void 0);
- if (enoughVideoSamples) {
- const audioTrackLength = audio ? audio.endPTS - audio.startPTS : 0;
- if (!videoTrack.inputTimeScale) {
- this.warn("regenerate InitSegment as video detected");
- initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
- }
- video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, audioTrackLength);
- }
- } else if (enoughVideoSamples) {
- video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, 0);
- }
- if (video) {
- video.firstKeyFrame = firstKeyFrameIndex;
- video.independent = firstKeyFrameIndex !== -1;
- video.firstKeyFramePTS = firstKeyFramePTS;
- }
- }
- }
- if (this.ISGenerated && this._initPTS && this._initDTS) {
- if (id3Track.samples.length) {
- id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, this._initPTS, this._initDTS);
- }
- if (textTrack.samples.length) {
- text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, this._initPTS);
- }
- }
- return {
- audio,
- video,
- initSegment,
- independent,
- text,
- id3
- };
- }
- computeInitPts(basetime, timescale, presentationTime, type) {
- const offset = Math.round(presentationTime * timescale);
- let timestamp = normalizePts(basetime, offset);
- if (timestamp < offset + timescale) {
- this.log(`Adjusting PTS for rollover in timeline near ${(offset - timestamp) / timescale} ${type}`);
- while (timestamp < offset + timescale) {
- timestamp += 8589934592;
- }
- }
- return timestamp - offset;
- }
- generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset) {
- const audioSamples = audioTrack.samples;
- const videoSamples = videoTrack.samples;
- const typeSupported = this.typeSupported;
- const tracks = {};
- const _initPTS = this._initPTS;
- let computePTSDTS = !_initPTS || accurateTimeOffset;
- let container = "audio/mp4";
- let initPTS;
- let initDTS;
- let timescale;
- let trackId = -1;
- if (computePTSDTS) {
- initPTS = initDTS = Infinity;
- }
- if (audioTrack.config && audioSamples.length) {
- audioTrack.timescale = audioTrack.samplerate;
- switch (audioTrack.segmentCodec) {
- case "mp3":
- if (typeSupported.mpeg) {
- container = "audio/mpeg";
- audioTrack.codec = "";
- } else if (typeSupported.mp3) {
- audioTrack.codec = "mp3";
- }
- break;
- case "ac3":
- audioTrack.codec = "ac-3";
- break;
- }
- tracks.audio = {
- id: "audio",
- container,
- codec: audioTrack.codec,
- initSegment: audioTrack.segmentCodec === "mp3" && typeSupported.mpeg ? new Uint8Array(0) : MP4.initSegment([audioTrack]),
- metadata: {
- channelCount: audioTrack.channelCount
- }
- };
- if (computePTSDTS) {
- trackId = audioTrack.id;
- timescale = audioTrack.inputTimeScale;
- if (!_initPTS || timescale !== _initPTS.timescale) {
- initPTS = initDTS = this.computeInitPts(audioSamples[0].pts, timescale, timeOffset, "audio");
- } else {
- computePTSDTS = false;
- }
- }
- }
- if (videoTrack.sps && videoTrack.pps && videoSamples.length) {
- videoTrack.timescale = videoTrack.inputTimeScale;
- tracks.video = {
- id: "main",
- container: "video/mp4",
- codec: videoTrack.codec,
- initSegment: MP4.initSegment([videoTrack]),
- metadata: {
- width: videoTrack.width,
- height: videoTrack.height
- }
- };
- if (computePTSDTS) {
- trackId = videoTrack.id;
- timescale = videoTrack.inputTimeScale;
- if (!_initPTS || timescale !== _initPTS.timescale) {
- const basePTS = this.getVideoStartPts(videoSamples);
- const baseDTS = normalizePts(videoSamples[0].dts, basePTS);
- const videoInitDTS = this.computeInitPts(baseDTS, timescale, timeOffset, "video");
- const videoInitPTS = this.computeInitPts(basePTS, timescale, timeOffset, "video");
- initDTS = Math.min(initDTS, videoInitDTS);
- initPTS = Math.min(initPTS, videoInitPTS);
- } else {
- computePTSDTS = false;
- }
- }
- this.videoTrackConfig = {
- width: videoTrack.width,
- height: videoTrack.height,
- pixelRatio: videoTrack.pixelRatio
- };
- }
- if (Object.keys(tracks).length) {
- this.ISGenerated = true;
- if (computePTSDTS) {
- if (_initPTS) {
- this.warn(`Timestamps at playlist time: ${accurateTimeOffset ? "" : "~"}${timeOffset} ${initPTS / timescale} != initPTS: ${_initPTS.baseTime / _initPTS.timescale} (${_initPTS.baseTime}/${_initPTS.timescale}) trackId: ${_initPTS.trackId}`);
- }
- this.log(`Found initPTS at playlist time: ${timeOffset} offset: ${initPTS / timescale} (${initPTS}/${timescale}) trackId: ${trackId}`);
- this._initPTS = {
- baseTime: initPTS,
- timescale,
- trackId
- };
- this._initDTS = {
- baseTime: initDTS,
- timescale,
- trackId
- };
- } else {
- initPTS = timescale = void 0;
- }
- return {
- tracks,
- initPTS,
- timescale,
- trackId
- };
- }
- }
- remuxVideo(track, timeOffset, contiguous, audioTrackLength) {
- const timeScale = track.inputTimeScale;
- const inputSamples = track.samples;
- const outputSamples = [];
- const nbSamples = inputSamples.length;
- const initPTS = this._initPTS;
- const initTime = initPTS.baseTime * timeScale / initPTS.timescale;
- let nextVideoTs = this.nextVideoTs;
- let offset = 8;
- let mp4SampleDuration = this.videoSampleDuration;
- let firstDTS;
- let lastDTS;
- let minPTS = Number.POSITIVE_INFINITY;
- let maxPTS = Number.NEGATIVE_INFINITY;
- let sortSamples = false;
- if (!contiguous || nextVideoTs === null) {
- const pts = initTime + timeOffset * timeScale;
- const cts = inputSamples[0].pts - normalizePts(inputSamples[0].dts, inputSamples[0].pts);
- if (chromeVersion && nextVideoTs !== null && Math.abs(pts - cts - (nextVideoTs + initTime)) < 15e3) {
- contiguous = true;
- } else {
- nextVideoTs = pts - cts - initTime;
- }
- }
- const nextVideoPts = nextVideoTs + initTime;
- for (let i = 0; i < nbSamples; i++) {
- const sample = inputSamples[i];
- sample.pts = normalizePts(sample.pts, nextVideoPts);
- sample.dts = normalizePts(sample.dts, nextVideoPts);
- if (sample.dts < inputSamples[i > 0 ? i - 1 : i].dts) {
- sortSamples = true;
- }
- }
- if (sortSamples) {
- inputSamples.sort(function(a, b) {
- const deltadts = a.dts - b.dts;
- const deltapts = a.pts - b.pts;
- return deltadts || deltapts;
- });
- }
- firstDTS = inputSamples[0].dts;
- lastDTS = inputSamples[inputSamples.length - 1].dts;
- const inputDuration = lastDTS - firstDTS;
- const averageSampleDuration = inputDuration ? Math.round(inputDuration / (nbSamples - 1)) : mp4SampleDuration || track.inputTimeScale / 30;
- if (contiguous) {
- const delta = firstDTS - nextVideoPts;
- const foundHole = delta > averageSampleDuration;
- const foundOverlap = delta < -1;
- if (foundHole || foundOverlap) {
- if (foundHole) {
- this.warn(`${(track.segmentCodec || "").toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
- } else {
- this.warn(`${(track.segmentCodec || "").toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
- }
- if (!foundOverlap || nextVideoPts >= inputSamples[0].pts || chromeVersion) {
- firstDTS = nextVideoPts;
- const firstPTS = inputSamples[0].pts - delta;
- if (foundHole) {
- inputSamples[0].dts = firstDTS;
- inputSamples[0].pts = firstPTS;
- } else {
- let isPTSOrderRetained = true;
- for (let i = 0; i < inputSamples.length; i++) {
- if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
- break;
- }
- const prevPTS = inputSamples[i].pts;
- inputSamples[i].dts -= delta;
- inputSamples[i].pts -= delta;
- if (i < inputSamples.length - 1) {
- const nextSamplePTS = inputSamples[i + 1].pts;
- const currentSamplePTS = inputSamples[i].pts;
- const currentOrder = nextSamplePTS <= currentSamplePTS;
- const prevOrder = nextSamplePTS <= prevPTS;
- isPTSOrderRetained = currentOrder == prevOrder;
- }
- }
- }
- this.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
- }
- }
- }
- firstDTS = Math.max(0, firstDTS);
- let nbNalu = 0;
- let naluLen = 0;
- let dtsStep = firstDTS;
- for (let i = 0; i < nbSamples; i++) {
- const sample = inputSamples[i];
- const units = sample.units;
- const nbUnits = units.length;
- let sampleLen = 0;
- for (let j = 0; j < nbUnits; j++) {
- sampleLen += units[j].data.length;
- }
- naluLen += sampleLen;
- nbNalu += nbUnits;
- sample.length = sampleLen;
- if (sample.dts < dtsStep) {
- sample.dts = dtsStep;
- dtsStep += averageSampleDuration / 4 | 0 || 1;
- } else {
- dtsStep = sample.dts;
- }
- minPTS = Math.min(sample.pts, minPTS);
- maxPTS = Math.max(sample.pts, maxPTS);
- }
- lastDTS = inputSamples[nbSamples - 1].dts;
- const mdatSize = naluLen + 4 * nbNalu + 8;
- let mdat;
- try {
- mdat = new Uint8Array(mdatSize);
- } catch (err) {
- this.observer.emit(Events.ERROR, Events.ERROR, {
- type: ErrorTypes.MUX_ERROR,
- details: ErrorDetails.REMUX_ALLOC_ERROR,
- fatal: false,
- error: err,
- bytes: mdatSize,
- reason: `fail allocating video mdat ${mdatSize}`
- });
- return;
- }
- const view2 = new DataView(mdat.buffer);
- view2.setUint32(0, mdatSize);
- mdat.set(MP4.types.mdat, 4);
- let stretchedLastFrame = false;
- let minDtsDelta = Number.POSITIVE_INFINITY;
- let minPtsDelta = Number.POSITIVE_INFINITY;
- let maxDtsDelta = Number.NEGATIVE_INFINITY;
- let maxPtsDelta = Number.NEGATIVE_INFINITY;
- for (let i = 0; i < nbSamples; i++) {
- const VideoSample = inputSamples[i];
- const VideoSampleUnits = VideoSample.units;
- let mp4SampleLength = 0;
- for (let j = 0, nbUnits = VideoSampleUnits.length; j < nbUnits; j++) {
- const unit = VideoSampleUnits[j];
- const unitData = unit.data;
- const unitDataLen = unit.data.byteLength;
- view2.setUint32(offset, unitDataLen);
- offset += 4;
- mdat.set(unitData, offset);
- offset += unitDataLen;
- mp4SampleLength += 4 + unitDataLen;
- }
- let ptsDelta;
- if (i < nbSamples - 1) {
- mp4SampleDuration = inputSamples[i + 1].dts - VideoSample.dts;
- ptsDelta = inputSamples[i + 1].pts - VideoSample.pts;
- } else {
- const config = this.config;
- const lastFrameDuration = i > 0 ? VideoSample.dts - inputSamples[i - 1].dts : averageSampleDuration;
- ptsDelta = i > 0 ? VideoSample.pts - inputSamples[i - 1].pts : averageSampleDuration;
- if (config.stretchShortVideoTrack && this.nextAudioTs !== null) {
- const gapTolerance = Math.floor(config.maxBufferHole * timeScale);
- const deltaToFrameEnd = (audioTrackLength ? minPTS + audioTrackLength * timeScale : this.nextAudioTs + initTime) - VideoSample.pts;
- if (deltaToFrameEnd > gapTolerance) {
- mp4SampleDuration = deltaToFrameEnd - lastFrameDuration;
- if (mp4SampleDuration < 0) {
- mp4SampleDuration = lastFrameDuration;
- } else {
- stretchedLastFrame = true;
- }
- this.log(`It is approximately ${deltaToFrameEnd / 90} ms to the next segment; using duration ${mp4SampleDuration / 90} ms for the last video frame.`);
- } else {
- mp4SampleDuration = lastFrameDuration;
- }
- } else {
- mp4SampleDuration = lastFrameDuration;
- }
- }
- const compositionTimeOffset = Math.round(VideoSample.pts - VideoSample.dts);
- minDtsDelta = Math.min(minDtsDelta, mp4SampleDuration);
- maxDtsDelta = Math.max(maxDtsDelta, mp4SampleDuration);
- minPtsDelta = Math.min(minPtsDelta, ptsDelta);
- maxPtsDelta = Math.max(maxPtsDelta, ptsDelta);
- outputSamples.push(createMp4Sample(VideoSample.key, mp4SampleDuration, mp4SampleLength, compositionTimeOffset));
- }
- if (outputSamples.length) {
- if (chromeVersion) {
- if (chromeVersion < 70) {
- const flags = outputSamples[0].flags;
- flags.dependsOn = 2;
- flags.isNonSync = 0;
- }
- } else if (safariWebkitVersion) {
- if (maxPtsDelta - minPtsDelta < maxDtsDelta - minDtsDelta && averageSampleDuration / maxDtsDelta < 0.025 && outputSamples[0].cts === 0) {
- this.warn("Found irregular gaps in sample duration. Using PTS instead of DTS to determine MP4 sample duration.");
- let dts = firstDTS;
- for (let i = 0, len = outputSamples.length; i < len; i++) {
- const nextDts = dts + outputSamples[i].duration;
- const pts = dts + outputSamples[i].cts;
- if (i < len - 1) {
- const nextPts = nextDts + outputSamples[i + 1].cts;
- outputSamples[i].duration = nextPts - pts;
- } else {
- outputSamples[i].duration = i ? outputSamples[i - 1].duration : averageSampleDuration;
- }
- outputSamples[i].cts = 0;
- dts = nextDts;
- }
- }
- }
- }
- mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
- const endDTS = lastDTS + mp4SampleDuration;
- this.nextVideoTs = nextVideoTs = endDTS - initTime;
- this.videoSampleDuration = mp4SampleDuration;
- this.isVideoContiguous = true;
- const moof = MP4.moof(track.sequenceNumber++, firstDTS, _extends(track, {
- samples: outputSamples
- }));
- const type = "video";
- const data = {
- data1: moof,
- data2: mdat,
- startPTS: (minPTS - initTime) / timeScale,
- endPTS: (maxPTS + mp4SampleDuration - initTime) / timeScale,
- startDTS: (firstDTS - initTime) / timeScale,
- endDTS: nextVideoTs / timeScale,
- type,
- hasAudio: false,
- hasVideo: true,
- nb: outputSamples.length,
- dropped: track.dropped
- };
- track.samples = [];
- track.dropped = 0;
- return data;
- }
- getSamplesPerFrame(track) {
- switch (track.segmentCodec) {
- case "mp3":
- return MPEG_AUDIO_SAMPLE_PER_FRAME;
- case "ac3":
- return AC3_SAMPLES_PER_FRAME;
- default:
- return AAC_SAMPLES_PER_FRAME;
- }
- }
- remuxAudio(track, timeOffset, contiguous, accurateTimeOffset, videoTimeOffset) {
- const inputTimeScale = track.inputTimeScale;
- const mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale;
- const scaleFactor = inputTimeScale / mp4timeScale;
- const mp4SampleDuration = this.getSamplesPerFrame(track);
- const inputSampleDuration = mp4SampleDuration * scaleFactor;
- const initPTS = this._initPTS;
- const rawMPEG = track.segmentCodec === "mp3" && this.typeSupported.mpeg;
- const outputSamples = [];
- const alignedWithVideo = videoTimeOffset !== void 0;
- let inputSamples = track.samples;
- let offset = rawMPEG ? 0 : 8;
- let nextAudioTs = this.nextAudioTs || -1;
- const initTime = initPTS.baseTime * inputTimeScale / initPTS.timescale;
- const timeOffsetMpegTS = initTime + timeOffset * inputTimeScale;
- this.isAudioContiguous = contiguous = contiguous || inputSamples.length && nextAudioTs > 0 && (accurateTimeOffset && Math.abs(timeOffsetMpegTS - (nextAudioTs + initTime)) < 9e3 || Math.abs(normalizePts(inputSamples[0].pts, timeOffsetMpegTS) - (nextAudioTs + initTime)) < 20 * inputSampleDuration);
- inputSamples.forEach(function(sample) {
- sample.pts = normalizePts(sample.pts, timeOffsetMpegTS);
- });
- if (!contiguous || nextAudioTs < 0) {
- const sampleCount = inputSamples.length;
- inputSamples = inputSamples.filter((sample) => sample.pts >= 0);
- if (sampleCount !== inputSamples.length) {
- this.warn(`Removed ${inputSamples.length - sampleCount} of ${sampleCount} samples (initPTS ${initTime} / ${inputTimeScale})`);
- }
- if (!inputSamples.length) {
- return;
- }
- if (videoTimeOffset === 0) {
- nextAudioTs = 0;
- } else if (accurateTimeOffset && !alignedWithVideo) {
- nextAudioTs = Math.max(0, timeOffsetMpegTS - initTime);
- } else {
- nextAudioTs = inputSamples[0].pts - initTime;
- }
- }
- if (track.segmentCodec === "aac") {
- const maxAudioFramesDrift = this.config.maxAudioFramesDrift;
- for (let i = 0, nextPts = nextAudioTs + initTime; i < inputSamples.length; i++) {
- const sample = inputSamples[i];
- const pts = sample.pts;
- const delta = pts - nextPts;
- const duration = Math.abs(1e3 * delta / inputTimeScale);
- if (delta <= -maxAudioFramesDrift * inputSampleDuration && alignedWithVideo) {
- if (i === 0) {
- this.warn(`Audio frame @ ${(pts / inputTimeScale).toFixed(3)}s overlaps marker by ${Math.round(1e3 * delta / inputTimeScale)} ms.`);
- this.nextAudioTs = nextAudioTs = pts - initTime;
- nextPts = pts;
- }
- } else if (delta >= maxAudioFramesDrift * inputSampleDuration && duration < MAX_SILENT_FRAME_DURATION && alignedWithVideo) {
- let missing = Math.round(delta / inputSampleDuration);
- nextPts = pts - missing * inputSampleDuration;
- while (nextPts < 0 && missing && inputSampleDuration) {
- missing--;
- nextPts += inputSampleDuration;
- }
- if (i === 0) {
- this.nextAudioTs = nextAudioTs = nextPts - initTime;
- }
- this.warn(`Injecting ${missing} audio frames @ ${((nextPts - initTime) / inputTimeScale).toFixed(3)}s due to ${Math.round(1e3 * delta / inputTimeScale)} ms gap.`);
- for (let j = 0; j < missing; j++) {
- let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
- if (!fillFrame) {
- this.log("Unable to get silent frame for given audio codec; duplicating last frame instead.");
- fillFrame = sample.unit.subarray();
- }
- inputSamples.splice(i, 0, {
- unit: fillFrame,
- pts: nextPts
- });
- nextPts += inputSampleDuration;
- i++;
- }
- }
- sample.pts = nextPts;
- nextPts += inputSampleDuration;
- }
- }
- let firstPTS = null;
- let lastPTS = null;
- let mdat;
- let mdatSize = 0;
- let sampleLength = inputSamples.length;
- while (sampleLength--) {
- mdatSize += inputSamples[sampleLength].unit.byteLength;
- }
- for (let j = 0, _nbSamples = inputSamples.length; j < _nbSamples; j++) {
- const audioSample = inputSamples[j];
- const unit = audioSample.unit;
- let pts = audioSample.pts;
- if (lastPTS !== null) {
- const prevSample = outputSamples[j - 1];
- prevSample.duration = Math.round((pts - lastPTS) / scaleFactor);
- } else {
- if (contiguous && track.segmentCodec === "aac") {
- pts = nextAudioTs + initTime;
- }
- firstPTS = pts;
- if (mdatSize > 0) {
- mdatSize += offset;
- try {
- mdat = new Uint8Array(mdatSize);
- } catch (err) {
- this.observer.emit(Events.ERROR, Events.ERROR, {
- type: ErrorTypes.MUX_ERROR,
- details: ErrorDetails.REMUX_ALLOC_ERROR,
- fatal: false,
- error: err,
- bytes: mdatSize,
- reason: `fail allocating audio mdat ${mdatSize}`
- });
- return;
- }
- if (!rawMPEG) {
- const view2 = new DataView(mdat.buffer);
- view2.setUint32(0, mdatSize);
- mdat.set(MP4.types.mdat, 4);
- }
- } else {
- return;
- }
- }
- mdat.set(unit, offset);
- const unitLen = unit.byteLength;
- offset += unitLen;
- outputSamples.push(createMp4Sample(true, mp4SampleDuration, unitLen, 0));
- lastPTS = pts;
- }
- const nbSamples = outputSamples.length;
- if (!nbSamples) {
- return;
- }
- const lastSample = outputSamples[outputSamples.length - 1];
- nextAudioTs = lastPTS - initTime;
- this.nextAudioTs = nextAudioTs + scaleFactor * lastSample.duration;
- const moof = rawMPEG ? new Uint8Array(0) : MP4.moof(track.sequenceNumber++, firstPTS / scaleFactor, _extends({}, track, {
- samples: outputSamples
- }));
- track.samples = [];
- const start = (firstPTS - initTime) / inputTimeScale;
- const end = nextAudioTs / inputTimeScale;
- const type = "audio";
- const audioData = {
- data1: moof,
- data2: mdat,
- startPTS: start,
- endPTS: end,
- startDTS: start,
- endDTS: end,
- type,
- hasAudio: true,
- hasVideo: false,
- nb: nbSamples
- };
- this.isAudioContiguous = true;
- return audioData;
- }
- };
- function normalizePts(value, reference) {
- let offset;
- if (reference === null) {
- return value;
- }
- if (reference < value) {
- offset = -8589934592;
- } else {
- offset = 8589934592;
- }
- while (Math.abs(value - reference) > 4294967296) {
- value += offset;
- }
- return value;
- }
- function findKeyframeIndex(samples) {
- for (let i = 0; i < samples.length; i++) {
- if (samples[i].key) {
- return i;
- }
- }
- return -1;
- }
- function flushTextTrackMetadataCueSamples(track, timeOffset, initPTS, initDTS) {
- const length = track.samples.length;
- if (!length) {
- return;
- }
- const inputTimeScale = track.inputTimeScale;
- for (let index = 0; index < length; index++) {
- const sample = track.samples[index];
- sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
- sample.dts = normalizePts(sample.dts - initDTS.baseTime * inputTimeScale / initDTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
- }
- const samples = track.samples;
- track.samples = [];
- return {
- samples
- };
- }
- function flushTextTrackUserdataCueSamples(track, timeOffset, initPTS) {
- const length = track.samples.length;
- if (!length) {
- return;
- }
- const inputTimeScale = track.inputTimeScale;
- for (let index = 0; index < length; index++) {
- const sample = track.samples[index];
- sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
- }
- track.samples.sort((a, b) => a.pts - b.pts);
- const samples = track.samples;
- track.samples = [];
- return {
- samples
- };
- }
- var PassThroughRemuxer = class extends Logger {
- constructor(observer, config, typeSupported, logger2) {
- super("passthrough-remuxer", logger2);
- this.emitInitSegment = false;
- this.audioCodec = void 0;
- this.videoCodec = void 0;
- this.initData = void 0;
- this.initPTS = null;
- this.initTracks = void 0;
- this.lastEndTime = null;
- this.isVideoContiguous = false;
- }
- destroy() {
- }
- resetTimeStamp(defaultInitPTS) {
- this.lastEndTime = null;
- const initPTS = this.initPTS;
- if (initPTS && defaultInitPTS) {
- if (initPTS.baseTime === defaultInitPTS.baseTime && initPTS.timescale === defaultInitPTS.timescale) {
- return;
- }
- }
- this.initPTS = defaultInitPTS;
- }
- resetNextTimestamp() {
- this.isVideoContiguous = false;
- this.lastEndTime = null;
- }
- resetInitSegment(initSegment, audioCodec, videoCodec, decryptdata) {
- this.audioCodec = audioCodec;
- this.videoCodec = videoCodec;
- this.generateInitSegment(initSegment, decryptdata);
- this.emitInitSegment = true;
- }
- generateInitSegment(initSegment, decryptdata) {
- let {
- audioCodec,
- videoCodec
- } = this;
- if (!(initSegment != null && initSegment.byteLength)) {
- this.initTracks = void 0;
- this.initData = void 0;
- return;
- }
- const {
- audio,
- video
- } = this.initData = parseInitSegment(initSegment);
- if (decryptdata) {
- patchEncyptionData(initSegment, decryptdata);
- } else {
- const eitherTrack = audio || video;
- if (eitherTrack != null && eitherTrack.encrypted) {
- this.warn(`Init segment with encrypted track with has no key ("${eitherTrack.codec}")!`);
- }
- }
- if (audio) {
- audioCodec = getParsedTrackCodec(audio, ElementaryStreamTypes.AUDIO, this);
- }
- if (video) {
- videoCodec = getParsedTrackCodec(video, ElementaryStreamTypes.VIDEO, this);
- }
- const tracks = {};
- if (audio && video) {
- tracks.audiovideo = {
- container: "video/mp4",
- codec: audioCodec + "," + videoCodec,
- supplemental: video.supplemental,
- encrypted: video.encrypted,
- initSegment,
- id: "main"
- };
- } else if (audio) {
- tracks.audio = {
- container: "audio/mp4",
- codec: audioCodec,
- encrypted: audio.encrypted,
- initSegment,
- id: "audio"
- };
- } else if (video) {
- tracks.video = {
- container: "video/mp4",
- codec: videoCodec,
- supplemental: video.supplemental,
- encrypted: video.encrypted,
- initSegment,
- id: "main"
- };
- } else {
- this.warn("initSegment does not contain moov or trak boxes.");
- }
- this.initTracks = tracks;
- }
- remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset) {
- var _initData, _initData2;
- let {
- initPTS,
- lastEndTime
- } = this;
- const result = {
- audio: void 0,
- video: void 0,
- text: textTrack,
- id3: id3Track,
- initSegment: void 0
- };
- if (!isFiniteNumber(lastEndTime)) {
- lastEndTime = this.lastEndTime = timeOffset || 0;
- }
- const data = videoTrack.samples;
- if (!data.length) {
- return result;
- }
- const initSegment = {
- initPTS: void 0,
- timescale: void 0,
- trackId: void 0
- };
- let initData = this.initData;
- if (!((_initData = initData) != null && _initData.length)) {
- this.generateInitSegment(data);
- initData = this.initData;
- }
- if (!((_initData2 = initData) != null && _initData2.length)) {
- this.warn("Failed to generate initSegment.");
- return result;
- }
- if (this.emitInitSegment) {
- initSegment.tracks = this.initTracks;
- this.emitInitSegment = false;
- }
- const trackSampleData = getSampleData(data, initData, this);
- const audioSampleTimestamps = initData.audio ? trackSampleData[initData.audio.id] : null;
- const videoSampleTimestamps = initData.video ? trackSampleData[initData.video.id] : null;
- const videoStartTime = toStartEndOrDefault(videoSampleTimestamps, Infinity);
- const audioStartTime = toStartEndOrDefault(audioSampleTimestamps, Infinity);
- const videoEndTime = toStartEndOrDefault(videoSampleTimestamps, 0, true);
- const audioEndTime = toStartEndOrDefault(audioSampleTimestamps, 0, true);
- let decodeTime = timeOffset;
- let duration = 0;
- const syncOnAudio = audioSampleTimestamps && (!videoSampleTimestamps || !initPTS && audioStartTime < videoStartTime || initPTS && initPTS.trackId === initData.audio.id);
- const baseOffsetSamples = syncOnAudio ? audioSampleTimestamps : videoSampleTimestamps;
- if (baseOffsetSamples) {
- const timescale = baseOffsetSamples.timescale;
- const baseTime = baseOffsetSamples.start - timeOffset * timescale;
- const trackId = syncOnAudio ? initData.audio.id : initData.video.id;
- decodeTime = baseOffsetSamples.start / timescale;
- duration = syncOnAudio ? audioEndTime - audioStartTime : videoEndTime - videoStartTime;
- if ((accurateTimeOffset || !initPTS) && (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) || timescale !== initPTS.timescale)) {
- if (initPTS) {
- this.warn(`Timestamps at playlist time: ${accurateTimeOffset ? "" : "~"}${timeOffset} ${baseTime / timescale} != initPTS: ${initPTS.baseTime / initPTS.timescale} (${initPTS.baseTime}/${initPTS.timescale}) trackId: ${initPTS.trackId}`);
- }
- this.log(`Found initPTS at playlist time: ${timeOffset} offset: ${decodeTime - timeOffset} (${baseTime}/${timescale}) trackId: ${trackId}`);
- initPTS = null;
- initSegment.initPTS = baseTime;
- initSegment.timescale = timescale;
- initSegment.trackId = trackId;
- }
- } else {
- this.warn(`No audio or video samples found for initPTS at playlist time: ${timeOffset}`);
- }
- if (!initPTS) {
- if (!initSegment.timescale || initSegment.trackId === void 0 || initSegment.initPTS === void 0) {
- this.warn("Could not set initPTS");
- initSegment.initPTS = decodeTime;
- initSegment.timescale = 1;
- initSegment.trackId = -1;
- }
- this.initPTS = initPTS = {
- baseTime: initSegment.initPTS,
- timescale: initSegment.timescale,
- trackId: initSegment.trackId
- };
- } else {
- initSegment.initPTS = initPTS.baseTime;
- initSegment.timescale = initPTS.timescale;
- initSegment.trackId = initPTS.trackId;
- }
- const startTime = decodeTime - initPTS.baseTime / initPTS.timescale;
- const endTime = startTime + duration;
- if (duration > 0) {
- this.lastEndTime = endTime;
- } else {
- this.warn("Duration parsed from mp4 should be greater than zero");
- this.resetNextTimestamp();
- }
- const hasAudio = !!initData.audio;
- const hasVideo = !!initData.video;
- let type = "";
- if (hasAudio) {
- type += "audio";
- }
- if (hasVideo) {
- type += "video";
- }
- const encrypted = (initData.audio ? initData.audio.encrypted : false) || (initData.video ? initData.video.encrypted : false);
- const track = {
- data1: data,
- startPTS: startTime,
- startDTS: startTime,
- endPTS: endTime,
- endDTS: endTime,
- type,
- hasAudio,
- hasVideo,
- nb: 1,
- dropped: 0,
- encrypted
- };
- result.audio = hasAudio && !hasVideo ? track : void 0;
- result.video = hasVideo ? track : void 0;
- const videoSampleCount = videoSampleTimestamps == null ? void 0 : videoSampleTimestamps.sampleCount;
- if (videoSampleCount) {
- const firstKeyFrame = videoSampleTimestamps.keyFrameIndex;
- const independent = firstKeyFrame !== -1;
- track.nb = videoSampleCount;
- track.dropped = firstKeyFrame === 0 || this.isVideoContiguous ? 0 : independent ? firstKeyFrame : videoSampleCount;
- track.independent = independent;
- track.firstKeyFrame = firstKeyFrame;
- if (independent && videoSampleTimestamps.keyFrameStart) {
- track.firstKeyFramePTS = (videoSampleTimestamps.keyFrameStart - initPTS.baseTime) / initPTS.timescale;
- }
- if (!this.isVideoContiguous) {
- result.independent = independent;
- }
- this.isVideoContiguous || (this.isVideoContiguous = independent);
- if (track.dropped) {
- this.warn(`fmp4 does not start with IDR: firstIDR ${firstKeyFrame}/${videoSampleCount} dropped: ${track.dropped} start: ${track.firstKeyFramePTS || "NA"}`);
- }
- }
- result.initSegment = initSegment;
- result.id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, initPTS, initPTS);
- if (textTrack.samples.length) {
- result.text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, initPTS);
- }
- return result;
- }
- };
- function toStartEndOrDefault(trackTimes, defaultValue, end = false) {
- return (trackTimes == null ? void 0 : trackTimes.start) !== void 0 ? (trackTimes.start + (end ? trackTimes.duration : 0)) / trackTimes.timescale : defaultValue;
- }
- function isInvalidInitPts(initPTS, startDTS, timeOffset, duration) {
- if (initPTS === null) {
- return true;
- }
- const minDuration = Math.max(duration, 1);
- const startTime = startDTS - initPTS.baseTime / initPTS.timescale;
- return Math.abs(startTime - timeOffset) > minDuration;
- }
- function getParsedTrackCodec(track, type, logger2) {
- const parsedCodec = track.codec;
- if (parsedCodec && parsedCodec.length > 4) {
- return parsedCodec;
- }
- if (type === ElementaryStreamTypes.AUDIO) {
- if (parsedCodec === "ec-3" || parsedCodec === "ac-3" || parsedCodec === "alac") {
- return parsedCodec;
- }
- if (parsedCodec === "fLaC" || parsedCodec === "Opus") {
- const preferManagedMediaSource = false;
- return getCodecCompatibleName(parsedCodec, preferManagedMediaSource);
- }
- logger2.warn(`Unhandled audio codec "${parsedCodec}" in mp4 MAP`);
- return parsedCodec || "mp4a";
- }
- logger2.warn(`Unhandled video codec "${parsedCodec}" in mp4 MAP`);
- return parsedCodec || "avc1";
- }
- var now;
- try {
- now = self.performance.now.bind(self.performance);
- } catch (err) {
- now = Date.now;
- }
- var muxConfig = [{
- demux: MP4Demuxer,
- remux: PassThroughRemuxer
- }, {
- demux: TSDemuxer,
- remux: MP4Remuxer
- }, {
- demux: AACDemuxer,
- remux: MP4Remuxer
- }, {
- demux: MP3Demuxer,
- remux: MP4Remuxer
- }];
- {
- muxConfig.splice(2, 0, {
- demux: AC3Demuxer,
- remux: MP4Remuxer
- });
- }
- var Transmuxer = class {
- constructor(observer, typeSupported, config, vendor, id, logger2) {
- this.asyncResult = false;
- this.logger = void 0;
- this.observer = void 0;
- this.typeSupported = void 0;
- this.config = void 0;
- this.id = void 0;
- this.demuxer = void 0;
- this.remuxer = void 0;
- this.decrypter = void 0;
- this.probe = void 0;
- this.decryptionPromise = null;
- this.transmuxConfig = void 0;
- this.currentTransmuxState = void 0;
- this.observer = observer;
- this.typeSupported = typeSupported;
- this.config = config;
- this.id = id;
- this.logger = logger2;
- }
- configure(transmuxConfig) {
- this.transmuxConfig = transmuxConfig;
- if (this.decrypter) {
- this.decrypter.reset();
- }
- }
- push(data, decryptdata, chunkMeta, state) {
- const stats = chunkMeta.transmuxing;
- stats.executeStart = now();
- let uintData = new Uint8Array(data);
- const {
- currentTransmuxState,
- transmuxConfig
- } = this;
- if (state) {
- this.currentTransmuxState = state;
- }
- const {
- contiguous,
- discontinuity,
- trackSwitch,
- accurateTimeOffset,
- timeOffset,
- initSegmentChange
- } = state || currentTransmuxState;
- const {
- audioCodec,
- videoCodec,
- defaultInitPts,
- duration,
- initSegmentData
- } = transmuxConfig;
- const keyData = getEncryptionType(uintData, decryptdata);
- if (keyData && isFullSegmentEncryption(keyData.method)) {
- const decrypter = this.getDecrypter();
- const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
- if (decrypter.isSync()) {
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
- const loadingParts = chunkMeta.part > -1;
- if (loadingParts) {
- const _data = decrypter.flush();
- decryptedData = _data ? _data.buffer : _data;
- }
- if (!decryptedData) {
- stats.executeEnd = now();
- return emptyResult(chunkMeta);
- }
- uintData = new Uint8Array(decryptedData);
- } else {
- this.asyncResult = true;
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then((decryptedData) => {
- const result2 = this.push(decryptedData, null, chunkMeta);
- this.decryptionPromise = null;
- return result2;
- });
- return this.decryptionPromise;
- }
- }
- const resetMuxers = this.needsProbing(discontinuity, trackSwitch);
- if (resetMuxers) {
- const error = this.configureTransmuxer(uintData);
- if (error) {
- this.logger.warn(`[transmuxer] ${error.message}`);
- this.observer.emit(Events.ERROR, Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.FRAG_PARSING_ERROR,
- fatal: false,
- error,
- reason: error.message
- });
- stats.executeEnd = now();
- return emptyResult(chunkMeta);
- }
- }
- if (discontinuity || trackSwitch || initSegmentChange || resetMuxers) {
- this.resetInitSegment(initSegmentData, audioCodec, videoCodec, duration, decryptdata);
- }
- if (discontinuity || initSegmentChange || resetMuxers) {
- this.resetInitialTimestamp(defaultInitPts);
- }
- if (!contiguous) {
- this.resetContiguity();
- }
- const result = this.transmux(uintData, keyData, timeOffset, accurateTimeOffset, chunkMeta);
- this.asyncResult = isPromise(result);
- const currentState = this.currentTransmuxState;
- currentState.contiguous = true;
- currentState.discontinuity = false;
- currentState.trackSwitch = false;
- stats.executeEnd = now();
- return result;
- }
- // Due to data caching, flush calls can produce more than one TransmuxerResult (hence the Array type)
- flush(chunkMeta) {
- const stats = chunkMeta.transmuxing;
- stats.executeStart = now();
- const {
- decrypter,
- currentTransmuxState,
- decryptionPromise
- } = this;
- if (decryptionPromise) {
- this.asyncResult = true;
- return decryptionPromise.then(() => {
- return this.flush(chunkMeta);
- });
- }
- const transmuxResults = [];
- const {
- timeOffset
- } = currentTransmuxState;
- if (decrypter) {
- const decryptedData = decrypter.flush();
- if (decryptedData) {
- transmuxResults.push(this.push(decryptedData.buffer, null, chunkMeta));
- }
- }
- const {
- demuxer,
- remuxer
- } = this;
- if (!demuxer || !remuxer) {
- stats.executeEnd = now();
- const emptyResults = [emptyResult(chunkMeta)];
- if (this.asyncResult) {
- return Promise.resolve(emptyResults);
- }
- return emptyResults;
- }
- const demuxResultOrPromise = demuxer.flush(timeOffset);
- if (isPromise(demuxResultOrPromise)) {
- this.asyncResult = true;
- return demuxResultOrPromise.then((demuxResult) => {
- this.flushRemux(transmuxResults, demuxResult, chunkMeta);
- return transmuxResults;
- });
- }
- this.flushRemux(transmuxResults, demuxResultOrPromise, chunkMeta);
- if (this.asyncResult) {
- return Promise.resolve(transmuxResults);
- }
- return transmuxResults;
- }
- flushRemux(transmuxResults, demuxResult, chunkMeta) {
- const {
- audioTrack,
- videoTrack,
- id3Track,
- textTrack
- } = demuxResult;
- const {
- accurateTimeOffset,
- timeOffset
- } = this.currentTransmuxState;
- this.logger.log(`[transmuxer.ts]: Flushed ${this.id} sn: ${chunkMeta.sn}${chunkMeta.part > -1 ? " part: " + chunkMeta.part : ""} of ${this.id === PlaylistLevelType.MAIN ? "level" : "track"} ${chunkMeta.level}`);
- const remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, true, this.id);
- transmuxResults.push({
- remuxResult,
- chunkMeta
- });
- chunkMeta.transmuxing.executeEnd = now();
- }
- resetInitialTimestamp(defaultInitPts) {
- const {
- demuxer,
- remuxer
- } = this;
- if (!demuxer || !remuxer) {
- return;
- }
- demuxer.resetTimeStamp(defaultInitPts);
- remuxer.resetTimeStamp(defaultInitPts);
- }
- resetContiguity() {
- const {
- demuxer,
- remuxer
- } = this;
- if (!demuxer || !remuxer) {
- return;
- }
- demuxer.resetContiguity();
- remuxer.resetNextTimestamp();
- }
- resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration, decryptdata) {
- const {
- demuxer,
- remuxer
- } = this;
- if (!demuxer || !remuxer) {
- return;
- }
- demuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration);
- remuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, decryptdata);
- }
- destroy() {
- if (this.demuxer) {
- this.demuxer.destroy();
- this.demuxer = void 0;
- }
- if (this.remuxer) {
- this.remuxer.destroy();
- this.remuxer = void 0;
- }
- }
- transmux(data, keyData, timeOffset, accurateTimeOffset, chunkMeta) {
- let result;
- if (keyData && keyData.method === "SAMPLE-AES") {
- result = this.transmuxSampleAes(data, keyData, timeOffset, accurateTimeOffset, chunkMeta);
- } else {
- result = this.transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta);
- }
- return result;
- }
- transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta) {
- const {
- audioTrack,
- videoTrack,
- id3Track,
- textTrack
- } = this.demuxer.demux(data, timeOffset, false, !this.config.progressive);
- const remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, false, this.id);
- return {
- remuxResult,
- chunkMeta
- };
- }
- transmuxSampleAes(data, decryptData, timeOffset, accurateTimeOffset, chunkMeta) {
- return this.demuxer.demuxSampleAes(data, decryptData, timeOffset).then((demuxResult) => {
- const remuxResult = this.remuxer.remux(demuxResult.audioTrack, demuxResult.videoTrack, demuxResult.id3Track, demuxResult.textTrack, timeOffset, accurateTimeOffset, false, this.id);
- return {
- remuxResult,
- chunkMeta
- };
- });
- }
- configureTransmuxer(data) {
- const {
- config,
- observer,
- typeSupported
- } = this;
- let mux;
- for (let i = 0, len = muxConfig.length; i < len; i++) {
- var _muxConfig$i$demux;
- if ((_muxConfig$i$demux = muxConfig[i].demux) != null && _muxConfig$i$demux.probe(data, this.logger)) {
- mux = muxConfig[i];
- break;
- }
- }
- if (!mux) {
- return new Error("Failed to find demuxer by probing fragment data");
- }
- const demuxer = this.demuxer;
- const remuxer = this.remuxer;
- const Remuxer = mux.remux;
- const Demuxer = mux.demux;
- if (!remuxer || !(remuxer instanceof Remuxer)) {
- this.remuxer = new Remuxer(observer, config, typeSupported, this.logger);
- }
- if (!demuxer || !(demuxer instanceof Demuxer)) {
- this.demuxer = new Demuxer(observer, config, typeSupported, this.logger);
- this.probe = Demuxer.probe;
- }
- }
- needsProbing(discontinuity, trackSwitch) {
- return !this.demuxer || !this.remuxer || discontinuity || trackSwitch;
- }
- getDecrypter() {
- let decrypter = this.decrypter;
- if (!decrypter) {
- decrypter = this.decrypter = new Decrypter(this.config);
- }
- return decrypter;
- }
- };
- function getEncryptionType(data, decryptData) {
- let encryptionType = null;
- if (data.byteLength > 0 && (decryptData == null ? void 0 : decryptData.key) != null && decryptData.iv !== null && decryptData.method != null) {
- encryptionType = decryptData;
- }
- return encryptionType;
- }
- var emptyResult = (chunkMeta) => ({
- remuxResult: {},
- chunkMeta
- });
- function isPromise(p) {
- return "then" in p && p.then instanceof Function;
- }
- var TransmuxConfig = class {
- constructor(audioCodec, videoCodec, initSegmentData, duration, defaultInitPts) {
- this.audioCodec = void 0;
- this.videoCodec = void 0;
- this.initSegmentData = void 0;
- this.duration = void 0;
- this.defaultInitPts = void 0;
- this.audioCodec = audioCodec;
- this.videoCodec = videoCodec;
- this.initSegmentData = initSegmentData;
- this.duration = duration;
- this.defaultInitPts = defaultInitPts || null;
- }
- };
- var TransmuxState = class {
- constructor(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange) {
- this.discontinuity = void 0;
- this.contiguous = void 0;
- this.accurateTimeOffset = void 0;
- this.trackSwitch = void 0;
- this.timeOffset = void 0;
- this.initSegmentChange = void 0;
- this.discontinuity = discontinuity;
- this.contiguous = contiguous;
- this.accurateTimeOffset = accurateTimeOffset;
- this.trackSwitch = trackSwitch;
- this.timeOffset = timeOffset;
- this.initSegmentChange = initSegmentChange;
- }
- };
- var transmuxerInstanceCount = 0;
- var TransmuxerInterface = class {
- constructor(_hls, id, onTransmuxComplete, onFlush) {
- this.error = null;
- this.hls = void 0;
- this.id = void 0;
- this.instanceNo = transmuxerInstanceCount++;
- this.observer = void 0;
- this.frag = null;
- this.part = null;
- this.useWorker = void 0;
- this.workerContext = null;
- this.transmuxer = null;
- this.onTransmuxComplete = void 0;
- this.onFlush = void 0;
- this.onWorkerMessage = (event) => {
- const data = event.data;
- const hls = this.hls;
- if (!hls || !(data != null && data.event) || data.instanceNo !== this.instanceNo) {
- return;
- }
- switch (data.event) {
- case "init": {
- var _this$workerContext;
- const objectURL = (_this$workerContext = this.workerContext) == null ? void 0 : _this$workerContext.objectURL;
- if (objectURL) {
- self.URL.revokeObjectURL(objectURL);
- }
- break;
- }
- case "transmuxComplete": {
- this.handleTransmuxComplete(data.data);
- break;
- }
- case "flush": {
- this.onFlush(data.data);
- break;
- }
- case "workerLog": {
- if (hls.logger[data.data.logType]) {
- hls.logger[data.data.logType](data.data.message);
- }
- break;
- }
- default: {
- data.data = data.data || {};
- data.data.frag = this.frag;
- data.data.part = this.part;
- data.data.id = this.id;
- hls.trigger(data.event, data.data);
- break;
- }
- }
- };
- this.onWorkerError = (event) => {
- if (!this.hls) {
- return;
- }
- const error = new Error(`${event.message} (${event.filename}:${event.lineno})`);
- this.hls.config.enableWorker = false;
- this.hls.logger.warn(`Error in "${this.id}" Web Worker, fallback to inline`);
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.INTERNAL_EXCEPTION,
- fatal: false,
- event: "demuxerWorker",
- error
- });
- };
- const config = _hls.config;
- this.hls = _hls;
- this.id = id;
- this.useWorker = !!config.enableWorker;
- this.onTransmuxComplete = onTransmuxComplete;
- this.onFlush = onFlush;
- const forwardMessage = (ev, data) => {
- data = data || {};
- data.frag = this.frag || void 0;
- if (ev === Events.ERROR) {
- data = data;
- data.parent = this.id;
- data.part = this.part;
- this.error = data.error;
- }
- this.hls.trigger(ev, data);
- };
- this.observer = new EventEmitter();
- this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
- this.observer.on(Events.ERROR, forwardMessage);
- const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
- if (this.useWorker && typeof Worker !== "undefined") {
- const logger2 = this.hls.logger;
- const canCreateWorker = config.workerPath || hasUMDWorker();
- if (canCreateWorker) {
- try {
- if (config.workerPath) {
- logger2.log(`loading Web Worker ${config.workerPath} for "${id}"`);
- this.workerContext = loadWorker(config.workerPath);
- } else {
- logger2.log(`injecting Web Worker for "${id}"`);
- this.workerContext = injectWorker();
- }
- const {
- worker
- } = this.workerContext;
- worker.addEventListener("message", this.onWorkerMessage);
- worker.addEventListener("error", this.onWorkerError);
- worker.postMessage({
- instanceNo: this.instanceNo,
- cmd: "init",
- typeSupported: m2tsTypeSupported,
- id,
- config: stringify(config)
- });
- } catch (err) {
- logger2.warn(`Error setting up "${id}" Web Worker, fallback to inline`, err);
- this.terminateWorker();
- this.error = null;
- this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, "", id, _hls.logger);
- }
- return;
- }
- }
- this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, "", id, _hls.logger);
- }
- reset() {
- this.frag = null;
- this.part = null;
- if (this.workerContext) {
- const instanceNo = this.instanceNo;
- this.instanceNo = transmuxerInstanceCount++;
- const config = this.hls.config;
- const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
- this.workerContext.worker.postMessage({
- instanceNo: this.instanceNo,
- cmd: "reset",
- resetNo: instanceNo,
- typeSupported: m2tsTypeSupported,
- id: this.id,
- config: stringify(config)
- });
- }
- }
- terminateWorker() {
- if (this.workerContext) {
- const {
- worker
- } = this.workerContext;
- this.workerContext = null;
- worker.removeEventListener("message", this.onWorkerMessage);
- worker.removeEventListener("error", this.onWorkerError);
- removeWorkerFromStore(this.hls.config.workerPath);
- }
- }
- destroy() {
- if (this.workerContext) {
- this.terminateWorker();
- this.onWorkerMessage = this.onWorkerError = null;
- } else {
- const transmuxer = this.transmuxer;
- if (transmuxer) {
- transmuxer.destroy();
- this.transmuxer = null;
- }
- }
- const observer = this.observer;
- if (observer) {
- observer.removeAllListeners();
- }
- this.frag = null;
- this.part = null;
- this.observer = null;
- this.hls = null;
- }
- push(data, initSegmentData, audioCodec, videoCodec, frag, part, duration, accurateTimeOffset, chunkMeta, defaultInitPTS) {
- var _frag$initSegment, _lastFrag$initSegment;
- chunkMeta.transmuxing.start = self.performance.now();
- const {
- instanceNo,
- transmuxer
- } = this;
- const timeOffset = part ? part.start : frag.start;
- const decryptdata = frag.decryptdata;
- const lastFrag = this.frag;
- const discontinuity = !(lastFrag && frag.cc === lastFrag.cc);
- const trackSwitch = !(lastFrag && chunkMeta.level === lastFrag.level);
- const snDiff = lastFrag ? chunkMeta.sn - lastFrag.sn : -1;
- const partDiff = this.part ? chunkMeta.part - this.part.index : -1;
- const progressive = snDiff === 0 && chunkMeta.id > 1 && chunkMeta.id === (lastFrag == null ? void 0 : lastFrag.stats.chunkCount);
- const contiguous = !trackSwitch && (snDiff === 1 || snDiff === 0 && (partDiff === 1 || progressive && partDiff <= 0));
- const now2 = self.performance.now();
- if (trackSwitch || snDiff || frag.stats.parsing.start === 0) {
- frag.stats.parsing.start = now2;
- }
- if (part && (partDiff || !contiguous)) {
- part.stats.parsing.start = now2;
- }
- const initSegmentChange = !(lastFrag && ((_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.url) === ((_lastFrag$initSegment = lastFrag.initSegment) == null ? void 0 : _lastFrag$initSegment.url));
- const state = new TransmuxState(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange);
- if (!contiguous || discontinuity || initSegmentChange) {
- this.hls.logger.log(`[transmuxer-interface]: Starting new transmux session for ${frag.type} sn: ${chunkMeta.sn}${chunkMeta.part > -1 ? " part: " + chunkMeta.part : ""} ${this.id === PlaylistLevelType.MAIN ? "level" : "track"}: ${chunkMeta.level} id: ${chunkMeta.id}
- discontinuity: ${discontinuity}
- trackSwitch: ${trackSwitch}
- contiguous: ${contiguous}
- accurateTimeOffset: ${accurateTimeOffset}
- timeOffset: ${timeOffset}
- initSegmentChange: ${initSegmentChange}`);
- const config = new TransmuxConfig(audioCodec, videoCodec, initSegmentData, duration, defaultInitPTS);
- this.configureTransmuxer(config);
- }
- this.frag = frag;
- this.part = part;
- if (this.workerContext) {
- this.workerContext.worker.postMessage({
- instanceNo,
- cmd: "demux",
- data,
- decryptdata,
- chunkMeta,
- state
- }, data instanceof ArrayBuffer ? [data] : []);
- } else if (transmuxer) {
- const transmuxResult = transmuxer.push(data, decryptdata, chunkMeta, state);
- if (isPromise(transmuxResult)) {
- transmuxResult.then((data2) => {
- this.handleTransmuxComplete(data2);
- }).catch((error) => {
- this.transmuxerError(error, chunkMeta, "transmuxer-interface push error");
- });
- } else {
- this.handleTransmuxComplete(transmuxResult);
- }
- }
- }
- flush(chunkMeta) {
- chunkMeta.transmuxing.start = self.performance.now();
- const {
- instanceNo,
- transmuxer
- } = this;
- if (this.workerContext) {
- this.workerContext.worker.postMessage({
- instanceNo,
- cmd: "flush",
- chunkMeta
- });
- } else if (transmuxer) {
- const transmuxResult = transmuxer.flush(chunkMeta);
- if (isPromise(transmuxResult)) {
- transmuxResult.then((data) => {
- this.handleFlushResult(data, chunkMeta);
- }).catch((error) => {
- this.transmuxerError(error, chunkMeta, "transmuxer-interface flush error");
- });
- } else {
- this.handleFlushResult(transmuxResult, chunkMeta);
- }
- }
- }
- transmuxerError(error, chunkMeta, reason) {
- if (!this.hls) {
- return;
- }
- this.error = error;
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.FRAG_PARSING_ERROR,
- chunkMeta,
- frag: this.frag || void 0,
- part: this.part || void 0,
- fatal: false,
- error,
- err: error,
- reason
- });
- }
- handleFlushResult(results, chunkMeta) {
- results.forEach((result) => {
- this.handleTransmuxComplete(result);
- });
- this.onFlush(chunkMeta);
- }
- configureTransmuxer(config) {
- const {
- instanceNo,
- transmuxer
- } = this;
- if (this.workerContext) {
- this.workerContext.worker.postMessage({
- instanceNo,
- cmd: "configure",
- config
- });
- } else if (transmuxer) {
- transmuxer.configure(config);
- }
- }
- handleTransmuxComplete(result) {
- result.chunkMeta.transmuxing.end = self.performance.now();
- this.onTransmuxComplete(result);
- }
- };
- var TICK_INTERVAL$3 = 100;
- var AudioStreamController = class extends BaseStreamController {
- constructor(hls, fragmentTracker, keyLoader) {
- super(hls, fragmentTracker, keyLoader, "audio-stream-controller", PlaylistLevelType.AUDIO);
- this.mainAnchor = null;
- this.mainFragLoading = null;
- this.audioOnly = false;
- this.bufferedTrack = null;
- this.switchingTrack = null;
- this.trackId = -1;
- this.waitingData = null;
- this.mainDetails = null;
- this.flushing = false;
- this.bufferFlushed = false;
- this.cachedTrackLoadedData = null;
- this.registerListeners();
- }
- onHandlerDestroying() {
- this.unregisterListeners();
- super.onHandlerDestroying();
- this.resetItem();
- }
- resetItem() {
- this.mainDetails = this.mainAnchor = this.mainFragLoading = this.bufferedTrack = this.switchingTrack = this.waitingData = this.cachedTrackLoadedData = null;
- }
- registerListeners() {
- super.registerListeners();
- const {
- hls
- } = this;
- hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
- hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
- hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
- hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
- hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
- hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
- hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
- hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
- hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
- hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
- hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
- }
- unregisterListeners() {
- const {
- hls
- } = this;
- if (!hls) {
- return;
- }
- super.unregisterListeners();
- hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
- hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
- hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
- hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
- hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
- hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
- hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
- hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
- hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
- hls.off(Events.FRAG_LOADING, this.onFragLoading, this);
- hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
- }
- // INIT_PTS_FOUND is triggered when the video track parsed in the stream-controller has a new PTS value
- onInitPtsFound(event, {
- frag,
- id,
- initPTS,
- timescale,
- trackId
- }) {
- if (id === PlaylistLevelType.MAIN) {
- const cc = frag.cc;
- const inFlightFrag = this.fragCurrent;
- this.initPTS[cc] = {
- baseTime: initPTS,
- timescale,
- trackId
- };
- this.log(`InitPTS for cc: ${cc} found from main: ${initPTS / timescale} (${initPTS}/${timescale}) trackId: ${trackId}`);
- this.mainAnchor = frag;
- if (this.state === State.WAITING_INIT_PTS) {
- const waitingData = this.waitingData;
- if (!waitingData && !this.loadingParts || waitingData && waitingData.frag.cc !== cc) {
- this.syncWithAnchor(frag, waitingData == null ? void 0 : waitingData.frag);
- }
- } else if (!this.hls.hasEnoughToStart && inFlightFrag && inFlightFrag.cc !== cc) {
- inFlightFrag.abortRequests();
- this.syncWithAnchor(frag, inFlightFrag);
- } else if (this.state === State.IDLE) {
- this.tick();
- }
- }
- }
- getLoadPosition() {
- if (!this.startFragRequested && this.nextLoadPosition >= 0) {
- return this.nextLoadPosition;
- }
- return super.getLoadPosition();
- }
- syncWithAnchor(mainAnchor, waitingToAppend) {
- var _this$mainFragLoading;
- const mainFragLoading = ((_this$mainFragLoading = this.mainFragLoading) == null ? void 0 : _this$mainFragLoading.frag) || null;
- if (waitingToAppend) {
- if ((mainFragLoading == null ? void 0 : mainFragLoading.cc) === waitingToAppend.cc) {
- return;
- }
- }
- const targetDiscontinuity = (mainFragLoading || mainAnchor).cc;
- const trackDetails = this.getLevelDetails();
- const pos = this.getLoadPosition();
- const syncFrag = findNearestWithCC(trackDetails, targetDiscontinuity, pos);
- if (syncFrag) {
- this.log(`Syncing with main frag at ${syncFrag.start} cc ${syncFrag.cc}`);
- this.startFragRequested = false;
- this.nextLoadPosition = syncFrag.start;
- this.resetLoadingState();
- if (this.state === State.IDLE) {
- this.doTickIdle();
- }
- }
- }
- startLoad(startPosition, skipSeekToStartPosition) {
- if (!this.levels) {
- this.startPosition = startPosition;
- this.state = State.STOPPED;
- return;
- }
- const lastCurrentTime = this.lastCurrentTime;
- this.stopLoad();
- this.setInterval(TICK_INTERVAL$3);
- if (lastCurrentTime > 0 && startPosition === -1) {
- this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`);
- startPosition = lastCurrentTime;
- this.state = State.IDLE;
- } else {
- this.state = State.WAITING_TRACK;
- }
- this.nextLoadPosition = this.lastCurrentTime = startPosition + this.timelineOffset;
- this.startPosition = skipSeekToStartPosition ? -1 : startPosition;
- this.tick();
- }
- doTick() {
- switch (this.state) {
- case State.IDLE:
- this.doTickIdle();
- break;
- case State.WAITING_TRACK: {
- const {
- levels,
- trackId
- } = this;
- const currenTrack = levels == null ? void 0 : levels[trackId];
- const details = currenTrack == null ? void 0 : currenTrack.details;
- if (details && !this.waitForLive(currenTrack)) {
- if (this.waitForCdnTuneIn(details)) {
- break;
- }
- this.state = State.WAITING_INIT_PTS;
- }
- break;
- }
- case State.FRAG_LOADING_WAITING_RETRY: {
- this.checkRetryDate();
- break;
- }
- case State.WAITING_INIT_PTS: {
- const waitingData = this.waitingData;
- if (waitingData) {
- const {
- frag,
- part,
- cache,
- complete
- } = waitingData;
- const mainAnchor = this.mainAnchor;
- if (this.initPTS[frag.cc] !== void 0) {
- this.waitingData = null;
- this.state = State.FRAG_LOADING;
- const payload = cache.flush().buffer;
- const data = {
- frag,
- part,
- payload,
- networkDetails: null
- };
- this._handleFragmentLoadProgress(data);
- if (complete) {
- super._handleFragmentLoadComplete(data);
- }
- } else if (mainAnchor && mainAnchor.cc !== waitingData.frag.cc) {
- this.syncWithAnchor(mainAnchor, waitingData.frag);
- }
- } else {
- this.state = State.IDLE;
- }
- }
- }
- this.onTickEnd();
- }
- resetLoadingState() {
- const waitingData = this.waitingData;
- if (waitingData) {
- this.fragmentTracker.removeFragment(waitingData.frag);
- this.waitingData = null;
- }
- super.resetLoadingState();
- }
- onTickEnd() {
- const {
- media
- } = this;
- if (!(media != null && media.readyState)) {
- return;
- }
- this.lastCurrentTime = media.currentTime;
- }
- doTickIdle() {
- var _this$mainFragLoading2;
- const {
- hls,
- levels,
- media,
- trackId
- } = this;
- const config = hls.config;
- if (!this.buffering || !media && !this.primaryPrefetch && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
- return;
- }
- const levelInfo = levels[trackId];
- const trackDetails = levelInfo.details;
- if (!trackDetails || this.waitForLive(levelInfo) || this.waitForCdnTuneIn(trackDetails)) {
- this.state = State.WAITING_TRACK;
- this.startFragRequested = false;
- return;
- }
- const bufferable = this.mediaBuffer ? this.mediaBuffer : this.media;
- if (this.bufferFlushed && bufferable) {
- this.bufferFlushed = false;
- this.afterBufferFlushed(bufferable, ElementaryStreamTypes.AUDIO, PlaylistLevelType.AUDIO);
- }
- const bufferInfo = this.getFwdBufferInfo(bufferable, PlaylistLevelType.AUDIO);
- if (bufferInfo === null) {
- return;
- }
- if (!this.switchingTrack && this._streamEnded(bufferInfo, trackDetails)) {
- hls.trigger(Events.BUFFER_EOS, {
- type: "audio"
- });
- this.state = State.ENDED;
- return;
- }
- const bufferLen = bufferInfo.len;
- const maxBufLen = hls.maxBufferLength;
- const fragments = trackDetails.fragments;
- const start = fragments[0].start;
- const loadPosition = this.getLoadPosition();
- const targetBufferTime = this.flushing ? loadPosition : bufferInfo.end;
- if (this.switchingTrack && media) {
- const pos = loadPosition;
- if (trackDetails.PTSKnown && pos < start) {
- if (bufferInfo.end > start || bufferInfo.nextStart) {
- this.log("Alt audio track ahead of main track, seek to start of alt audio track");
- media.currentTime = start + 0.05;
- }
- }
- }
- if (bufferLen >= maxBufLen && !this.switchingTrack && targetBufferTime < fragments[fragments.length - 1].start) {
- return;
- }
- let frag = this.getNextFragment(targetBufferTime, trackDetails);
- if (frag && this.isLoopLoading(frag, targetBufferTime)) {
- frag = this.getNextFragmentLoopLoading(frag, trackDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen);
- }
- if (!frag) {
- this.bufferFlushed = true;
- return;
- }
- let mainFragLoading = ((_this$mainFragLoading2 = this.mainFragLoading) == null ? void 0 : _this$mainFragLoading2.frag) || null;
- if (!this.audioOnly && this.startFragRequested && mainFragLoading && isMediaFragment(frag) && !frag.endList && (!trackDetails.live || !this.loadingParts && targetBufferTime < this.hls.liveSyncPosition)) {
- if (this.fragmentTracker.getState(mainFragLoading) === FragmentState.OK) {
- this.mainFragLoading = mainFragLoading = null;
- }
- if (mainFragLoading && isMediaFragment(mainFragLoading)) {
- if (frag.start > mainFragLoading.end) {
- const mainFragAtPos = this.fragmentTracker.getFragAtPos(targetBufferTime, PlaylistLevelType.MAIN);
- if (mainFragAtPos && mainFragAtPos.end > mainFragLoading.end) {
- mainFragLoading = mainFragAtPos;
- this.mainFragLoading = {
- frag: mainFragAtPos,
- targetBufferTime: null
- };
- }
- }
- const atBufferSyncLimit = frag.start > mainFragLoading.end;
- if (atBufferSyncLimit) {
- return;
- }
- }
- }
- this.loadFragment(frag, levelInfo, targetBufferTime);
- }
- onMediaDetaching(event, data) {
- this.bufferFlushed = this.flushing = false;
- super.onMediaDetaching(event, data);
- }
- onAudioTracksUpdated(event, {
- audioTracks
- }) {
- this.resetTransmuxer();
- this.levels = audioTracks.map((mediaPlaylist) => new Level(mediaPlaylist));
- }
- onAudioTrackSwitching(event, data) {
- const altAudio = !!data.url;
- this.trackId = data.id;
- const {
- fragCurrent
- } = this;
- if (fragCurrent) {
- fragCurrent.abortRequests();
- this.removeUnbufferedFrags(fragCurrent.start);
- }
- this.resetLoadingState();
- if (altAudio) {
- this.switchingTrack = data;
- this.flushAudioIfNeeded(data);
- if (this.state !== State.STOPPED) {
- this.setInterval(TICK_INTERVAL$3);
- this.state = State.IDLE;
- this.tick();
- }
- } else {
- this.resetTransmuxer();
- this.switchingTrack = null;
- this.bufferedTrack = data;
- this.clearInterval();
- }
- }
- onManifestLoading() {
- super.onManifestLoading();
- this.bufferFlushed = this.flushing = this.audioOnly = false;
- this.resetItem();
- this.trackId = -1;
- }
- onLevelLoaded(event, data) {
- this.mainDetails = data.details;
- const cachedTrackLoadedData = this.cachedTrackLoadedData;
- if (cachedTrackLoadedData) {
- this.cachedTrackLoadedData = null;
- this.onAudioTrackLoaded(Events.AUDIO_TRACK_LOADED, cachedTrackLoadedData);
- }
- }
- onAudioTrackLoaded(event, data) {
- var _trackLevel$details;
- const {
- levels
- } = this;
- const {
- details: newDetails,
- id: trackId,
- groupId,
- track
- } = data;
- if (!levels) {
- this.warn(`Audio tracks reset while loading track ${trackId} "${track.name}" of "${groupId}"`);
- return;
- }
- const mainDetails = this.mainDetails;
- if (!mainDetails || newDetails.endCC > mainDetails.endCC || mainDetails.expired) {
- this.cachedTrackLoadedData = data;
- if (this.state !== State.STOPPED) {
- this.state = State.WAITING_TRACK;
- }
- return;
- }
- this.cachedTrackLoadedData = null;
- this.log(`Audio track ${trackId} "${track.name}" of "${groupId}" loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ""},duration:${newDetails.totalduration}`);
- const trackLevel = levels[trackId];
- let sliding = 0;
- if (newDetails.live || (_trackLevel$details = trackLevel.details) != null && _trackLevel$details.live) {
- this.checkLiveUpdate(newDetails);
- if (newDetails.deltaUpdateFailed) {
- return;
- }
- if (trackLevel.details) {
- var _this$levelLastLoaded;
- sliding = this.alignPlaylists(newDetails, trackLevel.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details);
- }
- if (!newDetails.alignedSliding) {
- alignDiscontinuities(newDetails, mainDetails);
- if (!newDetails.alignedSliding) {
- alignMediaPlaylistByPDT(newDetails, mainDetails);
- }
- sliding = newDetails.fragmentStart;
- }
- }
- trackLevel.details = newDetails;
- this.levelLastLoaded = trackLevel;
- if (!this.startFragRequested) {
- this.setStartPosition(mainDetails, sliding);
- }
- this.hls.trigger(Events.AUDIO_TRACK_UPDATED, {
- details: newDetails,
- id: trackId,
- groupId: data.groupId
- });
- if (this.state === State.WAITING_TRACK && !this.waitForCdnTuneIn(newDetails)) {
- this.state = State.IDLE;
- }
- this.tick();
- }
- _handleFragmentLoadProgress(data) {
- var _frag$initSegment;
- const frag = data.frag;
- const {
- part,
- payload
- } = data;
- const {
- config,
- trackId,
- levels
- } = this;
- if (!levels) {
- this.warn(`Audio tracks were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`);
- return;
- }
- const track = levels[trackId];
- if (!track) {
- this.warn("Audio track is undefined on fragment load progress");
- return;
- }
- const details = track.details;
- if (!details) {
- this.warn("Audio track details undefined on fragment load progress");
- this.removeUnbufferedFrags(frag.start);
- return;
- }
- const audioCodec = config.defaultAudioCodec || track.audioCodec || "mp4a.40.2";
- let transmuxer = this.transmuxer;
- if (!transmuxer) {
- transmuxer = this.transmuxer = new TransmuxerInterface(this.hls, PlaylistLevelType.AUDIO, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this));
- }
- const initPTS = this.initPTS[frag.cc];
- const initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data;
- if (initPTS !== void 0) {
- const accurateTimeOffset = false;
- const partIndex = part ? part.index : -1;
- const partial = partIndex !== -1;
- const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial);
- transmuxer.push(payload, initSegmentData, audioCodec, "", frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS);
- } else {
- this.log(`Unknown video PTS for cc ${frag.cc}, waiting for video PTS before demuxing audio frag ${frag.sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`);
- const {
- cache
- } = this.waitingData = this.waitingData || {
- frag,
- part,
- cache: new ChunkCache(),
- complete: false
- };
- cache.push(new Uint8Array(payload));
- if (this.state !== State.STOPPED) {
- this.state = State.WAITING_INIT_PTS;
- }
- }
- }
- _handleFragmentLoadComplete(fragLoadedData) {
- if (this.waitingData) {
- this.waitingData.complete = true;
- return;
- }
- super._handleFragmentLoadComplete(fragLoadedData);
- }
- onBufferReset() {
- this.mediaBuffer = null;
- }
- onBufferCreated(event, data) {
- this.bufferFlushed = this.flushing = false;
- const audioTrack = data.tracks.audio;
- if (audioTrack) {
- this.mediaBuffer = audioTrack.buffer || null;
- }
- }
- onFragLoading(event, data) {
- if (!this.audioOnly && data.frag.type === PlaylistLevelType.MAIN && isMediaFragment(data.frag)) {
- this.mainFragLoading = data;
- if (this.state === State.IDLE) {
- this.tick();
- }
- }
- }
- onFragBuffered(event, data) {
- const {
- frag,
- part
- } = data;
- if (frag.type !== PlaylistLevelType.AUDIO) {
- if (!this.audioOnly && frag.type === PlaylistLevelType.MAIN && !frag.elementaryStreams.video && !frag.elementaryStreams.audiovideo) {
- this.audioOnly = true;
- this.mainFragLoading = null;
- }
- return;
- }
- if (this.fragContextChanged(frag)) {
- this.warn(`Fragment ${frag.sn}${part ? " p: " + part.index : ""} of level ${frag.level} finished buffering, but was aborted. state: ${this.state}, audioSwitch: ${this.switchingTrack ? this.switchingTrack.name : "false"}`);
- return;
- }
- if (isMediaFragment(frag)) {
- this.fragPrevious = frag;
- const track = this.switchingTrack;
- if (track) {
- this.bufferedTrack = track;
- this.switchingTrack = null;
- this.hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, track));
- }
- }
- this.fragBufferedComplete(frag, part);
- if (this.media) {
- this.tick();
- }
- }
- onError(event, data) {
- var _data$context;
- if (data.fatal) {
- this.state = State.ERROR;
- return;
- }
- switch (data.details) {
- case ErrorDetails.FRAG_GAP:
- case ErrorDetails.FRAG_PARSING_ERROR:
- case ErrorDetails.FRAG_DECRYPT_ERROR:
- case ErrorDetails.FRAG_LOAD_ERROR:
- case ErrorDetails.FRAG_LOAD_TIMEOUT:
- case ErrorDetails.KEY_LOAD_ERROR:
- case ErrorDetails.KEY_LOAD_TIMEOUT:
- this.onFragmentOrKeyLoadError(PlaylistLevelType.AUDIO, data);
- break;
- case ErrorDetails.AUDIO_TRACK_LOAD_ERROR:
- case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:
- case ErrorDetails.LEVEL_PARSING_ERROR:
- if (!data.levelRetry && this.state === State.WAITING_TRACK && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.AUDIO_TRACK) {
- this.state = State.IDLE;
- }
- break;
- case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
- case ErrorDetails.BUFFER_APPEND_ERROR:
- if (data.parent !== "audio") {
- return;
- }
- if (!this.reduceLengthAndFlushBuffer(data)) {
- this.resetLoadingState();
- }
- break;
- case ErrorDetails.BUFFER_FULL_ERROR:
- if (data.parent !== "audio") {
- return;
- }
- if (this.reduceLengthAndFlushBuffer(data)) {
- this.bufferedTrack = null;
- super.flushMainBuffer(0, Number.POSITIVE_INFINITY, "audio");
- }
- break;
- case ErrorDetails.INTERNAL_EXCEPTION:
- this.recoverWorkerError(data);
- break;
- }
- }
- onBufferFlushing(event, {
- type
- }) {
- if (type !== ElementaryStreamTypes.VIDEO) {
- this.flushing = true;
- }
- }
- onBufferFlushed(event, {
- type
- }) {
- if (type !== ElementaryStreamTypes.VIDEO) {
- this.flushing = false;
- this.bufferFlushed = true;
- if (this.state === State.ENDED) {
- this.state = State.IDLE;
- }
- const mediaBuffer = this.mediaBuffer || this.media;
- if (mediaBuffer) {
- this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.AUDIO);
- this.tick();
- }
- }
- }
- _handleTransmuxComplete(transmuxResult) {
- var _id3$samples;
- const id = "audio";
- const {
- hls
- } = this;
- const {
- remuxResult,
- chunkMeta
- } = transmuxResult;
- const context = this.getCurrentContext(chunkMeta);
- if (!context) {
- this.resetWhenMissingContext(chunkMeta);
- return;
- }
- const {
- frag,
- part,
- level
- } = context;
- const {
- details
- } = level;
- const {
- audio,
- text,
- id3,
- initSegment
- } = remuxResult;
- if (this.fragContextChanged(frag) || !details) {
- this.fragmentTracker.removeFragment(frag);
- return;
- }
- this.state = State.PARSING;
- if (this.switchingTrack && audio) {
- this.completeAudioSwitch(this.switchingTrack);
- }
- if (initSegment != null && initSegment.tracks) {
- const mapFragment = frag.initSegment || frag;
- if (this.unhandledEncryptionError(initSegment, frag)) {
- return;
- }
- this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta);
- hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, {
- frag: mapFragment,
- id,
- tracks: initSegment.tracks
- });
- }
- if (audio) {
- const {
- startPTS,
- endPTS,
- startDTS,
- endDTS
- } = audio;
- if (part) {
- part.elementaryStreams[ElementaryStreamTypes.AUDIO] = {
- startPTS,
- endPTS,
- startDTS,
- endDTS
- };
- }
- frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS);
- this.bufferFragmentData(audio, frag, part, chunkMeta);
- }
- if (id3 != null && (_id3$samples = id3.samples) != null && _id3$samples.length) {
- const emittedID3 = _extends({
- id,
- frag,
- details
- }, id3);
- hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3);
- }
- if (text) {
- const emittedText = _extends({
- id,
- frag,
- details
- }, text);
- hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText);
- }
- }
- _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) {
- if (this.state !== State.PARSING) {
- return;
- }
- if (tracks.video) {
- delete tracks.video;
- }
- if (tracks.audiovideo) {
- delete tracks.audiovideo;
- }
- if (!tracks.audio) {
- return;
- }
- const track = tracks.audio;
- track.id = PlaylistLevelType.AUDIO;
- const variantAudioCodecs = currentLevel.audioCodec;
- this.log(`Init audio buffer, container:${track.container}, codecs[level/parsed]=[${variantAudioCodecs}/${track.codec}]`);
- if (variantAudioCodecs && variantAudioCodecs.split(",").length === 1) {
- track.levelCodec = variantAudioCodecs;
- }
- this.hls.trigger(Events.BUFFER_CODECS, tracks);
- const initSegment = track.initSegment;
- if (initSegment != null && initSegment.byteLength) {
- const segment = {
- type: "audio",
- frag,
- part: null,
- chunkMeta,
- parent: frag.type,
- data: initSegment
- };
- this.hls.trigger(Events.BUFFER_APPENDING, segment);
- }
- this.tickImmediate();
- }
- loadFragment(frag, track, targetBufferTime) {
- const fragState = this.fragmentTracker.getState(frag);
- if (this.switchingTrack || fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
- var _track$details;
- if (!isMediaFragment(frag)) {
- this._loadInitSegment(frag, track);
- } else if ((_track$details = track.details) != null && _track$details.live && !this.initPTS[frag.cc]) {
- this.log(`Waiting for video PTS in continuity counter ${frag.cc} of live stream before loading audio fragment ${frag.sn} of level ${this.trackId}`);
- this.state = State.WAITING_INIT_PTS;
- const mainDetails = this.mainDetails;
- if (mainDetails && mainDetails.fragmentStart !== track.details.fragmentStart) {
- alignMediaPlaylistByPDT(track.details, mainDetails);
- }
- } else {
- super.loadFragment(frag, track, targetBufferTime);
- }
- } else {
- this.clearTrackerIfNeeded(frag);
- }
- }
- flushAudioIfNeeded(switchingTrack) {
- if (this.media && this.bufferedTrack) {
- const {
- name,
- lang,
- assocLang,
- characteristics,
- audioCodec,
- channels
- } = this.bufferedTrack;
- if (!matchesOption({
- name,
- lang,
- assocLang,
- characteristics,
- audioCodec,
- channels
- }, switchingTrack, audioMatchPredicate)) {
- if (useAlternateAudio(switchingTrack.url, this.hls)) {
- this.log("Switching audio track : flushing all audio");
- super.flushMainBuffer(0, Number.POSITIVE_INFINITY, "audio");
- this.bufferedTrack = null;
- } else {
- this.bufferedTrack = switchingTrack;
- }
- }
- }
- }
- completeAudioSwitch(switchingTrack) {
- const {
- hls
- } = this;
- this.flushAudioIfNeeded(switchingTrack);
- this.bufferedTrack = switchingTrack;
- this.switchingTrack = null;
- hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, switchingTrack));
- }
- };
- var BasePlaylistController = class extends Logger {
- constructor(hls, logPrefix) {
- super(logPrefix, hls.logger);
- this.hls = void 0;
- this.canLoad = false;
- this.timer = -1;
- this.hls = hls;
- }
- destroy() {
- this.clearTimer();
- this.hls = this.log = this.warn = null;
- }
- clearTimer() {
- if (this.timer !== -1) {
- self.clearTimeout(this.timer);
- this.timer = -1;
- }
- }
- startLoad() {
- this.canLoad = true;
- this.loadPlaylist();
- }
- stopLoad() {
- this.canLoad = false;
- this.clearTimer();
- }
- switchParams(playlistUri, previous, current) {
- const renditionReports = previous == null ? void 0 : previous.renditionReports;
- if (renditionReports) {
- let foundIndex = -1;
- for (let i = 0; i < renditionReports.length; i++) {
- const attr = renditionReports[i];
- let uri;
- try {
- uri = new self.URL(attr.URI, previous.url).href;
- } catch (error) {
- this.warn(`Could not construct new URL for Rendition Report: ${error}`);
- uri = attr.URI || "";
- }
- if (uri === playlistUri) {
- foundIndex = i;
- break;
- } else if (uri === playlistUri.substring(0, uri.length)) {
- foundIndex = i;
- }
- }
- if (foundIndex !== -1) {
- const attr = renditionReports[foundIndex];
- const msn = parseInt(attr["LAST-MSN"]) || previous.lastPartSn;
- let part = parseInt(attr["LAST-PART"]) || previous.lastPartIndex;
- if (this.hls.config.lowLatencyMode) {
- const currentGoal = Math.min(previous.age - previous.partTarget, previous.targetduration);
- if (part >= 0 && currentGoal > previous.partTarget) {
- part += 1;
- }
- }
- const skip = current && getSkipValue(current);
- return new HlsUrlParameters(msn, part >= 0 ? part : void 0, skip);
- }
- }
- }
- loadPlaylist(hlsUrlParameters) {
- this.clearTimer();
- }
- loadingPlaylist(playlist, hlsUrlParameters) {
- this.clearTimer();
- }
- shouldLoadPlaylist(playlist) {
- return this.canLoad && !!playlist && !!playlist.url && (!playlist.details || playlist.details.live);
- }
- getUrlWithDirectives(uri, hlsUrlParameters) {
- if (hlsUrlParameters) {
- try {
- return hlsUrlParameters.addDirectives(uri);
- } catch (error) {
- this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`);
- }
- }
- return uri;
- }
- playlistLoaded(index, data, previousDetails) {
- const {
- details,
- stats
- } = data;
- const now2 = self.performance.now();
- const elapsed = stats.loading.first ? Math.max(0, now2 - stats.loading.first) : 0;
- details.advancedDateTime = Date.now() - elapsed;
- const timelineOffset = this.hls.config.timelineOffset;
- if (timelineOffset !== details.appliedTimelineOffset) {
- const offset = Math.max(timelineOffset || 0, 0);
- details.appliedTimelineOffset = offset;
- details.fragments.forEach((frag) => {
- frag.setStart(frag.playlistOffset + offset);
- });
- }
- if (details.live || previousDetails != null && previousDetails.live) {
- const levelOrTrack = "levelInfo" in data ? data.levelInfo : data.track;
- details.reloaded(previousDetails);
- if (previousDetails && details.fragments.length > 0) {
- mergeDetails(previousDetails, details, this);
- const error = details.playlistParsingError;
- if (error) {
- this.warn(error);
- const hls = this.hls;
- if (!hls.config.ignorePlaylistParsingErrors) {
- var _details$fragments$;
- const {
- networkDetails
- } = data;
- hls.trigger(Events.ERROR, {
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.LEVEL_PARSING_ERROR,
- fatal: false,
- url: details.url,
- error,
- reason: error.message,
- level: data.level || void 0,
- parent: (_details$fragments$ = details.fragments[0]) == null ? void 0 : _details$fragments$.type,
- networkDetails,
- stats
- });
- return;
- }
- details.playlistParsingError = null;
- }
- }
- if (details.requestScheduled === -1) {
- details.requestScheduled = stats.loading.start;
- }
- const bufferInfo = this.hls.mainForwardBufferInfo;
- const position = bufferInfo ? bufferInfo.end - bufferInfo.len : 0;
- const distanceToLiveEdgeMs = (details.edge - position) * 1e3;
- const reloadInterval = computeReloadInterval(details, distanceToLiveEdgeMs);
- if (details.requestScheduled + reloadInterval < now2) {
- details.requestScheduled = now2;
- } else {
- details.requestScheduled += reloadInterval;
- }
- this.log(`live playlist ${index} ${details.advanced ? "REFRESHED " + details.lastPartSn + "-" + details.lastPartIndex : details.updated ? "UPDATED" : "MISSED"}`);
- if (!this.canLoad || !details.live) {
- return;
- }
- let deliveryDirectives;
- let msn = void 0;
- let part = void 0;
- if (details.canBlockReload && details.endSN && details.advanced) {
- const lowLatencyMode = this.hls.config.lowLatencyMode;
- const lastPartSn = details.lastPartSn;
- const endSn = details.endSN;
- const lastPartIndex = details.lastPartIndex;
- const hasParts = lastPartIndex !== -1;
- const atLastPartOfSegment = lastPartSn === endSn;
- if (hasParts) {
- if (atLastPartOfSegment) {
- msn = endSn + 1;
- part = lowLatencyMode ? 0 : lastPartIndex;
- } else {
- msn = lastPartSn;
- part = lowLatencyMode ? lastPartIndex + 1 : details.maxPartIndex;
- }
- } else {
- msn = endSn + 1;
- }
- const lastAdvanced = details.age;
- const cdnAge = lastAdvanced + details.ageHeader;
- let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
- if (currentGoal > 0) {
- if (cdnAge > details.targetduration * 3) {
- this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
- msn = void 0;
- part = void 0;
- } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
- this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
- currentGoal = 0;
- } else {
- const segments = Math.floor(currentGoal / details.targetduration);
- msn += segments;
- if (part !== void 0) {
- const parts = Math.round(currentGoal % details.targetduration / details.partTarget);
- part += parts;
- }
- this.log(`CDN Tune-in age: ${details.ageHeader}s last advanced ${lastAdvanced.toFixed(2)}s goal: ${currentGoal} skip sn ${segments} to part ${part}`);
- }
- details.tuneInGoal = currentGoal;
- }
- deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part);
- if (lowLatencyMode || !atLastPartOfSegment) {
- details.requestScheduled = now2;
- this.loadingPlaylist(levelOrTrack, deliveryDirectives);
- return;
- }
- } else if (details.canBlockReload || details.canSkipUntil) {
- deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part);
- }
- if (deliveryDirectives && msn !== void 0 && details.canBlockReload) {
- details.requestScheduled = stats.loading.first + Math.max(reloadInterval - elapsed * 2, reloadInterval / 2);
- }
- this.scheduleLoading(levelOrTrack, deliveryDirectives, details);
- } else {
- this.clearTimer();
- }
- }
- scheduleLoading(levelOrTrack, deliveryDirectives, updatedDetails) {
- const details = updatedDetails || levelOrTrack.details;
- if (!details) {
- this.loadingPlaylist(levelOrTrack, deliveryDirectives);
- return;
- }
- const now2 = self.performance.now();
- const requestScheduled = details.requestScheduled;
- if (now2 >= requestScheduled) {
- this.loadingPlaylist(levelOrTrack, deliveryDirectives);
- return;
- }
- const estimatedTimeUntilUpdate = requestScheduled - now2;
- this.log(`reload live playlist ${levelOrTrack.name || levelOrTrack.bitrate + "bps"} in ${Math.round(estimatedTimeUntilUpdate)} ms`);
- this.clearTimer();
- this.timer = self.setTimeout(() => this.loadingPlaylist(levelOrTrack, deliveryDirectives), estimatedTimeUntilUpdate);
- }
- getDeliveryDirectives(details, previousDeliveryDirectives, msn, part) {
- let skip = getSkipValue(details);
- if (previousDeliveryDirectives != null && previousDeliveryDirectives.skip && details.deltaUpdateFailed) {
- msn = previousDeliveryDirectives.msn;
- part = previousDeliveryDirectives.part;
- skip = HlsSkip.No;
- }
- return new HlsUrlParameters(msn, part, skip);
- }
- checkRetry(errorEvent) {
- const errorDetails = errorEvent.details;
- const isTimeout = isTimeoutError(errorEvent);
- const errorAction = errorEvent.errorAction;
- const {
- action,
- retryCount = 0,
- retryConfig
- } = errorAction || {};
- const retry = !!errorAction && !!retryConfig && (action === NetworkErrorAction.RetryRequest || !errorAction.resolved && action === NetworkErrorAction.SendAlternateToPenaltyBox);
- if (retry) {
- var _errorEvent$context;
- if (retryCount >= retryConfig.maxNumRetry) {
- return false;
- }
- if (isTimeout && (_errorEvent$context = errorEvent.context) != null && _errorEvent$context.deliveryDirectives) {
- this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after "${errorDetails}" without delivery-directives`);
- this.loadPlaylist();
- } else {
- const delay = getRetryDelay(retryConfig, retryCount);
- this.clearTimer();
- this.timer = self.setTimeout(() => this.loadPlaylist(), delay);
- this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after "${errorDetails}" in ${delay}ms`);
- }
- errorEvent.levelRetry = true;
- errorAction.resolved = true;
- }
- return retry;
- }
- };
- function subtitleOptionsIdentical(trackList1, trackList2) {
- if (trackList1.length !== trackList2.length) {
- return false;
- }
- for (let i = 0; i < trackList1.length; i++) {
- if (!mediaAttributesIdentical(trackList1[i].attrs, trackList2[i].attrs)) {
- return false;
- }
- }
- return true;
- }
- function mediaAttributesIdentical(attrs1, attrs2, customAttributes) {
- const stableRenditionId = attrs1["STABLE-RENDITION-ID"];
- if (stableRenditionId && !customAttributes) {
- return stableRenditionId === attrs2["STABLE-RENDITION-ID"];
- }
- return !(customAttributes || ["LANGUAGE", "NAME", "CHARACTERISTICS", "AUTOSELECT", "DEFAULT", "FORCED", "ASSOC-LANGUAGE"]).some((subtitleAttribute) => attrs1[subtitleAttribute] !== attrs2[subtitleAttribute]);
- }
- function subtitleTrackMatchesTextTrack(subtitleTrack, textTrack) {
- return textTrack.label.toLowerCase() === subtitleTrack.name.toLowerCase() && (!textTrack.language || textTrack.language.toLowerCase() === (subtitleTrack.lang || "").toLowerCase());
- }
- var AudioTrackController = class extends BasePlaylistController {
- constructor(hls) {
- super(hls, "audio-track-controller");
- this.tracks = [];
- this.groupIds = null;
- this.tracksInGroup = [];
- this.trackId = -1;
- this.currentTrack = null;
- this.selectDefaultTrack = true;
- this.registerListeners();
- }
- registerListeners() {
- const {
- hls
- } = this;
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
- hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
- hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
- hls.on(Events.ERROR, this.onError, this);
- }
- unregisterListeners() {
- const {
- hls
- } = this;
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
- hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
- hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
- hls.off(Events.ERROR, this.onError, this);
- }
- destroy() {
- this.unregisterListeners();
- this.tracks.length = 0;
- this.tracksInGroup.length = 0;
- this.currentTrack = null;
- super.destroy();
- }
- onManifestLoading() {
- this.tracks = [];
- this.tracksInGroup = [];
- this.groupIds = null;
- this.currentTrack = null;
- this.trackId = -1;
- this.selectDefaultTrack = true;
- }
- onManifestParsed(event, data) {
- this.tracks = data.audioTracks || [];
- }
- onAudioTrackLoaded(event, data) {
- const {
- id,
- groupId,
- details
- } = data;
- const trackInActiveGroup = this.tracksInGroup[id];
- if (!trackInActiveGroup || trackInActiveGroup.groupId !== groupId) {
- this.warn(`Audio track with id:${id} and group:${groupId} not found in active group ${trackInActiveGroup == null ? void 0 : trackInActiveGroup.groupId}`);
- return;
- }
- const curDetails = trackInActiveGroup.details;
- trackInActiveGroup.details = data.details;
- this.log(`Audio track ${id} "${trackInActiveGroup.name}" lang:${trackInActiveGroup.lang} group:${groupId} loaded [${details.startSN}-${details.endSN}]`);
- if (id === this.trackId) {
- this.playlistLoaded(id, data, curDetails);
- }
- }
- onLevelLoading(event, data) {
- this.switchLevel(data.level);
- }
- onLevelSwitching(event, data) {
- this.switchLevel(data.level);
- }
- switchLevel(levelIndex) {
- const levelInfo = this.hls.levels[levelIndex];
- if (!levelInfo) {
- return;
- }
- const audioGroups = levelInfo.audioGroups || null;
- const currentGroups = this.groupIds;
- let currentTrack = this.currentTrack;
- if (!audioGroups || (currentGroups == null ? void 0 : currentGroups.length) !== (audioGroups == null ? void 0 : audioGroups.length) || audioGroups != null && audioGroups.some((groupId) => (currentGroups == null ? void 0 : currentGroups.indexOf(groupId)) === -1)) {
- this.groupIds = audioGroups;
- this.trackId = -1;
- this.currentTrack = null;
- const audioTracks = this.tracks.filter((track) => !audioGroups || audioGroups.indexOf(track.groupId) !== -1);
- if (audioTracks.length) {
- if (this.selectDefaultTrack && !audioTracks.some((track) => track.default)) {
- this.selectDefaultTrack = false;
- }
- audioTracks.forEach((track, i) => {
- track.id = i;
- });
- } else if (!currentTrack && !this.tracksInGroup.length) {
- return;
- }
- this.tracksInGroup = audioTracks;
- const audioPreference = this.hls.config.audioPreference;
- if (!currentTrack && audioPreference) {
- const groupIndex = findMatchingOption(audioPreference, audioTracks, audioMatchPredicate);
- if (groupIndex > -1) {
- currentTrack = audioTracks[groupIndex];
- } else {
- const allIndex = findMatchingOption(audioPreference, this.tracks);
- currentTrack = this.tracks[allIndex];
- }
- }
- let trackId = this.findTrackId(currentTrack);
- if (trackId === -1 && currentTrack) {
- trackId = this.findTrackId(null);
- }
- const audioTracksUpdated = {
- audioTracks
- };
- this.log(`Updating audio tracks, ${audioTracks.length} track(s) found in group(s): ${audioGroups == null ? void 0 : audioGroups.join(",")}`);
- this.hls.trigger(Events.AUDIO_TRACKS_UPDATED, audioTracksUpdated);
- const selectedTrackId = this.trackId;
- if (trackId !== -1 && selectedTrackId === -1) {
- this.setAudioTrack(trackId);
- } else if (audioTracks.length && selectedTrackId === -1) {
- var _this$groupIds;
- const error = new Error(`No audio track selected for current audio group-ID(s): ${(_this$groupIds = this.groupIds) == null ? void 0 : _this$groupIds.join(",")} track count: ${audioTracks.length}`);
- this.warn(error.message);
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.AUDIO_TRACK_LOAD_ERROR,
- fatal: true,
- error
- });
- }
- }
- }
- onError(event, data) {
- if (data.fatal || !data.context) {
- return;
- }
- if (data.context.type === PlaylistContextType.AUDIO_TRACK && data.context.id === this.trackId && (!this.groupIds || this.groupIds.indexOf(data.context.groupId) !== -1)) {
- this.checkRetry(data);
- }
- }
- get allAudioTracks() {
- return this.tracks;
- }
- get audioTracks() {
- return this.tracksInGroup;
- }
- get audioTrack() {
- return this.trackId;
- }
- set audioTrack(newId) {
- this.selectDefaultTrack = false;
- this.setAudioTrack(newId);
- }
- setAudioOption(audioOption) {
- const hls = this.hls;
- hls.config.audioPreference = audioOption;
- if (audioOption) {
- const allAudioTracks = this.allAudioTracks;
- this.selectDefaultTrack = false;
- if (allAudioTracks.length) {
- const currentTrack = this.currentTrack;
- if (currentTrack && matchesOption(audioOption, currentTrack, audioMatchPredicate)) {
- return currentTrack;
- }
- const groupIndex = findMatchingOption(audioOption, this.tracksInGroup, audioMatchPredicate);
- if (groupIndex > -1) {
- const track = this.tracksInGroup[groupIndex];
- this.setAudioTrack(groupIndex);
- return track;
- } else if (currentTrack) {
- let searchIndex = hls.loadLevel;
- if (searchIndex === -1) {
- searchIndex = hls.firstAutoLevel;
- }
- const switchIndex = findClosestLevelWithAudioGroup(audioOption, hls.levels, allAudioTracks, searchIndex, audioMatchPredicate);
- if (switchIndex === -1) {
- return null;
- }
- hls.nextLoadLevel = switchIndex;
- }
- if (audioOption.channels || audioOption.audioCodec) {
- const withoutCodecAndChannelsMatch = findMatchingOption(audioOption, allAudioTracks);
- if (withoutCodecAndChannelsMatch > -1) {
- return allAudioTracks[withoutCodecAndChannelsMatch];
- }
- }
- }
- }
- return null;
- }
- setAudioTrack(newId) {
- const tracks = this.tracksInGroup;
- if (newId < 0 || newId >= tracks.length) {
- this.warn(`Invalid audio track id: ${newId}`);
- return;
- }
- this.selectDefaultTrack = false;
- const lastTrack = this.currentTrack;
- const track = tracks[newId];
- const trackLoaded = track.details && !track.details.live;
- if (newId === this.trackId && track === lastTrack && trackLoaded) {
- return;
- }
- this.log(`Switching to audio-track ${newId} "${track.name}" lang:${track.lang} group:${track.groupId} channels:${track.channels}`);
- this.trackId = newId;
- this.currentTrack = track;
- this.hls.trigger(Events.AUDIO_TRACK_SWITCHING, _objectSpread2({}, track));
- if (trackLoaded) {
- return;
- }
- const hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details, track.details);
- this.loadPlaylist(hlsUrlParameters);
- }
- findTrackId(currentTrack) {
- const audioTracks = this.tracksInGroup;
- for (let i = 0; i < audioTracks.length; i++) {
- const track = audioTracks[i];
- if (this.selectDefaultTrack && !track.default) {
- continue;
- }
- if (!currentTrack || matchesOption(currentTrack, track, audioMatchPredicate)) {
- return i;
- }
- }
- if (currentTrack) {
- const {
- name,
- lang,
- assocLang,
- characteristics,
- audioCodec,
- channels
- } = currentTrack;
- for (let i = 0; i < audioTracks.length; i++) {
- const track = audioTracks[i];
- if (matchesOption({
- name,
- lang,
- assocLang,
- characteristics,
- audioCodec,
- channels
- }, track, audioMatchPredicate)) {
- return i;
- }
- }
- for (let i = 0; i < audioTracks.length; i++) {
- const track = audioTracks[i];
- if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ["LANGUAGE", "ASSOC-LANGUAGE", "CHARACTERISTICS"])) {
- return i;
- }
- }
- for (let i = 0; i < audioTracks.length; i++) {
- const track = audioTracks[i];
- if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ["LANGUAGE"])) {
- return i;
- }
- }
- }
- return -1;
- }
- loadPlaylist(hlsUrlParameters) {
- super.loadPlaylist();
- const audioTrack = this.currentTrack;
- if (!this.shouldLoadPlaylist(audioTrack)) {
- return;
- }
- if (useAlternateAudio(audioTrack.url, this.hls)) {
- this.scheduleLoading(audioTrack, hlsUrlParameters);
- }
- }
- loadingPlaylist(audioTrack, hlsUrlParameters) {
- super.loadingPlaylist(audioTrack, hlsUrlParameters);
- const id = audioTrack.id;
- const groupId = audioTrack.groupId;
- const url = this.getUrlWithDirectives(audioTrack.url, hlsUrlParameters);
- const details = audioTrack.details;
- const age = details == null ? void 0 : details.age;
- this.log(`Loading audio-track ${id} "${audioTrack.name}" lang:${audioTrack.lang} group:${groupId}${(hlsUrlParameters == null ? void 0 : hlsUrlParameters.msn) !== void 0 ? " at sn " + hlsUrlParameters.msn + " part " + hlsUrlParameters.part : ""}${age && details.live ? " age " + age.toFixed(1) + (details.type ? " " + details.type || "" : "") : ""} ${url}`);
- this.hls.trigger(Events.AUDIO_TRACK_LOADING, {
- url,
- id,
- groupId,
- deliveryDirectives: hlsUrlParameters || null,
- track: audioTrack
- });
- }
- };
- var BufferOperationQueue = class {
- constructor(sourceBufferReference) {
- this.tracks = void 0;
- this.queues = {
- video: [],
- audio: [],
- audiovideo: []
- };
- this.tracks = sourceBufferReference;
- }
- destroy() {
- this.tracks = this.queues = null;
- }
- append(operation, type, pending) {
- if (this.queues === null || this.tracks === null) {
- return;
- }
- const queue = this.queues[type];
- queue.push(operation);
- if (queue.length === 1 && !pending) {
- this.executeNext(type);
- }
- }
- appendBlocker(type) {
- return new Promise((resolve) => {
- const operation = {
- label: "async-blocker",
- execute: resolve,
- onStart: () => {
- },
- onComplete: () => {
- },
- onError: () => {
- }
- };
- this.append(operation, type);
- });
- }
- prependBlocker(type) {
- return new Promise((resolve) => {
- if (this.queues) {
- const operation = {
- label: "async-blocker-prepend",
- execute: resolve,
- onStart: () => {
- },
- onComplete: () => {
- },
- onError: () => {
- }
- };
- this.queues[type].unshift(operation);
- }
- });
- }
- removeBlockers() {
- if (this.queues === null) {
- return;
- }
- [this.queues.video, this.queues.audio, this.queues.audiovideo].forEach((queue) => {
- var _queue$;
- const label = (_queue$ = queue[0]) == null ? void 0 : _queue$.label;
- if (label === "async-blocker" || label === "async-blocker-prepend") {
- queue[0].execute();
- queue.splice(0, 1);
- }
- });
- }
- unblockAudio(op) {
- if (this.queues === null) {
- return;
- }
- const queue = this.queues.audio;
- if (queue[0] === op) {
- this.shiftAndExecuteNext("audio");
- }
- }
- executeNext(type) {
- if (this.queues === null || this.tracks === null) {
- return;
- }
- const queue = this.queues[type];
- if (queue.length) {
- const operation = queue[0];
- try {
- operation.execute();
- } catch (error) {
- var _this$tracks$type;
- operation.onError(error);
- if (this.queues === null || this.tracks === null) {
- return;
- }
- const sb = (_this$tracks$type = this.tracks[type]) == null ? void 0 : _this$tracks$type.buffer;
- if (!(sb != null && sb.updating)) {
- this.shiftAndExecuteNext(type);
- }
- }
- }
- }
- shiftAndExecuteNext(type) {
- if (this.queues === null) {
- return;
- }
- this.queues[type].shift();
- this.executeNext(type);
- }
- current(type) {
- var _this$queues;
- return ((_this$queues = this.queues) == null ? void 0 : _this$queues[type][0]) || null;
- }
- toString() {
- const {
- queues,
- tracks
- } = this;
- if (queues === null || tracks === null) {
- return `<destroyed>`;
- }
- return `
- ${this.list("video")}
- ${this.list("audio")}
- ${this.list("audiovideo")}}`;
- }
- list(type) {
- var _this$queues2, _this$tracks;
- return (_this$queues2 = this.queues) != null && _this$queues2[type] || (_this$tracks = this.tracks) != null && _this$tracks[type] ? `${type}: (${this.listSbInfo(type)}) ${this.listOps(type)}` : "";
- }
- listSbInfo(type) {
- var _this$tracks2;
- const track = (_this$tracks2 = this.tracks) == null ? void 0 : _this$tracks2[type];
- const sb = track == null ? void 0 : track.buffer;
- if (!sb) {
- return "none";
- }
- return `SourceBuffer${sb.updating ? " updating" : ""}${track.ended ? " ended" : ""}${track.ending ? " ending" : ""}`;
- }
- listOps(type) {
- var _this$queues3;
- return ((_this$queues3 = this.queues) == null ? void 0 : _this$queues3[type].map((op) => op.label).join(", ")) || "";
- }
- };
- var VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
- var TRACK_REMOVED_ERROR_NAME = "HlsJsTrackRemovedError";
- var HlsJsTrackRemovedError = class extends Error {
- constructor(message) {
- super(message);
- this.name = TRACK_REMOVED_ERROR_NAME;
- }
- };
- var BufferController = class extends Logger {
- constructor(hls, fragmentTracker) {
- super("buffer-controller", hls.logger);
- this.hls = void 0;
- this.fragmentTracker = void 0;
- this.details = null;
- this._objectUrl = null;
- this.operationQueue = null;
- this.bufferCodecEventsTotal = 0;
- this.media = null;
- this.mediaSource = null;
- this.lastMpegAudioChunk = null;
- this.blockedAudioAppend = null;
- this.lastVideoAppendEnd = 0;
- this.appendSource = void 0;
- this.transferData = void 0;
- this.overrides = void 0;
- this.appendErrors = {
- audio: 0,
- video: 0,
- audiovideo: 0
- };
- this.tracks = {};
- this.sourceBuffers = [[null, null], [null, null]];
- this._onEndStreaming = (event) => {
- var _this$mediaSource;
- if (!this.hls) {
- return;
- }
- if (((_this$mediaSource = this.mediaSource) == null ? void 0 : _this$mediaSource.readyState) !== "open") {
- return;
- }
- this.hls.pauseBuffering();
- };
- this._onStartStreaming = (event) => {
- if (!this.hls) {
- return;
- }
- this.hls.resumeBuffering();
- };
- this._onMediaSourceOpen = (e) => {
- const {
- media,
- mediaSource
- } = this;
- if (e) {
- this.log("Media source opened");
- }
- if (!media || !mediaSource) {
- return;
- }
- mediaSource.removeEventListener("sourceopen", this._onMediaSourceOpen);
- media.removeEventListener("emptied", this._onMediaEmptied);
- this.updateDuration();
- this.hls.trigger(Events.MEDIA_ATTACHED, {
- media,
- mediaSource
- });
- if (this.mediaSource !== null) {
- this.checkPendingTracks();
- }
- };
- this._onMediaSourceClose = () => {
- this.log("Media source closed");
- };
- this._onMediaSourceEnded = () => {
- this.log("Media source ended");
- };
- this._onMediaEmptied = () => {
- const {
- mediaSrc,
- _objectUrl
- } = this;
- if (mediaSrc !== _objectUrl) {
- this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
- }
- };
- this.hls = hls;
- this.fragmentTracker = fragmentTracker;
- this.appendSource = isManagedMediaSource(getMediaSource(hls.config.preferManagedMediaSource));
- this.initTracks();
- this.registerListeners();
- }
- hasSourceTypes() {
- return Object.keys(this.tracks).length > 0;
- }
- destroy() {
- this.unregisterListeners();
- this.details = null;
- this.lastMpegAudioChunk = this.blockedAudioAppend = null;
- this.transferData = this.overrides = void 0;
- if (this.operationQueue) {
- this.operationQueue.destroy();
- this.operationQueue = null;
- }
- this.hls = this.fragmentTracker = null;
- this._onMediaSourceOpen = this._onMediaSourceClose = null;
- this._onMediaSourceEnded = null;
- this._onStartStreaming = this._onEndStreaming = null;
- }
- registerListeners() {
- const {
- hls
- } = this;
- hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
- hls.on(Events.BUFFER_APPENDING, this.onBufferAppending, this);
- hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);
- hls.on(Events.BUFFER_EOS, this.onBufferEos, this);
- hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
- hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
- hls.on(Events.FRAG_PARSED, this.onFragParsed, this);
- hls.on(Events.FRAG_CHANGED, this.onFragChanged, this);
- hls.on(Events.ERROR, this.onError, this);
- }
- unregisterListeners() {
- const {
- hls
- } = this;
- hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
- hls.off(Events.BUFFER_APPENDING, this.onBufferAppending, this);
- hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
- hls.off(Events.BUFFER_EOS, this.onBufferEos, this);
- hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
- hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
- hls.off(Events.FRAG_PARSED, this.onFragParsed, this);
- hls.off(Events.FRAG_CHANGED, this.onFragChanged, this);
- hls.off(Events.ERROR, this.onError, this);
- }
- transferMedia() {
- const {
- media,
- mediaSource
- } = this;
- if (!media) {
- return null;
- }
- const tracks = {};
- if (this.operationQueue) {
- const updating = this.isUpdating();
- if (!updating) {
- this.operationQueue.removeBlockers();
- }
- const queued = this.isQueued();
- if (updating || queued) {
- this.warn(`Transfering MediaSource with${queued ? " operations in queue" : ""}${updating ? " updating SourceBuffer(s)" : ""} ${this.operationQueue}`);
- }
- this.operationQueue.destroy();
- }
- const transferData = this.transferData;
- if (!this.sourceBufferCount && transferData && transferData.mediaSource === mediaSource) {
- _extends(tracks, transferData.tracks);
- } else {
- this.sourceBuffers.forEach((tuple) => {
- const [type] = tuple;
- if (type) {
- tracks[type] = _extends({}, this.tracks[type]);
- this.removeBuffer(type);
- }
- tuple[0] = tuple[1] = null;
- });
- }
- return {
- media,
- mediaSource,
- tracks
- };
- }
- initTracks() {
- const tracks = {};
- this.sourceBuffers = [[null, null], [null, null]];
- this.tracks = tracks;
- this.resetQueue();
- this.resetAppendErrors();
- this.lastMpegAudioChunk = this.blockedAudioAppend = null;
- this.lastVideoAppendEnd = 0;
- }
- onManifestLoading() {
- this.bufferCodecEventsTotal = 0;
- this.details = null;
- }
- onManifestParsed(event, data) {
- var _this$transferData;
- let codecEvents = 2;
- if (data.audio && !data.video || !data.altAudio) {
- codecEvents = 1;
- }
- this.bufferCodecEventsTotal = codecEvents;
- this.log(`${codecEvents} bufferCodec event(s) expected.`);
- if ((_this$transferData = this.transferData) != null && _this$transferData.mediaSource && this.sourceBufferCount && codecEvents) {
- this.bufferCreated();
- }
- }
- onMediaAttaching(event, data) {
- const media = this.media = data.media;
- this.transferData = this.overrides = void 0;
- const MediaSource = getMediaSource(this.appendSource);
- if (MediaSource) {
- const transferringMedia = !!data.mediaSource;
- if (transferringMedia || data.overrides) {
- this.transferData = data;
- this.overrides = data.overrides;
- }
- const ms = this.mediaSource = data.mediaSource || new MediaSource();
- this.assignMediaSource(ms);
- if (transferringMedia) {
- this._objectUrl = media.src;
- this.attachTransferred();
- } else {
- const objectUrl = this._objectUrl = self.URL.createObjectURL(ms);
- if (this.appendSource) {
- try {
- media.removeAttribute("src");
- const MMS = self.ManagedMediaSource;
- media.disableRemotePlayback = media.disableRemotePlayback || MMS && ms instanceof MMS;
- removeSourceChildren(media);
- addSource(media, objectUrl);
- media.load();
- } catch (error) {
- media.src = objectUrl;
- }
- } else {
- media.src = objectUrl;
- }
- }
- media.addEventListener("emptied", this._onMediaEmptied);
- }
- }
- assignMediaSource(ms) {
- var _this$transferData2, _ms$constructor;
- this.log(`${((_this$transferData2 = this.transferData) == null ? void 0 : _this$transferData2.mediaSource) === ms ? "transferred" : "created"} media source: ${(_ms$constructor = ms.constructor) == null ? void 0 : _ms$constructor.name}`);
- ms.addEventListener("sourceopen", this._onMediaSourceOpen);
- ms.addEventListener("sourceended", this._onMediaSourceEnded);
- ms.addEventListener("sourceclose", this._onMediaSourceClose);
- if (this.appendSource) {
- ms.addEventListener("startstreaming", this._onStartStreaming);
- ms.addEventListener("endstreaming", this._onEndStreaming);
- }
- }
- attachTransferred() {
- const media = this.media;
- const data = this.transferData;
- if (!data || !media) {
- return;
- }
- const requiredTracks = this.tracks;
- const transferredTracks = data.tracks;
- const trackNames = transferredTracks ? Object.keys(transferredTracks) : null;
- const trackCount = trackNames ? trackNames.length : 0;
- const mediaSourceOpenCallback = () => {
- Promise.resolve().then(() => {
- if (this.media && this.mediaSourceOpenOrEnded) {
- this._onMediaSourceOpen();
- }
- });
- };
- if (transferredTracks && trackNames && trackCount) {
- if (!this.tracksReady) {
- this.hls.config.startFragPrefetch = true;
- this.log(`attachTransferred: waiting for SourceBuffer track info`);
- return;
- }
- this.log(`attachTransferred: (bufferCodecEventsTotal ${this.bufferCodecEventsTotal})
- required tracks: ${stringify(requiredTracks, (key, value) => key === "initSegment" ? void 0 : value)};
- transfer tracks: ${stringify(transferredTracks, (key, value) => key === "initSegment" ? void 0 : value)}}`);
- if (!isCompatibleTrackChange(transferredTracks, requiredTracks)) {
- data.mediaSource = null;
- data.tracks = void 0;
- const currentTime = media.currentTime;
- const details = this.details;
- const startTime = Math.max(currentTime, (details == null ? void 0 : details.fragments[0].start) || 0);
- if (startTime - currentTime > 1) {
- this.log(`attachTransferred: waiting for playback to reach new tracks start time ${currentTime} -> ${startTime}`);
- return;
- }
- this.warn(`attachTransferred: resetting MediaSource for incompatible tracks ("${Object.keys(transferredTracks)}"->"${Object.keys(requiredTracks)}") start time: ${startTime} currentTime: ${currentTime}`);
- this.onMediaDetaching(Events.MEDIA_DETACHING, {});
- this.onMediaAttaching(Events.MEDIA_ATTACHING, data);
- media.currentTime = startTime;
- return;
- }
- this.transferData = void 0;
- trackNames.forEach((trackName) => {
- const type = trackName;
- const track = transferredTracks[type];
- if (track) {
- const sb = track.buffer;
- if (sb) {
- const fragmentTracker = this.fragmentTracker;
- const playlistType = track.id;
- if (fragmentTracker.hasFragments(playlistType) || fragmentTracker.hasParts(playlistType)) {
- const bufferedTimeRanges = BufferHelper.getBuffered(sb);
- fragmentTracker.detectEvictedFragments(type, bufferedTimeRanges, playlistType, null, true);
- }
- const sbIndex = sourceBufferNameToIndex(type);
- const sbTuple = [type, sb];
- this.sourceBuffers[sbIndex] = sbTuple;
- if (sb.updating && this.operationQueue) {
- this.operationQueue.prependBlocker(type);
- }
- this.trackSourceBuffer(type, track);
- }
- }
- });
- mediaSourceOpenCallback();
- this.bufferCreated();
- } else {
- this.log(`attachTransferred: MediaSource w/o SourceBuffers`);
- mediaSourceOpenCallback();
- }
- }
- get mediaSourceOpenOrEnded() {
- var _this$mediaSource2;
- const readyState = (_this$mediaSource2 = this.mediaSource) == null ? void 0 : _this$mediaSource2.readyState;
- return readyState === "open" || readyState === "ended";
- }
- onMediaDetaching(event, data) {
- const transferringMedia = !!data.transferMedia;
- this.transferData = this.overrides = void 0;
- const {
- media,
- mediaSource,
- _objectUrl
- } = this;
- if (mediaSource) {
- this.log(`media source ${transferringMedia ? "transferring" : "detaching"}`);
- if (transferringMedia) {
- this.sourceBuffers.forEach(([type]) => {
- if (type) {
- this.removeBuffer(type);
- }
- });
- this.resetQueue();
- } else {
- if (this.mediaSourceOpenOrEnded) {
- const open = mediaSource.readyState === "open";
- try {
- const sourceBuffers = mediaSource.sourceBuffers;
- for (let i = sourceBuffers.length; i--; ) {
- if (open) {
- sourceBuffers[i].abort();
- }
- mediaSource.removeSourceBuffer(sourceBuffers[i]);
- }
- if (open) {
- mediaSource.endOfStream();
- }
- } catch (err) {
- this.warn(`onMediaDetaching: ${err.message} while calling endOfStream`);
- }
- }
- if (this.sourceBufferCount) {
- this.onBufferReset();
- }
- }
- mediaSource.removeEventListener("sourceopen", this._onMediaSourceOpen);
- mediaSource.removeEventListener("sourceended", this._onMediaSourceEnded);
- mediaSource.removeEventListener("sourceclose", this._onMediaSourceClose);
- if (this.appendSource) {
- mediaSource.removeEventListener("startstreaming", this._onStartStreaming);
- mediaSource.removeEventListener("endstreaming", this._onEndStreaming);
- }
- this.mediaSource = null;
- this._objectUrl = null;
- }
- if (media) {
- media.removeEventListener("emptied", this._onMediaEmptied);
- if (!transferringMedia) {
- if (_objectUrl) {
- self.URL.revokeObjectURL(_objectUrl);
- }
- if (this.mediaSrc === _objectUrl) {
- media.removeAttribute("src");
- if (this.appendSource) {
- removeSourceChildren(media);
- }
- media.load();
- } else {
- this.warn("media|source.src was changed by a third party - skip cleanup");
- }
- }
- this.media = null;
- }
- this.hls.trigger(Events.MEDIA_DETACHED, data);
- }
- onBufferReset() {
- this.sourceBuffers.forEach(([type]) => {
- if (type) {
- this.resetBuffer(type);
- }
- });
- this.initTracks();
- }
- resetBuffer(type) {
- var _this$tracks$type;
- const sb = (_this$tracks$type = this.tracks[type]) == null ? void 0 : _this$tracks$type.buffer;
- this.removeBuffer(type);
- if (sb) {
- try {
- var _this$mediaSource3;
- if ((_this$mediaSource3 = this.mediaSource) != null && _this$mediaSource3.sourceBuffers.length) {
- this.mediaSource.removeSourceBuffer(sb);
- }
- } catch (err) {
- this.warn(`onBufferReset ${type}`, err);
- }
- }
- delete this.tracks[type];
- }
- removeBuffer(type) {
- this.removeBufferListeners(type);
- this.sourceBuffers[sourceBufferNameToIndex(type)] = [null, null];
- const track = this.tracks[type];
- if (track) {
- track.buffer = void 0;
- }
- }
- resetQueue() {
- if (this.operationQueue) {
- this.operationQueue.destroy();
- }
- this.operationQueue = new BufferOperationQueue(this.tracks);
- }
- onBufferCodecs(event, data) {
- var _data$audio;
- const tracks = this.tracks;
- const trackNames = Object.keys(data);
- this.log(`BUFFER_CODECS: "${trackNames}" (current SB count ${this.sourceBufferCount})`);
- const unmuxedToMuxed = "audiovideo" in data && (tracks.audio || tracks.video) || tracks.audiovideo && ("audio" in data || "video" in data);
- const muxedToUnmuxed = !unmuxedToMuxed && this.sourceBufferCount && this.media && trackNames.some((sbName) => !tracks[sbName]);
- if (unmuxedToMuxed || muxedToUnmuxed) {
- this.warn(`Unsupported transition between "${Object.keys(tracks)}" and "${trackNames}" SourceBuffers`);
- return;
- }
- trackNames.forEach((trackName) => {
- var _this$transferData3, _trackCodec;
- const parsedTrack = data[trackName];
- const {
- id,
- codec,
- levelCodec,
- container,
- metadata,
- supplemental
- } = parsedTrack;
- let track = tracks[trackName];
- const transferredTrack = (_this$transferData3 = this.transferData) == null || (_this$transferData3 = _this$transferData3.tracks) == null ? void 0 : _this$transferData3[trackName];
- const sbTrack = transferredTrack != null && transferredTrack.buffer ? transferredTrack : track;
- const sbCodec = (sbTrack == null ? void 0 : sbTrack.pendingCodec) || (sbTrack == null ? void 0 : sbTrack.codec);
- const trackLevelCodec = sbTrack == null ? void 0 : sbTrack.levelCodec;
- if (!track) {
- track = tracks[trackName] = {
- buffer: void 0,
- listeners: [],
- codec,
- supplemental,
- container,
- levelCodec,
- metadata,
- id
- };
- }
- const currentCodecFull = pickMostCompleteCodecName(sbCodec, trackLevelCodec);
- const currentCodec = currentCodecFull == null ? void 0 : currentCodecFull.replace(VIDEO_CODEC_PROFILE_REPLACE, "$1");
- let trackCodec = pickMostCompleteCodecName(codec, levelCodec);
- const nextCodec = (_trackCodec = trackCodec) == null ? void 0 : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, "$1");
- if (trackCodec && currentCodecFull && currentCodec !== nextCodec) {
- if (trackName.slice(0, 5) === "audio") {
- trackCodec = getCodecCompatibleName(trackCodec, this.appendSource);
- }
- this.log(`switching codec ${sbCodec} to ${trackCodec}`);
- if (trackCodec !== (track.pendingCodec || track.codec)) {
- track.pendingCodec = trackCodec;
- }
- track.container = container;
- this.appendChangeType(trackName, container, trackCodec);
- }
- });
- if (this.tracksReady || this.sourceBufferCount) {
- data.tracks = this.sourceBufferTracks;
- }
- if (this.sourceBufferCount) {
- return;
- }
- if (this.bufferCodecEventsTotal > 1 && !this.tracks.video && !data.video && ((_data$audio = data.audio) == null ? void 0 : _data$audio.id) === "main") {
- this.log(`Main audio-only`);
- this.bufferCodecEventsTotal = 1;
- }
- if (this.mediaSourceOpenOrEnded) {
- this.checkPendingTracks();
- }
- }
- get sourceBufferTracks() {
- return Object.keys(this.tracks).reduce((baseTracks, type) => {
- const track = this.tracks[type];
- baseTracks[type] = {
- id: track.id,
- container: track.container,
- codec: track.codec,
- levelCodec: track.levelCodec
- };
- return baseTracks;
- }, {});
- }
- appendChangeType(type, container, codec) {
- const mimeType = `${container};codecs=${codec}`;
- const operation = {
- label: `change-type=${mimeType}`,
- execute: () => {
- const track = this.tracks[type];
- if (track) {
- const sb = track.buffer;
- if (sb != null && sb.changeType) {
- this.log(`changing ${type} sourceBuffer type to ${mimeType}`);
- sb.changeType(mimeType);
- track.codec = codec;
- track.container = container;
- }
- }
- this.shiftAndExecuteNext(type);
- },
- onStart: () => {
- },
- onComplete: () => {
- },
- onError: (error) => {
- this.warn(`Failed to change ${type} SourceBuffer type`, error);
- }
- };
- this.append(operation, type, this.isPending(this.tracks[type]));
- }
- blockAudio(partOrFrag) {
- var _this$fragmentTracker;
- const pStart = partOrFrag.start;
- const pTime = pStart + partOrFrag.duration * 0.05;
- const atGap = ((_this$fragmentTracker = this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker.gap) === true;
- if (atGap) {
- return;
- }
- const op = {
- label: "block-audio",
- execute: () => {
- var _this$fragmentTracker2;
- const videoTrack = this.tracks.video;
- if (this.lastVideoAppendEnd > pTime || videoTrack != null && videoTrack.buffer && BufferHelper.isBuffered(videoTrack.buffer, pTime) || ((_this$fragmentTracker2 = this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker2.gap) === true) {
- this.blockedAudioAppend = null;
- this.shiftAndExecuteNext("audio");
- }
- },
- onStart: () => {
- },
- onComplete: () => {
- },
- onError: (error) => {
- this.warn("Error executing block-audio operation", error);
- }
- };
- this.blockedAudioAppend = {
- op,
- frag: partOrFrag
- };
- this.append(op, "audio", true);
- }
- unblockAudio() {
- const {
- blockedAudioAppend,
- operationQueue
- } = this;
- if (blockedAudioAppend && operationQueue) {
- this.blockedAudioAppend = null;
- operationQueue.unblockAudio(blockedAudioAppend.op);
- }
- }
- onBufferAppending(event, eventData) {
- const {
- tracks
- } = this;
- const {
- data,
- type,
- parent,
- frag,
- part,
- chunkMeta,
- offset
- } = eventData;
- const chunkStats = chunkMeta.buffering[type];
- const {
- sn,
- cc
- } = frag;
- const bufferAppendingStart = self.performance.now();
- chunkStats.start = bufferAppendingStart;
- const fragBuffering = frag.stats.buffering;
- const partBuffering = part ? part.stats.buffering : null;
- if (fragBuffering.start === 0) {
- fragBuffering.start = bufferAppendingStart;
- }
- if (partBuffering && partBuffering.start === 0) {
- partBuffering.start = bufferAppendingStart;
- }
- const audioTrack = tracks.audio;
- let checkTimestampOffset = false;
- if (type === "audio" && (audioTrack == null ? void 0 : audioTrack.container) === "audio/mpeg") {
- checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
- this.lastMpegAudioChunk = chunkMeta;
- }
- const videoTrack = tracks.video;
- const videoSb = videoTrack == null ? void 0 : videoTrack.buffer;
- if (videoSb && sn !== "initSegment") {
- const partOrFrag = part || frag;
- const blockedAudioAppend = this.blockedAudioAppend;
- if (type === "audio" && parent !== "main" && !this.blockedAudioAppend && !(videoTrack.ending || videoTrack.ended)) {
- const pStart = partOrFrag.start;
- const pTime = pStart + partOrFrag.duration * 0.05;
- const vbuffered = videoSb.buffered;
- const vappending = this.currentOp("video");
- if (!vbuffered.length && !vappending) {
- this.blockAudio(partOrFrag);
- } else if (!vappending && !BufferHelper.isBuffered(videoSb, pTime) && this.lastVideoAppendEnd < pTime) {
- this.blockAudio(partOrFrag);
- }
- } else if (type === "video") {
- const videoAppendEnd = partOrFrag.end;
- if (blockedAudioAppend) {
- const audioStart = blockedAudioAppend.frag.start;
- if (videoAppendEnd > audioStart || videoAppendEnd < this.lastVideoAppendEnd || BufferHelper.isBuffered(videoSb, audioStart)) {
- this.unblockAudio();
- }
- }
- this.lastVideoAppendEnd = videoAppendEnd;
- }
- }
- const fragStart = (part || frag).start;
- const operation = {
- label: `append-${type}`,
- execute: () => {
- var _this$tracks$type2;
- chunkStats.executeStart = self.performance.now();
- const sb = (_this$tracks$type2 = this.tracks[type]) == null ? void 0 : _this$tracks$type2.buffer;
- if (sb) {
- if (checkTimestampOffset) {
- this.updateTimestampOffset(sb, fragStart, 0.1, type, sn, cc);
- } else if (offset !== void 0 && isFiniteNumber(offset)) {
- this.updateTimestampOffset(sb, offset, 1e-6, type, sn, cc);
- }
- }
- this.appendExecutor(data, type);
- },
- onStart: () => {
- },
- onComplete: () => {
- const end = self.performance.now();
- chunkStats.executeEnd = chunkStats.end = end;
- if (fragBuffering.first === 0) {
- fragBuffering.first = end;
- }
- if (partBuffering && partBuffering.first === 0) {
- partBuffering.first = end;
- }
- const timeRanges = {};
- this.sourceBuffers.forEach(([type2, sb]) => {
- if (type2) {
- timeRanges[type2] = BufferHelper.getBuffered(sb);
- }
- });
- this.appendErrors[type] = 0;
- if (type === "audio" || type === "video") {
- this.appendErrors.audiovideo = 0;
- } else {
- this.appendErrors.audio = 0;
- this.appendErrors.video = 0;
- }
- this.hls.trigger(Events.BUFFER_APPENDED, {
- type,
- frag,
- part,
- chunkMeta,
- parent: frag.type,
- timeRanges
- });
- },
- onError: (error) => {
- var _this$media;
- const event2 = {
- type: ErrorTypes.MEDIA_ERROR,
- parent: frag.type,
- details: ErrorDetails.BUFFER_APPEND_ERROR,
- sourceBufferName: type,
- frag,
- part,
- chunkMeta,
- error,
- err: error,
- fatal: false
- };
- const mediaError = (_this$media = this.media) == null ? void 0 : _this$media.error;
- if (error.code === DOMException.QUOTA_EXCEEDED_ERR || error.name == "QuotaExceededError" || `quota` in error) {
- event2.details = ErrorDetails.BUFFER_FULL_ERROR;
- } else if (error.code === DOMException.INVALID_STATE_ERR && this.mediaSourceOpenOrEnded && !mediaError) {
- event2.errorAction = createDoNothingErrorAction(true);
- } else if (error.name === TRACK_REMOVED_ERROR_NAME && this.sourceBufferCount === 0) {
- event2.errorAction = createDoNothingErrorAction(true);
- } else {
- const appendErrorCount = ++this.appendErrors[type];
- this.warn(`Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer (${mediaError ? mediaError : "no media error"})`);
- if (appendErrorCount >= this.hls.config.appendErrorMaxRetry || !!mediaError) {
- event2.fatal = true;
- }
- }
- this.hls.trigger(Events.ERROR, event2);
- }
- };
- this.log(`queuing "${type}" append sn: ${sn}${part ? " p: " + part.index : ""} of ${frag.type === PlaylistLevelType.MAIN ? "level" : "track"} ${frag.level} cc: ${cc}`);
- this.append(operation, type, this.isPending(this.tracks[type]));
- }
- getFlushOp(type, start, end) {
- this.log(`queuing "${type}" remove ${start}-${end}`);
- return {
- label: "remove",
- execute: () => {
- this.removeExecutor(type, start, end);
- },
- onStart: () => {
- },
- onComplete: () => {
- this.hls.trigger(Events.BUFFER_FLUSHED, {
- type
- });
- },
- onError: (error) => {
- this.warn(`Failed to remove ${start}-${end} from "${type}" SourceBuffer`, error);
- }
- };
- }
- onBufferFlushing(event, data) {
- const {
- type,
- startOffset,
- endOffset
- } = data;
- if (type) {
- this.append(this.getFlushOp(type, startOffset, endOffset), type);
- } else {
- this.sourceBuffers.forEach(([type2]) => {
- if (type2) {
- this.append(this.getFlushOp(type2, startOffset, endOffset), type2);
- }
- });
- }
- }
- onFragParsed(event, data) {
- const {
- frag,
- part
- } = data;
- const buffersAppendedTo = [];
- const elementaryStreams = part ? part.elementaryStreams : frag.elementaryStreams;
- if (elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO]) {
- buffersAppendedTo.push("audiovideo");
- } else {
- if (elementaryStreams[ElementaryStreamTypes.AUDIO]) {
- buffersAppendedTo.push("audio");
- }
- if (elementaryStreams[ElementaryStreamTypes.VIDEO]) {
- buffersAppendedTo.push("video");
- }
- }
- const onUnblocked = () => {
- const now2 = self.performance.now();
- frag.stats.buffering.end = now2;
- if (part) {
- part.stats.buffering.end = now2;
- }
- const stats = part ? part.stats : frag.stats;
- this.hls.trigger(Events.FRAG_BUFFERED, {
- frag,
- part,
- stats,
- id: frag.type
- });
- };
- if (buffersAppendedTo.length === 0) {
- this.warn(`Fragments must have at least one ElementaryStreamType set. type: ${frag.type} level: ${frag.level} sn: ${frag.sn}`);
- }
- this.blockBuffers(onUnblocked, buffersAppendedTo).catch((error) => {
- this.warn(`Fragment buffered callback ${error}`);
- this.stepOperationQueue(this.sourceBufferTypes);
- });
- }
- onFragChanged(event, data) {
- this.trimBuffers();
- }
- get bufferedToEnd() {
- return this.sourceBufferCount > 0 && !this.sourceBuffers.some(([type]) => {
- if (type) {
- const track = this.tracks[type];
- if (track) {
- return !track.ended || track.ending;
- }
- }
- return false;
- });
- }
- // on BUFFER_EOS mark matching sourcebuffer(s) as "ending" and "ended" and queue endOfStream after remaining operations(s)
- // an undefined data.type will mark all buffers as EOS.
- onBufferEos(event, data) {
- var _this$overrides;
- this.sourceBuffers.forEach(([type]) => {
- if (type) {
- const track = this.tracks[type];
- if (!data.type || data.type === type) {
- track.ending = true;
- if (!track.ended) {
- track.ended = true;
- this.log(`${type} buffer reached EOS`);
- }
- }
- }
- });
- const allowEndOfStream = ((_this$overrides = this.overrides) == null ? void 0 : _this$overrides.endOfStream) !== false;
- const allTracksEnding = this.sourceBufferCount > 0 && !this.sourceBuffers.some(([type]) => {
- var _this$tracks$type3;
- return type && !((_this$tracks$type3 = this.tracks[type]) != null && _this$tracks$type3.ended);
- });
- if (allTracksEnding) {
- if (allowEndOfStream) {
- this.log(`Queueing EOS`);
- this.blockUntilOpen(() => {
- this.tracksEnded();
- const {
- mediaSource
- } = this;
- if (!mediaSource || mediaSource.readyState !== "open") {
- if (mediaSource) {
- this.log(`Could not call mediaSource.endOfStream(). mediaSource.readyState: ${mediaSource.readyState}`);
- }
- return;
- }
- this.log(`Calling mediaSource.endOfStream()`);
- mediaSource.endOfStream();
- this.hls.trigger(Events.BUFFERED_TO_END, void 0);
- });
- } else {
- this.tracksEnded();
- this.hls.trigger(Events.BUFFERED_TO_END, void 0);
- }
- } else if (data.type === "video") {
- this.unblockAudio();
- }
- }
- tracksEnded() {
- this.sourceBuffers.forEach(([type]) => {
- if (type !== null) {
- const track = this.tracks[type];
- if (track) {
- track.ending = false;
- }
- }
- });
- }
- onLevelUpdated(event, {
- details
- }) {
- if (!details.fragments.length) {
- return;
- }
- this.details = details;
- this.updateDuration();
- }
- updateDuration() {
- this.blockUntilOpen(() => {
- const durationAndRange = this.getDurationAndRange();
- if (!durationAndRange) {
- return;
- }
- this.updateMediaSource(durationAndRange);
- });
- }
- onError(event, data) {
- if (data.details === ErrorDetails.BUFFER_APPEND_ERROR && data.frag) {
- var _data$errorAction;
- const nextAutoLevel = (_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.nextAutoLevel;
- if (isFiniteNumber(nextAutoLevel) && nextAutoLevel !== data.frag.level) {
- this.resetAppendErrors();
- }
- }
- }
- resetAppendErrors() {
- this.appendErrors = {
- audio: 0,
- video: 0,
- audiovideo: 0
- };
- }
- trimBuffers() {
- const {
- hls,
- details,
- media
- } = this;
- if (!media || details === null) {
- return;
- }
- if (!this.sourceBufferCount) {
- return;
- }
- const config = hls.config;
- const currentTime = media.currentTime;
- const targetDuration = details.levelTargetDuration;
- const backBufferLength = details.live && config.liveBackBufferLength !== null ? config.liveBackBufferLength : config.backBufferLength;
- if (isFiniteNumber(backBufferLength) && backBufferLength >= 0) {
- const maxBackBufferLength = Math.max(backBufferLength, targetDuration);
- const targetBackBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration - maxBackBufferLength;
- this.flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition);
- }
- const frontBufferFlushThreshold = config.frontBufferFlushThreshold;
- if (isFiniteNumber(frontBufferFlushThreshold) && frontBufferFlushThreshold > 0) {
- const frontBufferLength = Math.max(config.maxBufferLength, frontBufferFlushThreshold);
- const maxFrontBufferLength = Math.max(frontBufferLength, targetDuration);
- const targetFrontBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration + maxFrontBufferLength;
- this.flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition);
- }
- }
- flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition) {
- this.sourceBuffers.forEach(([type, sb]) => {
- if (sb) {
- const buffered = BufferHelper.getBuffered(sb);
- if (buffered.length > 0 && targetBackBufferPosition > buffered.start(0)) {
- var _this$details;
- this.hls.trigger(Events.BACK_BUFFER_REACHED, {
- bufferEnd: targetBackBufferPosition
- });
- const track = this.tracks[type];
- if ((_this$details = this.details) != null && _this$details.live) {
- this.hls.trigger(Events.LIVE_BACK_BUFFER_REACHED, {
- bufferEnd: targetBackBufferPosition
- });
- } else if (track != null && track.ended) {
- this.log(`Cannot flush ${type} back buffer while SourceBuffer is in ended state`);
- return;
- }
- this.hls.trigger(Events.BUFFER_FLUSHING, {
- startOffset: 0,
- endOffset: targetBackBufferPosition,
- type
- });
- }
- }
- });
- }
- flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition) {
- this.sourceBuffers.forEach(([type, sb]) => {
- if (sb) {
- const buffered = BufferHelper.getBuffered(sb);
- const numBufferedRanges = buffered.length;
- if (numBufferedRanges < 2) {
- return;
- }
- const bufferStart = buffered.start(numBufferedRanges - 1);
- const bufferEnd = buffered.end(numBufferedRanges - 1);
- if (targetFrontBufferPosition > bufferStart || currentTime >= bufferStart && currentTime <= bufferEnd) {
- return;
- }
- this.hls.trigger(Events.BUFFER_FLUSHING, {
- startOffset: bufferStart,
- endOffset: Infinity,
- type
- });
- }
- });
- }
- /**
- * Update Media Source duration to current level duration or override to Infinity if configuration parameter
- * 'liveDurationInfinity` is set to `true`
- * More details: https://github.com/video-dev/hls.js/issues/355
- */
- getDurationAndRange() {
- var _this$overrides2;
- const {
- details,
- mediaSource
- } = this;
- if (!details || !this.media || (mediaSource == null ? void 0 : mediaSource.readyState) !== "open") {
- return null;
- }
- const playlistEnd = details.edge;
- if (details.live && this.hls.config.liveDurationInfinity) {
- const len = details.fragments.length;
- if (len && !!mediaSource.setLiveSeekableRange) {
- const start = Math.max(0, details.fragmentStart);
- const end = Math.max(start, playlistEnd);
- return {
- duration: Infinity,
- start,
- end
- };
- }
- return {
- duration: Infinity
- };
- }
- const overrideDuration = (_this$overrides2 = this.overrides) == null ? void 0 : _this$overrides2.duration;
- if (overrideDuration) {
- if (!isFiniteNumber(overrideDuration)) {
- return null;
- }
- return {
- duration: overrideDuration
- };
- }
- const mediaDuration = this.media.duration;
- const msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : 0;
- if (playlistEnd > msDuration && playlistEnd > mediaDuration || !isFiniteNumber(mediaDuration)) {
- return {
- duration: playlistEnd
- };
- }
- return null;
- }
- updateMediaSource({
- duration,
- start,
- end
- }) {
- const mediaSource = this.mediaSource;
- if (!this.media || !mediaSource || mediaSource.readyState !== "open") {
- return;
- }
- if (mediaSource.duration !== duration) {
- if (isFiniteNumber(duration)) {
- this.log(`Updating MediaSource duration to ${duration.toFixed(3)}`);
- }
- mediaSource.duration = duration;
- }
- if (start !== void 0 && end !== void 0) {
- this.log(`MediaSource duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
- mediaSource.setLiveSeekableRange(start, end);
- }
- }
- get tracksReady() {
- const pendingTrackCount = this.pendingTrackCount;
- return pendingTrackCount > 0 && (pendingTrackCount >= this.bufferCodecEventsTotal || this.isPending(this.tracks.audiovideo));
- }
- checkPendingTracks() {
- const {
- bufferCodecEventsTotal,
- pendingTrackCount,
- tracks
- } = this;
- this.log(`checkPendingTracks (pending: ${pendingTrackCount} codec events expected: ${bufferCodecEventsTotal}) ${stringify(tracks)}`);
- if (this.tracksReady) {
- var _this$transferData4;
- const transferredTracks = (_this$transferData4 = this.transferData) == null ? void 0 : _this$transferData4.tracks;
- if (transferredTracks && Object.keys(transferredTracks).length) {
- this.attachTransferred();
- } else {
- this.createSourceBuffers();
- }
- }
- }
- bufferCreated() {
- if (this.sourceBufferCount) {
- const tracks = {};
- this.sourceBuffers.forEach(([type, buffer]) => {
- if (type) {
- const track = this.tracks[type];
- tracks[type] = {
- buffer,
- container: track.container,
- codec: track.codec,
- supplemental: track.supplemental,
- levelCodec: track.levelCodec,
- id: track.id,
- metadata: track.metadata
- };
- }
- });
- this.hls.trigger(Events.BUFFER_CREATED, {
- tracks
- });
- this.log(`SourceBuffers created. Running queue: ${this.operationQueue}`);
- this.sourceBuffers.forEach(([type]) => {
- this.executeNext(type);
- });
- } else {
- const error = new Error("could not create source buffer for media codec(s)");
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.BUFFER_INCOMPATIBLE_CODECS_ERROR,
- fatal: true,
- error,
- reason: error.message
- });
- }
- }
- createSourceBuffers() {
- const {
- tracks,
- sourceBuffers,
- mediaSource
- } = this;
- if (!mediaSource) {
- throw new Error("createSourceBuffers called when mediaSource was null");
- }
- for (const trackName in tracks) {
- const type = trackName;
- const track = tracks[type];
- if (this.isPending(track)) {
- const codec = this.getTrackCodec(track, type);
- const mimeType = `${track.container};codecs=${codec}`;
- track.codec = codec;
- this.log(`creating sourceBuffer(${mimeType})${this.currentOp(type) ? " Queued" : ""} ${stringify(track)}`);
- try {
- const sb = mediaSource.addSourceBuffer(mimeType);
- const sbIndex = sourceBufferNameToIndex(type);
- const sbTuple = [type, sb];
- sourceBuffers[sbIndex] = sbTuple;
- track.buffer = sb;
- } catch (error) {
- var _this$operationQueue;
- this.error(`error while trying to add sourceBuffer: ${error.message}`);
- this.shiftAndExecuteNext(type);
- (_this$operationQueue = this.operationQueue) == null || _this$operationQueue.removeBlockers();
- delete this.tracks[type];
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.BUFFER_ADD_CODEC_ERROR,
- fatal: false,
- error,
- sourceBufferName: type,
- mimeType,
- parent: track.id
- });
- return;
- }
- this.trackSourceBuffer(type, track);
- }
- }
- this.bufferCreated();
- }
- getTrackCodec(track, trackName) {
- const supplementalCodec = track.supplemental;
- let trackCodec = track.codec;
- if (supplementalCodec && (trackName === "video" || trackName === "audiovideo") && areCodecsMediaSourceSupported(supplementalCodec, "video")) {
- trackCodec = replaceVideoCodec(trackCodec, supplementalCodec);
- }
- const codec = pickMostCompleteCodecName(trackCodec, track.levelCodec);
- if (codec) {
- if (trackName.slice(0, 5) === "audio") {
- return getCodecCompatibleName(codec, this.appendSource);
- }
- return codec;
- }
- return "";
- }
- trackSourceBuffer(type, track) {
- const buffer = track.buffer;
- if (!buffer) {
- return;
- }
- const codec = this.getTrackCodec(track, type);
- this.tracks[type] = {
- buffer,
- codec,
- container: track.container,
- levelCodec: track.levelCodec,
- supplemental: track.supplemental,
- metadata: track.metadata,
- id: track.id,
- listeners: []
- };
- this.removeBufferListeners(type);
- this.addBufferListener(type, "updatestart", this.onSBUpdateStart);
- this.addBufferListener(type, "updateend", this.onSBUpdateEnd);
- this.addBufferListener(type, "error", this.onSBUpdateError);
- if (this.appendSource) {
- this.addBufferListener(type, "bufferedchange", (type2, event) => {
- const removedRanges = event.removedRanges;
- if (removedRanges != null && removedRanges.length) {
- this.hls.trigger(Events.BUFFER_FLUSHED, {
- type: type2
- });
- }
- });
- }
- }
- get mediaSrc() {
- var _this$media2, _this$media2$querySel;
- const media = ((_this$media2 = this.media) == null || (_this$media2$querySel = _this$media2.querySelector) == null ? void 0 : _this$media2$querySel.call(_this$media2, "source")) || this.media;
- return media == null ? void 0 : media.src;
- }
- onSBUpdateStart(type) {
- const operation = this.currentOp(type);
- if (!operation) {
- return;
- }
- operation.onStart();
- }
- onSBUpdateEnd(type) {
- var _this$mediaSource4;
- if (((_this$mediaSource4 = this.mediaSource) == null ? void 0 : _this$mediaSource4.readyState) === "closed") {
- this.resetBuffer(type);
- return;
- }
- const operation = this.currentOp(type);
- if (!operation) {
- return;
- }
- operation.onComplete();
- this.shiftAndExecuteNext(type);
- }
- onSBUpdateError(type, event) {
- var _this$mediaSource5;
- const error = new Error(`${type} SourceBuffer error. MediaSource readyState: ${(_this$mediaSource5 = this.mediaSource) == null ? void 0 : _this$mediaSource5.readyState}`);
- this.error(`${error}`, event);
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.BUFFER_APPENDING_ERROR,
- sourceBufferName: type,
- error,
- fatal: false
- });
- const operation = this.currentOp(type);
- if (operation) {
- operation.onError(error);
- }
- }
- updateTimestampOffset(sb, timestampOffset, tolerance, type, sn, cc) {
- const delta = timestampOffset - sb.timestampOffset;
- if (Math.abs(delta) >= tolerance) {
- this.log(`Updating ${type} SourceBuffer timestampOffset to ${timestampOffset} (sn: ${sn} cc: ${cc})`);
- sb.timestampOffset = timestampOffset;
- }
- }
- // This method must result in an updateend event; if remove is not called, onSBUpdateEnd must be called manually
- removeExecutor(type, startOffset, endOffset) {
- const {
- media,
- mediaSource
- } = this;
- const track = this.tracks[type];
- const sb = track == null ? void 0 : track.buffer;
- if (!media || !mediaSource || !sb) {
- this.warn(`Attempting to remove from the ${type} SourceBuffer, but it does not exist`);
- this.shiftAndExecuteNext(type);
- return;
- }
- const mediaDuration = isFiniteNumber(media.duration) ? media.duration : Infinity;
- const msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : Infinity;
- const removeStart = Math.max(0, startOffset);
- const removeEnd = Math.min(endOffset, mediaDuration, msDuration);
- if (removeEnd > removeStart && (!track.ending || track.ended)) {
- track.ended = false;
- this.log(`Removing [${removeStart},${removeEnd}] from the ${type} SourceBuffer`);
- sb.remove(removeStart, removeEnd);
- } else {
- this.shiftAndExecuteNext(type);
- }
- }
- // This method must result in an updateend event; if append is not called, onSBUpdateEnd must be called manually
- appendExecutor(data, type) {
- const track = this.tracks[type];
- const sb = track == null ? void 0 : track.buffer;
- if (!sb) {
- throw new HlsJsTrackRemovedError(`Attempting to append to the ${type} SourceBuffer, but it does not exist`);
- }
- track.ending = false;
- track.ended = false;
- sb.appendBuffer(data);
- }
- blockUntilOpen(callback) {
- if (this.isUpdating() || this.isQueued()) {
- this.blockBuffers(callback).catch((error) => {
- this.warn(`SourceBuffer blocked callback ${error}`);
- this.stepOperationQueue(this.sourceBufferTypes);
- });
- } else {
- try {
- callback();
- } catch (error) {
- this.warn(`Callback run without blocking ${this.operationQueue} ${error}`);
- }
- }
- }
- isUpdating() {
- return this.sourceBuffers.some(([type, sb]) => type && sb.updating);
- }
- isQueued() {
- return this.sourceBuffers.some(([type]) => type && !!this.currentOp(type));
- }
- isPending(track) {
- return !!track && !track.buffer;
- }
- // Enqueues an operation to each SourceBuffer queue which, upon execution, resolves a promise. When all promises
- // resolve, the onUnblocked function is executed. Functions calling this method do not need to unblock the queue
- // upon completion, since we already do it here
- blockBuffers(onUnblocked, bufferNames = this.sourceBufferTypes) {
- if (!bufferNames.length) {
- this.log("Blocking operation requested, but no SourceBuffers exist");
- return Promise.resolve().then(onUnblocked);
- }
- const {
- operationQueue
- } = this;
- const blockingOperations = bufferNames.map((type) => this.appendBlocker(type));
- const audioBlocked = bufferNames.length > 1 && !!this.blockedAudioAppend;
- if (audioBlocked) {
- this.unblockAudio();
- }
- return Promise.all(blockingOperations).then((result) => {
- if (operationQueue !== this.operationQueue) {
- return;
- }
- onUnblocked();
- this.stepOperationQueue(this.sourceBufferTypes);
- });
- }
- stepOperationQueue(bufferNames) {
- bufferNames.forEach((type) => {
- var _this$tracks$type4;
- const sb = (_this$tracks$type4 = this.tracks[type]) == null ? void 0 : _this$tracks$type4.buffer;
- if (!sb || sb.updating) {
- return;
- }
- this.shiftAndExecuteNext(type);
- });
- }
- append(operation, type, pending) {
- if (this.operationQueue) {
- this.operationQueue.append(operation, type, pending);
- }
- }
- appendBlocker(type) {
- if (this.operationQueue) {
- return this.operationQueue.appendBlocker(type);
- }
- }
- currentOp(type) {
- if (this.operationQueue) {
- return this.operationQueue.current(type);
- }
- return null;
- }
- executeNext(type) {
- if (type && this.operationQueue) {
- this.operationQueue.executeNext(type);
- }
- }
- shiftAndExecuteNext(type) {
- if (this.operationQueue) {
- this.operationQueue.shiftAndExecuteNext(type);
- }
- }
- get pendingTrackCount() {
- return Object.keys(this.tracks).reduce((acc, type) => acc + (this.isPending(this.tracks[type]) ? 1 : 0), 0);
- }
- get sourceBufferCount() {
- return this.sourceBuffers.reduce((acc, [type]) => acc + (type ? 1 : 0), 0);
- }
- get sourceBufferTypes() {
- return this.sourceBuffers.map(([type]) => type).filter((type) => !!type);
- }
- addBufferListener(type, event, fn) {
- const track = this.tracks[type];
- if (!track) {
- return;
- }
- const buffer = track.buffer;
- if (!buffer) {
- return;
- }
- const listener = fn.bind(this, type);
- track.listeners.push({
- event,
- listener
- });
- buffer.addEventListener(event, listener);
- }
- removeBufferListeners(type) {
- const track = this.tracks[type];
- if (!track) {
- return;
- }
- const buffer = track.buffer;
- if (!buffer) {
- return;
- }
- track.listeners.forEach((l) => {
- buffer.removeEventListener(l.event, l.listener);
- });
- track.listeners.length = 0;
- }
- };
- function removeSourceChildren(node) {
- const sourceChildren = node.querySelectorAll("source");
- [].slice.call(sourceChildren).forEach((source) => {
- node.removeChild(source);
- });
- }
- function addSource(media, url) {
- const source = self.document.createElement("source");
- source.type = "video/mp4";
- source.src = url;
- media.appendChild(source);
- }
- function sourceBufferNameToIndex(type) {
- return type === "audio" ? 1 : 0;
- }
- var CapLevelController = class _CapLevelController {
- constructor(hls) {
- this.hls = void 0;
- this.autoLevelCapping = void 0;
- this.firstLevel = void 0;
- this.media = void 0;
- this.restrictedLevels = void 0;
- this.timer = void 0;
- this.clientRect = void 0;
- this.streamController = void 0;
- this.hls = hls;
- this.autoLevelCapping = Number.POSITIVE_INFINITY;
- this.firstLevel = -1;
- this.media = null;
- this.restrictedLevels = [];
- this.timer = void 0;
- this.clientRect = null;
- this.registerListeners();
- }
- setStreamController(streamController) {
- this.streamController = streamController;
- }
- destroy() {
- if (this.hls) {
- this.unregisterListener();
- }
- if (this.timer) {
- this.stopCapping();
- }
- this.media = null;
- this.clientRect = null;
- this.hls = this.streamController = null;
- }
- registerListeners() {
- const {
- hls
- } = this;
- hls.on(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this);
- hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
- hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- }
- unregisterListener() {
- const {
- hls
- } = this;
- hls.off(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this);
- hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
- hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- }
- onFpsDropLevelCapping(event, data) {
- const level = this.hls.levels[data.droppedLevel];
- if (this.isLevelAllowed(level)) {
- this.restrictedLevels.push({
- bitrate: level.bitrate,
- height: level.height,
- width: level.width
- });
- }
- }
- onMediaAttaching(event, data) {
- this.media = data.media instanceof HTMLVideoElement ? data.media : null;
- this.clientRect = null;
- if (this.timer && this.hls.levels.length) {
- this.detectPlayerSize();
- }
- }
- onManifestParsed(event, data) {
- const hls = this.hls;
- this.restrictedLevels = [];
- this.firstLevel = data.firstLevel;
- if (hls.config.capLevelToPlayerSize && data.video) {
- this.startCapping();
- }
- }
- onLevelsUpdated(event, data) {
- if (this.timer && isFiniteNumber(this.autoLevelCapping)) {
- this.detectPlayerSize();
- }
- }
- // Only activate capping when playing a video stream; otherwise, multi-bitrate audio-only streams will be restricted
- // to the first level
- onBufferCodecs(event, data) {
- const hls = this.hls;
- if (hls.config.capLevelToPlayerSize && data.video) {
- this.startCapping();
- }
- }
- onMediaDetaching() {
- this.stopCapping();
- this.media = null;
- }
- detectPlayerSize() {
- if (this.media) {
- if (this.mediaHeight <= 0 || this.mediaWidth <= 0) {
- this.clientRect = null;
- return;
- }
- const levels = this.hls.levels;
- if (levels.length) {
- const hls = this.hls;
- const maxLevel = this.getMaxLevel(levels.length - 1);
- if (maxLevel !== this.autoLevelCapping) {
- hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
- }
- hls.autoLevelCapping = maxLevel;
- if (hls.autoLevelEnabled && hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
- this.streamController.nextLevelSwitch();
- }
- this.autoLevelCapping = hls.autoLevelCapping;
- }
- }
- }
- /*
- * returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled)
- */
- getMaxLevel(capLevelIndex) {
- const levels = this.hls.levels;
- if (!levels.length) {
- return -1;
- }
- const validLevels = levels.filter((level, index) => this.isLevelAllowed(level) && index <= capLevelIndex);
- this.clientRect = null;
- return _CapLevelController.getMaxLevelByMediaSize(validLevels, this.mediaWidth, this.mediaHeight);
- }
- startCapping() {
- if (this.timer) {
- return;
- }
- this.autoLevelCapping = Number.POSITIVE_INFINITY;
- self.clearInterval(this.timer);
- this.timer = self.setInterval(this.detectPlayerSize.bind(this), 1e3);
- this.detectPlayerSize();
- }
- stopCapping() {
- this.restrictedLevels = [];
- this.firstLevel = -1;
- this.autoLevelCapping = Number.POSITIVE_INFINITY;
- if (this.timer) {
- self.clearInterval(this.timer);
- this.timer = void 0;
- }
- }
- getDimensions() {
- if (this.clientRect) {
- return this.clientRect;
- }
- const media = this.media;
- const boundsRect = {
- width: 0,
- height: 0
- };
- if (media) {
- const clientRect = media.getBoundingClientRect();
- boundsRect.width = clientRect.width;
- boundsRect.height = clientRect.height;
- if (!boundsRect.width && !boundsRect.height) {
- boundsRect.width = clientRect.right - clientRect.left || media.width || 0;
- boundsRect.height = clientRect.bottom - clientRect.top || media.height || 0;
- }
- }
- this.clientRect = boundsRect;
- return boundsRect;
- }
- get mediaWidth() {
- return this.getDimensions().width * this.contentScaleFactor;
- }
- get mediaHeight() {
- return this.getDimensions().height * this.contentScaleFactor;
- }
- get contentScaleFactor() {
- let pixelRatio = 1;
- if (!this.hls.config.ignoreDevicePixelRatio) {
- try {
- pixelRatio = self.devicePixelRatio;
- } catch (e) {
- }
- }
- return Math.min(pixelRatio, this.hls.config.maxDevicePixelRatio);
- }
- isLevelAllowed(level) {
- const restrictedLevels = this.restrictedLevels;
- return !restrictedLevels.some((restrictedLevel) => {
- return level.bitrate === restrictedLevel.bitrate && level.width === restrictedLevel.width && level.height === restrictedLevel.height;
- });
- }
- static getMaxLevelByMediaSize(levels, width, height) {
- if (!(levels != null && levels.length)) {
- return -1;
- }
- const atGreatestBandwidth = (curLevel, nextLevel) => {
- if (!nextLevel) {
- return true;
- }
- return curLevel.width !== nextLevel.width || curLevel.height !== nextLevel.height;
- };
- let maxLevelIndex = levels.length - 1;
- const squareSize = Math.max(width, height);
- for (let i = 0; i < levels.length; i += 1) {
- const level = levels[i];
- if ((level.width >= squareSize || level.height >= squareSize) && atGreatestBandwidth(level, levels[i + 1])) {
- maxLevelIndex = i;
- break;
- }
- }
- return maxLevelIndex;
- }
- };
- var CmObjectType = {
- /**
- * text file, such as a manifest or playlist
- */
- MANIFEST: "m",
- /**
- * audio only
- */
- AUDIO: "a",
- /**
- * video only
- */
- VIDEO: "v",
- /**
- * muxed audio and video
- */
- MUXED: "av",
- /**
- * init segment
- */
- INIT: "i",
- /**
- * caption or subtitle
- */
- CAPTION: "c",
- /**
- * ISOBMFF timed text track
- */
- TIMED_TEXT: "tt",
- /**
- * cryptographic key, license or certificate.
- */
- KEY: "k",
- /**
- * other
- */
- OTHER: "o"
- };
- var CmcdObjectType = CmObjectType;
- var CmStreamingFormat = {
- /**
- * HTTP Live Streaming (HLS)
- */
- HLS: "h"
- };
- var CmcdStreamingFormat = CmStreamingFormat;
- var SfItem = class _SfItem {
- constructor(value, params) {
- if (Array.isArray(value)) {
- value = value.map((v) => v instanceof _SfItem ? v : new _SfItem(v));
- }
- this.value = value;
- this.params = params;
- }
- };
- var DICT = "Dict";
- function format(value) {
- if (Array.isArray(value)) {
- return JSON.stringify(value);
- }
- if (value instanceof Map) {
- return "Map{}";
- }
- if (value instanceof Set) {
- return "Set{}";
- }
- if (typeof value === "object") {
- return JSON.stringify(value);
- }
- return String(value);
- }
- function throwError(action, src, type, cause) {
- return new Error(`failed to ${action} "${format(src)}" as ${type}`, {
- cause
- });
- }
- function serializeError(src, type, cause) {
- return throwError("serialize", src, type, cause);
- }
- var SfToken = class {
- constructor(description) {
- this.description = description;
- }
- };
- var BARE_ITEM = "Bare Item";
- var BOOLEAN = "Boolean";
- function serializeBoolean(value) {
- if (typeof value !== "boolean") {
- throw serializeError(value, BOOLEAN);
- }
- return value ? "?1" : "?0";
- }
- function encodeBase64(binary) {
- return btoa(String.fromCharCode(...binary));
- }
- var BYTES = "Byte Sequence";
- function serializeByteSequence(value) {
- if (ArrayBuffer.isView(value) === false) {
- throw serializeError(value, BYTES);
- }
- return `:${encodeBase64(value)}:`;
- }
- var INTEGER = "Integer";
- function isInvalidInt(value) {
- return value < -999999999999999 || 999999999999999 < value;
- }
- function serializeInteger(value) {
- if (isInvalidInt(value)) {
- throw serializeError(value, INTEGER);
- }
- return value.toString();
- }
- function serializeDate(value) {
- return `@${serializeInteger(value.getTime() / 1e3)}`;
- }
- function roundToEven(value, precision) {
- if (value < 0) {
- return -roundToEven(-value, precision);
- }
- const decimalShift = Math.pow(10, precision);
- const isEquidistant = Math.abs(value * decimalShift % 1 - 0.5) < Number.EPSILON;
- if (isEquidistant) {
- const flooredValue = Math.floor(value * decimalShift);
- return (flooredValue % 2 === 0 ? flooredValue : flooredValue + 1) / decimalShift;
- } else {
- return Math.round(value * decimalShift) / decimalShift;
- }
- }
- var DECIMAL = "Decimal";
- function serializeDecimal(value) {
- const roundedValue = roundToEven(value, 3);
- if (Math.floor(Math.abs(roundedValue)).toString().length > 12) {
- throw serializeError(value, DECIMAL);
- }
- const stringValue = roundedValue.toString();
- return stringValue.includes(".") ? stringValue : `${stringValue}.0`;
- }
- var STRING = "String";
- var STRING_REGEX = /[\x00-\x1f\x7f]+/;
- function serializeString(value) {
- if (STRING_REGEX.test(value)) {
- throw serializeError(value, STRING);
- }
- return `"${value.replace(/\\/g, `\\\\`).replace(/"/g, `\\"`)}"`;
- }
- function symbolToStr(symbol) {
- return symbol.description || symbol.toString().slice(7, -1);
- }
- var TOKEN = "Token";
- function serializeToken(token) {
- const value = symbolToStr(token);
- if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
- throw serializeError(value, TOKEN);
- }
- return value;
- }
- function serializeBareItem(value) {
- switch (typeof value) {
- case "number":
- if (!isFiniteNumber(value)) {
- throw serializeError(value, BARE_ITEM);
- }
- if (Number.isInteger(value)) {
- return serializeInteger(value);
- }
- return serializeDecimal(value);
- case "string":
- return serializeString(value);
- case "symbol":
- return serializeToken(value);
- case "boolean":
- return serializeBoolean(value);
- case "object":
- if (value instanceof Date) {
- return serializeDate(value);
- }
- if (value instanceof Uint8Array) {
- return serializeByteSequence(value);
- }
- if (value instanceof SfToken) {
- return serializeToken(value);
- }
- default:
- throw serializeError(value, BARE_ITEM);
- }
- }
- var KEY = "Key";
- function serializeKey(value) {
- if (/^[a-z*][a-z0-9\-_.*]*$/.test(value) === false) {
- throw serializeError(value, KEY);
- }
- return value;
- }
- function serializeParams(params) {
- if (params == null) {
- return "";
- }
- return Object.entries(params).map(([key, value]) => {
- if (value === true) {
- return `;${serializeKey(key)}`;
- }
- return `;${serializeKey(key)}=${serializeBareItem(value)}`;
- }).join("");
- }
- function serializeItem(value) {
- if (value instanceof SfItem) {
- return `${serializeBareItem(value.value)}${serializeParams(value.params)}`;
- } else {
- return serializeBareItem(value);
- }
- }
- function serializeInnerList(value) {
- return `(${value.value.map(serializeItem).join(" ")})${serializeParams(value.params)}`;
- }
- function serializeDict(dict, options = {
- whitespace: true
- }) {
- if (typeof dict !== "object" || dict == null) {
- throw serializeError(dict, DICT);
- }
- const entries = dict instanceof Map ? dict.entries() : Object.entries(dict);
- const optionalWhiteSpace = (options === null || options === void 0 ? void 0 : options.whitespace) ? " " : "";
- return Array.from(entries).map(([key, item]) => {
- if (item instanceof SfItem === false) {
- item = new SfItem(item);
- }
- let output = serializeKey(key);
- if (item.value === true) {
- output += serializeParams(item.params);
- } else {
- output += "=";
- if (Array.isArray(item.value)) {
- output += serializeInnerList(item);
- } else {
- output += serializeItem(item);
- }
- }
- return output;
- }).join(`,${optionalWhiteSpace}`);
- }
- function encodeSfDict(value, options) {
- return serializeDict(value, options);
- }
- var CMCD_OBJECT = "CMCD-Object";
- var CMCD_REQUEST = "CMCD-Request";
- var CMCD_SESSION = "CMCD-Session";
- var CMCD_STATUS = "CMCD-Status";
- var CMCD_HEADER_MAP = {
- // Object
- br: CMCD_OBJECT,
- ab: CMCD_OBJECT,
- d: CMCD_OBJECT,
- ot: CMCD_OBJECT,
- tb: CMCD_OBJECT,
- tpb: CMCD_OBJECT,
- lb: CMCD_OBJECT,
- tab: CMCD_OBJECT,
- lab: CMCD_OBJECT,
- url: CMCD_OBJECT,
- // Request
- pb: CMCD_REQUEST,
- bl: CMCD_REQUEST,
- tbl: CMCD_REQUEST,
- dl: CMCD_REQUEST,
- ltc: CMCD_REQUEST,
- mtp: CMCD_REQUEST,
- nor: CMCD_REQUEST,
- nrr: CMCD_REQUEST,
- rc: CMCD_REQUEST,
- sn: CMCD_REQUEST,
- sta: CMCD_REQUEST,
- su: CMCD_REQUEST,
- ttfb: CMCD_REQUEST,
- ttfbb: CMCD_REQUEST,
- ttlb: CMCD_REQUEST,
- cmsdd: CMCD_REQUEST,
- cmsds: CMCD_REQUEST,
- smrt: CMCD_REQUEST,
- df: CMCD_REQUEST,
- cs: CMCD_REQUEST,
- // TODO: Which header to put the `ts` field is not defined yet.
- ts: CMCD_REQUEST,
- // Session
- cid: CMCD_SESSION,
- pr: CMCD_SESSION,
- sf: CMCD_SESSION,
- sid: CMCD_SESSION,
- st: CMCD_SESSION,
- v: CMCD_SESSION,
- msd: CMCD_SESSION,
- // Status
- bs: CMCD_STATUS,
- bsd: CMCD_STATUS,
- cdn: CMCD_STATUS,
- rtp: CMCD_STATUS,
- bg: CMCD_STATUS,
- pt: CMCD_STATUS,
- ec: CMCD_STATUS,
- e: CMCD_STATUS
- };
- var CmcdHeaderField = {
- /**
- * keys whose values vary with each request.
- */
- REQUEST: CMCD_REQUEST
- };
- function createHeaderMap(headerMap) {
- return Object.keys(headerMap).reduce((acc, field) => {
- var _a;
- (_a = headerMap[field]) === null || _a === void 0 ? void 0 : _a.forEach((key) => acc[key] = field);
- return acc;
- }, {});
- }
- function groupCmcdHeaders(cmcd, customHeaderMap) {
- const result = {};
- if (!cmcd) {
- return result;
- }
- const keys = Object.keys(cmcd);
- const custom = customHeaderMap ? createHeaderMap(customHeaderMap) : {};
- return keys.reduce((acc, key) => {
- var _a;
- const field = CMCD_HEADER_MAP[key] || custom[key] || CmcdHeaderField.REQUEST;
- const data = (_a = acc[field]) !== null && _a !== void 0 ? _a : acc[field] = {};
- data[key] = cmcd[key];
- return acc;
- }, result);
- }
- function isTokenField(key) {
- return ["ot", "sf", "st", "e", "sta"].includes(key);
- }
- function isValid(value) {
- if (typeof value === "number") {
- return isFiniteNumber(value);
- }
- return value != null && value !== "" && value !== false;
- }
- var CMCD_EVENT_MODE = "event";
- function urlToRelativePath(url, base) {
- const to = new URL(url);
- const from = new URL(base);
- if (to.origin !== from.origin) {
- return url;
- }
- const toPath = to.pathname.split("/").slice(1);
- const fromPath = from.pathname.split("/").slice(1, -1);
- while (toPath[0] === fromPath[0]) {
- toPath.shift();
- fromPath.shift();
- }
- while (fromPath.length) {
- fromPath.shift();
- toPath.unshift("..");
- }
- const relativePath = toPath.join("/");
- return relativePath + to.search + to.hash;
- }
- var toRounded = (value) => Math.round(value);
- var toUrlSafe = (value, options) => {
- if (Array.isArray(value)) {
- return value.map((item) => toUrlSafe(item, options));
- }
- if (value instanceof SfItem && typeof value.value === "string") {
- return new SfItem(toUrlSafe(value.value, options), value.params);
- } else {
- if (options.baseUrl) {
- value = urlToRelativePath(value, options.baseUrl);
- }
- return options.version === 1 ? encodeURIComponent(value) : value;
- }
- };
- var toHundred = (value) => toRounded(value / 100) * 100;
- var nor = (value, options) => {
- let norValue = value;
- if (options.version >= 2) {
- if (value instanceof SfItem && typeof value.value === "string") {
- norValue = new SfItem([value]);
- } else if (typeof value === "string") {
- norValue = [value];
- }
- }
- return toUrlSafe(norValue, options);
- };
- var CMCD_FORMATTER_MAP = {
- /**
- * Bitrate (kbps) rounded integer
- */
- br: toRounded,
- /**
- * Duration (milliseconds) rounded integer
- */
- d: toRounded,
- /**
- * Buffer Length (milliseconds) rounded nearest 100ms
- */
- bl: toHundred,
- /**
- * Deadline (milliseconds) rounded nearest 100ms
- */
- dl: toHundred,
- /**
- * Measured Throughput (kbps) rounded nearest 100kbps
- */
- mtp: toHundred,
- /**
- * Next Object Request URL encoded
- */
- nor,
- /**
- * Requested maximum throughput (kbps) rounded nearest 100kbps
- */
- rtp: toHundred,
- /**
- * Top Bitrate (kbps) rounded integer
- */
- tb: toRounded
- };
- var CMCD_REQUEST_MODE = "request";
- var CMCD_RESPONSE_MODE = "response";
- var CMCD_COMMON_KEYS = ["ab", "bg", "bl", "br", "bs", "bsd", "cdn", "cid", "cs", "df", "ec", "lab", "lb", "ltc", "msd", "mtp", "pb", "pr", "pt", "sf", "sid", "sn", "st", "sta", "tab", "tb", "tbl", "tpb", "ts", "v"];
- var CMCD_EVENT_KEYS = ["e"];
- var CUSTOM_KEY_REGEX = /^[a-zA-Z0-9-.]+-[a-zA-Z0-9-.]+$/;
- function isCmcdCustomKey(key) {
- return CUSTOM_KEY_REGEX.test(key);
- }
- function isCmcdEventKey(key) {
- return CMCD_COMMON_KEYS.includes(key) || CMCD_EVENT_KEYS.includes(key) || isCmcdCustomKey(key);
- }
- var CMCD_REQUEST_KEYS = ["d", "dl", "nor", "ot", "rtp", "su"];
- function isCmcdRequestKey(key) {
- return CMCD_COMMON_KEYS.includes(key) || CMCD_REQUEST_KEYS.includes(key) || isCmcdCustomKey(key);
- }
- var CMCD_RESPONSE_KEYS = ["cmsdd", "cmsds", "rc", "smrt", "ttfb", "ttfbb", "ttlb", "url"];
- function isCmcdResponseKey(key) {
- return CMCD_COMMON_KEYS.includes(key) || CMCD_REQUEST_KEYS.includes(key) || CMCD_RESPONSE_KEYS.includes(key) || isCmcdCustomKey(key);
- }
- var CMCD_V1_KEYS = ["bl", "br", "bs", "cid", "d", "dl", "mtp", "nor", "nrr", "ot", "pr", "rtp", "sf", "sid", "st", "su", "tb", "v"];
- function isCmcdV1Key(key) {
- return CMCD_V1_KEYS.includes(key) || isCmcdCustomKey(key);
- }
- var filterMap = {
- [CMCD_RESPONSE_MODE]: isCmcdResponseKey,
- [CMCD_EVENT_MODE]: isCmcdEventKey,
- [CMCD_REQUEST_MODE]: isCmcdRequestKey
- };
- function prepareCmcdData(obj, options = {}) {
- const results = {};
- if (obj == null || typeof obj !== "object") {
- return results;
- }
- const version2 = options.version || obj["v"] || 1;
- const reportingMode = options.reportingMode || CMCD_REQUEST_MODE;
- const keyFilter = version2 === 1 ? isCmcdV1Key : filterMap[reportingMode];
- let keys = Object.keys(obj).filter(keyFilter);
- const filter = options.filter;
- if (typeof filter === "function") {
- keys = keys.filter(filter);
- }
- const needsTimestamp = reportingMode === CMCD_RESPONSE_MODE || reportingMode === CMCD_EVENT_MODE;
- if (needsTimestamp && !keys.includes("ts")) {
- keys.push("ts");
- }
- if (version2 > 1 && !keys.includes("v")) {
- keys.push("v");
- }
- const formatters = _extends({}, CMCD_FORMATTER_MAP, options.formatters);
- const formatterOptions = {
- version: version2,
- reportingMode,
- baseUrl: options.baseUrl
- };
- keys.sort().forEach((key) => {
- let value = obj[key];
- const formatter = formatters[key];
- if (typeof formatter === "function") {
- value = formatter(value, formatterOptions);
- }
- if (key === "v") {
- if (version2 === 1) {
- return;
- } else {
- value = version2;
- }
- }
- if (key == "pr" && value === 1) {
- return;
- }
- if (needsTimestamp && key === "ts" && !isFiniteNumber(value)) {
- value = Date.now();
- }
- if (!isValid(value)) {
- return;
- }
- if (isTokenField(key) && typeof value === "string") {
- value = new SfToken(value);
- }
- results[key] = value;
- });
- return results;
- }
- function toCmcdHeaders(cmcd, options = {}) {
- const result = {};
- if (!cmcd) {
- return result;
- }
- const data = prepareCmcdData(cmcd, options);
- const shards = groupCmcdHeaders(data, options === null || options === void 0 ? void 0 : options.customHeaderMap);
- return Object.entries(shards).reduce((acc, [field, value]) => {
- const shard = encodeSfDict(value, {
- whitespace: false
- });
- if (shard) {
- acc[field] = shard;
- }
- return acc;
- }, result);
- }
- function appendCmcdHeaders(headers, cmcd, options) {
- return _extends(headers, toCmcdHeaders(cmcd, options));
- }
- var CMCD_PARAM = "CMCD";
- function encodeCmcd(cmcd, options = {}) {
- if (!cmcd) {
- return "";
- }
- return encodeSfDict(prepareCmcdData(cmcd, options), {
- whitespace: false
- });
- }
- function toCmcdUrl(cmcd, options = {}) {
- if (!cmcd) {
- return "";
- }
- const params = encodeCmcd(cmcd, options);
- return encodeURIComponent(params);
- }
- function toCmcdQuery(cmcd, options = {}) {
- if (!cmcd) {
- return "";
- }
- const value = toCmcdUrl(cmcd, options);
- return `${CMCD_PARAM}=${value}`;
- }
- var REGEX = /CMCD=[^&#]+/;
- function appendCmcdQuery(url, cmcd, options) {
- const query = toCmcdQuery(cmcd, options);
- if (!query) {
- return url;
- }
- if (REGEX.test(url)) {
- return url.replace(REGEX, query);
- }
- const separator = url.includes("?") ? "&" : "?";
- return `${url}${separator}${query}`;
- }
- var CMCDController = class {
- constructor(hls) {
- this.hls = void 0;
- this.config = void 0;
- this.media = void 0;
- this.sid = void 0;
- this.cid = void 0;
- this.useHeaders = false;
- this.includeKeys = void 0;
- this.initialized = false;
- this.starved = false;
- this.buffering = true;
- this.audioBuffer = void 0;
- this.videoBuffer = void 0;
- this.onWaiting = () => {
- if (this.initialized) {
- this.starved = true;
- }
- this.buffering = true;
- };
- this.onPlaying = () => {
- if (!this.initialized) {
- this.initialized = true;
- }
- this.buffering = false;
- };
- this.applyPlaylistData = (context) => {
- try {
- this.apply(context, {
- ot: CmcdObjectType.MANIFEST,
- su: !this.initialized
- });
- } catch (error) {
- this.hls.logger.warn("Could not generate manifest CMCD data.", error);
- }
- };
- this.applyFragmentData = (context) => {
- try {
- const {
- frag,
- part
- } = context;
- const level = this.hls.levels[frag.level];
- const ot = this.getObjectType(frag);
- const data = {
- d: (part || frag).duration * 1e3,
- ot
- };
- if (ot === CmcdObjectType.VIDEO || ot === CmcdObjectType.AUDIO || ot == CmcdObjectType.MUXED) {
- data.br = level.bitrate / 1e3;
- data.tb = this.getTopBandwidth(ot) / 1e3;
- data.bl = this.getBufferLength(ot);
- }
- const next = part ? this.getNextPart(part) : this.getNextFrag(frag);
- if (next != null && next.url && next.url !== frag.url) {
- data.nor = next.url;
- }
- this.apply(context, data);
- } catch (error) {
- this.hls.logger.warn("Could not generate segment CMCD data.", error);
- }
- };
- this.hls = hls;
- const config = this.config = hls.config;
- const {
- cmcd
- } = config;
- if (cmcd != null) {
- config.pLoader = this.createPlaylistLoader();
- config.fLoader = this.createFragmentLoader();
- this.sid = cmcd.sessionId || hls.sessionId;
- this.cid = cmcd.contentId;
- this.useHeaders = cmcd.useHeaders === true;
- this.includeKeys = cmcd.includeKeys;
- this.registerListeners();
- }
- }
- registerListeners() {
- const hls = this.hls;
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this);
- hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
- }
- unregisterListeners() {
- const hls = this.hls;
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this);
- hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
- }
- destroy() {
- this.unregisterListeners();
- this.onMediaDetached();
- this.hls = this.config = this.audioBuffer = this.videoBuffer = null;
- this.onWaiting = this.onPlaying = this.media = null;
- }
- onMediaAttached(event, data) {
- this.media = data.media;
- this.media.addEventListener("waiting", this.onWaiting);
- this.media.addEventListener("playing", this.onPlaying);
- }
- onMediaDetached() {
- if (!this.media) {
- return;
- }
- this.media.removeEventListener("waiting", this.onWaiting);
- this.media.removeEventListener("playing", this.onPlaying);
- this.media = null;
- }
- onBufferCreated(event, data) {
- var _data$tracks$audio, _data$tracks$video;
- this.audioBuffer = (_data$tracks$audio = data.tracks.audio) == null ? void 0 : _data$tracks$audio.buffer;
- this.videoBuffer = (_data$tracks$video = data.tracks.video) == null ? void 0 : _data$tracks$video.buffer;
- }
- /**
- * Create baseline CMCD data
- */
- createData() {
- var _this$media;
- return {
- v: 1,
- sf: CmcdStreamingFormat.HLS,
- sid: this.sid,
- cid: this.cid,
- pr: (_this$media = this.media) == null ? void 0 : _this$media.playbackRate,
- mtp: this.hls.bandwidthEstimate / 1e3
- };
- }
- /**
- * Apply CMCD data to a request.
- */
- apply(context, data = {}) {
- _extends(data, this.createData());
- const isVideo = data.ot === CmcdObjectType.INIT || data.ot === CmcdObjectType.VIDEO || data.ot === CmcdObjectType.MUXED;
- if (this.starved && isVideo) {
- data.bs = true;
- data.su = true;
- this.starved = false;
- }
- if (data.su == null) {
- data.su = this.buffering;
- }
- const {
- includeKeys
- } = this;
- if (includeKeys) {
- data = Object.keys(data).reduce((acc, key) => {
- includeKeys.includes(key) && (acc[key] = data[key]);
- return acc;
- }, {});
- }
- const options = {
- baseUrl: context.url
- };
- if (this.useHeaders) {
- if (!context.headers) {
- context.headers = {};
- }
- appendCmcdHeaders(context.headers, data, options);
- } else {
- context.url = appendCmcdQuery(context.url, data, options);
- }
- }
- getNextFrag(fragment) {
- var _this$hls$levels$frag;
- const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
- if (levelDetails) {
- const index = fragment.sn - levelDetails.startSN;
- return levelDetails.fragments[index + 1];
- }
- return void 0;
- }
- getNextPart(part) {
- var _this$hls$levels$frag2;
- const {
- index,
- fragment
- } = part;
- const partList = (_this$hls$levels$frag2 = this.hls.levels[fragment.level]) == null || (_this$hls$levels$frag2 = _this$hls$levels$frag2.details) == null ? void 0 : _this$hls$levels$frag2.partList;
- if (partList) {
- const {
- sn
- } = fragment;
- for (let i = partList.length - 1; i >= 0; i--) {
- const p = partList[i];
- if (p.index === index && p.fragment.sn === sn) {
- return partList[i + 1];
- }
- }
- }
- return void 0;
- }
- /**
- * The CMCD object type.
- */
- getObjectType(fragment) {
- const {
- type
- } = fragment;
- if (type === "subtitle") {
- return CmcdObjectType.TIMED_TEXT;
- }
- if (fragment.sn === "initSegment") {
- return CmcdObjectType.INIT;
- }
- if (type === "audio") {
- return CmcdObjectType.AUDIO;
- }
- if (type === "main") {
- if (!this.hls.audioTracks.length) {
- return CmcdObjectType.MUXED;
- }
- return CmcdObjectType.VIDEO;
- }
- return void 0;
- }
- /**
- * Get the highest bitrate.
- */
- getTopBandwidth(type) {
- let bitrate = 0;
- let levels;
- const hls = this.hls;
- if (type === CmcdObjectType.AUDIO) {
- levels = hls.audioTracks;
- } else {
- const max = hls.maxAutoLevel;
- const len = max > -1 ? max + 1 : hls.levels.length;
- levels = hls.levels.slice(0, len);
- }
- levels.forEach((level) => {
- if (level.bitrate > bitrate) {
- bitrate = level.bitrate;
- }
- });
- return bitrate > 0 ? bitrate : NaN;
- }
- /**
- * Get the buffer length for a media type in milliseconds
- */
- getBufferLength(type) {
- const media = this.media;
- const buffer = type === CmcdObjectType.AUDIO ? this.audioBuffer : this.videoBuffer;
- if (!buffer || !media) {
- return NaN;
- }
- const info = BufferHelper.bufferInfo(buffer, media.currentTime, this.config.maxBufferHole);
- return info.len * 1e3;
- }
- /**
- * Create a playlist loader
- */
- createPlaylistLoader() {
- const {
- pLoader
- } = this.config;
- const apply = this.applyPlaylistData;
- const Ctor = pLoader || this.config.loader;
- return class CmcdPlaylistLoader {
- constructor(config) {
- this.loader = void 0;
- this.loader = new Ctor(config);
- }
- get stats() {
- return this.loader.stats;
- }
- get context() {
- return this.loader.context;
- }
- destroy() {
- this.loader.destroy();
- }
- abort() {
- this.loader.abort();
- }
- load(context, config, callbacks) {
- apply(context);
- this.loader.load(context, config, callbacks);
- }
- };
- }
- /**
- * Create a playlist loader
- */
- createFragmentLoader() {
- const {
- fLoader
- } = this.config;
- const apply = this.applyFragmentData;
- const Ctor = fLoader || this.config.loader;
- return class CmcdFragmentLoader {
- constructor(config) {
- this.loader = void 0;
- this.loader = new Ctor(config);
- }
- get stats() {
- return this.loader.stats;
- }
- get context() {
- return this.loader.context;
- }
- destroy() {
- this.loader.destroy();
- }
- abort() {
- this.loader.abort();
- }
- load(context, config, callbacks) {
- apply(context);
- this.loader.load(context, config, callbacks);
- }
- };
- }
- };
- var PATHWAY_PENALTY_DURATION_MS = 3e5;
- var ContentSteeringController = class extends Logger {
- constructor(hls) {
- super("content-steering", hls.logger);
- this.hls = void 0;
- this.loader = null;
- this.uri = null;
- this.pathwayId = ".";
- this._pathwayPriority = null;
- this.timeToLoad = 300;
- this.reloadTimer = -1;
- this.updated = 0;
- this.started = false;
- this.enabled = true;
- this.levels = null;
- this.audioTracks = null;
- this.subtitleTracks = null;
- this.penalizedPathways = {};
- this.hls = hls;
- this.registerListeners();
- }
- registerListeners() {
- const hls = this.hls;
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
- hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.on(Events.ERROR, this.onError, this);
- }
- unregisterListeners() {
- const hls = this.hls;
- if (!hls) {
- return;
- }
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
- hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.off(Events.ERROR, this.onError, this);
- }
- pathways() {
- return (this.levels || []).reduce((pathways, level) => {
- if (pathways.indexOf(level.pathwayId) === -1) {
- pathways.push(level.pathwayId);
- }
- return pathways;
- }, []);
- }
- get pathwayPriority() {
- return this._pathwayPriority;
- }
- set pathwayPriority(pathwayPriority) {
- this.updatePathwayPriority(pathwayPriority);
- }
- startLoad() {
- this.started = true;
- this.clearTimeout();
- if (this.enabled && this.uri) {
- if (this.updated) {
- const ttl = this.timeToLoad * 1e3 - (performance.now() - this.updated);
- if (ttl > 0) {
- this.scheduleRefresh(this.uri, ttl);
- return;
- }
- }
- this.loadSteeringManifest(this.uri);
- }
- }
- stopLoad() {
- this.started = false;
- if (this.loader) {
- this.loader.destroy();
- this.loader = null;
- }
- this.clearTimeout();
- }
- clearTimeout() {
- if (this.reloadTimer !== -1) {
- self.clearTimeout(this.reloadTimer);
- this.reloadTimer = -1;
- }
- }
- destroy() {
- this.unregisterListeners();
- this.stopLoad();
- this.hls = null;
- this.levels = this.audioTracks = this.subtitleTracks = null;
- }
- removeLevel(levelToRemove) {
- const levels = this.levels;
- if (levels) {
- this.levels = levels.filter((level) => level !== levelToRemove);
- }
- }
- onManifestLoading() {
- this.stopLoad();
- this.enabled = true;
- this.timeToLoad = 300;
- this.updated = 0;
- this.uri = null;
- this.pathwayId = ".";
- this.levels = this.audioTracks = this.subtitleTracks = null;
- }
- onManifestLoaded(event, data) {
- const {
- contentSteering
- } = data;
- if (contentSteering === null) {
- return;
- }
- this.pathwayId = contentSteering.pathwayId;
- this.uri = contentSteering.uri;
- if (this.started) {
- this.startLoad();
- }
- }
- onManifestParsed(event, data) {
- this.audioTracks = data.audioTracks;
- this.subtitleTracks = data.subtitleTracks;
- }
- onError(event, data) {
- const {
- errorAction
- } = data;
- if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox && errorAction.flags === ErrorActionFlags.MoveAllAlternatesMatchingHost) {
- const levels = this.levels;
- let pathwayPriority = this._pathwayPriority;
- let errorPathway = this.pathwayId;
- if (data.context) {
- const {
- groupId,
- pathwayId,
- type
- } = data.context;
- if (groupId && levels) {
- errorPathway = this.getPathwayForGroupId(groupId, type, errorPathway);
- } else if (pathwayId) {
- errorPathway = pathwayId;
- }
- }
- if (!(errorPathway in this.penalizedPathways)) {
- this.penalizedPathways[errorPathway] = performance.now();
- }
- if (!pathwayPriority && levels) {
- pathwayPriority = this.pathways();
- }
- if (pathwayPriority && pathwayPriority.length > 1) {
- this.updatePathwayPriority(pathwayPriority);
- errorAction.resolved = this.pathwayId !== errorPathway;
- }
- if (data.details === ErrorDetails.BUFFER_APPEND_ERROR && !data.fatal) {
- errorAction.resolved = true;
- } else if (!errorAction.resolved) {
- this.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${stringify(pathwayPriority)} penalized: ${stringify(this.penalizedPathways)}`);
- }
- }
- }
- filterParsedLevels(levels) {
- this.levels = levels;
- let pathwayLevels = this.getLevelsForPathway(this.pathwayId);
- if (pathwayLevels.length === 0) {
- const pathwayId = levels[0].pathwayId;
- this.log(`No levels found in Pathway ${this.pathwayId}. Setting initial Pathway to "${pathwayId}"`);
- pathwayLevels = this.getLevelsForPathway(pathwayId);
- this.pathwayId = pathwayId;
- }
- if (pathwayLevels.length !== levels.length) {
- this.log(`Found ${pathwayLevels.length}/${levels.length} levels in Pathway "${this.pathwayId}"`);
- }
- return pathwayLevels;
- }
- getLevelsForPathway(pathwayId) {
- if (this.levels === null) {
- return [];
- }
- return this.levels.filter((level) => pathwayId === level.pathwayId);
- }
- updatePathwayPriority(pathwayPriority) {
- this._pathwayPriority = pathwayPriority;
- let levels;
- const penalizedPathways = this.penalizedPathways;
- const now2 = performance.now();
- Object.keys(penalizedPathways).forEach((pathwayId) => {
- if (now2 - penalizedPathways[pathwayId] > PATHWAY_PENALTY_DURATION_MS) {
- delete penalizedPathways[pathwayId];
- }
- });
- for (let i = 0; i < pathwayPriority.length; i++) {
- const pathwayId = pathwayPriority[i];
- if (pathwayId in penalizedPathways) {
- continue;
- }
- if (pathwayId === this.pathwayId) {
- return;
- }
- const selectedIndex = this.hls.nextLoadLevel;
- const selectedLevel = this.hls.levels[selectedIndex];
- levels = this.getLevelsForPathway(pathwayId);
- if (levels.length > 0) {
- this.log(`Setting Pathway to "${pathwayId}"`);
- this.pathwayId = pathwayId;
- reassignFragmentLevelIndexes(levels);
- this.hls.trigger(Events.LEVELS_UPDATED, {
- levels
- });
- const levelAfterChange = this.hls.levels[selectedIndex];
- if (selectedLevel && levelAfterChange && this.levels) {
- if (levelAfterChange.attrs["STABLE-VARIANT-ID"] !== selectedLevel.attrs["STABLE-VARIANT-ID"] && levelAfterChange.bitrate !== selectedLevel.bitrate) {
- this.log(`Unstable Pathways change from bitrate ${selectedLevel.bitrate} to ${levelAfterChange.bitrate}`);
- }
- this.hls.nextLoadLevel = selectedIndex;
- }
- break;
- }
- }
- }
- getPathwayForGroupId(groupId, type, defaultPathway) {
- const levels = this.getLevelsForPathway(defaultPathway).concat(this.levels || []);
- for (let i = 0; i < levels.length; i++) {
- if (type === PlaylistContextType.AUDIO_TRACK && levels[i].hasAudioGroup(groupId) || type === PlaylistContextType.SUBTITLE_TRACK && levels[i].hasSubtitleGroup(groupId)) {
- return levels[i].pathwayId;
- }
- }
- return defaultPathway;
- }
- clonePathways(pathwayClones) {
- const levels = this.levels;
- if (!levels) {
- return;
- }
- const audioGroupCloneMap = {};
- const subtitleGroupCloneMap = {};
- pathwayClones.forEach((pathwayClone) => {
- const {
- ID: cloneId,
- "BASE-ID": baseId,
- "URI-REPLACEMENT": uriReplacement
- } = pathwayClone;
- if (levels.some((level) => level.pathwayId === cloneId)) {
- return;
- }
- const clonedVariants = this.getLevelsForPathway(baseId).map((baseLevel) => {
- const attributes = new AttrList(baseLevel.attrs);
- attributes["PATHWAY-ID"] = cloneId;
- const clonedAudioGroupId = attributes.AUDIO && `${attributes.AUDIO}_clone_${cloneId}`;
- const clonedSubtitleGroupId = attributes.SUBTITLES && `${attributes.SUBTITLES}_clone_${cloneId}`;
- if (clonedAudioGroupId) {
- audioGroupCloneMap[attributes.AUDIO] = clonedAudioGroupId;
- attributes.AUDIO = clonedAudioGroupId;
- }
- if (clonedSubtitleGroupId) {
- subtitleGroupCloneMap[attributes.SUBTITLES] = clonedSubtitleGroupId;
- attributes.SUBTITLES = clonedSubtitleGroupId;
- }
- const url = performUriReplacement(baseLevel.uri, attributes["STABLE-VARIANT-ID"], "PER-VARIANT-URIS", uriReplacement);
- const clonedLevel = new Level({
- attrs: attributes,
- audioCodec: baseLevel.audioCodec,
- bitrate: baseLevel.bitrate,
- height: baseLevel.height,
- name: baseLevel.name,
- url,
- videoCodec: baseLevel.videoCodec,
- width: baseLevel.width
- });
- if (baseLevel.audioGroups) {
- for (let i = 1; i < baseLevel.audioGroups.length; i++) {
- clonedLevel.addGroupId("audio", `${baseLevel.audioGroups[i]}_clone_${cloneId}`);
- }
- }
- if (baseLevel.subtitleGroups) {
- for (let i = 1; i < baseLevel.subtitleGroups.length; i++) {
- clonedLevel.addGroupId("text", `${baseLevel.subtitleGroups[i]}_clone_${cloneId}`);
- }
- }
- return clonedLevel;
- });
- levels.push(...clonedVariants);
- cloneRenditionGroups(this.audioTracks, audioGroupCloneMap, uriReplacement, cloneId);
- cloneRenditionGroups(this.subtitleTracks, subtitleGroupCloneMap, uriReplacement, cloneId);
- });
- }
- loadSteeringManifest(uri) {
- const config = this.hls.config;
- const Loader = config.loader;
- if (this.loader) {
- this.loader.destroy();
- }
- this.loader = new Loader(config);
- let url;
- try {
- url = new self.URL(uri);
- } catch (error) {
- this.enabled = false;
- this.log(`Failed to parse Steering Manifest URI: ${uri}`);
- return;
- }
- if (url.protocol !== "data:") {
- const throughput = (this.hls.bandwidthEstimate || config.abrEwmaDefaultEstimate) | 0;
- url.searchParams.set("_HLS_pathway", this.pathwayId);
- url.searchParams.set("_HLS_throughput", "" + throughput);
- }
- const context = {
- responseType: "json",
- url: url.href
- };
- const loadPolicy = config.steeringManifestLoadPolicy.default;
- const legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {};
- const loaderConfig = {
- loadPolicy,
- timeout: loadPolicy.maxLoadTimeMs,
- maxRetry: legacyRetryCompatibility.maxNumRetry || 0,
- retryDelay: legacyRetryCompatibility.retryDelayMs || 0,
- maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0
- };
- const callbacks = {
- onSuccess: (response, stats, context2, networkDetails) => {
- this.log(`Loaded steering manifest: "${url}"`);
- const steeringData = response.data;
- if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
- this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
- return;
- }
- this.updated = performance.now();
- this.timeToLoad = steeringData.TTL;
- const {
- "RELOAD-URI": reloadUri,
- "PATHWAY-CLONES": pathwayClones,
- "PATHWAY-PRIORITY": pathwayPriority
- } = steeringData;
- if (reloadUri) {
- try {
- this.uri = new self.URL(reloadUri, url).href;
- } catch (error) {
- this.enabled = false;
- this.log(`Failed to parse Steering Manifest RELOAD-URI: ${reloadUri}`);
- return;
- }
- }
- this.scheduleRefresh(this.uri || context2.url);
- if (pathwayClones) {
- this.clonePathways(pathwayClones);
- }
- const loadedSteeringData = {
- steeringManifest: steeringData,
- url: url.toString()
- };
- this.hls.trigger(Events.STEERING_MANIFEST_LOADED, loadedSteeringData);
- if (pathwayPriority) {
- this.updatePathwayPriority(pathwayPriority);
- }
- },
- onError: (error, context2, networkDetails, stats) => {
- this.log(`Error loading steering manifest: ${error.code} ${error.text} (${context2.url})`);
- this.stopLoad();
- if (error.code === 410) {
- this.enabled = false;
- this.log(`Steering manifest ${context2.url} no longer available`);
- return;
- }
- let ttl = this.timeToLoad * 1e3;
- if (error.code === 429) {
- const loader = this.loader;
- if (typeof (loader == null ? void 0 : loader.getResponseHeader) === "function") {
- const retryAfter = loader.getResponseHeader("Retry-After");
- if (retryAfter) {
- ttl = parseFloat(retryAfter) * 1e3;
- }
- }
- this.log(`Steering manifest ${context2.url} rate limited`);
- return;
- }
- this.scheduleRefresh(this.uri || context2.url, ttl);
- },
- onTimeout: (stats, context2, networkDetails) => {
- this.log(`Timeout loading steering manifest (${context2.url})`);
- this.scheduleRefresh(this.uri || context2.url);
- }
- };
- this.log(`Requesting steering manifest: ${url}`);
- this.loader.load(context, loaderConfig, callbacks);
- }
- scheduleRefresh(uri, ttlMs = this.timeToLoad * 1e3) {
- this.clearTimeout();
- this.reloadTimer = self.setTimeout(() => {
- var _this$hls;
- const media = (_this$hls = this.hls) == null ? void 0 : _this$hls.media;
- if (media && !media.ended) {
- this.loadSteeringManifest(uri);
- return;
- }
- this.scheduleRefresh(uri, this.timeToLoad * 1e3);
- }, ttlMs);
- }
- };
- function cloneRenditionGroups(tracks, groupCloneMap, uriReplacement, cloneId) {
- if (!tracks) {
- return;
- }
- Object.keys(groupCloneMap).forEach((audioGroupId) => {
- const clonedTracks = tracks.filter((track) => track.groupId === audioGroupId).map((track) => {
- const clonedTrack = _extends({}, track);
- clonedTrack.details = void 0;
- clonedTrack.attrs = new AttrList(clonedTrack.attrs);
- clonedTrack.url = clonedTrack.attrs.URI = performUriReplacement(track.url, track.attrs["STABLE-RENDITION-ID"], "PER-RENDITION-URIS", uriReplacement);
- clonedTrack.groupId = clonedTrack.attrs["GROUP-ID"] = groupCloneMap[audioGroupId];
- clonedTrack.attrs["PATHWAY-ID"] = cloneId;
- return clonedTrack;
- });
- tracks.push(...clonedTracks);
- });
- }
- function performUriReplacement(uri, stableId, perOptionKey, uriReplacement) {
- const {
- HOST: host,
- PARAMS: params,
- [perOptionKey]: perOptionUris
- } = uriReplacement;
- let perVariantUri;
- if (stableId) {
- perVariantUri = perOptionUris == null ? void 0 : perOptionUris[stableId];
- if (perVariantUri) {
- uri = perVariantUri;
- }
- }
- const url = new self.URL(uri);
- if (host && !perVariantUri) {
- url.host = host;
- }
- if (params) {
- Object.keys(params).sort().forEach((key) => {
- if (key) {
- url.searchParams.set(key, params[key]);
- }
- });
- }
- return url.href;
- }
- var EMEController = class _EMEController extends Logger {
- constructor(hls) {
- super("eme", hls.logger);
- this.hls = void 0;
- this.config = void 0;
- this.media = null;
- this.keyFormatPromise = null;
- this.keySystemAccessPromises = {};
- this._requestLicenseFailureCount = 0;
- this.mediaKeySessions = [];
- this.keyIdToKeySessionPromise = {};
- this.mediaKeys = null;
- this.setMediaKeysQueue = _EMEController.CDMCleanupPromise ? [_EMEController.CDMCleanupPromise] : [];
- this.bannedKeyIds = {};
- this.onMediaEncrypted = (event) => {
- const {
- initDataType,
- initData
- } = event;
- const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
- this.debug(logMessage);
- if (initData === null) {
- return;
- }
- if (!this.keyFormatPromise) {
- let keySystems = Object.keys(this.keySystemAccessPromises);
- if (!keySystems.length) {
- keySystems = getKeySystemsForConfig(this.config);
- }
- const keyFormats = keySystems.map(keySystemDomainToKeySystemFormat).filter((k) => !!k);
- this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
- }
- this.keyFormatPromise.then((keySystemFormat) => {
- const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat);
- if (initDataType !== "sinf" || keySystem !== KeySystems.FAIRPLAY) {
- this.log(`Ignoring "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`);
- return;
- }
- let keyId;
- try {
- const json = bin2str(new Uint8Array(initData));
- const sinf = base64Decode(JSON.parse(json).sinf);
- const tenc = parseSinf(sinf);
- if (!tenc) {
- throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`);
- }
- keyId = new Uint8Array(tenc.subarray(8, 24));
- } catch (error) {
- this.warn(`${logMessage} Failed to parse sinf: ${error}`);
- return;
- }
- const keyIdHex = arrayToHex(keyId);
- const {
- keyIdToKeySessionPromise,
- mediaKeySessions
- } = this;
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
- for (let i = 0; i < mediaKeySessions.length; i++) {
- const keyContext = mediaKeySessions[i];
- const decryptdata = keyContext.decryptdata;
- if (!decryptdata.keyId) {
- continue;
- }
- const oldKeyIdHex = arrayToHex(decryptdata.keyId);
- if (arrayValuesMatch(keyId, decryptdata.keyId) || decryptdata.uri.replace(/-/g, "").indexOf(keyIdHex) !== -1) {
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
- if (!keySessionContextPromise) {
- continue;
- }
- if (decryptdata.pssh) {
- break;
- }
- delete keyIdToKeySessionPromise[oldKeyIdHex];
- decryptdata.pssh = new Uint8Array(initData);
- decryptdata.keyId = keyId;
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
- return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, "encrypted-event-key-match");
- });
- keySessionContextPromise.catch((error) => this.handleError(error));
- break;
- }
- }
- if (!keySessionContextPromise) {
- this.handleError(new Error(`Key ID ${keyIdHex} not encountered in playlist. Key-system sessions ${mediaKeySessions.length}.`));
- }
- }).catch((error) => this.handleError(error));
- };
- this.onWaitingForKey = (event) => {
- this.log(`"${event.type}" event`);
- };
- this.hls = hls;
- this.config = hls.config;
- this.registerListeners();
- }
- destroy() {
- this.onDestroying();
- this.onMediaDetached();
- const config = this.config;
- config.requestMediaKeySystemAccessFunc = null;
- config.licenseXhrSetup = config.licenseResponseCallback = void 0;
- config.drmSystems = config.drmSystemOptions = {};
- this.hls = this.config = this.keyIdToKeySessionPromise = null;
- this.onMediaEncrypted = this.onWaitingForKey = null;
- }
- registerListeners() {
- this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- this.hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this);
- this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- this.hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
- this.hls.on(Events.DESTROYING, this.onDestroying, this);
- }
- unregisterListeners() {
- this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- this.hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this);
- this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
- this.hls.off(Events.DESTROYING, this.onDestroying, this);
- }
- getLicenseServerUrl(keySystem) {
- const {
- drmSystems,
- widevineLicenseUrl
- } = this.config;
- const keySystemConfiguration = drmSystems == null ? void 0 : drmSystems[keySystem];
- if (keySystemConfiguration) {
- return keySystemConfiguration.licenseUrl;
- }
- if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) {
- return widevineLicenseUrl;
- }
- }
- getLicenseServerUrlOrThrow(keySystem) {
- const url = this.getLicenseServerUrl(keySystem);
- if (url === void 0) {
- throw new Error(`no license server URL configured for key-system "${keySystem}"`);
- }
- return url;
- }
- getServerCertificateUrl(keySystem) {
- const {
- drmSystems
- } = this.config;
- const keySystemConfiguration = drmSystems == null ? void 0 : drmSystems[keySystem];
- if (keySystemConfiguration) {
- return keySystemConfiguration.serverCertificateUrl;
- } else {
- this.log(`No Server Certificate in config.drmSystems["${keySystem}"]`);
- }
- }
- attemptKeySystemAccess(keySystemsToAttempt) {
- const levels = this.hls.levels;
- const uniqueCodec = (value, i, a) => !!value && a.indexOf(value) === i;
- const audioCodecs = levels.map((level) => level.audioCodec).filter(uniqueCodec);
- const videoCodecs = levels.map((level) => level.videoCodec).filter(uniqueCodec);
- if (audioCodecs.length + videoCodecs.length === 0) {
- videoCodecs.push("avc1.42e01e");
- }
- return new Promise((resolve, reject) => {
- const attempt = (keySystems) => {
- const keySystem = keySystems.shift();
- this.getMediaKeysPromise(keySystem, audioCodecs, videoCodecs).then((mediaKeys) => resolve({
- keySystem,
- mediaKeys
- })).catch((error) => {
- if (keySystems.length) {
- attempt(keySystems);
- } else if (error instanceof EMEKeyError) {
- reject(error);
- } else {
- reject(new EMEKeyError({
- type: ErrorTypes.KEY_SYSTEM_ERROR,
- details: ErrorDetails.KEY_SYSTEM_NO_ACCESS,
- error,
- fatal: true
- }, error.message));
- }
- });
- };
- attempt(keySystemsToAttempt);
- });
- }
- requestMediaKeySystemAccess(keySystem, supportedConfigurations) {
- const {
- requestMediaKeySystemAccessFunc
- } = this.config;
- if (!(typeof requestMediaKeySystemAccessFunc === "function")) {
- let errMessage = `Configured requestMediaKeySystemAccess is not a function ${requestMediaKeySystemAccessFunc}`;
- if (requestMediaKeySystemAccess === null && self.location.protocol === "http:") {
- errMessage = `navigator.requestMediaKeySystemAccess is not available over insecure protocol ${location.protocol}`;
- }
- return Promise.reject(new Error(errMessage));
- }
- return requestMediaKeySystemAccessFunc(keySystem, supportedConfigurations);
- }
- getMediaKeysPromise(keySystem, audioCodecs, videoCodecs) {
- var _keySystemAccessPromi;
- const mediaKeySystemConfigs = getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, this.config.drmSystemOptions || {});
- let keySystemAccessPromises = this.keySystemAccessPromises[keySystem];
- let keySystemAccess = (_keySystemAccessPromi = keySystemAccessPromises) == null ? void 0 : _keySystemAccessPromi.keySystemAccess;
- if (!keySystemAccess) {
- this.log(`Requesting encrypted media "${keySystem}" key-system access with config: ${stringify(mediaKeySystemConfigs)}`);
- keySystemAccess = this.requestMediaKeySystemAccess(keySystem, mediaKeySystemConfigs);
- const keySystemAccessPromisesNew = keySystemAccessPromises = this.keySystemAccessPromises[keySystem] = {
- keySystemAccess
- };
- keySystemAccess.catch((error) => {
- this.log(`Failed to obtain access to key-system "${keySystem}": ${error}`);
- });
- return keySystemAccess.then((mediaKeySystemAccess) => {
- this.log(`Access for key-system "${mediaKeySystemAccess.keySystem}" obtained`);
- const certificateRequest = this.fetchServerCertificate(keySystem);
- this.log(`Create media-keys for "${keySystem}"`);
- const mediaKeys = keySystemAccessPromisesNew.mediaKeys = mediaKeySystemAccess.createMediaKeys().then((mediaKeys2) => {
- this.log(`Media-keys created for "${keySystem}"`);
- keySystemAccessPromisesNew.hasMediaKeys = true;
- return certificateRequest.then((certificate) => {
- if (certificate) {
- return this.setMediaKeysServerCertificate(mediaKeys2, keySystem, certificate);
- }
- return mediaKeys2;
- });
- });
- mediaKeys.catch((error) => {
- this.error(`Failed to create media-keys for "${keySystem}"}: ${error}`);
- });
- return mediaKeys;
- });
- }
- return keySystemAccess.then(() => keySystemAccessPromises.mediaKeys);
- }
- createMediaKeySessionContext({
- decryptdata,
- keySystem,
- mediaKeys
- }) {
- this.log(`Creating key-system session "${keySystem}" keyId: ${arrayToHex(decryptdata.keyId || [])} keyUri: ${decryptdata.uri}`);
- const mediaKeysSession = mediaKeys.createSession();
- const mediaKeySessionContext = {
- decryptdata,
- keySystem,
- mediaKeys,
- mediaKeysSession,
- keyStatus: "status-pending"
- };
- this.mediaKeySessions.push(mediaKeySessionContext);
- return mediaKeySessionContext;
- }
- renewKeySession(mediaKeySessionContext) {
- const decryptdata = mediaKeySessionContext.decryptdata;
- if (decryptdata.pssh) {
- const keySessionContext = this.createMediaKeySessionContext(mediaKeySessionContext);
- const keyId = getKeyIdString(decryptdata);
- const scheme = "cenc";
- this.keyIdToKeySessionPromise[keyId] = this.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh.buffer, "expired");
- } else {
- this.warn(`Could not renew expired session. Missing pssh initData.`);
- }
- this.removeSession(mediaKeySessionContext);
- }
- updateKeySession(mediaKeySessionContext, data) {
- const keySession = mediaKeySessionContext.mediaKeysSession;
- this.log(`Updating key-session "${keySession.sessionId}" for keyId ${arrayToHex(mediaKeySessionContext.decryptdata.keyId || [])}
- } (data length: ${data.byteLength})`);
- return keySession.update(data);
- }
- getSelectedKeySystemFormats() {
- return Object.keys(this.keySystemAccessPromises).map((keySystem) => ({
- keySystem,
- hasMediaKeys: this.keySystemAccessPromises[keySystem].hasMediaKeys
- })).filter(({
- hasMediaKeys
- }) => !!hasMediaKeys).map(({
- keySystem
- }) => keySystemDomainToKeySystemFormat(keySystem)).filter((keySystem) => !!keySystem);
- }
- getKeySystemAccess(keySystemsToAttempt) {
- return this.getKeySystemSelectionPromise(keySystemsToAttempt).then(({
- keySystem,
- mediaKeys
- }) => {
- return this.attemptSetMediaKeys(keySystem, mediaKeys);
- });
- }
- selectKeySystem(keySystemsToAttempt) {
- return new Promise((resolve, reject) => {
- this.getKeySystemSelectionPromise(keySystemsToAttempt).then(({
- keySystem
- }) => {
- const keySystemFormat = keySystemDomainToKeySystemFormat(keySystem);
- if (keySystemFormat) {
- resolve(keySystemFormat);
- } else {
- reject(new Error(`Unable to find format for key-system "${keySystem}"`));
- }
- }).catch(reject);
- });
- }
- selectKeySystemFormat(frag) {
- const keyFormats = Object.keys(frag.levelkeys || {});
- if (!this.keyFormatPromise) {
- this.log(`Selecting key-system from fragment (sn: ${frag.sn} ${frag.type}: ${frag.level}) key formats ${keyFormats.join(", ")}`);
- this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
- }
- return this.keyFormatPromise;
- }
- getKeyFormatPromise(keyFormats) {
- const keySystemsInConfig = getKeySystemsForConfig(this.config);
- const keySystemsToAttempt = keyFormats.map(keySystemFormatToKeySystemDomain).filter((value) => !!value && keySystemsInConfig.indexOf(value) !== -1);
- return this.selectKeySystem(keySystemsToAttempt);
- }
- getKeyStatus(decryptdata) {
- const {
- mediaKeySessions
- } = this;
- for (let i = 0; i < mediaKeySessions.length; i++) {
- const status2 = getKeyStatus(decryptdata, mediaKeySessions[i]);
- if (status2) {
- return status2;
- }
- }
- return void 0;
- }
- loadKey(data) {
- const decryptdata = data.keyInfo.decryptdata;
- const keyId = getKeyIdString(decryptdata);
- const badStatus = this.bannedKeyIds[keyId];
- if (badStatus || this.getKeyStatus(decryptdata) === "internal-error") {
- const error = getKeyStatusError(badStatus || "internal-error", decryptdata);
- this.handleError(error, data.frag);
- return Promise.reject(error);
- }
- const keyDetails = `(keyId: ${keyId} format: "${decryptdata.keyFormat}" method: ${decryptdata.method} uri: ${decryptdata.uri})`;
- this.log(`Starting session for key ${keyDetails}`);
- const keyContextPromise = this.keyIdToKeySessionPromise[keyId];
- if (!keyContextPromise) {
- const keySessionContextPromise = this.getKeySystemForKeyPromise(decryptdata).then(({
- keySystem,
- mediaKeys
- }) => {
- this.throwIfDestroyed();
- this.log(`Handle encrypted media sn: ${data.frag.sn} ${data.frag.type}: ${data.frag.level} using key ${keyDetails}`);
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
- this.throwIfDestroyed();
- return this.createMediaKeySessionContext({
- keySystem,
- mediaKeys,
- decryptdata
- });
- });
- }).then((keySessionContext) => {
- const scheme = "cenc";
- const initData = decryptdata.pssh ? decryptdata.pssh.buffer : null;
- return this.generateRequestWithPreferredKeySession(keySessionContext, scheme, initData, "playlist-key");
- });
- keySessionContextPromise.catch((error) => this.handleError(error, data.frag));
- this.keyIdToKeySessionPromise[keyId] = keySessionContextPromise;
- return keySessionContextPromise;
- }
- keyContextPromise.catch((error) => {
- if (error instanceof EMEKeyError) {
- const errorData = _objectSpread2({}, error.data);
- if (this.getKeyStatus(decryptdata) === "internal-error") {
- errorData.decryptdata = decryptdata;
- }
- const clonedError = new EMEKeyError(errorData, error.message);
- this.handleError(clonedError, data.frag);
- }
- });
- return keyContextPromise;
- }
- throwIfDestroyed(message = "Invalid state") {
- if (!this.hls) {
- throw new Error("invalid state");
- }
- }
- handleError(error, frag) {
- if (!this.hls) {
- return;
- }
- if (error instanceof EMEKeyError) {
- if (frag) {
- error.data.frag = frag;
- }
- const levelKey = error.data.decryptdata;
- this.error(`${error.message}${levelKey ? ` (${arrayToHex(levelKey.keyId || [])})` : ""}`);
- this.hls.trigger(Events.ERROR, error.data);
- } else {
- this.error(error.message);
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.KEY_SYSTEM_ERROR,
- details: ErrorDetails.KEY_SYSTEM_NO_KEYS,
- error,
- fatal: true
- });
- }
- }
- getKeySystemForKeyPromise(decryptdata) {
- const keyId = getKeyIdString(decryptdata);
- const mediaKeySessionContext = this.keyIdToKeySessionPromise[keyId];
- if (!mediaKeySessionContext) {
- const keySystem = keySystemFormatToKeySystemDomain(decryptdata.keyFormat);
- const keySystemsToAttempt = keySystem ? [keySystem] : getKeySystemsForConfig(this.config);
- return this.attemptKeySystemAccess(keySystemsToAttempt);
- }
- return mediaKeySessionContext;
- }
- getKeySystemSelectionPromise(keySystemsToAttempt) {
- if (!keySystemsToAttempt.length) {
- keySystemsToAttempt = getKeySystemsForConfig(this.config);
- }
- if (keySystemsToAttempt.length === 0) {
- throw new EMEKeyError({
- type: ErrorTypes.KEY_SYSTEM_ERROR,
- details: ErrorDetails.KEY_SYSTEM_NO_CONFIGURED_LICENSE,
- fatal: true
- }, `Missing key-system license configuration options ${stringify({
- drmSystems: this.config.drmSystems
- })}`);
- }
- return this.attemptKeySystemAccess(keySystemsToAttempt);
- }
- attemptSetMediaKeys(keySystem, mediaKeys) {
- if (this.mediaKeys === mediaKeys) {
- return Promise.resolve();
- }
- const queue = this.setMediaKeysQueue.slice();
- this.log(`Setting media-keys for "${keySystem}"`);
- const setMediaKeysPromise = Promise.all(queue).then(() => {
- if (!this.media) {
- this.mediaKeys = null;
- throw new Error("Attempted to set mediaKeys without media element attached");
- }
- return this.media.setMediaKeys(mediaKeys);
- });
- this.mediaKeys = mediaKeys;
- this.setMediaKeysQueue.push(setMediaKeysPromise);
- return setMediaKeysPromise.then(() => {
- this.log(`Media-keys set for "${keySystem}"`);
- queue.push(setMediaKeysPromise);
- this.setMediaKeysQueue = this.setMediaKeysQueue.filter((p) => queue.indexOf(p) === -1);
- });
- }
- generateRequestWithPreferredKeySession(context, initDataType, initData, reason) {
- var _this$config$drmSyste;
- const generateRequestFilter = (_this$config$drmSyste = this.config.drmSystems) == null || (_this$config$drmSyste = _this$config$drmSyste[context.keySystem]) == null ? void 0 : _this$config$drmSyste.generateRequest;
- if (generateRequestFilter) {
- try {
- const mappedInitData = generateRequestFilter.call(this.hls, initDataType, initData, context);
- if (!mappedInitData) {
- throw new Error("Invalid response from configured generateRequest filter");
- }
- initDataType = mappedInitData.initDataType;
- initData = mappedInitData.initData ? mappedInitData.initData : null;
- context.decryptdata.pssh = initData ? new Uint8Array(initData) : null;
- } catch (error) {
- this.warn(error.message);
- if (this.hls && this.hls.config.debug) {
- throw error;
- }
- }
- }
- if (initData === null) {
- this.log(`Skipping key-session request for "${reason}" (no initData)`);
- return Promise.resolve(context);
- }
- const keyId = getKeyIdString(context.decryptdata);
- const keyUri = context.decryptdata.uri;
- this.log(`Generating key-session request for "${reason}" keyId: ${keyId} URI: ${keyUri} (init data type: ${initDataType} length: ${initData.byteLength})`);
- const licenseStatus = new EventEmitter();
- const onmessage = context._onmessage = (event) => {
- const keySession = context.mediaKeysSession;
- if (!keySession) {
- licenseStatus.emit("error", new Error("invalid state"));
- return;
- }
- const {
- messageType,
- message
- } = event;
- this.log(`"${messageType}" message event for session "${keySession.sessionId}" message size: ${message.byteLength}`);
- if (messageType === "license-request" || messageType === "license-renewal") {
- this.renewLicense(context, message).catch((error) => {
- if (licenseStatus.eventNames().length) {
- licenseStatus.emit("error", error);
- } else {
- this.handleError(error);
- }
- });
- } else if (messageType === "license-release") {
- if (context.keySystem === KeySystems.FAIRPLAY) {
- this.updateKeySession(context, strToUtf8array("acknowledged")).then(() => this.removeSession(context)).catch((error) => this.handleError(error));
- }
- } else {
- this.warn(`unhandled media key message type "${messageType}"`);
- }
- };
- const handleKeyStatus = (keyStatus, context2) => {
- context2.keyStatus = keyStatus;
- let keyError;
- if (keyStatus.startsWith("usable")) {
- licenseStatus.emit("resolved");
- } else if (keyStatus === "internal-error" || keyStatus === "output-restricted" || keyStatus === "output-downscaled") {
- keyError = getKeyStatusError(keyStatus, context2.decryptdata);
- } else if (keyStatus === "expired") {
- keyError = new Error(`key expired (keyId: ${keyId})`);
- } else if (keyStatus === "released") {
- keyError = new Error(`key released`);
- } else if (keyStatus === "status-pending")
- ;
- else {
- this.warn(`unhandled key status change "${keyStatus}" (keyId: ${keyId})`);
- }
- if (keyError) {
- if (licenseStatus.eventNames().length) {
- licenseStatus.emit("error", keyError);
- } else {
- this.handleError(keyError);
- }
- }
- };
- const onkeystatuseschange = context._onkeystatuseschange = (event) => {
- const keySession = context.mediaKeysSession;
- if (!keySession) {
- licenseStatus.emit("error", new Error("invalid state"));
- return;
- }
- const keyStatuses = this.getKeyStatuses(context);
- const keyIds = Object.keys(keyStatuses);
- if (!keyIds.some((id) => keyStatuses[id] !== "status-pending")) {
- return;
- }
- if (keyStatuses[keyId] === "expired") {
- this.log(`Expired key ${stringify(keyStatuses)} in key-session "${context.mediaKeysSession.sessionId}"`);
- this.renewKeySession(context);
- return;
- }
- let keyStatus = keyStatuses[keyId];
- if (keyStatus) {
- handleKeyStatus(keyStatus, context);
- } else {
- var _context$keyStatusTim;
- const timeout = 1e3;
- context.keyStatusTimeouts || (context.keyStatusTimeouts = {});
- (_context$keyStatusTim = context.keyStatusTimeouts)[keyId] || (_context$keyStatusTim[keyId] = self.setTimeout(() => {
- if (!context.mediaKeysSession || !this.mediaKeys) {
- return;
- }
- const sessionKeyStatus = this.getKeyStatus(context.decryptdata);
- if (sessionKeyStatus && sessionKeyStatus !== "status-pending") {
- this.log(`No status for keyId ${keyId} in key-session "${context.mediaKeysSession.sessionId}". Using session key-status ${sessionKeyStatus} from other session.`);
- return handleKeyStatus(sessionKeyStatus, context);
- }
- this.log(`key status for ${keyId} in key-session "${context.mediaKeysSession.sessionId}" timed out after ${timeout}ms`);
- keyStatus = "internal-error";
- handleKeyStatus(keyStatus, context);
- }, timeout));
- this.log(`No status for keyId ${keyId} (${stringify(keyStatuses)}).`);
- }
- };
- addEventListener(context.mediaKeysSession, "message", onmessage);
- addEventListener(context.mediaKeysSession, "keystatuseschange", onkeystatuseschange);
- const keyUsablePromise = new Promise((resolve, reject) => {
- licenseStatus.on("error", reject);
- licenseStatus.on("resolved", resolve);
- });
- return context.mediaKeysSession.generateRequest(initDataType, initData).then(() => {
- this.log(`Request generated for key-session "${context.mediaKeysSession.sessionId}" keyId: ${keyId} URI: ${keyUri}`);
- }).catch((error) => {
- throw new EMEKeyError({
- type: ErrorTypes.KEY_SYSTEM_ERROR,
- details: ErrorDetails.KEY_SYSTEM_NO_SESSION,
- error,
- decryptdata: context.decryptdata,
- fatal: false
- }, `Error generating key-session request: ${error}`);
- }).then(() => keyUsablePromise).catch((error) => {
- licenseStatus.removeAllListeners();
- return this.removeSession(context).then(() => {
- throw error;
- });
- }).then(() => {
- licenseStatus.removeAllListeners();
- return context;
- });
- }
- getKeyStatuses(mediaKeySessionContext) {
- const keyStatuses = {};
- mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach((status2, keyId) => {
- if (typeof keyId === "string" && typeof status2 === "object") {
- const temp = keyId;
- keyId = status2;
- status2 = temp;
- }
- const keyIdArray = "buffer" in keyId ? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength) : new Uint8Array(keyId);
- if (mediaKeySessionContext.keySystem === KeySystems.PLAYREADY && keyIdArray.length === 16) {
- changeEndianness(keyIdArray);
- }
- const keyIdWithStatusChange = arrayToHex(keyIdArray);
- if (status2 === "internal-error") {
- this.bannedKeyIds[keyIdWithStatusChange] = status2;
- }
- this.log(`key status change "${status2}" for keyStatuses keyId: ${keyIdWithStatusChange} key-session "${mediaKeySessionContext.mediaKeysSession.sessionId}"`);
- keyStatuses[keyIdWithStatusChange] = status2;
- });
- return keyStatuses;
- }
- fetchServerCertificate(keySystem) {
- const config = this.config;
- const Loader = config.loader;
- const certLoader = new Loader(config);
- const url = this.getServerCertificateUrl(keySystem);
- if (!url) {
- return Promise.resolve();
- }
- this.log(`Fetching server certificate for "${keySystem}"`);
- return new Promise((resolve, reject) => {
- const loaderContext = {
- responseType: "arraybuffer",
- url
- };
- const loadPolicy = config.certLoadPolicy.default;
- const loaderConfig = {
- loadPolicy,
- timeout: loadPolicy.maxLoadTimeMs,
- maxRetry: 0,
- retryDelay: 0,
- maxRetryDelay: 0
- };
- const loaderCallbacks = {
- onSuccess: (response, stats, context, networkDetails) => {
- resolve(response.data);
- },
- onError: (response, contex, networkDetails, stats) => {
- reject(new EMEKeyError({
- type: ErrorTypes.KEY_SYSTEM_ERROR,
- details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,
- fatal: true,
- networkDetails,
- response: _objectSpread2({
- url: loaderContext.url,
- data: void 0
- }, response)
- }, `"${keySystem}" certificate request failed (${url}). Status: ${response.code} (${response.text})`));
- },
- onTimeout: (stats, context, networkDetails) => {
- reject(new EMEKeyError({
- type: ErrorTypes.KEY_SYSTEM_ERROR,
- details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,
- fatal: true,
- networkDetails,
- response: {
- url: loaderContext.url,
- data: void 0
- }
- }, `"${keySystem}" certificate request timed out (${url})`));
- },
- onAbort: (stats, context, networkDetails) => {
- reject(new Error("aborted"));
- }
- };
- certLoader.load(loaderContext, loaderConfig, loaderCallbacks);
- });
- }
- setMediaKeysServerCertificate(mediaKeys, keySystem, cert) {
- return new Promise((resolve, reject) => {
- mediaKeys.setServerCertificate(cert).then((success) => {
- this.log(`setServerCertificate ${success ? "success" : "not supported by CDM"} (${cert.byteLength}) on "${keySystem}"`);
- resolve(mediaKeys);
- }).catch((error) => {
- reject(new EMEKeyError({
- type: ErrorTypes.KEY_SYSTEM_ERROR,
- details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED,
- error,
- fatal: true
- }, error.message));
- });
- });
- }
- renewLicense(context, keyMessage) {
- return this.requestLicense(context, new Uint8Array(keyMessage)).then((data) => {
- return this.updateKeySession(context, new Uint8Array(data)).catch((error) => {
- throw new EMEKeyError({
- type: ErrorTypes.KEY_SYSTEM_ERROR,
- details: ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED,
- decryptdata: context.decryptdata,
- error,
- fatal: false
- }, error.message);
- });
- });
- }
- unpackPlayReadyKeyMessage(xhr, licenseChallenge) {
- const xmlString = String.fromCharCode.apply(null, new Uint16Array(licenseChallenge.buffer));
- if (!xmlString.includes("PlayReadyKeyMessage")) {
- xhr.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
- return licenseChallenge;
- }
- const keyMessageXml = new DOMParser().parseFromString(xmlString, "application/xml");
- const headers = keyMessageXml.querySelectorAll("HttpHeader");
- if (headers.length > 0) {
- let header;
- for (let i = 0, len = headers.length; i < len; i++) {
- var _header$querySelector, _header$querySelector2;
- header = headers[i];
- const name = (_header$querySelector = header.querySelector("name")) == null ? void 0 : _header$querySelector.textContent;
- const value = (_header$querySelector2 = header.querySelector("value")) == null ? void 0 : _header$querySelector2.textContent;
- if (name && value) {
- xhr.setRequestHeader(name, value);
- }
- }
- }
- const challengeElement = keyMessageXml.querySelector("Challenge");
- const challengeText = challengeElement == null ? void 0 : challengeElement.textContent;
- if (!challengeText) {
- throw new Error(`Cannot find <Challenge> in key message`);
- }
- return strToUtf8array(atob(challengeText));
- }
- setupLicenseXHR(xhr, url, keysListItem, licenseChallenge) {
- const licenseXhrSetup = this.config.licenseXhrSetup;
- if (!licenseXhrSetup) {
- xhr.open("POST", url, true);
- return Promise.resolve({
- xhr,
- licenseChallenge
- });
- }
- return Promise.resolve().then(() => {
- if (!keysListItem.decryptdata) {
- throw new Error("Key removed");
- }
- return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge);
- }).catch((error) => {
- if (!keysListItem.decryptdata) {
- throw error;
- }
- xhr.open("POST", url, true);
- return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge);
- }).then((licenseXhrSetupResult) => {
- if (!xhr.readyState) {
- xhr.open("POST", url, true);
- }
- const finalLicenseChallenge = licenseXhrSetupResult ? licenseXhrSetupResult : licenseChallenge;
- return {
- xhr,
- licenseChallenge: finalLicenseChallenge
- };
- });
- }
- requestLicense(keySessionContext, licenseChallenge) {
- const keyLoadPolicy = this.config.keyLoadPolicy.default;
- return new Promise((resolve, reject) => {
- const url = this.getLicenseServerUrlOrThrow(keySessionContext.keySystem);
- this.log(`Sending license request to URL: ${url}`);
- const xhr = new XMLHttpRequest();
- xhr.responseType = "arraybuffer";
- xhr.onreadystatechange = () => {
- if (!this.hls || !keySessionContext.mediaKeysSession) {
- return reject(new Error("invalid state"));
- }
- if (xhr.readyState === 4) {
- if (xhr.status === 200) {
- this._requestLicenseFailureCount = 0;
- let data = xhr.response;
- this.log(`License received ${data instanceof ArrayBuffer ? data.byteLength : data}`);
- const licenseResponseCallback = this.config.licenseResponseCallback;
- if (licenseResponseCallback) {
- try {
- data = licenseResponseCallback.call(this.hls, xhr, url, keySessionContext);
- } catch (error) {
- this.error(error);
- }
- }
- resolve(data);
- } else {
- const retryConfig = keyLoadPolicy.errorRetry;
- const maxNumRetry = retryConfig ? retryConfig.maxNumRetry : 0;
- this._requestLicenseFailureCount++;
- if (this._requestLicenseFailureCount > maxNumRetry || xhr.status >= 400 && xhr.status < 500) {
- reject(new EMEKeyError({
- type: ErrorTypes.KEY_SYSTEM_ERROR,
- details: ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED,
- decryptdata: keySessionContext.decryptdata,
- fatal: true,
- networkDetails: xhr,
- response: {
- url,
- data: void 0,
- code: xhr.status,
- text: xhr.statusText
- }
- }, `License Request XHR failed (${url}). Status: ${xhr.status} (${xhr.statusText})`));
- } else {
- const attemptsLeft = maxNumRetry - this._requestLicenseFailureCount + 1;
- this.warn(`Retrying license request, ${attemptsLeft} attempts left`);
- this.requestLicense(keySessionContext, licenseChallenge).then(resolve, reject);
- }
- }
- }
- };
- if (keySessionContext.licenseXhr && keySessionContext.licenseXhr.readyState !== XMLHttpRequest.DONE) {
- keySessionContext.licenseXhr.abort();
- }
- keySessionContext.licenseXhr = xhr;
- this.setupLicenseXHR(xhr, url, keySessionContext, licenseChallenge).then(({
- xhr: xhr2,
- licenseChallenge: licenseChallenge2
- }) => {
- if (keySessionContext.keySystem == KeySystems.PLAYREADY) {
- licenseChallenge2 = this.unpackPlayReadyKeyMessage(xhr2, licenseChallenge2);
- }
- xhr2.send(licenseChallenge2);
- }).catch(reject);
- });
- }
- onDestroying() {
- this.unregisterListeners();
- this._clear();
- }
- onMediaAttached(event, data) {
- if (!this.config.emeEnabled) {
- return;
- }
- const media = data.media;
- this.media = media;
- addEventListener(media, "encrypted", this.onMediaEncrypted);
- addEventListener(media, "waitingforkey", this.onWaitingForKey);
- }
- onMediaDetached() {
- const media = this.media;
- if (media) {
- removeEventListener(media, "encrypted", this.onMediaEncrypted);
- removeEventListener(media, "waitingforkey", this.onWaitingForKey);
- this.media = null;
- this.mediaKeys = null;
- }
- }
- _clear() {
- var _media$setMediaKeys;
- this._requestLicenseFailureCount = 0;
- this.keyIdToKeySessionPromise = {};
- this.bannedKeyIds = {};
- if (!this.mediaKeys && !this.mediaKeySessions.length) {
- return;
- }
- const media = this.media;
- const mediaKeysList = this.mediaKeySessions.slice();
- this.mediaKeySessions = [];
- this.mediaKeys = null;
- LevelKey.clearKeyUriToKeyIdMap();
- const keySessionCount = mediaKeysList.length;
- _EMEController.CDMCleanupPromise = Promise.all(mediaKeysList.map((mediaKeySessionContext) => this.removeSession(mediaKeySessionContext)).concat((media == null || (_media$setMediaKeys = media.setMediaKeys(null)) == null ? void 0 : _media$setMediaKeys.catch((error) => {
- this.log(`Could not clear media keys: ${error}`);
- if (!this.hls)
- return;
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.KEY_SYSTEM_DESTROY_MEDIA_KEYS_ERROR,
- fatal: false,
- error: new Error(`Could not clear media keys: ${error}`)
- });
- })) || Promise.resolve())).catch((error) => {
- this.log(`Could not close sessions and clear media keys: ${error}`);
- if (!this.hls)
- return;
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR,
- fatal: false,
- error: new Error(`Could not close sessions and clear media keys: ${error}`)
- });
- }).then(() => {
- if (keySessionCount) {
- this.log("finished closing key sessions and clearing media keys");
- }
- });
- }
- onManifestLoading() {
- this.keyFormatPromise = null;
- this.bannedKeyIds = {};
- }
- onManifestLoaded(event, {
- sessionKeys
- }) {
- if (!sessionKeys || !this.config.emeEnabled) {
- return;
- }
- if (!this.keyFormatPromise) {
- const keyFormats = sessionKeys.reduce((formats, sessionKey) => {
- if (formats.indexOf(sessionKey.keyFormat) === -1) {
- formats.push(sessionKey.keyFormat);
- }
- return formats;
- }, []);
- this.log(`Selecting key-system from session-keys ${keyFormats.join(", ")}`);
- this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
- }
- }
- removeSession(mediaKeySessionContext) {
- const {
- mediaKeysSession,
- licenseXhr,
- decryptdata
- } = mediaKeySessionContext;
- if (mediaKeysSession) {
- this.log(`Remove licenses and keys and close session "${mediaKeysSession.sessionId}" keyId: ${arrayToHex((decryptdata == null ? void 0 : decryptdata.keyId) || [])}`);
- if (mediaKeySessionContext._onmessage) {
- mediaKeysSession.removeEventListener("message", mediaKeySessionContext._onmessage);
- mediaKeySessionContext._onmessage = void 0;
- }
- if (mediaKeySessionContext._onkeystatuseschange) {
- mediaKeysSession.removeEventListener("keystatuseschange", mediaKeySessionContext._onkeystatuseschange);
- mediaKeySessionContext._onkeystatuseschange = void 0;
- }
- if (licenseXhr && licenseXhr.readyState !== XMLHttpRequest.DONE) {
- licenseXhr.abort();
- }
- mediaKeySessionContext.mediaKeysSession = mediaKeySessionContext.decryptdata = mediaKeySessionContext.licenseXhr = void 0;
- const index = this.mediaKeySessions.indexOf(mediaKeySessionContext);
- if (index > -1) {
- this.mediaKeySessions.splice(index, 1);
- }
- const {
- keyStatusTimeouts
- } = mediaKeySessionContext;
- if (keyStatusTimeouts) {
- Object.keys(keyStatusTimeouts).forEach((keyId) => self.clearTimeout(keyStatusTimeouts[keyId]));
- }
- const {
- drmSystemOptions
- } = this.config;
- const removePromise = isPersistentSessionType(drmSystemOptions) ? new Promise((resolve, reject) => {
- self.setTimeout(() => reject(new Error(`MediaKeySession.remove() timeout`)), 8e3);
- mediaKeysSession.remove().then(resolve).catch(reject);
- }) : Promise.resolve();
- return removePromise.catch((error) => {
- this.log(`Could not remove session: ${error}`);
- if (!this.hls)
- return;
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.KEY_SYSTEM_DESTROY_REMOVE_SESSION_ERROR,
- fatal: false,
- error: new Error(`Could not remove session: ${error}`)
- });
- }).then(() => {
- return mediaKeysSession.close();
- }).catch((error) => {
- this.log(`Could not close session: ${error}`);
- if (!this.hls)
- return;
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR,
- fatal: false,
- error: new Error(`Could not close session: ${error}`)
- });
- });
- }
- return Promise.resolve();
- }
- };
- EMEController.CDMCleanupPromise = void 0;
- function getKeyIdString(decryptdata) {
- if (!decryptdata) {
- throw new Error("Could not read keyId of undefined decryptdata");
- }
- if (decryptdata.keyId === null) {
- throw new Error("keyId is null");
- }
- return arrayToHex(decryptdata.keyId);
- }
- function getKeyStatus(decryptdata, keyContext) {
- if (decryptdata.keyId && keyContext.mediaKeysSession.keyStatuses.has(decryptdata.keyId)) {
- return keyContext.mediaKeysSession.keyStatuses.get(decryptdata.keyId);
- }
- if (decryptdata.matches(keyContext.decryptdata)) {
- return keyContext.keyStatus;
- }
- return void 0;
- }
- var EMEKeyError = class extends Error {
- constructor(data, message) {
- super(message);
- this.data = void 0;
- data.error || (data.error = new Error(message));
- this.data = data;
- data.err = data.error;
- }
- };
- function getKeyStatusError(keyStatus, decryptdata) {
- const outputRestricted = keyStatus === "output-restricted";
- const details = outputRestricted ? ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED : ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR;
- return new EMEKeyError({
- type: ErrorTypes.KEY_SYSTEM_ERROR,
- details,
- fatal: false,
- decryptdata
- }, outputRestricted ? "HDCP level output restricted" : `key status changed to "${keyStatus}"`);
- }
- var FPSController = class {
- constructor(hls) {
- this.hls = void 0;
- this.isVideoPlaybackQualityAvailable = false;
- this.timer = void 0;
- this.media = null;
- this.lastTime = void 0;
- this.lastDroppedFrames = 0;
- this.lastDecodedFrames = 0;
- this.streamController = void 0;
- this.hls = hls;
- this.registerListeners();
- }
- setStreamController(streamController) {
- this.streamController = streamController;
- }
- registerListeners() {
- this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- this.hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- }
- unregisterListeners() {
- this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- this.hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- }
- destroy() {
- if (this.timer) {
- clearInterval(this.timer);
- }
- this.unregisterListeners();
- this.isVideoPlaybackQualityAvailable = false;
- this.media = null;
- }
- onMediaAttaching(event, data) {
- const config = this.hls.config;
- if (config.capLevelOnFPSDrop) {
- const media = data.media instanceof self.HTMLVideoElement ? data.media : null;
- this.media = media;
- if (media && typeof media.getVideoPlaybackQuality === "function") {
- this.isVideoPlaybackQualityAvailable = true;
- }
- self.clearInterval(this.timer);
- this.timer = self.setInterval(this.checkFPSInterval.bind(this), config.fpsDroppedMonitoringPeriod);
- }
- }
- onMediaDetaching() {
- this.media = null;
- }
- checkFPS(video, decodedFrames, droppedFrames) {
- const currentTime = performance.now();
- if (decodedFrames) {
- if (this.lastTime) {
- const currentPeriod = currentTime - this.lastTime;
- const currentDropped = droppedFrames - this.lastDroppedFrames;
- const currentDecoded = decodedFrames - this.lastDecodedFrames;
- const droppedFPS = 1e3 * currentDropped / currentPeriod;
- const hls = this.hls;
- hls.trigger(Events.FPS_DROP, {
- currentDropped,
- currentDecoded,
- totalDroppedFrames: droppedFrames
- });
- if (droppedFPS > 0) {
- if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
- let currentLevel = hls.currentLevel;
- hls.logger.warn("drop FPS ratio greater than max allowed value for currentLevel: " + currentLevel);
- if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
- currentLevel = currentLevel - 1;
- hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
- level: currentLevel,
- droppedLevel: hls.currentLevel
- });
- hls.autoLevelCapping = currentLevel;
- this.streamController.nextLevelSwitch();
- }
- }
- }
- }
- this.lastTime = currentTime;
- this.lastDroppedFrames = droppedFrames;
- this.lastDecodedFrames = decodedFrames;
- }
- }
- checkFPSInterval() {
- const video = this.media;
- if (video) {
- if (this.isVideoPlaybackQualityAvailable) {
- const videoPlaybackQuality = video.getVideoPlaybackQuality();
- this.checkFPS(video, videoPlaybackQuality.totalVideoFrames, videoPlaybackQuality.droppedVideoFrames);
- } else {
- this.checkFPS(video, video.webkitDecodedFrameCount, video.webkitDroppedFrameCount);
- }
- }
- }
- };
- function sendAddTrackEvent(track, videoEl) {
- let event;
- try {
- event = new Event("addtrack");
- } catch (err) {
- event = document.createEvent("Event");
- event.initEvent("addtrack", false, false);
- }
- event.track = track;
- videoEl.dispatchEvent(event);
- }
- function addCueToTrack(track, cue) {
- const mode = track.mode;
- if (mode === "disabled") {
- track.mode = "hidden";
- }
- if (track.cues && !track.cues.getCueById(cue.id)) {
- try {
- track.addCue(cue);
- if (!track.cues.getCueById(cue.id)) {
- throw new Error(`addCue is failed for: ${cue}`);
- }
- } catch (err) {
- logger.debug(`[texttrack-utils]: ${err}`);
- try {
- const textTrackCue = new self.TextTrackCue(cue.startTime, cue.endTime, cue.text);
- textTrackCue.id = cue.id;
- track.addCue(textTrackCue);
- } catch (err2) {
- logger.debug(`[texttrack-utils]: Legacy TextTrackCue fallback failed: ${err2}`);
- }
- }
- }
- if (mode === "disabled") {
- track.mode = mode;
- }
- }
- function clearCurrentCues(track, enterHandler) {
- const mode = track.mode;
- if (mode === "disabled") {
- track.mode = "hidden";
- }
- if (track.cues) {
- for (let i = track.cues.length; i--; ) {
- if (enterHandler) {
- track.cues[i].removeEventListener("enter", enterHandler);
- }
- track.removeCue(track.cues[i]);
- }
- }
- if (mode === "disabled") {
- track.mode = mode;
- }
- }
- function removeCuesInRange(track, start, end, predicate) {
- const mode = track.mode;
- if (mode === "disabled") {
- track.mode = "hidden";
- }
- if (track.cues && track.cues.length > 0) {
- const cues = getCuesInRange(track.cues, start, end);
- for (let i = 0; i < cues.length; i++) {
- if (!predicate || predicate(cues[i])) {
- track.removeCue(cues[i]);
- }
- }
- }
- if (mode === "disabled") {
- track.mode = mode;
- }
- }
- function getFirstCueIndexFromTime(cues, time) {
- if (time <= cues[0].startTime) {
- return 0;
- }
- const len = cues.length - 1;
- if (time > cues[len].endTime) {
- return -1;
- }
- let left = 0;
- let right = len;
- let mid;
- while (left <= right) {
- mid = Math.floor((right + left) / 2);
- if (time < cues[mid].startTime) {
- right = mid - 1;
- } else if (time > cues[mid].startTime && left < len) {
- left = mid + 1;
- } else {
- return mid;
- }
- }
- return cues[left].startTime - time < time - cues[right].startTime ? left : right;
- }
- function getCuesInRange(cues, start, end) {
- const cuesFound = [];
- const firstCueInRange = getFirstCueIndexFromTime(cues, start);
- if (firstCueInRange > -1) {
- for (let i = firstCueInRange, len = cues.length; i < len; i++) {
- const cue = cues[i];
- if (cue.startTime >= start && cue.endTime <= end) {
- cuesFound.push(cue);
- } else if (cue.startTime > end) {
- return cuesFound;
- }
- }
- }
- return cuesFound;
- }
- function filterSubtitleTracks(textTrackList) {
- const tracks = [];
- for (let i = 0; i < textTrackList.length; i++) {
- const track = textTrackList[i];
- if ((track.kind === "subtitles" || track.kind === "captions") && track.label) {
- tracks.push(textTrackList[i]);
- }
- }
- return tracks;
- }
- var SubtitleTrackController = class extends BasePlaylistController {
- constructor(hls) {
- super(hls, "subtitle-track-controller");
- this.media = null;
- this.tracks = [];
- this.groupIds = null;
- this.tracksInGroup = [];
- this.trackId = -1;
- this.currentTrack = null;
- this.selectDefaultTrack = true;
- this.queuedDefaultTrack = -1;
- this.useTextTrackPolling = false;
- this.subtitlePollingInterval = -1;
- this._subtitleDisplay = true;
- this.asyncPollTrackChange = () => this.pollTrackChange(0);
- this.onTextTracksChanged = () => {
- if (!this.useTextTrackPolling) {
- self.clearInterval(this.subtitlePollingInterval);
- }
- if (!this.media || !this.hls.config.renderTextTracksNatively) {
- return;
- }
- let textTrack = null;
- const tracks = filterSubtitleTracks(this.media.textTracks);
- for (let i = 0; i < tracks.length; i++) {
- if (tracks[i].mode === "hidden") {
- textTrack = tracks[i];
- } else if (tracks[i].mode === "showing") {
- textTrack = tracks[i];
- break;
- }
- }
- const trackId = this.findTrackForTextTrack(textTrack);
- if (this.subtitleTrack !== trackId) {
- this.setSubtitleTrack(trackId);
- }
- };
- this.registerListeners();
- }
- destroy() {
- this.unregisterListeners();
- this.tracks.length = 0;
- this.tracksInGroup.length = 0;
- this.currentTrack = null;
- this.onTextTracksChanged = this.asyncPollTrackChange = null;
- super.destroy();
- }
- get subtitleDisplay() {
- return this._subtitleDisplay;
- }
- set subtitleDisplay(value) {
- this._subtitleDisplay = value;
- if (this.trackId > -1) {
- this.toggleTrackModes();
- }
- }
- registerListeners() {
- const {
- hls
- } = this;
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
- hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
- hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
- hls.on(Events.ERROR, this.onError, this);
- }
- unregisterListeners() {
- const {
- hls
- } = this;
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
- hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
- hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
- hls.off(Events.ERROR, this.onError, this);
- }
- // Listen for subtitle track change, then extract the current track ID.
- onMediaAttached(event, data) {
- this.media = data.media;
- if (!this.media) {
- return;
- }
- if (this.queuedDefaultTrack > -1) {
- this.subtitleTrack = this.queuedDefaultTrack;
- this.queuedDefaultTrack = -1;
- }
- this.useTextTrackPolling = !(this.media.textTracks && "onchange" in this.media.textTracks);
- if (this.useTextTrackPolling) {
- this.pollTrackChange(500);
- } else {
- this.media.textTracks.addEventListener("change", this.asyncPollTrackChange);
- }
- }
- pollTrackChange(timeout) {
- self.clearInterval(this.subtitlePollingInterval);
- this.subtitlePollingInterval = self.setInterval(this.onTextTracksChanged, timeout);
- }
- onMediaDetaching(event, data) {
- const media = this.media;
- if (!media) {
- return;
- }
- const transferringMedia = !!data.transferMedia;
- self.clearInterval(this.subtitlePollingInterval);
- if (!this.useTextTrackPolling) {
- media.textTracks.removeEventListener("change", this.asyncPollTrackChange);
- }
- if (this.trackId > -1) {
- this.queuedDefaultTrack = this.trackId;
- }
- this.subtitleTrack = -1;
- this.media = null;
- if (transferringMedia) {
- return;
- }
- const textTracks = filterSubtitleTracks(media.textTracks);
- textTracks.forEach((track) => {
- clearCurrentCues(track);
- });
- }
- onManifestLoading() {
- this.tracks = [];
- this.groupIds = null;
- this.tracksInGroup = [];
- this.trackId = -1;
- this.currentTrack = null;
- this.selectDefaultTrack = true;
- }
- // Fired whenever a new manifest is loaded.
- onManifestParsed(event, data) {
- this.tracks = data.subtitleTracks;
- }
- onSubtitleTrackLoaded(event, data) {
- const {
- id,
- groupId,
- details
- } = data;
- const trackInActiveGroup = this.tracksInGroup[id];
- if (!trackInActiveGroup || trackInActiveGroup.groupId !== groupId) {
- this.warn(`Subtitle track with id:${id} and group:${groupId} not found in active group ${trackInActiveGroup == null ? void 0 : trackInActiveGroup.groupId}`);
- return;
- }
- const curDetails = trackInActiveGroup.details;
- trackInActiveGroup.details = data.details;
- this.log(`Subtitle track ${id} "${trackInActiveGroup.name}" lang:${trackInActiveGroup.lang} group:${groupId} loaded [${details.startSN}-${details.endSN}]`);
- if (id === this.trackId) {
- this.playlistLoaded(id, data, curDetails);
- }
- }
- onLevelLoading(event, data) {
- this.switchLevel(data.level);
- }
- onLevelSwitching(event, data) {
- this.switchLevel(data.level);
- }
- switchLevel(levelIndex) {
- const levelInfo = this.hls.levels[levelIndex];
- if (!levelInfo) {
- return;
- }
- const subtitleGroups = levelInfo.subtitleGroups || null;
- const currentGroups = this.groupIds;
- let currentTrack = this.currentTrack;
- if (!subtitleGroups || (currentGroups == null ? void 0 : currentGroups.length) !== (subtitleGroups == null ? void 0 : subtitleGroups.length) || subtitleGroups != null && subtitleGroups.some((groupId) => (currentGroups == null ? void 0 : currentGroups.indexOf(groupId)) === -1)) {
- this.groupIds = subtitleGroups;
- this.trackId = -1;
- this.currentTrack = null;
- const subtitleTracks = this.tracks.filter((track) => !subtitleGroups || subtitleGroups.indexOf(track.groupId) !== -1);
- if (subtitleTracks.length) {
- if (this.selectDefaultTrack && !subtitleTracks.some((track) => track.default)) {
- this.selectDefaultTrack = false;
- }
- subtitleTracks.forEach((track, i) => {
- track.id = i;
- });
- } else if (!currentTrack && !this.tracksInGroup.length) {
- return;
- }
- this.tracksInGroup = subtitleTracks;
- const subtitlePreference = this.hls.config.subtitlePreference;
- if (!currentTrack && subtitlePreference) {
- this.selectDefaultTrack = false;
- const groupIndex = findMatchingOption(subtitlePreference, subtitleTracks);
- if (groupIndex > -1) {
- currentTrack = subtitleTracks[groupIndex];
- } else {
- const allIndex = findMatchingOption(subtitlePreference, this.tracks);
- currentTrack = this.tracks[allIndex];
- }
- }
- let trackId = this.findTrackId(currentTrack);
- if (trackId === -1 && currentTrack) {
- trackId = this.findTrackId(null);
- }
- const subtitleTracksUpdated = {
- subtitleTracks
- };
- this.log(`Updating subtitle tracks, ${subtitleTracks.length} track(s) found in "${subtitleGroups == null ? void 0 : subtitleGroups.join(",")}" group-id`);
- this.hls.trigger(Events.SUBTITLE_TRACKS_UPDATED, subtitleTracksUpdated);
- if (trackId !== -1 && this.trackId === -1) {
- this.setSubtitleTrack(trackId);
- }
- }
- }
- findTrackId(currentTrack) {
- const tracks = this.tracksInGroup;
- const selectDefault = this.selectDefaultTrack;
- for (let i = 0; i < tracks.length; i++) {
- const track = tracks[i];
- if (selectDefault && !track.default || !selectDefault && !currentTrack) {
- continue;
- }
- if (!currentTrack || matchesOption(track, currentTrack)) {
- return i;
- }
- }
- if (currentTrack) {
- for (let i = 0; i < tracks.length; i++) {
- const track = tracks[i];
- if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ["LANGUAGE", "ASSOC-LANGUAGE", "CHARACTERISTICS"])) {
- return i;
- }
- }
- for (let i = 0; i < tracks.length; i++) {
- const track = tracks[i];
- if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ["LANGUAGE"])) {
- return i;
- }
- }
- }
- return -1;
- }
- findTrackForTextTrack(textTrack) {
- if (textTrack) {
- const tracks = this.tracksInGroup;
- for (let i = 0; i < tracks.length; i++) {
- const track = tracks[i];
- if (subtitleTrackMatchesTextTrack(track, textTrack)) {
- return i;
- }
- }
- }
- return -1;
- }
- onError(event, data) {
- if (data.fatal || !data.context) {
- return;
- }
- if (data.context.type === PlaylistContextType.SUBTITLE_TRACK && data.context.id === this.trackId && (!this.groupIds || this.groupIds.indexOf(data.context.groupId) !== -1)) {
- this.checkRetry(data);
- }
- }
- get allSubtitleTracks() {
- return this.tracks;
- }
- /** get alternate subtitle tracks list from playlist **/
- get subtitleTracks() {
- return this.tracksInGroup;
- }
- /** get/set index of the selected subtitle track (based on index in subtitle track lists) **/
- get subtitleTrack() {
- return this.trackId;
- }
- set subtitleTrack(newId) {
- this.selectDefaultTrack = false;
- this.setSubtitleTrack(newId);
- }
- setSubtitleOption(subtitleOption) {
- this.hls.config.subtitlePreference = subtitleOption;
- if (subtitleOption) {
- if (subtitleOption.id === -1) {
- this.setSubtitleTrack(-1);
- return null;
- }
- const allSubtitleTracks = this.allSubtitleTracks;
- this.selectDefaultTrack = false;
- if (allSubtitleTracks.length) {
- const currentTrack = this.currentTrack;
- if (currentTrack && matchesOption(subtitleOption, currentTrack)) {
- return currentTrack;
- }
- const groupIndex = findMatchingOption(subtitleOption, this.tracksInGroup);
- if (groupIndex > -1) {
- const track = this.tracksInGroup[groupIndex];
- this.setSubtitleTrack(groupIndex);
- return track;
- } else if (currentTrack) {
- return null;
- } else {
- const allIndex = findMatchingOption(subtitleOption, allSubtitleTracks);
- if (allIndex > -1) {
- return allSubtitleTracks[allIndex];
- }
- }
- }
- }
- return null;
- }
- loadPlaylist(hlsUrlParameters) {
- super.loadPlaylist();
- if (this.shouldLoadPlaylist(this.currentTrack)) {
- this.scheduleLoading(this.currentTrack, hlsUrlParameters);
- }
- }
- loadingPlaylist(currentTrack, hlsUrlParameters) {
- super.loadingPlaylist(currentTrack, hlsUrlParameters);
- const id = currentTrack.id;
- const groupId = currentTrack.groupId;
- const url = this.getUrlWithDirectives(currentTrack.url, hlsUrlParameters);
- const details = currentTrack.details;
- const age = details == null ? void 0 : details.age;
- this.log(`Loading subtitle ${id} "${currentTrack.name}" lang:${currentTrack.lang} group:${groupId}${(hlsUrlParameters == null ? void 0 : hlsUrlParameters.msn) !== void 0 ? " at sn " + hlsUrlParameters.msn + " part " + hlsUrlParameters.part : ""}${age && details.live ? " age " + age.toFixed(1) + (details.type ? " " + details.type || "" : "") : ""} ${url}`);
- this.hls.trigger(Events.SUBTITLE_TRACK_LOADING, {
- url,
- id,
- groupId,
- deliveryDirectives: hlsUrlParameters || null,
- track: currentTrack
- });
- }
- /**
- * Disables the old subtitleTrack and sets current mode on the next subtitleTrack.
- * This operates on the DOM textTracks.
- * A value of -1 will disable all subtitle tracks.
- */
- toggleTrackModes() {
- const {
- media
- } = this;
- if (!media) {
- return;
- }
- const textTracks = filterSubtitleTracks(media.textTracks);
- const currentTrack = this.currentTrack;
- let nextTrack;
- if (currentTrack) {
- nextTrack = textTracks.filter((textTrack) => subtitleTrackMatchesTextTrack(currentTrack, textTrack))[0];
- if (!nextTrack) {
- this.warn(`Unable to find subtitle TextTrack with name "${currentTrack.name}" and language "${currentTrack.lang}"`);
- }
- }
- [].slice.call(textTracks).forEach((track) => {
- if (track.mode !== "disabled" && track !== nextTrack) {
- track.mode = "disabled";
- }
- });
- if (nextTrack) {
- const mode = this.subtitleDisplay ? "showing" : "hidden";
- if (nextTrack.mode !== mode) {
- nextTrack.mode = mode;
- }
- }
- }
- /**
- * This method is responsible for validating the subtitle index and periodically reloading if live.
- * Dispatches the SUBTITLE_TRACK_SWITCH event, which instructs the subtitle-stream-controller to load the selected track.
- */
- setSubtitleTrack(newId) {
- const tracks = this.tracksInGroup;
- if (!this.media) {
- this.queuedDefaultTrack = newId;
- return;
- }
- if (newId < -1 || newId >= tracks.length || !isFiniteNumber(newId)) {
- this.warn(`Invalid subtitle track id: ${newId}`);
- return;
- }
- this.selectDefaultTrack = false;
- const lastTrack = this.currentTrack;
- const track = tracks[newId] || null;
- this.trackId = newId;
- this.currentTrack = track;
- this.toggleTrackModes();
- if (!track) {
- this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, {
- id: newId
- });
- return;
- }
- const trackLoaded = !!track.details && !track.details.live;
- if (newId === this.trackId && track === lastTrack && trackLoaded) {
- return;
- }
- this.log(`Switching to subtitle-track ${newId}` + (track ? ` "${track.name}" lang:${track.lang} group:${track.groupId}` : ""));
- const {
- id,
- groupId = "",
- name,
- type,
- url
- } = track;
- this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, {
- id,
- groupId,
- name,
- type,
- url
- });
- const hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details, track.details);
- this.loadPlaylist(hlsUrlParameters);
- }
- };
- function uuid() {
- try {
- return crypto.randomUUID();
- } catch (error) {
- try {
- const url = URL.createObjectURL(new Blob());
- const uuid2 = url.toString();
- URL.revokeObjectURL(url);
- return uuid2.slice(uuid2.lastIndexOf("/") + 1);
- } catch (error2) {
- let dt = (/* @__PURE__ */ new Date()).getTime();
- const uuid2 = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
- const r = (dt + Math.random() * 16) % 16 | 0;
- dt = Math.floor(dt / 16);
- return (c == "x" ? r : r & 3 | 8).toString(16);
- });
- return uuid2;
- }
- }
- }
- function hash(text) {
- let hash2 = 5381;
- let i = text.length;
- while (i) {
- hash2 = hash2 * 33 ^ text.charCodeAt(--i);
- }
- return (hash2 >>> 0).toString();
- }
- var ALIGNED_END_THRESHOLD_SECONDS = 0.025;
- var TimelineOccupancy = function(TimelineOccupancy2) {
- TimelineOccupancy2[TimelineOccupancy2["Point"] = 0] = "Point";
- TimelineOccupancy2[TimelineOccupancy2["Range"] = 1] = "Range";
- return TimelineOccupancy2;
- }({});
- function generateAssetIdentifier(interstitial, uri, assetListIndex) {
- return `${interstitial.identifier}-${assetListIndex + 1}-${hash(uri)}`;
- }
- var InterstitialEvent = class {
- constructor(dateRange, base) {
- this.base = void 0;
- this._duration = null;
- this._timelineStart = null;
- this.appendInPlaceDisabled = void 0;
- this.appendInPlaceStarted = void 0;
- this.dateRange = void 0;
- this.hasPlayed = false;
- this.cumulativeDuration = 0;
- this.resumeOffset = NaN;
- this.playoutLimit = NaN;
- this.restrictions = {
- skip: false,
- jump: false
- };
- this.snapOptions = {
- out: false,
- in: false
- };
- this.assetList = [];
- this.assetListLoader = void 0;
- this.assetListResponse = null;
- this.resumeAnchor = void 0;
- this.error = void 0;
- this.resetOnResume = void 0;
- this.base = base;
- this.dateRange = dateRange;
- this.setDateRange(dateRange);
- }
- setDateRange(dateRange) {
- this.dateRange = dateRange;
- this.resumeOffset = dateRange.attr.optionalFloat("X-RESUME-OFFSET", this.resumeOffset);
- this.playoutLimit = dateRange.attr.optionalFloat("X-PLAYOUT-LIMIT", this.playoutLimit);
- this.restrictions = dateRange.attr.enumeratedStringList("X-RESTRICT", this.restrictions);
- this.snapOptions = dateRange.attr.enumeratedStringList("X-SNAP", this.snapOptions);
- }
- reset() {
- var _this$assetListLoader;
- this.appendInPlaceStarted = false;
- (_this$assetListLoader = this.assetListLoader) == null || _this$assetListLoader.destroy();
- this.assetListLoader = void 0;
- if (!this.supplementsPrimary) {
- this.assetListResponse = null;
- this.assetList = [];
- this._duration = null;
- }
- }
- isAssetPastPlayoutLimit(assetIndex) {
- var _this$assetList$asset;
- if (assetIndex > 0 && assetIndex >= this.assetList.length) {
- return true;
- }
- const playoutLimit = this.playoutLimit;
- if (assetIndex <= 0 || isNaN(playoutLimit)) {
- return false;
- }
- if (playoutLimit === 0) {
- return true;
- }
- const assetOffset = ((_this$assetList$asset = this.assetList[assetIndex]) == null ? void 0 : _this$assetList$asset.startOffset) || 0;
- return assetOffset > playoutLimit;
- }
- findAssetIndex(asset) {
- const index = this.assetList.indexOf(asset);
- return index;
- }
- get identifier() {
- return this.dateRange.id;
- }
- get startDate() {
- return this.dateRange.startDate;
- }
- get startTime() {
- const startTime = this.dateRange.startTime;
- if (this.snapOptions.out) {
- const frag = this.dateRange.tagAnchor;
- if (frag) {
- return getSnapToFragmentTime(startTime, frag);
- }
- }
- return startTime;
- }
- get startOffset() {
- return this.cue.pre ? 0 : this.startTime;
- }
- get startIsAligned() {
- if (this.startTime === 0 || this.snapOptions.out) {
- return true;
- }
- const frag = this.dateRange.tagAnchor;
- if (frag) {
- const startTime = this.dateRange.startTime;
- const snappedStart = getSnapToFragmentTime(startTime, frag);
- return startTime - snappedStart < 0.1;
- }
- return false;
- }
- get resumptionOffset() {
- const resumeOffset = this.resumeOffset;
- const offset = isFiniteNumber(resumeOffset) ? resumeOffset : this.duration;
- return this.cumulativeDuration + offset;
- }
- get resumeTime() {
- const resumeTime = this.startOffset + this.resumptionOffset;
- if (this.snapOptions.in) {
- const frag = this.resumeAnchor;
- if (frag) {
- return getSnapToFragmentTime(resumeTime, frag);
- }
- }
- return resumeTime;
- }
- get appendInPlace() {
- if (this.appendInPlaceStarted) {
- return true;
- }
- if (this.appendInPlaceDisabled) {
- return false;
- }
- if (!this.cue.once && !this.cue.pre && // preroll starts at startPosition before startPosition is known (live)
- this.startIsAligned && (isNaN(this.playoutLimit) && isNaN(this.resumeOffset) || this.resumeOffset && this.duration && Math.abs(this.resumeOffset - this.duration) < ALIGNED_END_THRESHOLD_SECONDS)) {
- return true;
- }
- return false;
- }
- set appendInPlace(value) {
- if (this.appendInPlaceStarted) {
- this.resetOnResume = !value;
- return;
- }
- this.appendInPlaceDisabled = !value;
- }
- // Extended timeline start time
- get timelineStart() {
- if (this._timelineStart !== null) {
- return this._timelineStart;
- }
- return this.startTime;
- }
- set timelineStart(value) {
- this._timelineStart = value;
- }
- get duration() {
- const playoutLimit = this.playoutLimit;
- let duration;
- if (this._duration !== null) {
- duration = this._duration;
- } else if (this.dateRange.duration) {
- duration = this.dateRange.duration;
- } else {
- duration = this.dateRange.plannedDuration || 0;
- }
- if (!isNaN(playoutLimit) && playoutLimit < duration) {
- duration = playoutLimit;
- }
- return duration;
- }
- set duration(value) {
- this._duration = value;
- }
- get cue() {
- return this.dateRange.cue;
- }
- get timelineOccupancy() {
- if (this.dateRange.attr["X-TIMELINE-OCCUPIES"] === "RANGE") {
- return TimelineOccupancy.Range;
- }
- return TimelineOccupancy.Point;
- }
- get supplementsPrimary() {
- return this.dateRange.attr["X-TIMELINE-STYLE"] === "PRIMARY";
- }
- get contentMayVary() {
- return this.dateRange.attr["X-CONTENT-MAY-VARY"] !== "NO";
- }
- get assetUrl() {
- return this.dateRange.attr["X-ASSET-URI"];
- }
- get assetListUrl() {
- return this.dateRange.attr["X-ASSET-LIST"];
- }
- get baseUrl() {
- return this.base.url;
- }
- get assetListLoaded() {
- return this.assetList.length > 0 || this.assetListResponse !== null;
- }
- toString() {
- return eventToString(this);
- }
- };
- function getSnapToFragmentTime(time, frag) {
- return time - frag.start < frag.duration / 2 && !(Math.abs(time - (frag.start + frag.duration)) < ALIGNED_END_THRESHOLD_SECONDS) ? frag.start : frag.start + frag.duration;
- }
- function getInterstitialUrl(uri, sessionId, baseUrl) {
- const url = new self.URL(uri, baseUrl);
- if (url.protocol !== "data:") {
- url.searchParams.set("_HLS_primary_id", sessionId);
- }
- return url;
- }
- function getNextAssetIndex(interstitial, assetListIndex) {
- while ((_interstitial$assetLi = interstitial.assetList[++assetListIndex]) != null && _interstitial$assetLi.error) {
- var _interstitial$assetLi;
- }
- return assetListIndex;
- }
- function eventToString(interstitial) {
- return `["${interstitial.identifier}" ${interstitial.cue.pre ? "<pre>" : interstitial.cue.post ? "<post>" : ""}${interstitial.timelineStart.toFixed(2)}-${interstitial.resumeTime.toFixed(2)}]`;
- }
- function eventAssetToString(asset) {
- const start = asset.timelineStart;
- const duration = asset.duration || 0;
- return `["${asset.identifier}" ${start.toFixed(2)}-${(start + duration).toFixed(2)}]`;
- }
- var HlsAssetPlayer = class {
- constructor(HlsPlayerClass, userConfig, interstitial, assetItem) {
- this.hls = void 0;
- this.interstitial = void 0;
- this.assetItem = void 0;
- this.tracks = null;
- this.hasDetails = false;
- this.mediaAttached = null;
- this._currentTime = void 0;
- this._bufferedEosTime = void 0;
- this.checkPlayout = () => {
- if (this.reachedPlayout(this.currentTime) && this.hls) {
- this.hls.trigger(Events.PLAYOUT_LIMIT_REACHED, {});
- }
- };
- const hls = this.hls = new HlsPlayerClass(userConfig);
- this.interstitial = interstitial;
- this.assetItem = assetItem;
- const detailsLoaded = () => {
- this.hasDetails = true;
- };
- hls.once(Events.LEVEL_LOADED, detailsLoaded);
- hls.once(Events.AUDIO_TRACK_LOADED, detailsLoaded);
- hls.once(Events.SUBTITLE_TRACK_LOADED, detailsLoaded);
- hls.on(Events.MEDIA_ATTACHING, (name, {
- media
- }) => {
- this.removeMediaListeners();
- this.mediaAttached = media;
- const event = this.interstitial;
- if (event.playoutLimit) {
- media.addEventListener("timeupdate", this.checkPlayout);
- if (this.appendInPlace) {
- hls.on(Events.BUFFER_APPENDED, () => {
- const bufferedEnd = this.bufferedEnd;
- if (this.reachedPlayout(bufferedEnd)) {
- this._bufferedEosTime = bufferedEnd;
- hls.trigger(Events.BUFFERED_TO_END, void 0);
- }
- });
- }
- }
- });
- }
- get appendInPlace() {
- return this.interstitial.appendInPlace;
- }
- loadSource() {
- const hls = this.hls;
- if (!hls) {
- return;
- }
- if (!hls.url) {
- let uri = this.assetItem.uri;
- try {
- uri = getInterstitialUrl(uri, hls.config.primarySessionId || "").href;
- } catch (error) {
- }
- hls.loadSource(uri);
- } else if (hls.levels.length && !hls.started) {
- hls.startLoad(-1, true);
- }
- }
- bufferedInPlaceToEnd(media) {
- var _this$hls;
- if (!this.appendInPlace) {
- return false;
- }
- if ((_this$hls = this.hls) != null && _this$hls.bufferedToEnd) {
- return true;
- }
- if (!media) {
- return false;
- }
- const duration = Math.min(this._bufferedEosTime || Infinity, this.duration);
- const start = this.timelineOffset;
- const bufferInfo = BufferHelper.bufferInfo(media, start, 0);
- const bufferedEnd = this.getAssetTime(bufferInfo.end);
- return bufferedEnd >= duration - 0.02;
- }
- reachedPlayout(time) {
- const interstitial = this.interstitial;
- const playoutLimit = interstitial.playoutLimit;
- return this.startOffset + time >= playoutLimit;
- }
- get destroyed() {
- var _this$hls2;
- return !((_this$hls2 = this.hls) != null && _this$hls2.userConfig);
- }
- get assetId() {
- return this.assetItem.identifier;
- }
- get interstitialId() {
- return this.assetItem.parentIdentifier;
- }
- get media() {
- var _this$hls3;
- return ((_this$hls3 = this.hls) == null ? void 0 : _this$hls3.media) || null;
- }
- get bufferedEnd() {
- const media = this.media || this.mediaAttached;
- if (!media) {
- if (this._bufferedEosTime) {
- return this._bufferedEosTime;
- }
- return this.currentTime;
- }
- const bufferInfo = BufferHelper.bufferInfo(media, media.currentTime, 1e-3);
- return this.getAssetTime(bufferInfo.end);
- }
- get currentTime() {
- const media = this.media || this.mediaAttached;
- if (!media) {
- return this._currentTime || 0;
- }
- return this.getAssetTime(media.currentTime);
- }
- get duration() {
- const duration = this.assetItem.duration;
- if (!duration) {
- return 0;
- }
- const playoutLimit = this.interstitial.playoutLimit;
- if (playoutLimit) {
- const assetPlayout = playoutLimit - this.startOffset;
- if (assetPlayout > 0 && assetPlayout < duration) {
- return assetPlayout;
- }
- }
- return duration;
- }
- get remaining() {
- const duration = this.duration;
- if (!duration) {
- return 0;
- }
- return Math.max(0, duration - this.currentTime);
- }
- get startOffset() {
- return this.assetItem.startOffset;
- }
- get timelineOffset() {
- var _this$hls4;
- return ((_this$hls4 = this.hls) == null ? void 0 : _this$hls4.config.timelineOffset) || 0;
- }
- set timelineOffset(value) {
- const timelineOffset = this.timelineOffset;
- if (value !== timelineOffset) {
- const diff = value - timelineOffset;
- if (Math.abs(diff) > 1 / 9e4 && this.hls) {
- if (this.hasDetails) {
- throw new Error(`Cannot set timelineOffset after playlists are loaded`);
- }
- this.hls.config.timelineOffset = value;
- }
- }
- }
- getAssetTime(time) {
- const timelineOffset = this.timelineOffset;
- const duration = this.duration;
- return Math.min(Math.max(0, time - timelineOffset), duration);
- }
- removeMediaListeners() {
- const media = this.mediaAttached;
- if (media) {
- this._currentTime = media.currentTime;
- this.bufferSnapShot();
- media.removeEventListener("timeupdate", this.checkPlayout);
- }
- }
- bufferSnapShot() {
- if (this.mediaAttached) {
- var _this$hls5;
- if ((_this$hls5 = this.hls) != null && _this$hls5.bufferedToEnd) {
- this._bufferedEosTime = this.bufferedEnd;
- }
- }
- }
- destroy() {
- this.removeMediaListeners();
- if (this.hls) {
- this.hls.destroy();
- }
- this.hls = null;
- this.tracks = this.mediaAttached = this.checkPlayout = null;
- }
- attachMedia(data) {
- var _this$hls6;
- this.loadSource();
- (_this$hls6 = this.hls) == null || _this$hls6.attachMedia(data);
- }
- detachMedia() {
- var _this$hls7;
- this.removeMediaListeners();
- this.mediaAttached = null;
- (_this$hls7 = this.hls) == null || _this$hls7.detachMedia();
- }
- resumeBuffering() {
- var _this$hls8;
- (_this$hls8 = this.hls) == null || _this$hls8.resumeBuffering();
- }
- pauseBuffering() {
- var _this$hls9;
- (_this$hls9 = this.hls) == null || _this$hls9.pauseBuffering();
- }
- transferMedia() {
- var _this$hls0;
- this.bufferSnapShot();
- return ((_this$hls0 = this.hls) == null ? void 0 : _this$hls0.transferMedia()) || null;
- }
- resetDetails() {
- const hls = this.hls;
- if (hls && this.hasDetails) {
- hls.stopLoad();
- const deleteDetails = (obj) => delete obj.details;
- hls.levels.forEach(deleteDetails);
- hls.allAudioTracks.forEach(deleteDetails);
- hls.allSubtitleTracks.forEach(deleteDetails);
- this.hasDetails = false;
- }
- }
- on(event, listener, context) {
- var _this$hls1;
- (_this$hls1 = this.hls) == null || _this$hls1.on(event, listener);
- }
- once(event, listener, context) {
- var _this$hls10;
- (_this$hls10 = this.hls) == null || _this$hls10.once(event, listener);
- }
- off(event, listener, context) {
- var _this$hls11;
- (_this$hls11 = this.hls) == null || _this$hls11.off(event, listener);
- }
- toString() {
- var _this$hls12;
- return `HlsAssetPlayer: ${eventAssetToString(this.assetItem)} ${(_this$hls12 = this.hls) == null ? void 0 : _this$hls12.sessionId} ${this.appendInPlace ? "append-in-place" : ""}`;
- }
- };
- var ABUTTING_THRESHOLD_SECONDS = 0.033;
- var InterstitialsSchedule = class extends Logger {
- constructor(onScheduleUpdate, logger2) {
- super("interstitials-sched", logger2);
- this.onScheduleUpdate = void 0;
- this.eventMap = {};
- this.events = null;
- this.items = null;
- this.durations = {
- primary: 0,
- playout: 0,
- integrated: 0
- };
- this.onScheduleUpdate = onScheduleUpdate;
- }
- destroy() {
- this.reset();
- this.onScheduleUpdate = null;
- }
- reset() {
- this.eventMap = {};
- this.setDurations(0, 0, 0);
- if (this.events) {
- this.events.forEach((interstitial) => interstitial.reset());
- }
- this.events = this.items = null;
- }
- resetErrorsInRange(start, end) {
- if (this.events) {
- return this.events.reduce((count, interstitial) => {
- if (start <= interstitial.startOffset && end > interstitial.startOffset) {
- delete interstitial.error;
- return count + 1;
- }
- return count;
- }, 0);
- }
- return 0;
- }
- get duration() {
- const items = this.items;
- return items ? items[items.length - 1].end : 0;
- }
- get length() {
- return this.items ? this.items.length : 0;
- }
- getEvent(identifier) {
- return identifier ? this.eventMap[identifier] || null : null;
- }
- hasEvent(identifier) {
- return identifier in this.eventMap;
- }
- findItemIndex(item, time) {
- if (item.event) {
- return this.findEventIndex(item.event.identifier);
- }
- let index = -1;
- if (item.nextEvent) {
- index = this.findEventIndex(item.nextEvent.identifier) - 1;
- } else if (item.previousEvent) {
- index = this.findEventIndex(item.previousEvent.identifier) + 1;
- }
- const items = this.items;
- if (items) {
- if (!items[index]) {
- if (time === void 0) {
- time = item.start;
- }
- index = this.findItemIndexAtTime(time);
- }
- while (index >= 0 && (_items$index = items[index]) != null && _items$index.event) {
- var _items$index;
- index--;
- }
- }
- return index;
- }
- findItemIndexAtTime(timelinePos, timelineType) {
- const items = this.items;
- if (items) {
- for (let i = 0; i < items.length; i++) {
- let timeRange = items[i];
- if (timelineType && timelineType !== "primary") {
- timeRange = timeRange[timelineType];
- }
- if (timelinePos === timeRange.start || timelinePos > timeRange.start && timelinePos < timeRange.end) {
- return i;
- }
- }
- }
- return -1;
- }
- findJumpRestrictedIndex(startIndex, endIndex) {
- const items = this.items;
- if (items) {
- for (let i = startIndex; i <= endIndex; i++) {
- if (!items[i]) {
- break;
- }
- const event = items[i].event;
- if (event != null && event.restrictions.jump && !event.appendInPlace) {
- return i;
- }
- }
- }
- return -1;
- }
- findEventIndex(identifier) {
- const items = this.items;
- if (items) {
- for (let i = items.length; i--; ) {
- var _items$i$event;
- if (((_items$i$event = items[i].event) == null ? void 0 : _items$i$event.identifier) === identifier) {
- return i;
- }
- }
- }
- return -1;
- }
- findAssetIndex(event, timelinePos) {
- const assetList = event.assetList;
- const length = assetList.length;
- if (length > 1) {
- for (let i = 0; i < length; i++) {
- const asset = assetList[i];
- if (!asset.error) {
- const timelineStart = asset.timelineStart;
- if (timelinePos === timelineStart || timelinePos > timelineStart && (timelinePos < timelineStart + (asset.duration || 0) || i === length - 1)) {
- return i;
- }
- }
- }
- }
- return 0;
- }
- get assetIdAtEnd() {
- var _this$items;
- const interstitialAtEnd = (_this$items = this.items) == null || (_this$items = _this$items[this.length - 1]) == null ? void 0 : _this$items.event;
- if (interstitialAtEnd) {
- const assetList = interstitialAtEnd.assetList;
- const assetAtEnd = assetList[assetList.length - 1];
- if (assetAtEnd) {
- return assetAtEnd.identifier;
- }
- }
- return null;
- }
- parseInterstitialDateRanges(mediaSelection, enableAppendInPlace) {
- const details = mediaSelection.main.details;
- const {
- dateRanges
- } = details;
- const previousInterstitialEvents = this.events;
- const interstitialEvents = this.parseDateRanges(dateRanges, {
- url: details.url
- }, enableAppendInPlace);
- const ids = Object.keys(dateRanges);
- const removedInterstitials = previousInterstitialEvents ? previousInterstitialEvents.filter((event) => !ids.includes(event.identifier)) : [];
- if (interstitialEvents.length) {
- interstitialEvents.sort((a, b) => {
- const aPre = a.cue.pre;
- const aPost = a.cue.post;
- const bPre = b.cue.pre;
- const bPost = b.cue.post;
- if (aPre && !bPre) {
- return -1;
- }
- if (bPre && !aPre) {
- return 1;
- }
- if (aPost && !bPost) {
- return 1;
- }
- if (bPost && !aPost) {
- return -1;
- }
- if (!aPre && !bPre && !aPost && !bPost) {
- const startA = a.startTime;
- const startB = b.startTime;
- if (startA !== startB) {
- return startA - startB;
- }
- }
- return a.dateRange.tagOrder - b.dateRange.tagOrder;
- });
- }
- this.events = interstitialEvents;
- removedInterstitials.forEach((interstitial) => {
- this.removeEvent(interstitial);
- });
- this.updateSchedule(mediaSelection, removedInterstitials);
- }
- updateSchedule(mediaSelection, removedInterstitials = [], forceUpdate = false) {
- const events = this.events || [];
- if (events.length || removedInterstitials.length || this.length < 2) {
- const currentItems = this.items;
- const updatedItems = this.parseSchedule(events, mediaSelection);
- const updated = forceUpdate || removedInterstitials.length || (currentItems == null ? void 0 : currentItems.length) !== updatedItems.length || updatedItems.some((item, i) => {
- return Math.abs(item.playout.start - currentItems[i].playout.start) > 5e-3 || Math.abs(item.playout.end - currentItems[i].playout.end) > 5e-3;
- });
- if (updated) {
- this.items = updatedItems;
- this.onScheduleUpdate(removedInterstitials, currentItems);
- }
- }
- }
- parseDateRanges(dateRanges, baseData, enableAppendInPlace) {
- const interstitialEvents = [];
- const ids = Object.keys(dateRanges);
- for (let i = 0; i < ids.length; i++) {
- const id = ids[i];
- const dateRange = dateRanges[id];
- if (dateRange.isInterstitial) {
- let interstitial = this.eventMap[id];
- if (interstitial) {
- interstitial.setDateRange(dateRange);
- } else {
- interstitial = new InterstitialEvent(dateRange, baseData);
- this.eventMap[id] = interstitial;
- if (enableAppendInPlace === false) {
- interstitial.appendInPlace = enableAppendInPlace;
- }
- }
- interstitialEvents.push(interstitial);
- }
- }
- return interstitialEvents;
- }
- parseSchedule(interstitialEvents, mediaSelection) {
- const schedule = [];
- const details = mediaSelection.main.details;
- const primaryDuration = details.live ? Infinity : details.edge;
- let playoutDuration = 0;
- interstitialEvents = interstitialEvents.filter((event) => !event.error && !(event.cue.once && event.hasPlayed));
- if (interstitialEvents.length) {
- this.resolveOffsets(interstitialEvents, mediaSelection);
- let primaryPosition = 0;
- let integratedTime = 0;
- interstitialEvents.forEach((interstitial, i) => {
- const preroll = interstitial.cue.pre;
- const postroll = interstitial.cue.post;
- const previousEvent = interstitialEvents[i - 1] || null;
- const appendInPlace = interstitial.appendInPlace;
- const eventStart = postroll ? primaryDuration : interstitial.startOffset;
- const interstitialDuration = interstitial.duration;
- const timelineDuration = interstitial.timelineOccupancy === TimelineOccupancy.Range ? interstitialDuration : 0;
- const resumptionOffset = interstitial.resumptionOffset;
- const inSameStartTimeSequence = (previousEvent == null ? void 0 : previousEvent.startTime) === eventStart;
- const start = eventStart + interstitial.cumulativeDuration;
- let end = appendInPlace ? start + interstitialDuration : eventStart + resumptionOffset;
- if (preroll || !postroll && eventStart <= 0) {
- const integratedStart = integratedTime;
- integratedTime += timelineDuration;
- interstitial.timelineStart = start;
- const playoutStart = playoutDuration;
- playoutDuration += interstitialDuration;
- schedule.push({
- event: interstitial,
- start,
- end,
- playout: {
- start: playoutStart,
- end: playoutDuration
- },
- integrated: {
- start: integratedStart,
- end: integratedTime
- }
- });
- } else if (eventStart <= primaryDuration) {
- if (!inSameStartTimeSequence) {
- const segmentDuration = eventStart - primaryPosition;
- if (segmentDuration > ABUTTING_THRESHOLD_SECONDS) {
- const timelineStart = primaryPosition;
- const _integratedStart = integratedTime;
- integratedTime += segmentDuration;
- const _playoutStart = playoutDuration;
- playoutDuration += segmentDuration;
- const primarySegment = {
- previousEvent: interstitialEvents[i - 1] || null,
- nextEvent: interstitial,
- start: timelineStart,
- end: timelineStart + segmentDuration,
- playout: {
- start: _playoutStart,
- end: playoutDuration
- },
- integrated: {
- start: _integratedStart,
- end: integratedTime
- }
- };
- schedule.push(primarySegment);
- } else if (segmentDuration > 0 && previousEvent) {
- previousEvent.cumulativeDuration += segmentDuration;
- schedule[schedule.length - 1].end = eventStart;
- }
- }
- if (postroll) {
- end = start;
- }
- interstitial.timelineStart = start;
- const integratedStart = integratedTime;
- integratedTime += timelineDuration;
- const playoutStart = playoutDuration;
- playoutDuration += interstitialDuration;
- schedule.push({
- event: interstitial,
- start,
- end,
- playout: {
- start: playoutStart,
- end: playoutDuration
- },
- integrated: {
- start: integratedStart,
- end: integratedTime
- }
- });
- } else {
- return;
- }
- const resumeTime = interstitial.resumeTime;
- if (postroll || resumeTime > primaryDuration) {
- primaryPosition = primaryDuration;
- } else {
- primaryPosition = resumeTime;
- }
- });
- if (primaryPosition < primaryDuration) {
- var _schedule;
- const timelineStart = primaryPosition;
- const integratedStart = integratedTime;
- const segmentDuration = primaryDuration - primaryPosition;
- integratedTime += segmentDuration;
- const playoutStart = playoutDuration;
- playoutDuration += segmentDuration;
- schedule.push({
- previousEvent: ((_schedule = schedule[schedule.length - 1]) == null ? void 0 : _schedule.event) || null,
- nextEvent: null,
- start: primaryPosition,
- end: timelineStart + segmentDuration,
- playout: {
- start: playoutStart,
- end: playoutDuration
- },
- integrated: {
- start: integratedStart,
- end: integratedTime
- }
- });
- }
- this.setDurations(primaryDuration, playoutDuration, integratedTime);
- } else {
- const start = 0;
- schedule.push({
- previousEvent: null,
- nextEvent: null,
- start,
- end: primaryDuration,
- playout: {
- start,
- end: primaryDuration
- },
- integrated: {
- start,
- end: primaryDuration
- }
- });
- this.setDurations(primaryDuration, primaryDuration, primaryDuration);
- }
- return schedule;
- }
- setDurations(primary, playout, integrated) {
- this.durations = {
- primary,
- playout,
- integrated
- };
- }
- resolveOffsets(interstitialEvents, mediaSelection) {
- const details = mediaSelection.main.details;
- const primaryDuration = details.live ? Infinity : details.edge;
- let cumulativeDuration = 0;
- let lastScheduledStart = -1;
- interstitialEvents.forEach((interstitial, i) => {
- const preroll = interstitial.cue.pre;
- const postroll = interstitial.cue.post;
- const eventStart = preroll ? 0 : postroll ? primaryDuration : interstitial.startTime;
- this.updateAssetDurations(interstitial);
- const inSameStartTimeSequence = lastScheduledStart === eventStart;
- if (inSameStartTimeSequence) {
- interstitial.cumulativeDuration = cumulativeDuration;
- } else {
- cumulativeDuration = 0;
- lastScheduledStart = eventStart;
- }
- if (!postroll && interstitial.snapOptions.in) {
- interstitial.resumeAnchor = findFragmentByPTS(null, details.fragments, interstitial.startOffset + interstitial.resumptionOffset, 0, 0) || void 0;
- }
- if (interstitial.appendInPlace && !interstitial.appendInPlaceStarted) {
- const alignedSegmentStart = this.primaryCanResumeInPlaceAt(interstitial, mediaSelection);
- if (!alignedSegmentStart) {
- interstitial.appendInPlace = false;
- }
- }
- if (!interstitial.appendInPlace && i + 1 < interstitialEvents.length) {
- const timeBetween = interstitialEvents[i + 1].startTime - interstitialEvents[i].resumeTime;
- if (timeBetween < ABUTTING_THRESHOLD_SECONDS) {
- interstitialEvents[i + 1].appendInPlace = false;
- if (interstitialEvents[i + 1].appendInPlace) {
- this.warn(`Could not change append strategy for abutting event ${interstitial}`);
- }
- }
- }
- const resumeOffset = isFiniteNumber(interstitial.resumeOffset) ? interstitial.resumeOffset : interstitial.duration;
- cumulativeDuration += resumeOffset;
- });
- }
- primaryCanResumeInPlaceAt(interstitial, mediaSelection) {
- const resumeTime = interstitial.resumeTime;
- const resumesInPlaceAt = interstitial.startTime + interstitial.resumptionOffset;
- if (Math.abs(resumeTime - resumesInPlaceAt) > ALIGNED_END_THRESHOLD_SECONDS) {
- this.log(`"${interstitial.identifier}" resumption ${resumeTime} not aligned with estimated timeline end ${resumesInPlaceAt}`);
- return false;
- }
- const playlists = Object.keys(mediaSelection);
- return !playlists.some((playlistType) => {
- const details = mediaSelection[playlistType].details;
- const playlistEnd = details.edge;
- if (resumeTime >= playlistEnd) {
- this.log(`"${interstitial.identifier}" resumption ${resumeTime} past ${playlistType} playlist end ${playlistEnd}`);
- return false;
- }
- const startFragment = findFragmentByPTS(null, details.fragments, resumeTime);
- if (!startFragment) {
- this.log(`"${interstitial.identifier}" resumption ${resumeTime} does not align with any fragments in ${playlistType} playlist (${details.fragStart}-${details.fragmentEnd})`);
- return true;
- }
- const allowance = playlistType === "audio" ? 0.175 : 0;
- const alignedWithSegment = Math.abs(startFragment.start - resumeTime) < ALIGNED_END_THRESHOLD_SECONDS + allowance || Math.abs(startFragment.end - resumeTime) < ALIGNED_END_THRESHOLD_SECONDS + allowance;
- if (!alignedWithSegment) {
- this.log(`"${interstitial.identifier}" resumption ${resumeTime} not aligned with ${playlistType} fragment bounds (${startFragment.start}-${startFragment.end} sn: ${startFragment.sn} cc: ${startFragment.cc})`);
- return true;
- }
- return false;
- });
- }
- updateAssetDurations(interstitial) {
- if (!interstitial.assetListLoaded) {
- return;
- }
- const eventStart = interstitial.timelineStart;
- let sumDuration = 0;
- let hasUnknownDuration = false;
- let hasErrors = false;
- for (let i = 0; i < interstitial.assetList.length; i++) {
- const asset = interstitial.assetList[i];
- const timelineStart = eventStart + sumDuration;
- asset.startOffset = sumDuration;
- asset.timelineStart = timelineStart;
- hasUnknownDuration || (hasUnknownDuration = asset.duration === null);
- hasErrors || (hasErrors = !!asset.error);
- const duration = asset.error ? 0 : asset.duration || 0;
- sumDuration += duration;
- }
- if (hasUnknownDuration && !hasErrors) {
- interstitial.duration = Math.max(sumDuration, interstitial.duration);
- } else {
- interstitial.duration = sumDuration;
- }
- }
- removeEvent(interstitial) {
- interstitial.reset();
- delete this.eventMap[interstitial.identifier];
- }
- };
- function segmentToString(segment) {
- return `[${segment.event ? '"' + segment.event.identifier + '"' : "primary"}: ${segment.start.toFixed(2)}-${segment.end.toFixed(2)}]`;
- }
- var AssetListLoader = class {
- constructor(hls) {
- this.hls = void 0;
- this.hls = hls;
- }
- destroy() {
- this.hls = null;
- }
- loadAssetList(interstitial, hlsStartOffset) {
- const assetListUrl = interstitial.assetListUrl;
- let url;
- try {
- url = getInterstitialUrl(assetListUrl, this.hls.sessionId, interstitial.baseUrl);
- } catch (error) {
- const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_LOAD_ERROR, error, assetListUrl);
- this.hls.trigger(Events.ERROR, errorData);
- return;
- }
- if (hlsStartOffset && url.protocol !== "data:") {
- url.searchParams.set("_HLS_start_offset", "" + hlsStartOffset);
- }
- const config = this.hls.config;
- const Loader = config.loader;
- const loader = new Loader(config);
- const context = {
- responseType: "json",
- url: url.href
- };
- const loadPolicy = config.interstitialAssetListLoadPolicy.default;
- const loaderConfig = {
- loadPolicy,
- timeout: loadPolicy.maxLoadTimeMs,
- maxRetry: 0,
- retryDelay: 0,
- maxRetryDelay: 0
- };
- const callbacks = {
- onSuccess: (response, stats, context2, networkDetails) => {
- const assetListResponse = response.data;
- const assets = assetListResponse == null ? void 0 : assetListResponse.ASSETS;
- if (!Array.isArray(assets)) {
- const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_PARSING_ERROR, new Error(`Invalid interstitial asset list`), context2.url, stats, networkDetails);
- this.hls.trigger(Events.ERROR, errorData);
- return;
- }
- interstitial.assetListResponse = assetListResponse;
- this.hls.trigger(Events.ASSET_LIST_LOADED, {
- event: interstitial,
- assetListResponse,
- networkDetails
- });
- },
- onError: (error, context2, networkDetails, stats) => {
- const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_LOAD_ERROR, new Error(`Error loading X-ASSET-LIST: HTTP status ${error.code} ${error.text} (${context2.url})`), context2.url, stats, networkDetails);
- this.hls.trigger(Events.ERROR, errorData);
- },
- onTimeout: (stats, context2, networkDetails) => {
- const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_LOAD_TIMEOUT, new Error(`Timeout loading X-ASSET-LIST (${context2.url})`), context2.url, stats, networkDetails);
- this.hls.trigger(Events.ERROR, errorData);
- }
- };
- loader.load(context, loaderConfig, callbacks);
- this.hls.trigger(Events.ASSET_LIST_LOADING, {
- event: interstitial
- });
- return loader;
- }
- assignAssetListError(interstitial, details, error, url, stats, networkDetails) {
- interstitial.error = error;
- return {
- type: ErrorTypes.NETWORK_ERROR,
- details,
- fatal: false,
- interstitial,
- url,
- error,
- networkDetails,
- stats
- };
- }
- };
- function playWithCatch(media) {
- media == null || media.play().catch(() => {
- });
- }
- function timelineMessage(label, time) {
- return `[${label}] Advancing timeline position to ${time}`;
- }
- var InterstitialsController = class extends Logger {
- constructor(hls, HlsPlayerClass) {
- super("interstitials", hls.logger);
- this.HlsPlayerClass = void 0;
- this.hls = void 0;
- this.assetListLoader = void 0;
- this.mediaSelection = null;
- this.altSelection = null;
- this.media = null;
- this.detachedData = null;
- this.requiredTracks = null;
- this.manager = null;
- this.playerQueue = [];
- this.bufferedPos = -1;
- this.timelinePos = -1;
- this.schedule = void 0;
- this.playingItem = null;
- this.bufferingItem = null;
- this.waitingItem = null;
- this.endedItem = null;
- this.playingAsset = null;
- this.endedAsset = null;
- this.bufferingAsset = null;
- this.shouldPlay = false;
- this.onPlay = () => {
- this.shouldPlay = true;
- };
- this.onPause = () => {
- this.shouldPlay = false;
- };
- this.onSeeking = () => {
- const currentTime = this.currentTime;
- if (currentTime === void 0 || this.playbackDisabled || !this.schedule) {
- return;
- }
- const diff = currentTime - this.timelinePos;
- const roundingError = Math.abs(diff) < 1 / 7056e5;
- if (roundingError) {
- return;
- }
- const backwardSeek = diff <= -0.01;
- this.timelinePos = currentTime;
- this.bufferedPos = currentTime;
- const playingItem = this.playingItem;
- if (!playingItem) {
- this.checkBuffer();
- return;
- }
- if (backwardSeek) {
- const resetCount = this.schedule.resetErrorsInRange(currentTime, currentTime - diff);
- if (resetCount) {
- this.updateSchedule(true);
- }
- }
- this.checkBuffer();
- if (backwardSeek && currentTime < playingItem.start || currentTime >= playingItem.end) {
- var _this$media;
- const playingIndex = this.findItemIndex(playingItem);
- let scheduleIndex = this.schedule.findItemIndexAtTime(currentTime);
- if (scheduleIndex === -1) {
- scheduleIndex = playingIndex + (backwardSeek ? -1 : 1);
- this.log(`seeked ${backwardSeek ? "back " : ""}to position not covered by schedule ${currentTime} (resolving from ${playingIndex} to ${scheduleIndex})`);
- }
- if (!this.isInterstitial(playingItem) && (_this$media = this.media) != null && _this$media.paused) {
- this.shouldPlay = false;
- }
- if (!backwardSeek) {
- if (scheduleIndex > playingIndex) {
- const jumpIndex = this.schedule.findJumpRestrictedIndex(playingIndex + 1, scheduleIndex);
- if (jumpIndex > playingIndex) {
- this.setSchedulePosition(jumpIndex);
- return;
- }
- }
- }
- this.setSchedulePosition(scheduleIndex);
- return;
- }
- const playingAsset = this.playingAsset;
- if (!playingAsset) {
- if (this.playingLastItem && this.isInterstitial(playingItem)) {
- const restartAsset = playingItem.event.assetList[0];
- if (restartAsset) {
- this.endedItem = this.playingItem;
- this.playingItem = null;
- this.setScheduleToAssetAtTime(currentTime, restartAsset);
- }
- }
- return;
- }
- const start = playingAsset.timelineStart;
- const duration = playingAsset.duration || 0;
- if (backwardSeek && currentTime < start || currentTime >= start + duration) {
- var _playingItem$event;
- if ((_playingItem$event = playingItem.event) != null && _playingItem$event.appendInPlace) {
- this.clearInterstitial(playingItem.event, playingItem);
- this.flushFrontBuffer(currentTime);
- }
- this.setScheduleToAssetAtTime(currentTime, playingAsset);
- }
- };
- this.onTimeupdate = () => {
- const currentTime = this.currentTime;
- if (currentTime === void 0 || this.playbackDisabled) {
- return;
- }
- if (currentTime > this.timelinePos) {
- this.timelinePos = currentTime;
- if (currentTime > this.bufferedPos) {
- this.checkBuffer();
- }
- } else {
- return;
- }
- const playingItem = this.playingItem;
- if (!playingItem || this.playingLastItem) {
- return;
- }
- if (currentTime >= playingItem.end) {
- this.timelinePos = playingItem.end;
- const playingIndex = this.findItemIndex(playingItem);
- this.setSchedulePosition(playingIndex + 1);
- }
- const playingAsset = this.playingAsset;
- if (!playingAsset) {
- return;
- }
- const end = playingAsset.timelineStart + (playingAsset.duration || 0);
- if (currentTime >= end) {
- this.setScheduleToAssetAtTime(currentTime, playingAsset);
- }
- };
- this.onScheduleUpdate = (removedInterstitials, previousItems) => {
- const schedule = this.schedule;
- if (!schedule) {
- return;
- }
- const playingItem = this.playingItem;
- const interstitialEvents = schedule.events || [];
- const scheduleItems = schedule.items || [];
- const durations = schedule.durations;
- const removedIds = removedInterstitials.map((interstitial) => interstitial.identifier);
- const interstitialsUpdated = !!(interstitialEvents.length || removedIds.length);
- if (interstitialsUpdated || previousItems) {
- this.log(`INTERSTITIALS_UPDATED (${interstitialEvents.length}): ${interstitialEvents}
- Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timelinePos}`);
- }
- if (removedIds.length) {
- this.log(`Removed events ${removedIds}`);
- }
- let updatedPlayingItem = null;
- let updatedBufferingItem = null;
- if (playingItem) {
- updatedPlayingItem = this.updateItem(playingItem, this.timelinePos);
- if (this.itemsMatch(playingItem, updatedPlayingItem)) {
- this.playingItem = updatedPlayingItem;
- } else {
- this.waitingItem = this.endedItem = null;
- }
- }
- this.waitingItem = this.updateItem(this.waitingItem);
- this.endedItem = this.updateItem(this.endedItem);
- const bufferingItem = this.bufferingItem;
- if (bufferingItem) {
- updatedBufferingItem = this.updateItem(bufferingItem, this.bufferedPos);
- if (this.itemsMatch(bufferingItem, updatedBufferingItem)) {
- this.bufferingItem = updatedBufferingItem;
- } else if (bufferingItem.event) {
- this.bufferingItem = this.playingItem;
- this.clearInterstitial(bufferingItem.event, null);
- }
- }
- removedInterstitials.forEach((interstitial) => {
- interstitial.assetList.forEach((asset) => {
- this.clearAssetPlayer(asset.identifier, null);
- });
- });
- this.playerQueue.forEach((player) => {
- if (player.interstitial.appendInPlace) {
- const timelineStart = player.assetItem.timelineStart;
- const diff = player.timelineOffset - timelineStart;
- if (diff) {
- try {
- player.timelineOffset = timelineStart;
- } catch (e) {
- if (Math.abs(diff) > ALIGNED_END_THRESHOLD_SECONDS) {
- this.warn(`${e} ("${player.assetId}" ${player.timelineOffset}->${timelineStart})`);
- }
- }
- }
- }
- });
- if (interstitialsUpdated || previousItems) {
- this.hls.trigger(Events.INTERSTITIALS_UPDATED, {
- events: interstitialEvents.slice(0),
- schedule: scheduleItems.slice(0),
- durations,
- removedIds
- });
- if (this.isInterstitial(playingItem) && removedIds.includes(playingItem.event.identifier)) {
- this.warn(`Interstitial "${playingItem.event.identifier}" removed while playing`);
- this.primaryFallback(playingItem.event);
- return;
- }
- if (playingItem) {
- this.trimInPlace(updatedPlayingItem, playingItem);
- }
- if (bufferingItem && updatedBufferingItem !== updatedPlayingItem) {
- this.trimInPlace(updatedBufferingItem, bufferingItem);
- }
- this.checkBuffer();
- }
- };
- this.hls = hls;
- this.HlsPlayerClass = HlsPlayerClass;
- this.assetListLoader = new AssetListLoader(hls);
- this.schedule = new InterstitialsSchedule(this.onScheduleUpdate, hls.logger);
- this.registerListeners();
- }
- registerListeners() {
- const hls = this.hls;
- if (hls) {
- hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
- hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
- hls.on(Events.AUDIO_TRACK_UPDATED, this.onAudioTrackUpdated, this);
- hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
- hls.on(Events.SUBTITLE_TRACK_UPDATED, this.onSubtitleTrackUpdated, this);
- hls.on(Events.EVENT_CUE_ENTER, this.onInterstitialCueEnter, this);
- hls.on(Events.ASSET_LIST_LOADED, this.onAssetListLoaded, this);
- hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
- hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
- hls.on(Events.BUFFERED_TO_END, this.onBufferedToEnd, this);
- hls.on(Events.MEDIA_ENDED, this.onMediaEnded, this);
- hls.on(Events.ERROR, this.onError, this);
- hls.on(Events.DESTROYING, this.onDestroying, this);
- }
- }
- unregisterListeners() {
- const hls = this.hls;
- if (hls) {
- hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
- hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
- hls.off(Events.AUDIO_TRACK_UPDATED, this.onAudioTrackUpdated, this);
- hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
- hls.off(Events.SUBTITLE_TRACK_UPDATED, this.onSubtitleTrackUpdated, this);
- hls.off(Events.EVENT_CUE_ENTER, this.onInterstitialCueEnter, this);
- hls.off(Events.ASSET_LIST_LOADED, this.onAssetListLoaded, this);
- hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
- hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
- hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
- hls.off(Events.BUFFERED_TO_END, this.onBufferedToEnd, this);
- hls.off(Events.MEDIA_ENDED, this.onMediaEnded, this);
- hls.off(Events.ERROR, this.onError, this);
- hls.off(Events.DESTROYING, this.onDestroying, this);
- }
- }
- startLoad() {
- this.resumeBuffering();
- }
- stopLoad() {
- this.pauseBuffering();
- }
- resumeBuffering() {
- var _this$getBufferingPla;
- (_this$getBufferingPla = this.getBufferingPlayer()) == null || _this$getBufferingPla.resumeBuffering();
- }
- pauseBuffering() {
- var _this$getBufferingPla2;
- (_this$getBufferingPla2 = this.getBufferingPlayer()) == null || _this$getBufferingPla2.pauseBuffering();
- }
- destroy() {
- this.unregisterListeners();
- this.stopLoad();
- if (this.assetListLoader) {
- this.assetListLoader.destroy();
- }
- this.emptyPlayerQueue();
- this.clearScheduleState();
- if (this.schedule) {
- this.schedule.destroy();
- }
- this.media = this.detachedData = this.mediaSelection = this.requiredTracks = this.altSelection = this.schedule = this.manager = null;
- this.hls = this.HlsPlayerClass = this.log = null;
- this.assetListLoader = null;
- this.onPlay = this.onPause = this.onSeeking = this.onTimeupdate = null;
- this.onScheduleUpdate = null;
- }
- onDestroying() {
- const media = this.primaryMedia || this.media;
- if (media) {
- this.removeMediaListeners(media);
- }
- }
- removeMediaListeners(media) {
- removeEventListener(media, "play", this.onPlay);
- removeEventListener(media, "pause", this.onPause);
- removeEventListener(media, "seeking", this.onSeeking);
- removeEventListener(media, "timeupdate", this.onTimeupdate);
- }
- onMediaAttaching(event, data) {
- const media = this.media = data.media;
- addEventListener(media, "seeking", this.onSeeking);
- addEventListener(media, "timeupdate", this.onTimeupdate);
- addEventListener(media, "play", this.onPlay);
- addEventListener(media, "pause", this.onPause);
- }
- onMediaAttached(event, data) {
- const playingItem = this.effectivePlayingItem;
- const detachedMedia = this.detachedData;
- this.detachedData = null;
- if (playingItem === null) {
- this.checkStart();
- } else if (!detachedMedia) {
- this.clearScheduleState();
- const playingIndex = this.findItemIndex(playingItem);
- this.setSchedulePosition(playingIndex);
- }
- }
- clearScheduleState() {
- this.log(`clear schedule state`);
- this.playingItem = this.bufferingItem = this.waitingItem = this.endedItem = this.playingAsset = this.endedAsset = this.bufferingAsset = null;
- }
- onMediaDetaching(event, data) {
- const transferringMedia = !!data.transferMedia;
- const media = this.media;
- this.media = null;
- if (transferringMedia) {
- return;
- }
- if (media) {
- this.removeMediaListeners(media);
- }
- if (this.detachedData) {
- const player = this.getBufferingPlayer();
- if (player) {
- this.log(`Removing schedule state for detachedData and ${player}`);
- this.playingAsset = this.endedAsset = this.bufferingAsset = this.bufferingItem = this.waitingItem = this.detachedData = null;
- player.detachMedia();
- }
- this.shouldPlay = false;
- }
- }
- get interstitialsManager() {
- if (!this.hls) {
- return null;
- }
- if (this.manager) {
- return this.manager;
- }
- const c = this;
- const effectiveBufferingItem = () => c.bufferingItem || c.waitingItem;
- const getAssetPlayer = (asset) => asset ? c.getAssetPlayer(asset.identifier) : asset;
- const getMappedTime = (item, timelineType, asset, controllerField, assetPlayerField) => {
- if (item) {
- let time = item[timelineType].start;
- const interstitial = item.event;
- if (interstitial) {
- if (timelineType === "playout" || interstitial.timelineOccupancy !== TimelineOccupancy.Point) {
- const assetPlayer = getAssetPlayer(asset);
- if ((assetPlayer == null ? void 0 : assetPlayer.interstitial) === interstitial) {
- time += assetPlayer.assetItem.startOffset + assetPlayer[assetPlayerField];
- }
- }
- } else {
- const value = controllerField === "bufferedPos" ? getBufferedEnd() : c[controllerField];
- time += value - item.start;
- }
- return time;
- }
- return 0;
- };
- const findMappedTime = (primaryTime, timelineType) => {
- var _c$schedule;
- if (primaryTime !== 0 && timelineType !== "primary" && (_c$schedule = c.schedule) != null && _c$schedule.length) {
- var _c$schedule$items;
- const index = c.schedule.findItemIndexAtTime(primaryTime);
- const item = (_c$schedule$items = c.schedule.items) == null ? void 0 : _c$schedule$items[index];
- if (item) {
- const diff = item[timelineType].start - item.start;
- return primaryTime + diff;
- }
- }
- return primaryTime;
- };
- const getBufferedEnd = () => {
- const value = c.bufferedPos;
- if (value === Number.MAX_VALUE) {
- return getMappedDuration("primary");
- }
- return Math.max(value, 0);
- };
- const getMappedDuration = (timelineType) => {
- var _c$primaryDetails, _c$schedule2;
- if ((_c$primaryDetails = c.primaryDetails) != null && _c$primaryDetails.live) {
- return c.primaryDetails.edge;
- }
- return ((_c$schedule2 = c.schedule) == null ? void 0 : _c$schedule2.durations[timelineType]) || 0;
- };
- const seekTo = (time, timelineType) => {
- var _item$event, _c$schedule$items2;
- const item = c.effectivePlayingItem;
- if (item != null && (_item$event = item.event) != null && _item$event.restrictions.skip || !c.schedule) {
- return;
- }
- c.log(`seek to ${time} "${timelineType}"`);
- const playingItem = c.effectivePlayingItem;
- const targetIndex = c.schedule.findItemIndexAtTime(time, timelineType);
- const targetItem = (_c$schedule$items2 = c.schedule.items) == null ? void 0 : _c$schedule$items2[targetIndex];
- const bufferingPlayer = c.getBufferingPlayer();
- const bufferingInterstitial = bufferingPlayer == null ? void 0 : bufferingPlayer.interstitial;
- const appendInPlace = bufferingInterstitial == null ? void 0 : bufferingInterstitial.appendInPlace;
- const seekInItem = playingItem && c.itemsMatch(playingItem, targetItem);
- if (playingItem && (appendInPlace || seekInItem)) {
- const assetPlayer = getAssetPlayer(c.playingAsset);
- const media = (assetPlayer == null ? void 0 : assetPlayer.media) || c.primaryMedia;
- if (media) {
- const currentTime = timelineType === "primary" ? media.currentTime : getMappedTime(playingItem, timelineType, c.playingAsset, "timelinePos", "currentTime");
- const diff = time - currentTime;
- const seekToTime = (appendInPlace ? currentTime : media.currentTime) + diff;
- if (seekToTime >= 0 && (!assetPlayer || appendInPlace || seekToTime <= assetPlayer.duration)) {
- media.currentTime = seekToTime;
- return;
- }
- }
- }
- if (targetItem) {
- let seekToTime = time;
- if (timelineType !== "primary") {
- const primarySegmentStart = targetItem[timelineType].start;
- const diff = time - primarySegmentStart;
- seekToTime = targetItem.start + diff;
- }
- const targetIsPrimary = !c.isInterstitial(targetItem);
- if ((!c.isInterstitial(playingItem) || playingItem.event.appendInPlace) && (targetIsPrimary || targetItem.event.appendInPlace)) {
- const media = c.media || (appendInPlace ? bufferingPlayer == null ? void 0 : bufferingPlayer.media : null);
- if (media) {
- media.currentTime = seekToTime;
- }
- } else if (playingItem) {
- const playingIndex = c.findItemIndex(playingItem);
- if (targetIndex > playingIndex) {
- const jumpIndex = c.schedule.findJumpRestrictedIndex(playingIndex + 1, targetIndex);
- if (jumpIndex > playingIndex) {
- c.setSchedulePosition(jumpIndex);
- return;
- }
- }
- let assetIndex = 0;
- if (targetIsPrimary) {
- c.timelinePos = seekToTime;
- c.checkBuffer();
- } else {
- const assetList = targetItem.event.assetList;
- const eventTime = time - (targetItem[timelineType] || targetItem).start;
- for (let i = assetList.length; i--; ) {
- const asset = assetList[i];
- if (asset.duration && eventTime >= asset.startOffset && eventTime < asset.startOffset + asset.duration) {
- assetIndex = i;
- break;
- }
- }
- }
- c.setSchedulePosition(targetIndex, assetIndex);
- }
- }
- };
- const getActiveInterstitial = () => {
- const playingItem = c.effectivePlayingItem;
- if (c.isInterstitial(playingItem)) {
- return playingItem;
- }
- const bufferingItem = effectiveBufferingItem();
- if (c.isInterstitial(bufferingItem)) {
- return bufferingItem;
- }
- return null;
- };
- const interstitialPlayer = {
- get bufferedEnd() {
- const interstitialItem = effectiveBufferingItem();
- const bufferingItem = c.bufferingItem;
- if (bufferingItem && bufferingItem === interstitialItem) {
- var _c$bufferingAsset;
- return getMappedTime(bufferingItem, "playout", c.bufferingAsset, "bufferedPos", "bufferedEnd") - bufferingItem.playout.start || ((_c$bufferingAsset = c.bufferingAsset) == null ? void 0 : _c$bufferingAsset.startOffset) || 0;
- }
- return 0;
- },
- get currentTime() {
- const interstitialItem = getActiveInterstitial();
- const playingItem = c.effectivePlayingItem;
- if (playingItem && playingItem === interstitialItem) {
- return getMappedTime(playingItem, "playout", c.effectivePlayingAsset, "timelinePos", "currentTime") - playingItem.playout.start;
- }
- return 0;
- },
- set currentTime(time) {
- const interstitialItem = getActiveInterstitial();
- const playingItem = c.effectivePlayingItem;
- if (playingItem && playingItem === interstitialItem) {
- seekTo(time + playingItem.playout.start, "playout");
- }
- },
- get duration() {
- const interstitialItem = getActiveInterstitial();
- if (interstitialItem) {
- return interstitialItem.playout.end - interstitialItem.playout.start;
- }
- return 0;
- },
- get assetPlayers() {
- var _getActiveInterstitia;
- const assetList = (_getActiveInterstitia = getActiveInterstitial()) == null ? void 0 : _getActiveInterstitia.event.assetList;
- if (assetList) {
- return assetList.map((asset) => c.getAssetPlayer(asset.identifier));
- }
- return [];
- },
- get playingIndex() {
- var _getActiveInterstitia2;
- const interstitial = (_getActiveInterstitia2 = getActiveInterstitial()) == null ? void 0 : _getActiveInterstitia2.event;
- if (interstitial && c.effectivePlayingAsset) {
- return interstitial.findAssetIndex(c.effectivePlayingAsset);
- }
- return -1;
- },
- get scheduleItem() {
- return getActiveInterstitial();
- }
- };
- return this.manager = {
- get events() {
- var _c$schedule3;
- return ((_c$schedule3 = c.schedule) == null || (_c$schedule3 = _c$schedule3.events) == null ? void 0 : _c$schedule3.slice(0)) || [];
- },
- get schedule() {
- var _c$schedule4;
- return ((_c$schedule4 = c.schedule) == null || (_c$schedule4 = _c$schedule4.items) == null ? void 0 : _c$schedule4.slice(0)) || [];
- },
- get interstitialPlayer() {
- if (getActiveInterstitial()) {
- return interstitialPlayer;
- }
- return null;
- },
- get playerQueue() {
- return c.playerQueue.slice(0);
- },
- get bufferingAsset() {
- return c.bufferingAsset;
- },
- get bufferingItem() {
- return effectiveBufferingItem();
- },
- get bufferingIndex() {
- const item = effectiveBufferingItem();
- return c.findItemIndex(item);
- },
- get playingAsset() {
- return c.effectivePlayingAsset;
- },
- get playingItem() {
- return c.effectivePlayingItem;
- },
- get playingIndex() {
- const item = c.effectivePlayingItem;
- return c.findItemIndex(item);
- },
- primary: {
- get bufferedEnd() {
- return getBufferedEnd();
- },
- get currentTime() {
- const timelinePos = c.timelinePos;
- return timelinePos > 0 ? timelinePos : 0;
- },
- set currentTime(time) {
- seekTo(time, "primary");
- },
- get duration() {
- return getMappedDuration("primary");
- },
- get seekableStart() {
- var _c$primaryDetails2;
- return ((_c$primaryDetails2 = c.primaryDetails) == null ? void 0 : _c$primaryDetails2.fragmentStart) || 0;
- }
- },
- integrated: {
- get bufferedEnd() {
- return getMappedTime(effectiveBufferingItem(), "integrated", c.bufferingAsset, "bufferedPos", "bufferedEnd");
- },
- get currentTime() {
- return getMappedTime(c.effectivePlayingItem, "integrated", c.effectivePlayingAsset, "timelinePos", "currentTime");
- },
- set currentTime(time) {
- seekTo(time, "integrated");
- },
- get duration() {
- return getMappedDuration("integrated");
- },
- get seekableStart() {
- var _c$primaryDetails3;
- return findMappedTime(((_c$primaryDetails3 = c.primaryDetails) == null ? void 0 : _c$primaryDetails3.fragmentStart) || 0, "integrated");
- }
- },
- skip: () => {
- const item = c.effectivePlayingItem;
- const event = item == null ? void 0 : item.event;
- if (event && !event.restrictions.skip) {
- const index = c.findItemIndex(item);
- if (event.appendInPlace) {
- const time = item.playout.start + item.event.duration;
- seekTo(time + 1e-3, "playout");
- } else {
- c.advanceAfterAssetEnded(event, index, Infinity);
- }
- }
- }
- };
- }
- // Schedule getters
- get effectivePlayingItem() {
- return this.waitingItem || this.playingItem || this.endedItem;
- }
- get effectivePlayingAsset() {
- return this.playingAsset || this.endedAsset;
- }
- get playingLastItem() {
- var _this$schedule;
- const playingItem = this.playingItem;
- const items = (_this$schedule = this.schedule) == null ? void 0 : _this$schedule.items;
- if (!this.playbackStarted || !playingItem || !items) {
- return false;
- }
- return this.findItemIndex(playingItem) === items.length - 1;
- }
- get playbackStarted() {
- return this.effectivePlayingItem !== null;
- }
- // Media getters and event callbacks
- get currentTime() {
- var _this$bufferingItem, _media;
- if (this.mediaSelection === null) {
- return void 0;
- }
- const queuedForPlayback = this.waitingItem || this.playingItem;
- if (this.isInterstitial(queuedForPlayback) && !queuedForPlayback.event.appendInPlace) {
- return void 0;
- }
- let media = this.media;
- if (!media && (_this$bufferingItem = this.bufferingItem) != null && (_this$bufferingItem = _this$bufferingItem.event) != null && _this$bufferingItem.appendInPlace) {
- media = this.primaryMedia;
- }
- const currentTime = (_media = media) == null ? void 0 : _media.currentTime;
- if (currentTime === void 0 || !isFiniteNumber(currentTime)) {
- return void 0;
- }
- return currentTime;
- }
- get primaryMedia() {
- var _this$detachedData;
- return this.media || ((_this$detachedData = this.detachedData) == null ? void 0 : _this$detachedData.media) || null;
- }
- isInterstitial(item) {
- return !!(item != null && item.event);
- }
- retreiveMediaSource(assetId, toSegment) {
- const player = this.getAssetPlayer(assetId);
- if (player) {
- this.transferMediaFromPlayer(player, toSegment);
- }
- }
- transferMediaFromPlayer(player, toSegment) {
- const appendInPlace = player.interstitial.appendInPlace;
- const playerMedia = player.media;
- if (appendInPlace && playerMedia === this.primaryMedia) {
- this.bufferingAsset = null;
- if (!toSegment || this.isInterstitial(toSegment) && !toSegment.event.appendInPlace) {
- if (toSegment && playerMedia) {
- this.detachedData = {
- media: playerMedia
- };
- return;
- }
- }
- const attachMediaSourceData = player.transferMedia();
- this.log(`transfer MediaSource from ${player} ${stringify(attachMediaSourceData)}`);
- this.detachedData = attachMediaSourceData;
- } else if (toSegment && playerMedia) {
- this.shouldPlay || (this.shouldPlay = !playerMedia.paused);
- }
- }
- transferMediaTo(player, media) {
- var _this$detachedData2, _attachMediaSourceDat;
- if (player.media === media) {
- return;
- }
- let attachMediaSourceData = null;
- const primaryPlayer = this.hls;
- const isAssetPlayer = player !== primaryPlayer;
- const appendInPlace = isAssetPlayer && player.interstitial.appendInPlace;
- const detachedMediaSource = (_this$detachedData2 = this.detachedData) == null ? void 0 : _this$detachedData2.mediaSource;
- let logFromSource;
- if (primaryPlayer.media) {
- if (appendInPlace) {
- attachMediaSourceData = primaryPlayer.transferMedia();
- this.detachedData = attachMediaSourceData;
- }
- logFromSource = `Primary`;
- } else if (detachedMediaSource) {
- const bufferingPlayer = this.getBufferingPlayer();
- if (bufferingPlayer) {
- attachMediaSourceData = bufferingPlayer.transferMedia();
- logFromSource = `${bufferingPlayer}`;
- } else {
- logFromSource = `detached MediaSource`;
- }
- } else {
- logFromSource = `detached media`;
- }
- if (!attachMediaSourceData) {
- if (detachedMediaSource) {
- attachMediaSourceData = this.detachedData;
- this.log(`using detachedData: MediaSource ${stringify(attachMediaSourceData)}`);
- } else if (!this.detachedData || primaryPlayer.media === media) {
- const playerQueue = this.playerQueue;
- if (playerQueue.length > 1) {
- playerQueue.forEach((queuedPlayer) => {
- if (isAssetPlayer && queuedPlayer.interstitial.appendInPlace !== appendInPlace) {
- const interstitial = queuedPlayer.interstitial;
- this.clearInterstitial(queuedPlayer.interstitial, null);
- interstitial.appendInPlace = false;
- if (interstitial.appendInPlace) {
- this.warn(`Could not change append strategy for queued assets ${interstitial}`);
- }
- }
- });
- }
- this.hls.detachMedia();
- this.detachedData = {
- media
- };
- }
- }
- const transferring = attachMediaSourceData && "mediaSource" in attachMediaSourceData && ((_attachMediaSourceDat = attachMediaSourceData.mediaSource) == null ? void 0 : _attachMediaSourceDat.readyState) !== "closed";
- const dataToAttach = transferring && attachMediaSourceData ? attachMediaSourceData : media;
- this.log(`${transferring ? "transfering MediaSource" : "attaching media"} to ${isAssetPlayer ? player : "Primary"} from ${logFromSource} (media.currentTime: ${media.currentTime})`);
- const schedule = this.schedule;
- if (dataToAttach === attachMediaSourceData && schedule) {
- const isAssetAtEndOfSchedule = isAssetPlayer && player.assetId === schedule.assetIdAtEnd;
- dataToAttach.overrides = {
- duration: schedule.duration,
- endOfStream: !isAssetPlayer || isAssetAtEndOfSchedule,
- cueRemoval: !isAssetPlayer
- };
- }
- player.attachMedia(dataToAttach);
- }
- onInterstitialCueEnter() {
- this.onTimeupdate();
- }
- // Scheduling methods
- checkStart() {
- const schedule = this.schedule;
- const interstitialEvents = schedule == null ? void 0 : schedule.events;
- if (!interstitialEvents || this.playbackDisabled || !this.media) {
- return;
- }
- if (this.bufferedPos === -1) {
- this.bufferedPos = 0;
- }
- const timelinePos = this.timelinePos;
- const effectivePlayingItem = this.effectivePlayingItem;
- if (timelinePos === -1) {
- const startPosition = this.hls.startPosition;
- this.log(timelineMessage("checkStart", startPosition));
- this.timelinePos = startPosition;
- if (interstitialEvents.length && interstitialEvents[0].cue.pre) {
- const index = schedule.findEventIndex(interstitialEvents[0].identifier);
- this.setSchedulePosition(index);
- } else if (startPosition >= 0 || !this.primaryLive) {
- const start = this.timelinePos = startPosition > 0 ? startPosition : 0;
- const index = schedule.findItemIndexAtTime(start);
- this.setSchedulePosition(index);
- }
- } else if (effectivePlayingItem && !this.playingItem) {
- const index = schedule.findItemIndex(effectivePlayingItem);
- this.setSchedulePosition(index);
- }
- }
- advanceAssetBuffering(item, assetItem) {
- const interstitial = item.event;
- const assetListIndex = interstitial.findAssetIndex(assetItem);
- const nextAssetIndex = getNextAssetIndex(interstitial, assetListIndex);
- if (!interstitial.isAssetPastPlayoutLimit(nextAssetIndex)) {
- this.bufferedToEvent(item, nextAssetIndex);
- } else if (this.schedule) {
- var _this$schedule$items;
- const nextItem = (_this$schedule$items = this.schedule.items) == null ? void 0 : _this$schedule$items[this.findItemIndex(item) + 1];
- if (nextItem) {
- this.bufferedToItem(nextItem);
- }
- }
- }
- advanceAfterAssetEnded(interstitial, index, assetListIndex) {
- const nextAssetIndex = getNextAssetIndex(interstitial, assetListIndex);
- if (!interstitial.isAssetPastPlayoutLimit(nextAssetIndex)) {
- if (interstitial.appendInPlace) {
- const assetItem = interstitial.assetList[nextAssetIndex];
- if (assetItem) {
- this.advanceInPlace(assetItem.timelineStart);
- }
- }
- this.setSchedulePosition(index, nextAssetIndex);
- } else if (this.schedule) {
- const scheduleItems = this.schedule.items;
- if (scheduleItems) {
- const nextIndex = index + 1;
- const scheduleLength = scheduleItems.length;
- if (nextIndex >= scheduleLength) {
- this.setSchedulePosition(-1);
- return;
- }
- const resumptionTime = interstitial.resumeTime;
- if (this.timelinePos < resumptionTime) {
- this.log(timelineMessage("advanceAfterAssetEnded", resumptionTime));
- this.timelinePos = resumptionTime;
- if (interstitial.appendInPlace) {
- this.advanceInPlace(resumptionTime);
- }
- this.checkBuffer(this.bufferedPos < resumptionTime);
- }
- this.setSchedulePosition(nextIndex);
- }
- }
- }
- setScheduleToAssetAtTime(time, playingAsset) {
- const schedule = this.schedule;
- if (!schedule) {
- return;
- }
- const parentIdentifier = playingAsset.parentIdentifier;
- const interstitial = schedule.getEvent(parentIdentifier);
- if (interstitial) {
- const itemIndex = schedule.findEventIndex(parentIdentifier);
- const assetListIndex = schedule.findAssetIndex(interstitial, time);
- this.advanceAfterAssetEnded(interstitial, itemIndex, assetListIndex - 1);
- }
- }
- setSchedulePosition(index, assetListIndex) {
- var _this$schedule2;
- const scheduleItems = (_this$schedule2 = this.schedule) == null ? void 0 : _this$schedule2.items;
- if (!scheduleItems || this.playbackDisabled) {
- return;
- }
- const scheduledItem = index >= 0 ? scheduleItems[index] : null;
- this.log(`setSchedulePosition ${index}, ${assetListIndex} (${scheduledItem ? segmentToString(scheduledItem) : scheduledItem}) pos: ${this.timelinePos}`);
- const currentItem = this.waitingItem || this.playingItem;
- const playingLastItem = this.playingLastItem;
- if (this.isInterstitial(currentItem)) {
- const interstitial = currentItem.event;
- const playingAsset = this.playingAsset;
- const assetId = playingAsset == null ? void 0 : playingAsset.identifier;
- const player = assetId ? this.getAssetPlayer(assetId) : null;
- if (player && assetId && (!this.eventItemsMatch(currentItem, scheduledItem) || assetListIndex !== void 0 && assetId !== interstitial.assetList[assetListIndex].identifier)) {
- var _this$detachedData3;
- const playingAssetListIndex = interstitial.findAssetIndex(playingAsset);
- this.log(`INTERSTITIAL_ASSET_ENDED ${playingAssetListIndex + 1}/${interstitial.assetList.length} ${eventAssetToString(playingAsset)}`);
- this.endedAsset = playingAsset;
- this.playingAsset = null;
- this.hls.trigger(Events.INTERSTITIAL_ASSET_ENDED, {
- asset: playingAsset,
- assetListIndex: playingAssetListIndex,
- event: interstitial,
- schedule: scheduleItems.slice(0),
- scheduleIndex: index,
- player
- });
- if (currentItem !== this.playingItem) {
- if (this.itemsMatch(currentItem, this.playingItem) && // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
- !this.playingAsset) {
- this.advanceAfterAssetEnded(interstitial, this.findItemIndex(this.playingItem), playingAssetListIndex);
- }
- return;
- }
- this.retreiveMediaSource(assetId, scheduledItem);
- if (player.media && !((_this$detachedData3 = this.detachedData) != null && _this$detachedData3.mediaSource)) {
- player.detachMedia();
- }
- }
- if (!this.eventItemsMatch(currentItem, scheduledItem)) {
- this.endedItem = currentItem;
- this.playingItem = null;
- this.log(`INTERSTITIAL_ENDED ${interstitial} ${segmentToString(currentItem)}`);
- interstitial.hasPlayed = true;
- this.hls.trigger(Events.INTERSTITIAL_ENDED, {
- event: interstitial,
- schedule: scheduleItems.slice(0),
- scheduleIndex: index
- });
- if (interstitial.cue.once) {
- var _this$schedule3;
- this.updateSchedule();
- const updatedScheduleItems = (_this$schedule3 = this.schedule) == null ? void 0 : _this$schedule3.items;
- if (scheduledItem && updatedScheduleItems) {
- const updatedIndex = this.findItemIndex(scheduledItem);
- this.advanceSchedule(updatedIndex, updatedScheduleItems, assetListIndex, currentItem, playingLastItem);
- }
- return;
- }
- }
- }
- this.advanceSchedule(index, scheduleItems, assetListIndex, currentItem, playingLastItem);
- }
- advanceSchedule(index, scheduleItems, assetListIndex, currentItem, playedLastItem) {
- const schedule = this.schedule;
- if (!schedule) {
- return;
- }
- const scheduledItem = scheduleItems[index] || null;
- const media = this.primaryMedia;
- const playerQueue = this.playerQueue;
- if (playerQueue.length) {
- playerQueue.forEach((player) => {
- const interstitial = player.interstitial;
- const queuedIndex = schedule.findEventIndex(interstitial.identifier);
- if (queuedIndex < index || queuedIndex > index + 1) {
- this.clearInterstitial(interstitial, scheduledItem);
- }
- });
- }
- if (this.isInterstitial(scheduledItem)) {
- this.timelinePos = Math.min(Math.max(this.timelinePos, scheduledItem.start), scheduledItem.end);
- const interstitial = scheduledItem.event;
- if (assetListIndex === void 0) {
- assetListIndex = schedule.findAssetIndex(interstitial, this.timelinePos);
- const assetIndexCandidate = getNextAssetIndex(interstitial, assetListIndex - 1);
- if (interstitial.isAssetPastPlayoutLimit(assetIndexCandidate) || interstitial.appendInPlace && this.timelinePos === scheduledItem.end) {
- this.advanceAfterAssetEnded(interstitial, index, assetListIndex);
- return;
- }
- assetListIndex = assetIndexCandidate;
- }
- const waitingItem = this.waitingItem;
- if (!this.assetsBuffered(scheduledItem, media)) {
- this.setBufferingItem(scheduledItem);
- }
- let player = this.preloadAssets(interstitial, assetListIndex);
- if (!this.eventItemsMatch(scheduledItem, waitingItem || currentItem)) {
- this.waitingItem = scheduledItem;
- this.log(`INTERSTITIAL_STARTED ${segmentToString(scheduledItem)} ${interstitial.appendInPlace ? "append in place" : ""}`);
- this.hls.trigger(Events.INTERSTITIAL_STARTED, {
- event: interstitial,
- schedule: scheduleItems.slice(0),
- scheduleIndex: index
- });
- }
- if (!interstitial.assetListLoaded) {
- this.log(`Waiting for ASSET-LIST to complete loading ${interstitial}`);
- return;
- }
- if (interstitial.assetListLoader) {
- interstitial.assetListLoader.destroy();
- interstitial.assetListLoader = void 0;
- }
- if (!media) {
- this.log(`Waiting for attachMedia to start Interstitial ${interstitial}`);
- return;
- }
- this.waitingItem = this.endedItem = null;
- this.playingItem = scheduledItem;
- const assetItem = interstitial.assetList[assetListIndex];
- if (!assetItem) {
- this.advanceAfterAssetEnded(interstitial, index, assetListIndex || 0);
- return;
- }
- if (!player) {
- player = this.getAssetPlayer(assetItem.identifier);
- }
- if (player === null || player.destroyed) {
- const assetListLength = interstitial.assetList.length;
- this.warn(`asset ${assetListIndex + 1}/${assetListLength} player destroyed ${interstitial}`);
- player = this.createAssetPlayer(interstitial, assetItem, assetListIndex);
- player.loadSource();
- }
- if (!this.eventItemsMatch(scheduledItem, this.bufferingItem)) {
- if (interstitial.appendInPlace && this.isAssetBuffered(assetItem)) {
- return;
- }
- }
- this.startAssetPlayer(player, assetListIndex, scheduleItems, index, media);
- if (this.shouldPlay) {
- playWithCatch(player.media);
- }
- } else if (scheduledItem) {
- this.resumePrimary(scheduledItem, index, currentItem);
- if (this.shouldPlay) {
- playWithCatch(this.hls.media);
- }
- } else if (playedLastItem && this.isInterstitial(currentItem)) {
- this.endedItem = null;
- this.playingItem = currentItem;
- if (!currentItem.event.appendInPlace) {
- this.attachPrimary(schedule.durations.primary, null);
- }
- }
- }
- get playbackDisabled() {
- return this.hls.config.enableInterstitialPlayback === false;
- }
- get primaryDetails() {
- var _this$mediaSelection;
- return (_this$mediaSelection = this.mediaSelection) == null ? void 0 : _this$mediaSelection.main.details;
- }
- get primaryLive() {
- var _this$primaryDetails;
- return !!((_this$primaryDetails = this.primaryDetails) != null && _this$primaryDetails.live);
- }
- resumePrimary(scheduledItem, index, fromItem) {
- var _this$detachedData4, _this$schedule4;
- this.playingItem = scheduledItem;
- this.playingAsset = this.endedAsset = null;
- this.waitingItem = this.endedItem = null;
- this.bufferedToItem(scheduledItem);
- this.log(`resuming ${segmentToString(scheduledItem)}`);
- if (!((_this$detachedData4 = this.detachedData) != null && _this$detachedData4.mediaSource)) {
- let timelinePos = this.timelinePos;
- if (timelinePos < scheduledItem.start || timelinePos >= scheduledItem.end) {
- timelinePos = this.getPrimaryResumption(scheduledItem, index);
- this.log(timelineMessage("resumePrimary", timelinePos));
- this.timelinePos = timelinePos;
- }
- this.attachPrimary(timelinePos, scheduledItem);
- }
- if (!fromItem) {
- return;
- }
- const scheduleItems = (_this$schedule4 = this.schedule) == null ? void 0 : _this$schedule4.items;
- if (!scheduleItems) {
- return;
- }
- this.log(`INTERSTITIALS_PRIMARY_RESUMED ${segmentToString(scheduledItem)}`);
- this.hls.trigger(Events.INTERSTITIALS_PRIMARY_RESUMED, {
- schedule: scheduleItems.slice(0),
- scheduleIndex: index
- });
- this.checkBuffer();
- }
- getPrimaryResumption(scheduledItem, index) {
- const itemStart = scheduledItem.start;
- if (this.primaryLive) {
- const details = this.primaryDetails;
- if (index === 0) {
- return this.hls.startPosition;
- } else if (details && (itemStart < details.fragmentStart || itemStart > details.edge)) {
- return this.hls.liveSyncPosition || -1;
- }
- }
- return itemStart;
- }
- isAssetBuffered(asset) {
- const player = this.getAssetPlayer(asset.identifier);
- if (player != null && player.hls) {
- return player.hls.bufferedToEnd;
- }
- const bufferInfo = BufferHelper.bufferInfo(this.primaryMedia, this.timelinePos, 0);
- return bufferInfo.end + 1 >= asset.timelineStart + (asset.duration || 0);
- }
- attachPrimary(timelinePos, item, skipSeekToStartPosition) {
- if (item) {
- this.setBufferingItem(item);
- } else {
- this.bufferingItem = this.playingItem;
- }
- this.bufferingAsset = null;
- const media = this.primaryMedia;
- if (!media) {
- return;
- }
- const hls = this.hls;
- if (hls.media) {
- this.checkBuffer();
- } else {
- this.transferMediaTo(hls, media);
- if (skipSeekToStartPosition) {
- this.startLoadingPrimaryAt(timelinePos, skipSeekToStartPosition);
- }
- }
- if (!skipSeekToStartPosition) {
- this.log(timelineMessage("attachPrimary", timelinePos));
- this.timelinePos = timelinePos;
- this.startLoadingPrimaryAt(timelinePos, skipSeekToStartPosition);
- }
- }
- startLoadingPrimaryAt(timelinePos, skipSeekToStartPosition) {
- var _hls$mainForwardBuffe;
- const hls = this.hls;
- if (!hls.loadingEnabled || !hls.media || Math.abs((((_hls$mainForwardBuffe = hls.mainForwardBufferInfo) == null ? void 0 : _hls$mainForwardBuffe.start) || hls.media.currentTime) - timelinePos) > 0.5) {
- hls.startLoad(timelinePos, skipSeekToStartPosition);
- } else if (!hls.bufferingEnabled) {
- hls.resumeBuffering();
- }
- }
- // HLS.js event callbacks
- onManifestLoading() {
- var _this$schedule5;
- this.stopLoad();
- (_this$schedule5 = this.schedule) == null || _this$schedule5.reset();
- this.emptyPlayerQueue();
- this.clearScheduleState();
- this.shouldPlay = false;
- this.bufferedPos = this.timelinePos = -1;
- this.mediaSelection = this.altSelection = this.manager = this.requiredTracks = null;
- this.hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
- this.hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);
- }
- onLevelUpdated(event, data) {
- if (data.level === -1 || !this.schedule) {
- return;
- }
- const main = this.hls.levels[data.level];
- if (!main.details) {
- return;
- }
- const currentSelection = _objectSpread2(_objectSpread2({}, this.mediaSelection || this.altSelection), {}, {
- main
- });
- this.mediaSelection = currentSelection;
- this.schedule.parseInterstitialDateRanges(currentSelection, this.hls.config.interstitialAppendInPlace);
- if (!this.effectivePlayingItem && this.schedule.items) {
- this.checkStart();
- }
- }
- onAudioTrackUpdated(event, data) {
- const audio = this.hls.audioTracks[data.id];
- const previousSelection = this.mediaSelection;
- if (!previousSelection) {
- this.altSelection = _objectSpread2(_objectSpread2({}, this.altSelection), {}, {
- audio
- });
- return;
- }
- const currentSelection = _objectSpread2(_objectSpread2({}, previousSelection), {}, {
- audio
- });
- this.mediaSelection = currentSelection;
- }
- onSubtitleTrackUpdated(event, data) {
- const subtitles = this.hls.subtitleTracks[data.id];
- const previousSelection = this.mediaSelection;
- if (!previousSelection) {
- this.altSelection = _objectSpread2(_objectSpread2({}, this.altSelection), {}, {
- subtitles
- });
- return;
- }
- const currentSelection = _objectSpread2(_objectSpread2({}, previousSelection), {}, {
- subtitles
- });
- this.mediaSelection = currentSelection;
- }
- onAudioTrackSwitching(event, data) {
- const audioOption = getBasicSelectionOption(data);
- this.playerQueue.forEach(({
- hls
- }) => hls && (hls.setAudioOption(data) || hls.setAudioOption(audioOption)));
- }
- onSubtitleTrackSwitch(event, data) {
- const subtitleOption = getBasicSelectionOption(data);
- this.playerQueue.forEach(({
- hls
- }) => hls && (hls.setSubtitleOption(data) || data.id !== -1 && hls.setSubtitleOption(subtitleOption)));
- }
- onBufferCodecs(event, data) {
- const requiredTracks = data.tracks;
- if (requiredTracks) {
- this.requiredTracks = requiredTracks;
- }
- }
- onBufferAppended(event, data) {
- this.checkBuffer();
- }
- onBufferFlushed(event, data) {
- const playingItem = this.playingItem;
- if (playingItem && !this.itemsMatch(playingItem, this.bufferingItem) && !this.isInterstitial(playingItem)) {
- const timelinePos = this.timelinePos;
- this.bufferedPos = timelinePos;
- this.checkBuffer();
- }
- }
- onBufferedToEnd(event) {
- if (!this.schedule) {
- return;
- }
- const interstitialEvents = this.schedule.events;
- if (this.bufferedPos < Number.MAX_VALUE && interstitialEvents) {
- for (let i = 0; i < interstitialEvents.length; i++) {
- const interstitial = interstitialEvents[i];
- if (interstitial.cue.post) {
- var _this$schedule$items2;
- const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier);
- const item = (_this$schedule$items2 = this.schedule.items) == null ? void 0 : _this$schedule$items2[scheduleIndex];
- if (this.isInterstitial(item) && this.eventItemsMatch(item, this.bufferingItem)) {
- this.bufferedToItem(item, 0);
- }
- break;
- }
- }
- this.bufferedPos = Number.MAX_VALUE;
- }
- }
- onMediaEnded(event) {
- const playingItem = this.playingItem;
- if (!this.playingLastItem && playingItem) {
- const playingIndex = this.findItemIndex(playingItem);
- this.setSchedulePosition(playingIndex + 1);
- } else {
- this.shouldPlay = false;
- }
- }
- updateItem(previousItem, time) {
- var _this$schedule6;
- const items = (_this$schedule6 = this.schedule) == null ? void 0 : _this$schedule6.items;
- if (previousItem && items) {
- const index = this.findItemIndex(previousItem, time);
- return items[index] || null;
- }
- return null;
- }
- trimInPlace(updatedItem, itemBeforeUpdate) {
- if (this.isInterstitial(updatedItem) && updatedItem.event.appendInPlace && itemBeforeUpdate.end - updatedItem.end > 0.25) {
- updatedItem.event.assetList.forEach((asset, index) => {
- if (updatedItem.event.isAssetPastPlayoutLimit(index)) {
- this.clearAssetPlayer(asset.identifier, null);
- }
- });
- const flushStart = updatedItem.end + 0.25;
- const bufferInfo = BufferHelper.bufferInfo(this.primaryMedia, flushStart, 0);
- if (bufferInfo.end > flushStart || (bufferInfo.nextStart || 0) > flushStart) {
- this.log(`trim buffered interstitial ${segmentToString(updatedItem)} (was ${segmentToString(itemBeforeUpdate)})`);
- const skipSeekToStartPosition = true;
- this.attachPrimary(flushStart, null, skipSeekToStartPosition);
- this.flushFrontBuffer(flushStart);
- }
- }
- }
- itemsMatch(a, b) {
- return !!b && (a === b || a.event && b.event && this.eventItemsMatch(a, b) || !a.event && !b.event && this.findItemIndex(a) === this.findItemIndex(b));
- }
- eventItemsMatch(a, b) {
- var _b$event;
- return !!b && (a === b || a.event.identifier === ((_b$event = b.event) == null ? void 0 : _b$event.identifier));
- }
- findItemIndex(item, time) {
- return item && this.schedule ? this.schedule.findItemIndex(item, time) : -1;
- }
- updateSchedule(forceUpdate = false) {
- var _this$schedule7;
- const mediaSelection = this.mediaSelection;
- if (!mediaSelection) {
- return;
- }
- (_this$schedule7 = this.schedule) == null || _this$schedule7.updateSchedule(mediaSelection, [], forceUpdate);
- }
- // Schedule buffer control
- checkBuffer(starved) {
- var _this$schedule8;
- const items = (_this$schedule8 = this.schedule) == null ? void 0 : _this$schedule8.items;
- if (!items) {
- return;
- }
- const bufferInfo = BufferHelper.bufferInfo(this.primaryMedia, this.timelinePos, 0);
- if (starved) {
- this.bufferedPos = this.timelinePos;
- }
- starved || (starved = bufferInfo.len < 1);
- this.updateBufferedPos(bufferInfo.end, items, starved);
- }
- updateBufferedPos(bufferEnd, items, bufferIsEmpty) {
- const schedule = this.schedule;
- const bufferingItem = this.bufferingItem;
- if (this.bufferedPos > bufferEnd || !schedule) {
- return;
- }
- if (items.length === 1 && this.itemsMatch(items[0], bufferingItem)) {
- this.bufferedPos = bufferEnd;
- return;
- }
- const playingItem = this.playingItem;
- const playingIndex = this.findItemIndex(playingItem);
- let bufferEndIndex = schedule.findItemIndexAtTime(bufferEnd);
- if (this.bufferedPos < bufferEnd) {
- var _nextItemToBuffer$eve;
- const bufferingIndex = this.findItemIndex(bufferingItem);
- const nextToBufferIndex = Math.min(bufferingIndex + 1, items.length - 1);
- const nextItemToBuffer = items[nextToBufferIndex];
- if (bufferEndIndex === -1 && bufferingItem && bufferEnd >= bufferingItem.end || (_nextItemToBuffer$eve = nextItemToBuffer.event) != null && _nextItemToBuffer$eve.appendInPlace && bufferEnd + 0.01 >= nextItemToBuffer.start) {
- bufferEndIndex = nextToBufferIndex;
- }
- if (this.isInterstitial(bufferingItem)) {
- const interstitial = bufferingItem.event;
- if (nextToBufferIndex - playingIndex > 1 && interstitial.appendInPlace === false) {
- return;
- }
- if (interstitial.assetList.length === 0 && interstitial.assetListLoader) {
- return;
- }
- }
- this.bufferedPos = bufferEnd;
- if (bufferEndIndex > bufferingIndex && bufferEndIndex > playingIndex) {
- this.bufferedToItem(nextItemToBuffer);
- } else {
- const details = this.primaryDetails;
- if (this.primaryLive && details && bufferEnd > details.edge - details.targetduration && nextItemToBuffer.start < details.edge + this.hls.config.interstitialLiveLookAhead && this.isInterstitial(nextItemToBuffer)) {
- this.preloadAssets(nextItemToBuffer.event, 0);
- }
- }
- } else if (bufferIsEmpty && playingItem && !this.itemsMatch(playingItem, bufferingItem)) {
- if (bufferEndIndex === playingIndex) {
- this.bufferedToItem(playingItem);
- } else if (bufferEndIndex === playingIndex + 1) {
- this.bufferedToItem(items[bufferEndIndex]);
- }
- }
- }
- assetsBuffered(item, media) {
- const assetList = item.event.assetList;
- if (assetList.length === 0) {
- return false;
- }
- return !item.event.assetList.some((asset) => {
- const player = this.getAssetPlayer(asset.identifier);
- return !(player != null && player.bufferedInPlaceToEnd(media));
- });
- }
- setBufferingItem(item) {
- const bufferingLast = this.bufferingItem;
- const schedule = this.schedule;
- if (!this.itemsMatch(item, bufferingLast) && schedule) {
- const {
- items,
- events
- } = schedule;
- if (!items || !events) {
- return bufferingLast;
- }
- const isInterstitial = this.isInterstitial(item);
- const bufferingPlayer = this.getBufferingPlayer();
- this.bufferingItem = item;
- this.bufferedPos = Math.max(item.start, Math.min(item.end, this.timelinePos));
- const timeRemaining = bufferingPlayer ? bufferingPlayer.remaining : bufferingLast ? bufferingLast.end - this.timelinePos : 0;
- this.log(`INTERSTITIALS_BUFFERED_TO_BOUNDARY ${segmentToString(item)}` + (bufferingLast ? ` (${timeRemaining.toFixed(2)} remaining)` : ""));
- if (!this.playbackDisabled) {
- if (isInterstitial) {
- const bufferIndex = schedule.findAssetIndex(item.event, this.bufferedPos);
- item.event.assetList.forEach((asset, i) => {
- const player = this.getAssetPlayer(asset.identifier);
- if (player) {
- if (i === bufferIndex) {
- player.loadSource();
- }
- player.resumeBuffering();
- }
- });
- } else {
- this.hls.resumeBuffering();
- this.playerQueue.forEach((player) => player.pauseBuffering());
- }
- }
- this.hls.trigger(Events.INTERSTITIALS_BUFFERED_TO_BOUNDARY, {
- events: events.slice(0),
- schedule: items.slice(0),
- bufferingIndex: this.findItemIndex(item),
- playingIndex: this.findItemIndex(this.playingItem)
- });
- } else if (this.bufferingItem !== item) {
- this.bufferingItem = item;
- }
- return bufferingLast;
- }
- bufferedToItem(item, assetListIndex = 0) {
- const bufferingLast = this.setBufferingItem(item);
- if (this.playbackDisabled) {
- return;
- }
- if (this.isInterstitial(item)) {
- this.bufferedToEvent(item, assetListIndex);
- } else if (bufferingLast !== null) {
- this.bufferingAsset = null;
- const detachedData = this.detachedData;
- if (detachedData) {
- if (detachedData.mediaSource) {
- const skipSeekToStartPosition = true;
- this.attachPrimary(item.start, item, skipSeekToStartPosition);
- } else {
- this.preloadPrimary(item);
- }
- } else {
- this.preloadPrimary(item);
- }
- }
- }
- preloadPrimary(item) {
- const index = this.findItemIndex(item);
- const timelinePos = this.getPrimaryResumption(item, index);
- this.startLoadingPrimaryAt(timelinePos);
- }
- bufferedToEvent(item, assetListIndex) {
- const interstitial = item.event;
- const neverLoaded = interstitial.assetList.length === 0 && !interstitial.assetListLoader;
- const playOnce = interstitial.cue.once;
- if (neverLoaded || !playOnce) {
- const player = this.preloadAssets(interstitial, assetListIndex);
- if (player != null && player.interstitial.appendInPlace) {
- const media = this.primaryMedia;
- if (media) {
- this.bufferAssetPlayer(player, media);
- }
- }
- }
- }
- preloadAssets(interstitial, assetListIndex) {
- const uri = interstitial.assetUrl;
- const assetListLength = interstitial.assetList.length;
- const neverLoaded = assetListLength === 0 && !interstitial.assetListLoader;
- const playOnce = interstitial.cue.once;
- if (neverLoaded) {
- const timelineStart = interstitial.timelineStart;
- if (interstitial.appendInPlace) {
- var _playingItem$nextEven;
- const playingItem = this.playingItem;
- if (!this.isInterstitial(playingItem) && (playingItem == null || (_playingItem$nextEven = playingItem.nextEvent) == null ? void 0 : _playingItem$nextEven.identifier) === interstitial.identifier) {
- this.flushFrontBuffer(timelineStart + 0.25);
- }
- }
- let hlsStartOffset;
- let liveStartPosition = 0;
- if (!this.playingItem && this.primaryLive) {
- liveStartPosition = this.hls.startPosition;
- if (liveStartPosition === -1) {
- liveStartPosition = this.hls.liveSyncPosition || 0;
- }
- }
- if (liveStartPosition && !(interstitial.cue.pre || interstitial.cue.post)) {
- const startOffset = liveStartPosition - timelineStart;
- if (startOffset > 0) {
- hlsStartOffset = Math.round(startOffset * 1e3) / 1e3;
- }
- }
- this.log(`Load interstitial asset ${assetListIndex + 1}/${uri ? 1 : assetListLength} ${interstitial}${hlsStartOffset ? ` live-start: ${liveStartPosition} start-offset: ${hlsStartOffset}` : ""}`);
- if (uri) {
- return this.createAsset(interstitial, 0, 0, timelineStart, interstitial.duration, uri);
- }
- const assetListLoader = this.assetListLoader.loadAssetList(interstitial, hlsStartOffset);
- if (assetListLoader) {
- interstitial.assetListLoader = assetListLoader;
- }
- } else if (!playOnce && assetListLength) {
- for (let i = assetListIndex; i < assetListLength; i++) {
- const _asset = interstitial.assetList[i];
- const playerIndex = this.getAssetPlayerQueueIndex(_asset.identifier);
- if ((playerIndex === -1 || this.playerQueue[playerIndex].destroyed) && !_asset.error) {
- this.createAssetPlayer(interstitial, _asset, i);
- }
- }
- const asset = interstitial.assetList[assetListIndex];
- if (asset) {
- const player = this.getAssetPlayer(asset.identifier);
- if (player) {
- player.loadSource();
- }
- return player;
- }
- }
- return null;
- }
- flushFrontBuffer(startOffset) {
- const requiredTracks = this.requiredTracks;
- if (!requiredTracks) {
- return;
- }
- this.log(`Removing front buffer starting at ${startOffset}`);
- const sourceBufferNames = Object.keys(requiredTracks);
- sourceBufferNames.forEach((type) => {
- this.hls.trigger(Events.BUFFER_FLUSHING, {
- startOffset,
- endOffset: Infinity,
- type
- });
- });
- }
- // Interstitial Asset Player control
- getAssetPlayerQueueIndex(assetId) {
- const playerQueue = this.playerQueue;
- for (let i = 0; i < playerQueue.length; i++) {
- if (assetId === playerQueue[i].assetId) {
- return i;
- }
- }
- return -1;
- }
- getAssetPlayer(assetId) {
- const index = this.getAssetPlayerQueueIndex(assetId);
- return this.playerQueue[index] || null;
- }
- getBufferingPlayer() {
- const {
- playerQueue,
- primaryMedia
- } = this;
- if (primaryMedia) {
- for (let i = 0; i < playerQueue.length; i++) {
- if (playerQueue[i].media === primaryMedia) {
- return playerQueue[i];
- }
- }
- }
- return null;
- }
- createAsset(interstitial, assetListIndex, startOffset, timelineStart, duration, uri) {
- const assetItem = {
- parentIdentifier: interstitial.identifier,
- identifier: generateAssetIdentifier(interstitial, uri, assetListIndex),
- duration,
- startOffset,
- timelineStart,
- uri
- };
- return this.createAssetPlayer(interstitial, assetItem, assetListIndex);
- }
- createAssetPlayer(interstitial, assetItem, assetListIndex) {
- const primary = this.hls;
- const userConfig = primary.userConfig;
- let videoPreference = userConfig.videoPreference;
- const currentLevel = primary.loadLevelObj || primary.levels[primary.currentLevel];
- if (videoPreference || currentLevel) {
- videoPreference = _extends({}, videoPreference);
- if (currentLevel.videoCodec) {
- videoPreference.videoCodec = currentLevel.videoCodec;
- }
- if (currentLevel.videoRange) {
- videoPreference.allowedVideoRanges = [currentLevel.videoRange];
- }
- }
- const selectedAudio = primary.audioTracks[primary.audioTrack];
- const selectedSubtitle = primary.subtitleTracks[primary.subtitleTrack];
- let startPosition = 0;
- if (this.primaryLive || interstitial.appendInPlace) {
- const timePastStart = this.timelinePos - assetItem.timelineStart;
- if (timePastStart > 1) {
- const duration = assetItem.duration;
- if (duration && timePastStart < duration) {
- startPosition = timePastStart;
- }
- }
- }
- const assetId = assetItem.identifier;
- const playerConfig = _objectSpread2(_objectSpread2({}, userConfig), {}, {
- maxMaxBufferLength: Math.min(180, primary.config.maxMaxBufferLength),
- autoStartLoad: true,
- startFragPrefetch: true,
- primarySessionId: primary.sessionId,
- assetPlayerId: assetId,
- abrEwmaDefaultEstimate: primary.bandwidthEstimate,
- interstitialsController: void 0,
- startPosition,
- liveDurationInfinity: false,
- testBandwidth: false,
- videoPreference,
- audioPreference: selectedAudio || userConfig.audioPreference,
- subtitlePreference: selectedSubtitle || userConfig.subtitlePreference
- });
- if (interstitial.appendInPlace) {
- interstitial.appendInPlaceStarted = true;
- if (assetItem.timelineStart) {
- playerConfig.timelineOffset = assetItem.timelineStart;
- }
- }
- const cmcd = playerConfig.cmcd;
- if (cmcd != null && cmcd.sessionId && cmcd.contentId) {
- playerConfig.cmcd = _extends({}, cmcd, {
- contentId: hash(assetItem.uri)
- });
- }
- if (this.getAssetPlayer(assetId)) {
- this.warn(`Duplicate date range identifier ${interstitial} and asset ${assetId}`);
- }
- const player = new HlsAssetPlayer(this.HlsPlayerClass, playerConfig, interstitial, assetItem);
- this.playerQueue.push(player);
- interstitial.assetList[assetListIndex] = assetItem;
- let initialDuration = true;
- const updateAssetPlayerDetails = (details) => {
- if (details.live) {
- var _this$schedule9;
- const error = new Error(`Interstitials MUST be VOD assets ${interstitial}`);
- const errorData = {
- fatal: true,
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.INTERSTITIAL_ASSET_ITEM_ERROR,
- error
- };
- const scheduleIndex = ((_this$schedule9 = this.schedule) == null ? void 0 : _this$schedule9.findEventIndex(interstitial.identifier)) || -1;
- this.handleAssetItemError(errorData, interstitial, scheduleIndex, assetListIndex, error.message);
- return;
- }
- const duration = details.edge - details.fragmentStart;
- const currentAssetDuration = assetItem.duration;
- if (initialDuration || currentAssetDuration === null || duration > currentAssetDuration) {
- initialDuration = false;
- this.log(`Interstitial asset "${assetId}" duration change ${currentAssetDuration} > ${duration}`);
- assetItem.duration = duration;
- this.updateSchedule();
- }
- };
- player.on(Events.LEVEL_UPDATED, (event, {
- details
- }) => updateAssetPlayerDetails(details));
- player.on(Events.LEVEL_PTS_UPDATED, (event, {
- details
- }) => updateAssetPlayerDetails(details));
- player.on(Events.EVENT_CUE_ENTER, () => this.onInterstitialCueEnter());
- const onBufferCodecs = (event, data) => {
- const inQueuPlayer = this.getAssetPlayer(assetId);
- if (inQueuPlayer && data.tracks) {
- inQueuPlayer.off(Events.BUFFER_CODECS, onBufferCodecs);
- inQueuPlayer.tracks = data.tracks;
- const media = this.primaryMedia;
- if (this.bufferingAsset === inQueuPlayer.assetItem && media && !inQueuPlayer.media) {
- this.bufferAssetPlayer(inQueuPlayer, media);
- }
- }
- };
- player.on(Events.BUFFER_CODECS, onBufferCodecs);
- const bufferedToEnd = () => {
- var _this$schedule$items3;
- const inQueuPlayer = this.getAssetPlayer(assetId);
- this.log(`buffered to end of asset ${inQueuPlayer}`);
- if (!inQueuPlayer || !this.schedule) {
- return;
- }
- const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier);
- const item = (_this$schedule$items3 = this.schedule.items) == null ? void 0 : _this$schedule$items3[scheduleIndex];
- if (this.isInterstitial(item)) {
- this.advanceAssetBuffering(item, assetItem);
- }
- };
- player.on(Events.BUFFERED_TO_END, bufferedToEnd);
- const endedWithAssetIndex = (assetIndex) => {
- return () => {
- const inQueuPlayer = this.getAssetPlayer(assetId);
- if (!inQueuPlayer || !this.schedule) {
- return;
- }
- this.shouldPlay = true;
- const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier);
- this.advanceAfterAssetEnded(interstitial, scheduleIndex, assetIndex);
- };
- };
- player.once(Events.MEDIA_ENDED, endedWithAssetIndex(assetListIndex));
- player.once(Events.PLAYOUT_LIMIT_REACHED, endedWithAssetIndex(Infinity));
- player.on(Events.ERROR, (event, data) => {
- if (!this.schedule) {
- return;
- }
- const inQueuPlayer = this.getAssetPlayer(assetId);
- if (data.details === ErrorDetails.BUFFER_STALLED_ERROR) {
- if (inQueuPlayer != null && inQueuPlayer.appendInPlace) {
- this.handleInPlaceStall(interstitial);
- return;
- }
- this.onTimeupdate();
- this.checkBuffer(true);
- return;
- }
- this.handleAssetItemError(data, interstitial, this.schedule.findEventIndex(interstitial.identifier), assetListIndex, `Asset player error ${data.error} ${interstitial}`);
- });
- player.on(Events.DESTROYING, () => {
- const inQueuPlayer = this.getAssetPlayer(assetId);
- if (!inQueuPlayer || !this.schedule) {
- return;
- }
- const error = new Error(`Asset player destroyed unexpectedly ${assetId}`);
- const errorData = {
- fatal: true,
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.INTERSTITIAL_ASSET_ITEM_ERROR,
- error
- };
- this.handleAssetItemError(errorData, interstitial, this.schedule.findEventIndex(interstitial.identifier), assetListIndex, error.message);
- });
- this.log(`INTERSTITIAL_ASSET_PLAYER_CREATED ${eventAssetToString(assetItem)}`);
- this.hls.trigger(Events.INTERSTITIAL_ASSET_PLAYER_CREATED, {
- asset: assetItem,
- assetListIndex,
- event: interstitial,
- player
- });
- return player;
- }
- clearInterstitial(interstitial, toSegment) {
- interstitial.assetList.forEach((asset) => {
- this.clearAssetPlayer(asset.identifier, toSegment);
- });
- interstitial.reset();
- }
- resetAssetPlayer(assetId) {
- const playerIndex = this.getAssetPlayerQueueIndex(assetId);
- if (playerIndex !== -1) {
- this.log(`reset asset player "${assetId}" after error`);
- const player = this.playerQueue[playerIndex];
- this.transferMediaFromPlayer(player, null);
- player.resetDetails();
- }
- }
- clearAssetPlayer(assetId, toSegment) {
- const playerIndex = this.getAssetPlayerQueueIndex(assetId);
- if (playerIndex !== -1) {
- const player = this.playerQueue[playerIndex];
- this.log(`clear ${player} toSegment: ${toSegment ? segmentToString(toSegment) : toSegment}`);
- this.transferMediaFromPlayer(player, toSegment);
- this.playerQueue.splice(playerIndex, 1);
- player.destroy();
- }
- }
- emptyPlayerQueue() {
- let player;
- while (player = this.playerQueue.pop()) {
- player.destroy();
- }
- this.playerQueue = [];
- }
- startAssetPlayer(player, assetListIndex, scheduleItems, scheduleIndex, media) {
- const {
- interstitial,
- assetItem,
- assetId
- } = player;
- const assetListLength = interstitial.assetList.length;
- const playingAsset = this.playingAsset;
- this.endedAsset = null;
- this.playingAsset = assetItem;
- if (!playingAsset || playingAsset.identifier !== assetId) {
- if (playingAsset) {
- this.clearAssetPlayer(playingAsset.identifier, scheduleItems[scheduleIndex]);
- delete playingAsset.error;
- }
- this.log(`INTERSTITIAL_ASSET_STARTED ${assetListIndex + 1}/${assetListLength} ${eventAssetToString(assetItem)}`);
- this.hls.trigger(Events.INTERSTITIAL_ASSET_STARTED, {
- asset: assetItem,
- assetListIndex,
- event: interstitial,
- schedule: scheduleItems.slice(0),
- scheduleIndex,
- player
- });
- }
- this.bufferAssetPlayer(player, media);
- }
- bufferAssetPlayer(player, media) {
- var _this$schedule$items4, _this$detachedData5;
- if (!this.schedule) {
- return;
- }
- const {
- interstitial,
- assetItem
- } = player;
- const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier);
- const item = (_this$schedule$items4 = this.schedule.items) == null ? void 0 : _this$schedule$items4[scheduleIndex];
- if (!item) {
- return;
- }
- player.loadSource();
- this.setBufferingItem(item);
- this.bufferingAsset = assetItem;
- const bufferingPlayer = this.getBufferingPlayer();
- if (bufferingPlayer === player) {
- return;
- }
- const appendInPlaceNext = interstitial.appendInPlace;
- if (appendInPlaceNext && (bufferingPlayer == null ? void 0 : bufferingPlayer.interstitial.appendInPlace) === false) {
- return;
- }
- const activeTracks = (bufferingPlayer == null ? void 0 : bufferingPlayer.tracks) || ((_this$detachedData5 = this.detachedData) == null ? void 0 : _this$detachedData5.tracks) || this.requiredTracks;
- if (appendInPlaceNext && assetItem !== this.playingAsset) {
- if (!player.tracks) {
- this.log(`Waiting for track info before buffering ${player}`);
- return;
- }
- if (activeTracks && !isCompatibleTrackChange(activeTracks, player.tracks)) {
- const error = new Error(`Asset ${eventAssetToString(assetItem)} SourceBuffer tracks ('${Object.keys(player.tracks)}') are not compatible with primary content tracks ('${Object.keys(activeTracks)}')`);
- const errorData = {
- fatal: true,
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.INTERSTITIAL_ASSET_ITEM_ERROR,
- error
- };
- const assetListIndex = interstitial.findAssetIndex(assetItem);
- this.handleAssetItemError(errorData, interstitial, scheduleIndex, assetListIndex, error.message);
- return;
- }
- }
- this.transferMediaTo(player, media);
- }
- handleInPlaceStall(interstitial) {
- const schedule = this.schedule;
- const media = this.primaryMedia;
- if (!schedule || !media) {
- return;
- }
- const currentTime = media.currentTime;
- const foundAssetIndex = schedule.findAssetIndex(interstitial, currentTime);
- const stallingAsset = interstitial.assetList[foundAssetIndex];
- if (stallingAsset) {
- const player = this.getAssetPlayer(stallingAsset.identifier);
- if (player) {
- const assetCurrentTime = player.currentTime || currentTime - stallingAsset.timelineStart;
- const distanceFromEnd = player.duration - assetCurrentTime;
- this.warn(`Stalled at ${assetCurrentTime} of ${assetCurrentTime + distanceFromEnd} in ${player} ${interstitial} (media.currentTime: ${currentTime})`);
- if (assetCurrentTime && (distanceFromEnd / media.playbackRate < 0.5 || player.bufferedInPlaceToEnd(media)) && player.hls) {
- const scheduleIndex = schedule.findEventIndex(interstitial.identifier);
- this.advanceAfterAssetEnded(interstitial, scheduleIndex, foundAssetIndex);
- }
- }
- }
- }
- advanceInPlace(time) {
- const media = this.primaryMedia;
- if (media && media.currentTime < time) {
- media.currentTime = time;
- }
- }
- handleAssetItemError(data, interstitial, scheduleIndex, assetListIndex, errorMessage) {
- if (data.details === ErrorDetails.BUFFER_STALLED_ERROR) {
- return;
- }
- const assetItem = interstitial.assetList[assetListIndex] || null;
- this.warn(`INTERSTITIAL_ASSET_ERROR ${assetItem ? eventAssetToString(assetItem) : assetItem} ${data.error}`);
- if (!this.schedule) {
- return;
- }
- const assetId = (assetItem == null ? void 0 : assetItem.identifier) || "";
- const playerIndex = this.getAssetPlayerQueueIndex(assetId);
- const player = this.playerQueue[playerIndex] || null;
- const items = this.schedule.items;
- const interstitialAssetError = _extends({}, data, {
- fatal: false,
- errorAction: createDoNothingErrorAction(true),
- asset: assetItem,
- assetListIndex,
- event: interstitial,
- schedule: items,
- scheduleIndex,
- player
- });
- this.hls.trigger(Events.INTERSTITIAL_ASSET_ERROR, interstitialAssetError);
- if (!data.fatal) {
- return;
- }
- const playingAsset = this.playingAsset;
- const bufferingAsset = this.bufferingAsset;
- const error = new Error(errorMessage);
- if (assetItem) {
- this.clearAssetPlayer(assetId, null);
- assetItem.error = error;
- }
- if (!interstitial.assetList.some((asset) => !asset.error)) {
- interstitial.error = error;
- } else {
- for (let i = assetListIndex; i < interstitial.assetList.length; i++) {
- this.resetAssetPlayer(interstitial.assetList[i].identifier);
- }
- }
- this.updateSchedule(true);
- if (interstitial.error) {
- this.primaryFallback(interstitial);
- } else if (playingAsset && playingAsset.identifier === assetId) {
- this.advanceAfterAssetEnded(interstitial, scheduleIndex, assetListIndex);
- } else if (bufferingAsset && bufferingAsset.identifier === assetId && this.isInterstitial(this.bufferingItem)) {
- this.advanceAssetBuffering(this.bufferingItem, bufferingAsset);
- }
- }
- primaryFallback(interstitial) {
- const flushStart = interstitial.timelineStart;
- const playingItem = this.effectivePlayingItem;
- if (playingItem) {
- this.log(`Fallback to primary from event "${interstitial.identifier}" start: ${flushStart} pos: ${this.timelinePos} playing: ${segmentToString(playingItem)} error: ${interstitial.error}`);
- let timelinePos = this.timelinePos;
- if (timelinePos === -1) {
- timelinePos = this.hls.startPosition;
- }
- const newPlayingItem = this.updateItem(playingItem, timelinePos);
- if (this.itemsMatch(playingItem, newPlayingItem)) {
- this.clearInterstitial(interstitial, null);
- }
- if (interstitial.appendInPlace) {
- this.attachPrimary(flushStart, null);
- this.flushFrontBuffer(flushStart);
- }
- if (!this.schedule) {
- return;
- }
- const scheduleIndex = this.schedule.findItemIndexAtTime(timelinePos);
- this.setSchedulePosition(scheduleIndex);
- } else {
- this.checkStart();
- }
- }
- // Asset List loading
- onAssetListLoaded(event, data) {
- var _this$schedule0, _this$bufferingItem2;
- const interstitial = data.event;
- const interstitialId = interstitial.identifier;
- const assets = data.assetListResponse.ASSETS;
- if (!((_this$schedule0 = this.schedule) != null && _this$schedule0.hasEvent(interstitialId))) {
- return;
- }
- const eventStart = interstitial.timelineStart;
- const previousDuration = interstitial.duration;
- let sumDuration = 0;
- assets.forEach((asset, assetListIndex) => {
- const duration = parseFloat(asset.DURATION);
- this.createAsset(interstitial, assetListIndex, sumDuration, eventStart + sumDuration, duration, asset.URI);
- sumDuration += duration;
- });
- interstitial.duration = sumDuration;
- this.log(`Loaded asset-list with duration: ${sumDuration} (was: ${previousDuration}) ${interstitial}`);
- const waitingItem = this.waitingItem;
- const waitingForItem = (waitingItem == null ? void 0 : waitingItem.event.identifier) === interstitialId;
- this.updateSchedule();
- const bufferingEvent = (_this$bufferingItem2 = this.bufferingItem) == null ? void 0 : _this$bufferingItem2.event;
- if (waitingForItem) {
- var _this$schedule$items5;
- const scheduleIndex = this.schedule.findEventIndex(interstitialId);
- const item = (_this$schedule$items5 = this.schedule.items) == null ? void 0 : _this$schedule$items5[scheduleIndex];
- if (item) {
- if (!this.playingItem && this.timelinePos > item.end) {
- const index = this.schedule.findItemIndexAtTime(this.timelinePos);
- if (index !== scheduleIndex) {
- interstitial.error = new Error(`Interstitial no longer within playback range ${this.timelinePos} ${interstitial}`);
- this.updateSchedule(true);
- this.primaryFallback(interstitial);
- return;
- }
- }
- this.setBufferingItem(item);
- }
- this.setSchedulePosition(scheduleIndex);
- } else if ((bufferingEvent == null ? void 0 : bufferingEvent.identifier) === interstitialId) {
- const assetItem = interstitial.assetList[0];
- if (assetItem) {
- const player = this.getAssetPlayer(assetItem.identifier);
- if (bufferingEvent.appendInPlace) {
- const media = this.primaryMedia;
- if (player && media) {
- this.bufferAssetPlayer(player, media);
- }
- } else if (player) {
- player.loadSource();
- }
- }
- }
- }
- onError(event, data) {
- if (!this.schedule) {
- return;
- }
- switch (data.details) {
- case ErrorDetails.ASSET_LIST_PARSING_ERROR:
- case ErrorDetails.ASSET_LIST_LOAD_ERROR:
- case ErrorDetails.ASSET_LIST_LOAD_TIMEOUT: {
- const interstitial = data.interstitial;
- if (interstitial) {
- this.updateSchedule(true);
- this.primaryFallback(interstitial);
- }
- break;
- }
- case ErrorDetails.BUFFER_STALLED_ERROR: {
- const stallingItem = this.endedItem || this.waitingItem || this.playingItem;
- if (this.isInterstitial(stallingItem) && stallingItem.event.appendInPlace) {
- this.handleInPlaceStall(stallingItem.event);
- return;
- }
- this.log(`Primary player stall @${this.timelinePos} bufferedPos: ${this.bufferedPos}`);
- this.onTimeupdate();
- this.checkBuffer(true);
- break;
- }
- }
- }
- };
- var TICK_INTERVAL$2 = 500;
- var SubtitleStreamController = class extends BaseStreamController {
- constructor(hls, fragmentTracker, keyLoader) {
- super(hls, fragmentTracker, keyLoader, "subtitle-stream-controller", PlaylistLevelType.SUBTITLE);
- this.currentTrackId = -1;
- this.tracksBuffered = [];
- this.mainDetails = null;
- this.registerListeners();
- }
- onHandlerDestroying() {
- this.unregisterListeners();
- super.onHandlerDestroying();
- this.mainDetails = null;
- }
- registerListeners() {
- super.registerListeners();
- const {
- hls
- } = this;
- hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
- hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
- hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
- hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
- hls.on(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);
- hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
- }
- unregisterListeners() {
- super.unregisterListeners();
- const {
- hls
- } = this;
- hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
- hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
- hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
- hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
- hls.off(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);
- hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
- }
- startLoad(startPosition, skipSeekToStartPosition) {
- this.stopLoad();
- this.state = State.IDLE;
- this.setInterval(TICK_INTERVAL$2);
- this.nextLoadPosition = this.lastCurrentTime = startPosition + this.timelineOffset;
- this.startPosition = skipSeekToStartPosition ? -1 : startPosition;
- this.tick();
- }
- onManifestLoading() {
- super.onManifestLoading();
- this.mainDetails = null;
- }
- onMediaDetaching(event, data) {
- this.tracksBuffered = [];
- super.onMediaDetaching(event, data);
- }
- onLevelLoaded(event, data) {
- this.mainDetails = data.details;
- }
- onSubtitleFragProcessed(event, data) {
- const {
- frag,
- success
- } = data;
- if (!this.fragContextChanged(frag)) {
- if (isMediaFragment(frag)) {
- this.fragPrevious = frag;
- }
- this.state = State.IDLE;
- }
- if (!success) {
- return;
- }
- const buffered = this.tracksBuffered[this.currentTrackId];
- if (!buffered) {
- return;
- }
- let timeRange;
- const fragStart = frag.start;
- for (let i = 0; i < buffered.length; i++) {
- if (fragStart >= buffered[i].start && fragStart <= buffered[i].end) {
- timeRange = buffered[i];
- break;
- }
- }
- const fragEnd = frag.start + frag.duration;
- if (timeRange) {
- timeRange.end = fragEnd;
- } else {
- timeRange = {
- start: fragStart,
- end: fragEnd
- };
- buffered.push(timeRange);
- }
- this.fragmentTracker.fragBuffered(frag);
- this.fragBufferedComplete(frag, null);
- if (this.media) {
- this.tick();
- }
- }
- onBufferFlushing(event, data) {
- const {
- startOffset,
- endOffset
- } = data;
- if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) {
- const endOffsetSubtitles = endOffset - 1;
- if (endOffsetSubtitles <= 0) {
- return;
- }
- data.endOffsetSubtitles = Math.max(0, endOffsetSubtitles);
- this.tracksBuffered.forEach((buffered) => {
- for (let i = 0; i < buffered.length; ) {
- if (buffered[i].end <= endOffsetSubtitles) {
- buffered.shift();
- continue;
- } else if (buffered[i].start < endOffsetSubtitles) {
- buffered[i].start = endOffsetSubtitles;
- } else {
- break;
- }
- i++;
- }
- });
- this.fragmentTracker.removeFragmentsInRange(startOffset, endOffsetSubtitles, PlaylistLevelType.SUBTITLE);
- }
- }
- // If something goes wrong, proceed to next frag, if we were processing one.
- onError(event, data) {
- const frag = data.frag;
- if ((frag == null ? void 0 : frag.type) === PlaylistLevelType.SUBTITLE) {
- if (data.details === ErrorDetails.FRAG_GAP) {
- this.fragmentTracker.fragBuffered(frag, true);
- }
- if (this.fragCurrent) {
- this.fragCurrent.abortRequests();
- }
- if (this.state !== State.STOPPED) {
- this.state = State.IDLE;
- }
- }
- }
- // Got all new subtitle levels.
- onSubtitleTracksUpdated(event, {
- subtitleTracks
- }) {
- if (this.levels && subtitleOptionsIdentical(this.levels, subtitleTracks)) {
- this.levels = subtitleTracks.map((mediaPlaylist) => new Level(mediaPlaylist));
- return;
- }
- this.tracksBuffered = [];
- this.levels = subtitleTracks.map((mediaPlaylist) => {
- const level = new Level(mediaPlaylist);
- this.tracksBuffered[level.id] = [];
- return level;
- });
- this.fragmentTracker.removeFragmentsInRange(0, Number.POSITIVE_INFINITY, PlaylistLevelType.SUBTITLE);
- this.fragPrevious = null;
- this.mediaBuffer = null;
- }
- onSubtitleTrackSwitch(event, data) {
- var _this$levels;
- this.currentTrackId = data.id;
- if (!((_this$levels = this.levels) != null && _this$levels.length) || this.currentTrackId === -1) {
- this.clearInterval();
- return;
- }
- const currentTrack = this.levels[this.currentTrackId];
- if (currentTrack != null && currentTrack.details) {
- this.mediaBuffer = this.mediaBufferTimeRanges;
- } else {
- this.mediaBuffer = null;
- }
- if (currentTrack && this.state !== State.STOPPED) {
- this.setInterval(TICK_INTERVAL$2);
- }
- }
- // Got a new set of subtitle fragments.
- onSubtitleTrackLoaded(event, data) {
- var _track$details;
- const {
- currentTrackId,
- levels
- } = this;
- const {
- details: newDetails,
- id: trackId
- } = data;
- if (!levels) {
- this.warn(`Subtitle tracks were reset while loading level ${trackId}`);
- return;
- }
- const track = levels[trackId];
- if (trackId >= levels.length || !track) {
- return;
- }
- this.log(`Subtitle track ${trackId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ""},duration:${newDetails.totalduration}`);
- this.mediaBuffer = this.mediaBufferTimeRanges;
- let sliding = 0;
- if (newDetails.live || (_track$details = track.details) != null && _track$details.live) {
- if (newDetails.deltaUpdateFailed) {
- return;
- }
- const mainDetails = this.mainDetails;
- if (!mainDetails) {
- this.startFragRequested = false;
- return;
- }
- const mainSlidingStartFragment = mainDetails.fragments[0];
- if (!track.details) {
- if (newDetails.hasProgramDateTime && mainDetails.hasProgramDateTime) {
- alignMediaPlaylistByPDT(newDetails, mainDetails);
- sliding = newDetails.fragmentStart;
- } else if (mainSlidingStartFragment) {
- sliding = mainSlidingStartFragment.start;
- addSliding(newDetails, sliding);
- }
- } else {
- var _this$levelLastLoaded;
- sliding = this.alignPlaylists(newDetails, track.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details);
- if (sliding === 0 && mainSlidingStartFragment) {
- sliding = mainSlidingStartFragment.start;
- addSliding(newDetails, sliding);
- }
- }
- if (mainDetails && !this.startFragRequested) {
- this.setStartPosition(mainDetails, sliding);
- }
- }
- track.details = newDetails;
- this.levelLastLoaded = track;
- if (trackId !== currentTrackId) {
- return;
- }
- this.hls.trigger(Events.SUBTITLE_TRACK_UPDATED, {
- details: newDetails,
- id: trackId,
- groupId: data.groupId
- });
- this.tick();
- if (newDetails.live && !this.fragCurrent && this.media && this.state === State.IDLE) {
- const foundFrag = findFragmentByPTS(null, newDetails.fragments, this.media.currentTime, 0);
- if (!foundFrag) {
- this.warn("Subtitle playlist not aligned with playback");
- track.details = void 0;
- }
- }
- }
- _handleFragmentLoadComplete(fragLoadedData) {
- const {
- frag,
- payload
- } = fragLoadedData;
- const decryptData = frag.decryptdata;
- const hls = this.hls;
- if (this.fragContextChanged(frag)) {
- return;
- }
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
- const startTime = performance.now();
- this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch((err) => {
- hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.FRAG_DECRYPT_ERROR,
- fatal: false,
- error: err,
- reason: err.message,
- frag
- });
- throw err;
- }).then((decryptedData) => {
- const endTime = performance.now();
- hls.trigger(Events.FRAG_DECRYPTED, {
- frag,
- payload: decryptedData,
- stats: {
- tstart: startTime,
- tdecrypt: endTime
- }
- });
- }).catch((err) => {
- this.warn(`${err.name}: ${err.message}`);
- this.state = State.IDLE;
- });
- }
- }
- doTick() {
- if (!this.media) {
- this.state = State.IDLE;
- return;
- }
- if (this.state === State.IDLE) {
- const {
- currentTrackId,
- levels
- } = this;
- const track = levels == null ? void 0 : levels[currentTrackId];
- if (!track || !levels.length || !track.details) {
- return;
- }
- if (this.waitForLive(track)) {
- return;
- }
- const {
- config
- } = this;
- const currentTime = this.getLoadPosition();
- const bufferedInfo = BufferHelper.bufferedInfo(this.tracksBuffered[this.currentTrackId] || [], currentTime, config.maxBufferHole);
- const {
- end: targetBufferTime,
- len: bufferLen
- } = bufferedInfo;
- const trackDetails = track.details;
- const maxBufLen = this.hls.maxBufferLength + trackDetails.levelTargetDuration;
- if (bufferLen > maxBufLen) {
- return;
- }
- const fragments = trackDetails.fragments;
- const fragLen = fragments.length;
- const end = trackDetails.edge;
- let foundFrag = null;
- const fragPrevious = this.fragPrevious;
- if (targetBufferTime < end) {
- const tolerance = config.maxFragLookUpTolerance;
- const lookupTolerance = targetBufferTime > end - tolerance ? 0 : tolerance;
- foundFrag = findFragmentByPTS(fragPrevious, fragments, Math.max(fragments[0].start, targetBufferTime), lookupTolerance);
- if (!foundFrag && fragPrevious && fragPrevious.start < fragments[0].start) {
- foundFrag = fragments[0];
- }
- } else {
- foundFrag = fragments[fragLen - 1];
- }
- foundFrag = this.filterReplacedPrimary(foundFrag, track.details);
- if (!foundFrag) {
- return;
- }
- const curSNIdx = foundFrag.sn - trackDetails.startSN;
- const prevFrag = fragments[curSNIdx - 1];
- if (prevFrag && prevFrag.cc === foundFrag.cc && this.fragmentTracker.getState(prevFrag) === FragmentState.NOT_LOADED) {
- foundFrag = prevFrag;
- }
- if (this.fragmentTracker.getState(foundFrag) === FragmentState.NOT_LOADED) {
- const fragToLoad = this.mapToInitFragWhenRequired(foundFrag);
- if (fragToLoad) {
- this.loadFragment(fragToLoad, track, targetBufferTime);
- }
- }
- }
- }
- loadFragment(frag, level, targetBufferTime) {
- if (!isMediaFragment(frag)) {
- this._loadInitSegment(frag, level);
- } else {
- super.loadFragment(frag, level, targetBufferTime);
- }
- }
- get mediaBufferTimeRanges() {
- return new BufferableInstance(this.tracksBuffered[this.currentTrackId] || []);
- }
- };
- var BufferableInstance = class {
- constructor(timeranges) {
- this.buffered = void 0;
- const getRange = (name, index, length) => {
- index = index >>> 0;
- if (index > length - 1) {
- throw new DOMException(`Failed to execute '${name}' on 'TimeRanges': The index provided (${index}) is greater than the maximum bound (${length})`);
- }
- return timeranges[index][name];
- };
- this.buffered = {
- get length() {
- return timeranges.length;
- },
- end(index) {
- return getRange("end", index, timeranges.length);
- },
- start(index) {
- return getRange("start", index, timeranges.length);
- }
- };
- }
- };
- var specialCea608CharsCodes = {
- 42: 225,
- // lowercase a, acute accent
- 92: 233,
- // lowercase e, acute accent
- 94: 237,
- // lowercase i, acute accent
- 95: 243,
- // lowercase o, acute accent
- 96: 250,
- // lowercase u, acute accent
- 123: 231,
- // lowercase c with cedilla
- 124: 247,
- // division symbol
- 125: 209,
- // uppercase N tilde
- 126: 241,
- // lowercase n tilde
- 127: 9608,
- // Full block
- // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
- // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F
- // THIS MEANS THAT \x50 MUST BE ADDED TO THE VALUES
- 128: 174,
- // Registered symbol (R)
- 129: 176,
- // degree sign
- 130: 189,
- // 1/2 symbol
- 131: 191,
- // Inverted (open) question mark
- 132: 8482,
- // Trademark symbol (TM)
- 133: 162,
- // Cents symbol
- 134: 163,
- // Pounds sterling
- 135: 9834,
- // Music 8'th note
- 136: 224,
- // lowercase a, grave accent
- 137: 32,
- // transparent space (regular)
- 138: 232,
- // lowercase e, grave accent
- 139: 226,
- // lowercase a, circumflex accent
- 140: 234,
- // lowercase e, circumflex accent
- 141: 238,
- // lowercase i, circumflex accent
- 142: 244,
- // lowercase o, circumflex accent
- 143: 251,
- // lowercase u, circumflex accent
- // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
- // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F
- 144: 193,
- // capital letter A with acute
- 145: 201,
- // capital letter E with acute
- 146: 211,
- // capital letter O with acute
- 147: 218,
- // capital letter U with acute
- 148: 220,
- // capital letter U with diaresis
- 149: 252,
- // lowercase letter U with diaeresis
- 150: 8216,
- // opening single quote
- 151: 161,
- // inverted exclamation mark
- 152: 42,
- // asterisk
- 153: 8217,
- // closing single quote
- 154: 9473,
- // box drawings heavy horizontal
- 155: 169,
- // copyright sign
- 156: 8480,
- // Service mark
- 157: 8226,
- // (round) bullet
- 158: 8220,
- // Left double quotation mark
- 159: 8221,
- // Right double quotation mark
- 160: 192,
- // uppercase A, grave accent
- 161: 194,
- // uppercase A, circumflex
- 162: 199,
- // uppercase C with cedilla
- 163: 200,
- // uppercase E, grave accent
- 164: 202,
- // uppercase E, circumflex
- 165: 203,
- // capital letter E with diaresis
- 166: 235,
- // lowercase letter e with diaresis
- 167: 206,
- // uppercase I, circumflex
- 168: 207,
- // uppercase I, with diaresis
- 169: 239,
- // lowercase i, with diaresis
- 170: 212,
- // uppercase O, circumflex
- 171: 217,
- // uppercase U, grave accent
- 172: 249,
- // lowercase u, grave accent
- 173: 219,
- // uppercase U, circumflex
- 174: 171,
- // left-pointing double angle quotation mark
- 175: 187,
- // right-pointing double angle quotation mark
- // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
- // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F
- 176: 195,
- // Uppercase A, tilde
- 177: 227,
- // Lowercase a, tilde
- 178: 205,
- // Uppercase I, acute accent
- 179: 204,
- // Uppercase I, grave accent
- 180: 236,
- // Lowercase i, grave accent
- 181: 210,
- // Uppercase O, grave accent
- 182: 242,
- // Lowercase o, grave accent
- 183: 213,
- // Uppercase O, tilde
- 184: 245,
- // Lowercase o, tilde
- 185: 123,
- // Open curly brace
- 186: 125,
- // Closing curly brace
- 187: 92,
- // Backslash
- 188: 94,
- // Caret
- 189: 95,
- // Underscore
- 190: 124,
- // Pipe (vertical line)
- 191: 8764,
- // Tilde operator
- 192: 196,
- // Uppercase A, umlaut
- 193: 228,
- // Lowercase A, umlaut
- 194: 214,
- // Uppercase O, umlaut
- 195: 246,
- // Lowercase o, umlaut
- 196: 223,
- // Esszett (sharp S)
- 197: 165,
- // Yen symbol
- 198: 164,
- // Generic currency sign
- 199: 9475,
- // Box drawings heavy vertical
- 200: 197,
- // Uppercase A, ring
- 201: 229,
- // Lowercase A, ring
- 202: 216,
- // Uppercase O, stroke
- 203: 248,
- // Lowercase o, strok
- 204: 9487,
- // Box drawings heavy down and right
- 205: 9491,
- // Box drawings heavy down and left
- 206: 9495,
- // Box drawings heavy up and right
- 207: 9499
- // Box drawings heavy up and left
- };
- var getCharForByte = (byte) => String.fromCharCode(specialCea608CharsCodes[byte] || byte);
- var NR_ROWS = 15;
- var NR_COLS = 100;
- var rowsLowCh1 = {
- 17: 1,
- 18: 3,
- 21: 5,
- 22: 7,
- 23: 9,
- 16: 11,
- 19: 12,
- 20: 14
- };
- var rowsHighCh1 = {
- 17: 2,
- 18: 4,
- 21: 6,
- 22: 8,
- 23: 10,
- 19: 13,
- 20: 15
- };
- var rowsLowCh2 = {
- 25: 1,
- 26: 3,
- 29: 5,
- 30: 7,
- 31: 9,
- 24: 11,
- 27: 12,
- 28: 14
- };
- var rowsHighCh2 = {
- 25: 2,
- 26: 4,
- 29: 6,
- 30: 8,
- 31: 10,
- 27: 13,
- 28: 15
- };
- var backgroundColors = ["white", "green", "blue", "cyan", "red", "yellow", "magenta", "black", "transparent"];
- var CaptionsLogger = class {
- constructor() {
- this.time = null;
- this.verboseLevel = 0;
- }
- log(severity, msg) {
- if (this.verboseLevel >= severity) {
- const m = typeof msg === "function" ? msg() : msg;
- logger.log(`${this.time} [${severity}] ${m}`);
- }
- }
- };
- var numArrayToHexArray = function numArrayToHexArray2(numArray) {
- const hexArray = [];
- for (let j = 0; j < numArray.length; j++) {
- hexArray.push(numArray[j].toString(16));
- }
- return hexArray;
- };
- var PenState = class {
- constructor() {
- this.foreground = "white";
- this.underline = false;
- this.italics = false;
- this.background = "black";
- this.flash = false;
- }
- reset() {
- this.foreground = "white";
- this.underline = false;
- this.italics = false;
- this.background = "black";
- this.flash = false;
- }
- setStyles(styles) {
- const attribs = ["foreground", "underline", "italics", "background", "flash"];
- for (let i = 0; i < attribs.length; i++) {
- const style = attribs[i];
- if (styles.hasOwnProperty(style)) {
- this[style] = styles[style];
- }
- }
- }
- isDefault() {
- return this.foreground === "white" && !this.underline && !this.italics && this.background === "black" && !this.flash;
- }
- equals(other) {
- return this.foreground === other.foreground && this.underline === other.underline && this.italics === other.italics && this.background === other.background && this.flash === other.flash;
- }
- copy(newPenState) {
- this.foreground = newPenState.foreground;
- this.underline = newPenState.underline;
- this.italics = newPenState.italics;
- this.background = newPenState.background;
- this.flash = newPenState.flash;
- }
- toString() {
- return "color=" + this.foreground + ", underline=" + this.underline + ", italics=" + this.italics + ", background=" + this.background + ", flash=" + this.flash;
- }
- };
- var StyledUnicodeChar = class {
- constructor() {
- this.uchar = " ";
- this.penState = new PenState();
- }
- reset() {
- this.uchar = " ";
- this.penState.reset();
- }
- setChar(uchar, newPenState) {
- this.uchar = uchar;
- this.penState.copy(newPenState);
- }
- setPenState(newPenState) {
- this.penState.copy(newPenState);
- }
- equals(other) {
- return this.uchar === other.uchar && this.penState.equals(other.penState);
- }
- copy(newChar) {
- this.uchar = newChar.uchar;
- this.penState.copy(newChar.penState);
- }
- isEmpty() {
- return this.uchar === " " && this.penState.isDefault();
- }
- };
- var Row = class {
- constructor(logger2) {
- this.chars = [];
- this.pos = 0;
- this.currPenState = new PenState();
- this.cueStartTime = null;
- this.logger = void 0;
- for (let i = 0; i < NR_COLS; i++) {
- this.chars.push(new StyledUnicodeChar());
- }
- this.logger = logger2;
- }
- equals(other) {
- for (let i = 0; i < NR_COLS; i++) {
- if (!this.chars[i].equals(other.chars[i])) {
- return false;
- }
- }
- return true;
- }
- copy(other) {
- for (let i = 0; i < NR_COLS; i++) {
- this.chars[i].copy(other.chars[i]);
- }
- }
- isEmpty() {
- let empty = true;
- for (let i = 0; i < NR_COLS; i++) {
- if (!this.chars[i].isEmpty()) {
- empty = false;
- break;
- }
- }
- return empty;
- }
- /**
- * Set the cursor to a valid column.
- */
- setCursor(absPos) {
- if (this.pos !== absPos) {
- this.pos = absPos;
- }
- if (this.pos < 0) {
- this.logger.log(3, "Negative cursor position " + this.pos);
- this.pos = 0;
- } else if (this.pos > NR_COLS) {
- this.logger.log(3, "Too large cursor position " + this.pos);
- this.pos = NR_COLS;
- }
- }
- /**
- * Move the cursor relative to current position.
- */
- moveCursor(relPos) {
- const newPos = this.pos + relPos;
- if (relPos > 1) {
- for (let i = this.pos + 1; i < newPos + 1; i++) {
- this.chars[i].setPenState(this.currPenState);
- }
- }
- this.setCursor(newPos);
- }
- /**
- * Backspace, move one step back and clear character.
- */
- backSpace() {
- this.moveCursor(-1);
- this.chars[this.pos].setChar(" ", this.currPenState);
- }
- insertChar(byte) {
- if (byte >= 144) {
- this.backSpace();
- }
- const char = getCharForByte(byte);
- if (this.pos >= NR_COLS) {
- this.logger.log(0, () => "Cannot insert " + byte.toString(16) + " (" + char + ") at position " + this.pos + ". Skipping it!");
- return;
- }
- this.chars[this.pos].setChar(char, this.currPenState);
- this.moveCursor(1);
- }
- clearFromPos(startPos) {
- let i;
- for (i = startPos; i < NR_COLS; i++) {
- this.chars[i].reset();
- }
- }
- clear() {
- this.clearFromPos(0);
- this.pos = 0;
- this.currPenState.reset();
- }
- clearToEndOfRow() {
- this.clearFromPos(this.pos);
- }
- getTextString() {
- const chars = [];
- let empty = true;
- for (let i = 0; i < NR_COLS; i++) {
- const char = this.chars[i].uchar;
- if (char !== " ") {
- empty = false;
- }
- chars.push(char);
- }
- if (empty) {
- return "";
- } else {
- return chars.join("");
- }
- }
- setPenStyles(styles) {
- this.currPenState.setStyles(styles);
- const currChar = this.chars[this.pos];
- currChar.setPenState(this.currPenState);
- }
- };
- var CaptionScreen = class {
- constructor(logger2) {
- this.rows = [];
- this.currRow = NR_ROWS - 1;
- this.nrRollUpRows = null;
- this.lastOutputScreen = null;
- this.logger = void 0;
- for (let i = 0; i < NR_ROWS; i++) {
- this.rows.push(new Row(logger2));
- }
- this.logger = logger2;
- }
- reset() {
- for (let i = 0; i < NR_ROWS; i++) {
- this.rows[i].clear();
- }
- this.currRow = NR_ROWS - 1;
- }
- equals(other) {
- let equal = true;
- for (let i = 0; i < NR_ROWS; i++) {
- if (!this.rows[i].equals(other.rows[i])) {
- equal = false;
- break;
- }
- }
- return equal;
- }
- copy(other) {
- for (let i = 0; i < NR_ROWS; i++) {
- this.rows[i].copy(other.rows[i]);
- }
- }
- isEmpty() {
- let empty = true;
- for (let i = 0; i < NR_ROWS; i++) {
- if (!this.rows[i].isEmpty()) {
- empty = false;
- break;
- }
- }
- return empty;
- }
- backSpace() {
- const row = this.rows[this.currRow];
- row.backSpace();
- }
- clearToEndOfRow() {
- const row = this.rows[this.currRow];
- row.clearToEndOfRow();
- }
- /**
- * Insert a character (without styling) in the current row.
- */
- insertChar(char) {
- const row = this.rows[this.currRow];
- row.insertChar(char);
- }
- setPen(styles) {
- const row = this.rows[this.currRow];
- row.setPenStyles(styles);
- }
- moveCursor(relPos) {
- const row = this.rows[this.currRow];
- row.moveCursor(relPos);
- }
- setCursor(absPos) {
- this.logger.log(2, "setCursor: " + absPos);
- const row = this.rows[this.currRow];
- row.setCursor(absPos);
- }
- setPAC(pacData) {
- this.logger.log(2, () => "pacData = " + stringify(pacData));
- let newRow = pacData.row - 1;
- if (this.nrRollUpRows && newRow < this.nrRollUpRows - 1) {
- newRow = this.nrRollUpRows - 1;
- }
- if (this.nrRollUpRows && this.currRow !== newRow) {
- for (let i = 0; i < NR_ROWS; i++) {
- this.rows[i].clear();
- }
- const topRowIndex = this.currRow + 1 - this.nrRollUpRows;
- const lastOutputScreen = this.lastOutputScreen;
- if (lastOutputScreen) {
- const prevLineTime = lastOutputScreen.rows[topRowIndex].cueStartTime;
- const time = this.logger.time;
- if (prevLineTime !== null && time !== null && prevLineTime < time) {
- for (let i = 0; i < this.nrRollUpRows; i++) {
- this.rows[newRow - this.nrRollUpRows + i + 1].copy(lastOutputScreen.rows[topRowIndex + i]);
- }
- }
- }
- }
- this.currRow = newRow;
- const row = this.rows[this.currRow];
- if (pacData.indent !== null) {
- const indent = pacData.indent;
- const prevPos = Math.max(indent - 1, 0);
- row.setCursor(pacData.indent);
- pacData.color = row.chars[prevPos].penState.foreground;
- }
- const styles = {
- foreground: pacData.color,
- underline: pacData.underline,
- italics: pacData.italics,
- background: "black",
- flash: false
- };
- this.setPen(styles);
- }
- /**
- * Set background/extra foreground, but first do back_space, and then insert space (backwards compatibility).
- */
- setBkgData(bkgData) {
- this.logger.log(2, () => "bkgData = " + stringify(bkgData));
- this.backSpace();
- this.setPen(bkgData);
- this.insertChar(32);
- }
- setRollUpRows(nrRows) {
- this.nrRollUpRows = nrRows;
- }
- rollUp() {
- if (this.nrRollUpRows === null) {
- this.logger.log(3, "roll_up but nrRollUpRows not set yet");
- return;
- }
- this.logger.log(1, () => this.getDisplayText());
- const topRowIndex = this.currRow + 1 - this.nrRollUpRows;
- const topRow = this.rows.splice(topRowIndex, 1)[0];
- topRow.clear();
- this.rows.splice(this.currRow, 0, topRow);
- this.logger.log(2, "Rolling up");
- }
- /**
- * Get all non-empty rows with as unicode text.
- */
- getDisplayText(asOneRow) {
- asOneRow = asOneRow || false;
- const displayText = [];
- let text = "";
- let rowNr = -1;
- for (let i = 0; i < NR_ROWS; i++) {
- const rowText = this.rows[i].getTextString();
- if (rowText) {
- rowNr = i + 1;
- if (asOneRow) {
- displayText.push("Row " + rowNr + ": '" + rowText + "'");
- } else {
- displayText.push(rowText.trim());
- }
- }
- }
- if (displayText.length > 0) {
- if (asOneRow) {
- text = "[" + displayText.join(" | ") + "]";
- } else {
- text = displayText.join("\n");
- }
- }
- return text;
- }
- getTextAndFormat() {
- return this.rows;
- }
- };
- var Cea608Channel = class {
- constructor(channelNumber, outputFilter, logger2) {
- this.chNr = void 0;
- this.outputFilter = void 0;
- this.mode = void 0;
- this.verbose = void 0;
- this.displayedMemory = void 0;
- this.nonDisplayedMemory = void 0;
- this.lastOutputScreen = void 0;
- this.currRollUpRow = void 0;
- this.writeScreen = void 0;
- this.cueStartTime = void 0;
- this.logger = void 0;
- this.chNr = channelNumber;
- this.outputFilter = outputFilter;
- this.mode = null;
- this.verbose = 0;
- this.displayedMemory = new CaptionScreen(logger2);
- this.nonDisplayedMemory = new CaptionScreen(logger2);
- this.lastOutputScreen = new CaptionScreen(logger2);
- this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1];
- this.writeScreen = this.displayedMemory;
- this.mode = null;
- this.cueStartTime = null;
- this.logger = logger2;
- }
- reset() {
- this.mode = null;
- this.displayedMemory.reset();
- this.nonDisplayedMemory.reset();
- this.lastOutputScreen.reset();
- this.outputFilter.reset();
- this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1];
- this.writeScreen = this.displayedMemory;
- this.mode = null;
- this.cueStartTime = null;
- }
- getHandler() {
- return this.outputFilter;
- }
- setHandler(newHandler) {
- this.outputFilter = newHandler;
- }
- setPAC(pacData) {
- this.writeScreen.setPAC(pacData);
- }
- setBkgData(bkgData) {
- this.writeScreen.setBkgData(bkgData);
- }
- setMode(newMode) {
- if (newMode === this.mode) {
- return;
- }
- this.mode = newMode;
- this.logger.log(2, () => "MODE=" + newMode);
- if (this.mode === "MODE_POP-ON") {
- this.writeScreen = this.nonDisplayedMemory;
- } else {
- this.writeScreen = this.displayedMemory;
- this.writeScreen.reset();
- }
- if (this.mode !== "MODE_ROLL-UP") {
- this.displayedMemory.nrRollUpRows = null;
- this.nonDisplayedMemory.nrRollUpRows = null;
- }
- this.mode = newMode;
- }
- insertChars(chars) {
- for (let i = 0; i < chars.length; i++) {
- this.writeScreen.insertChar(chars[i]);
- }
- const screen = this.writeScreen === this.displayedMemory ? "DISP" : "NON_DISP";
- this.logger.log(2, () => screen + ": " + this.writeScreen.getDisplayText(true));
- if (this.mode === "MODE_PAINT-ON" || this.mode === "MODE_ROLL-UP") {
- this.logger.log(1, () => "DISPLAYED: " + this.displayedMemory.getDisplayText(true));
- this.outputDataUpdate();
- }
- }
- ccRCL() {
- this.logger.log(2, "RCL - Resume Caption Loading");
- this.setMode("MODE_POP-ON");
- }
- ccBS() {
- this.logger.log(2, "BS - BackSpace");
- if (this.mode === "MODE_TEXT") {
- return;
- }
- this.writeScreen.backSpace();
- if (this.writeScreen === this.displayedMemory) {
- this.outputDataUpdate();
- }
- }
- ccAOF() {
- }
- ccAON() {
- }
- ccDER() {
- this.logger.log(2, "DER- Delete to End of Row");
- this.writeScreen.clearToEndOfRow();
- this.outputDataUpdate();
- }
- ccRU(nrRows) {
- this.logger.log(2, "RU(" + nrRows + ") - Roll Up");
- this.writeScreen = this.displayedMemory;
- this.setMode("MODE_ROLL-UP");
- this.writeScreen.setRollUpRows(nrRows);
- }
- ccFON() {
- this.logger.log(2, "FON - Flash On");
- this.writeScreen.setPen({
- flash: true
- });
- }
- ccRDC() {
- this.logger.log(2, "RDC - Resume Direct Captioning");
- this.setMode("MODE_PAINT-ON");
- }
- ccTR() {
- this.logger.log(2, "TR");
- this.setMode("MODE_TEXT");
- }
- ccRTD() {
- this.logger.log(2, "RTD");
- this.setMode("MODE_TEXT");
- }
- ccEDM() {
- this.logger.log(2, "EDM - Erase Displayed Memory");
- this.displayedMemory.reset();
- this.outputDataUpdate(true);
- }
- ccCR() {
- this.logger.log(2, "CR - Carriage Return");
- this.writeScreen.rollUp();
- this.outputDataUpdate(true);
- }
- ccENM() {
- this.logger.log(2, "ENM - Erase Non-displayed Memory");
- this.nonDisplayedMemory.reset();
- }
- ccEOC() {
- this.logger.log(2, "EOC - End Of Caption");
- if (this.mode === "MODE_POP-ON") {
- const tmp = this.displayedMemory;
- this.displayedMemory = this.nonDisplayedMemory;
- this.nonDisplayedMemory = tmp;
- this.writeScreen = this.nonDisplayedMemory;
- this.logger.log(1, () => "DISP: " + this.displayedMemory.getDisplayText());
- }
- this.outputDataUpdate(true);
- }
- ccTO(nrCols) {
- this.logger.log(2, "TO(" + nrCols + ") - Tab Offset");
- this.writeScreen.moveCursor(nrCols);
- }
- ccMIDROW(secondByte) {
- const styles = {
- flash: false
- };
- styles.underline = secondByte % 2 === 1;
- styles.italics = secondByte >= 46;
- if (!styles.italics) {
- const colorIndex = Math.floor(secondByte / 2) - 16;
- const colors = ["white", "green", "blue", "cyan", "red", "yellow", "magenta"];
- styles.foreground = colors[colorIndex];
- } else {
- styles.foreground = "white";
- }
- this.logger.log(2, "MIDROW: " + stringify(styles));
- this.writeScreen.setPen(styles);
- }
- outputDataUpdate(dispatch = false) {
- const time = this.logger.time;
- if (time === null) {
- return;
- }
- if (this.outputFilter) {
- if (this.cueStartTime === null && !this.displayedMemory.isEmpty()) {
- this.cueStartTime = time;
- } else {
- if (!this.displayedMemory.equals(this.lastOutputScreen)) {
- this.outputFilter.newCue(this.cueStartTime, time, this.lastOutputScreen);
- if (dispatch && this.outputFilter.dispatchCue) {
- this.outputFilter.dispatchCue();
- }
- this.cueStartTime = this.displayedMemory.isEmpty() ? null : time;
- }
- }
- this.lastOutputScreen.copy(this.displayedMemory);
- }
- }
- cueSplitAtTime(t) {
- if (this.outputFilter) {
- if (!this.displayedMemory.isEmpty()) {
- if (this.outputFilter.newCue) {
- this.outputFilter.newCue(this.cueStartTime, t, this.displayedMemory);
- }
- this.cueStartTime = t;
- }
- }
- }
- };
- var Cea608Parser = class {
- constructor(field, out1, out2) {
- this.channels = void 0;
- this.currentChannel = 0;
- this.cmdHistory = createCmdHistory();
- this.logger = void 0;
- const logger2 = this.logger = new CaptionsLogger();
- this.channels = [null, new Cea608Channel(field, out1, logger2), new Cea608Channel(field + 1, out2, logger2)];
- }
- getHandler(channel) {
- return this.channels[channel].getHandler();
- }
- setHandler(channel, newHandler) {
- this.channels[channel].setHandler(newHandler);
- }
- /**
- * Add data for time t in forms of list of bytes (unsigned ints). The bytes are treated as pairs.
- */
- addData(time, byteList) {
- this.logger.time = time;
- for (let i = 0; i < byteList.length; i += 2) {
- const a = byteList[i] & 127;
- const b = byteList[i + 1] & 127;
- let cmdFound = false;
- let charsFound = null;
- if (a === 0 && b === 0) {
- continue;
- } else {
- this.logger.log(3, () => "[" + numArrayToHexArray([byteList[i], byteList[i + 1]]) + "] -> (" + numArrayToHexArray([a, b]) + ")");
- }
- const cmdHistory = this.cmdHistory;
- const isControlCode = a >= 16 && a <= 31;
- if (isControlCode) {
- if (hasCmdRepeated(a, b, cmdHistory)) {
- setLastCmd(null, null, cmdHistory);
- this.logger.log(3, () => "Repeated command (" + numArrayToHexArray([a, b]) + ") is dropped");
- continue;
- }
- setLastCmd(a, b, this.cmdHistory);
- cmdFound = this.parseCmd(a, b);
- if (!cmdFound) {
- cmdFound = this.parseMidrow(a, b);
- }
- if (!cmdFound) {
- cmdFound = this.parsePAC(a, b);
- }
- if (!cmdFound) {
- cmdFound = this.parseBackgroundAttributes(a, b);
- }
- } else {
- setLastCmd(null, null, cmdHistory);
- }
- if (!cmdFound) {
- charsFound = this.parseChars(a, b);
- if (charsFound) {
- const currChNr = this.currentChannel;
- if (currChNr && currChNr > 0) {
- const channel = this.channels[currChNr];
- channel.insertChars(charsFound);
- } else {
- this.logger.log(2, "No channel found yet. TEXT-MODE?");
- }
- }
- }
- if (!cmdFound && !charsFound) {
- this.logger.log(2, () => "Couldn't parse cleaned data " + numArrayToHexArray([a, b]) + " orig: " + numArrayToHexArray([byteList[i], byteList[i + 1]]));
- }
- }
- }
- /**
- * Parse Command.
- * @returns True if a command was found
- */
- parseCmd(a, b) {
- const cond1 = (a === 20 || a === 28 || a === 21 || a === 29) && b >= 32 && b <= 47;
- const cond2 = (a === 23 || a === 31) && b >= 33 && b <= 35;
- if (!(cond1 || cond2)) {
- return false;
- }
- const chNr = a === 20 || a === 21 || a === 23 ? 1 : 2;
- const channel = this.channels[chNr];
- if (a === 20 || a === 21 || a === 28 || a === 29) {
- if (b === 32) {
- channel.ccRCL();
- } else if (b === 33) {
- channel.ccBS();
- } else if (b === 34) {
- channel.ccAOF();
- } else if (b === 35) {
- channel.ccAON();
- } else if (b === 36) {
- channel.ccDER();
- } else if (b === 37) {
- channel.ccRU(2);
- } else if (b === 38) {
- channel.ccRU(3);
- } else if (b === 39) {
- channel.ccRU(4);
- } else if (b === 40) {
- channel.ccFON();
- } else if (b === 41) {
- channel.ccRDC();
- } else if (b === 42) {
- channel.ccTR();
- } else if (b === 43) {
- channel.ccRTD();
- } else if (b === 44) {
- channel.ccEDM();
- } else if (b === 45) {
- channel.ccCR();
- } else if (b === 46) {
- channel.ccENM();
- } else if (b === 47) {
- channel.ccEOC();
- }
- } else {
- channel.ccTO(b - 32);
- }
- this.currentChannel = chNr;
- return true;
- }
- /**
- * Parse midrow styling command
- */
- parseMidrow(a, b) {
- let chNr = 0;
- if ((a === 17 || a === 25) && b >= 32 && b <= 47) {
- if (a === 17) {
- chNr = 1;
- } else {
- chNr = 2;
- }
- if (chNr !== this.currentChannel) {
- this.logger.log(0, "Mismatch channel in midrow parsing");
- return false;
- }
- const channel = this.channels[chNr];
- if (!channel) {
- return false;
- }
- channel.ccMIDROW(b);
- this.logger.log(3, () => "MIDROW (" + numArrayToHexArray([a, b]) + ")");
- return true;
- }
- return false;
- }
- /**
- * Parse Preable Access Codes (Table 53).
- * @returns {Boolean} Tells if PAC found
- */
- parsePAC(a, b) {
- let row;
- const case1 = (a >= 17 && a <= 23 || a >= 25 && a <= 31) && b >= 64 && b <= 127;
- const case2 = (a === 16 || a === 24) && b >= 64 && b <= 95;
- if (!(case1 || case2)) {
- return false;
- }
- const chNr = a <= 23 ? 1 : 2;
- if (b >= 64 && b <= 95) {
- row = chNr === 1 ? rowsLowCh1[a] : rowsLowCh2[a];
- } else {
- row = chNr === 1 ? rowsHighCh1[a] : rowsHighCh2[a];
- }
- const channel = this.channels[chNr];
- if (!channel) {
- return false;
- }
- channel.setPAC(this.interpretPAC(row, b));
- this.currentChannel = chNr;
- return true;
- }
- /**
- * Interpret the second byte of the pac, and return the information.
- * @returns pacData with style parameters
- */
- interpretPAC(row, byte) {
- let pacIndex;
- const pacData = {
- color: null,
- italics: false,
- indent: null,
- underline: false,
- row
- };
- if (byte > 95) {
- pacIndex = byte - 96;
- } else {
- pacIndex = byte - 64;
- }
- pacData.underline = (pacIndex & 1) === 1;
- if (pacIndex <= 13) {
- pacData.color = ["white", "green", "blue", "cyan", "red", "yellow", "magenta", "white"][Math.floor(pacIndex / 2)];
- } else if (pacIndex <= 15) {
- pacData.italics = true;
- pacData.color = "white";
- } else {
- pacData.indent = Math.floor((pacIndex - 16) / 2) * 4;
- }
- return pacData;
- }
- /**
- * Parse characters.
- * @returns An array with 1 to 2 codes corresponding to chars, if found. null otherwise.
- */
- parseChars(a, b) {
- let channelNr;
- let charCodes = null;
- let charCode1 = null;
- if (a >= 25) {
- channelNr = 2;
- charCode1 = a - 8;
- } else {
- channelNr = 1;
- charCode1 = a;
- }
- if (charCode1 >= 17 && charCode1 <= 19) {
- let oneCode;
- if (charCode1 === 17) {
- oneCode = b + 80;
- } else if (charCode1 === 18) {
- oneCode = b + 112;
- } else {
- oneCode = b + 144;
- }
- this.logger.log(2, () => "Special char '" + getCharForByte(oneCode) + "' in channel " + channelNr);
- charCodes = [oneCode];
- } else if (a >= 32 && a <= 127) {
- charCodes = b === 0 ? [a] : [a, b];
- }
- if (charCodes) {
- this.logger.log(3, () => "Char codes = " + numArrayToHexArray(charCodes).join(","));
- }
- return charCodes;
- }
- /**
- * Parse extended background attributes as well as new foreground color black.
- * @returns True if background attributes are found
- */
- parseBackgroundAttributes(a, b) {
- const case1 = (a === 16 || a === 24) && b >= 32 && b <= 47;
- const case2 = (a === 23 || a === 31) && b >= 45 && b <= 47;
- if (!(case1 || case2)) {
- return false;
- }
- let index;
- const bkgData = {};
- if (a === 16 || a === 24) {
- index = Math.floor((b - 32) / 2);
- bkgData.background = backgroundColors[index];
- if (b % 2 === 1) {
- bkgData.background = bkgData.background + "_semi";
- }
- } else if (b === 45) {
- bkgData.background = "transparent";
- } else {
- bkgData.foreground = "black";
- if (b === 47) {
- bkgData.underline = true;
- }
- }
- const chNr = a <= 23 ? 1 : 2;
- const channel = this.channels[chNr];
- channel.setBkgData(bkgData);
- return true;
- }
- /**
- * Reset state of parser and its channels.
- */
- reset() {
- for (let i = 0; i < Object.keys(this.channels).length; i++) {
- const channel = this.channels[i];
- if (channel) {
- channel.reset();
- }
- }
- setLastCmd(null, null, this.cmdHistory);
- }
- /**
- * Trigger the generation of a cue, and the start of a new one if displayScreens are not empty.
- */
- cueSplitAtTime(t) {
- for (let i = 0; i < this.channels.length; i++) {
- const channel = this.channels[i];
- if (channel) {
- channel.cueSplitAtTime(t);
- }
- }
- }
- };
- function setLastCmd(a, b, cmdHistory) {
- cmdHistory.a = a;
- cmdHistory.b = b;
- }
- function hasCmdRepeated(a, b, cmdHistory) {
- return cmdHistory.a === a && cmdHistory.b === b;
- }
- function createCmdHistory() {
- return {
- a: null,
- b: null
- };
- }
- var VTTCue = function() {
- if (optionalSelf != null && optionalSelf.VTTCue) {
- return self.VTTCue;
- }
- const AllowedDirections = ["", "lr", "rl"];
- const AllowedAlignments = ["start", "middle", "end", "left", "right"];
- function isAllowedValue(allowed, value) {
- if (typeof value !== "string") {
- return false;
- }
- if (!Array.isArray(allowed)) {
- return false;
- }
- const lcValue = value.toLowerCase();
- if (~allowed.indexOf(lcValue)) {
- return lcValue;
- }
- return false;
- }
- function findDirectionSetting(value) {
- return isAllowedValue(AllowedDirections, value);
- }
- function findAlignSetting(value) {
- return isAllowedValue(AllowedAlignments, value);
- }
- function extend(obj, ...rest) {
- let i = 1;
- for (; i < arguments.length; i++) {
- const cobj = arguments[i];
- for (const p in cobj) {
- obj[p] = cobj[p];
- }
- }
- return obj;
- }
- function VTTCue2(startTime, endTime, text) {
- const cue = this;
- const baseObj = {
- enumerable: true
- };
- cue.hasBeenReset = false;
- let _id = "";
- let _pauseOnExit = false;
- let _startTime = startTime;
- let _endTime = endTime;
- let _text = text;
- let _region = null;
- let _vertical = "";
- let _snapToLines = true;
- let _line = "auto";
- let _lineAlign = "start";
- let _position = 50;
- let _positionAlign = "middle";
- let _size = 50;
- let _align = "middle";
- Object.defineProperty(cue, "id", extend({}, baseObj, {
- get: function() {
- return _id;
- },
- set: function(value) {
- _id = "" + value;
- }
- }));
- Object.defineProperty(cue, "pauseOnExit", extend({}, baseObj, {
- get: function() {
- return _pauseOnExit;
- },
- set: function(value) {
- _pauseOnExit = !!value;
- }
- }));
- Object.defineProperty(cue, "startTime", extend({}, baseObj, {
- 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;
- }
- }));
- Object.defineProperty(cue, "endTime", extend({}, baseObj, {
- 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;
- }
- }));
- Object.defineProperty(cue, "text", extend({}, baseObj, {
- get: function() {
- return _text;
- },
- set: function(value) {
- _text = "" + value;
- this.hasBeenReset = true;
- }
- }));
- Object.defineProperty(cue, "region", extend({}, baseObj, {
- get: function() {
- return _region;
- },
- set: function(value) {
- _region = value;
- this.hasBeenReset = true;
- }
- }));
- Object.defineProperty(cue, "vertical", extend({}, baseObj, {
- get: function() {
- return _vertical;
- },
- set: function(value) {
- const setting = findDirectionSetting(value);
- if (setting === false) {
- throw new SyntaxError("An invalid or illegal string was specified.");
- }
- _vertical = setting;
- this.hasBeenReset = true;
- }
- }));
- Object.defineProperty(cue, "snapToLines", extend({}, baseObj, {
- get: function() {
- return _snapToLines;
- },
- set: function(value) {
- _snapToLines = !!value;
- this.hasBeenReset = true;
- }
- }));
- Object.defineProperty(cue, "line", extend({}, baseObj, {
- get: function() {
- return _line;
- },
- set: function(value) {
- if (typeof value !== "number" && value !== "auto") {
- throw new SyntaxError("An invalid number or illegal string was specified.");
- }
- _line = value;
- this.hasBeenReset = true;
- }
- }));
- Object.defineProperty(cue, "lineAlign", extend({}, baseObj, {
- get: function() {
- return _lineAlign;
- },
- set: function(value) {
- const setting = findAlignSetting(value);
- if (!setting) {
- throw new SyntaxError("An invalid or illegal string was specified.");
- }
- _lineAlign = setting;
- this.hasBeenReset = true;
- }
- }));
- Object.defineProperty(cue, "position", extend({}, baseObj, {
- 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;
- }
- }));
- Object.defineProperty(cue, "positionAlign", extend({}, baseObj, {
- get: function() {
- return _positionAlign;
- },
- set: function(value) {
- const setting = findAlignSetting(value);
- if (!setting) {
- throw new SyntaxError("An invalid or illegal string was specified.");
- }
- _positionAlign = setting;
- this.hasBeenReset = true;
- }
- }));
- Object.defineProperty(cue, "size", extend({}, baseObj, {
- 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;
- }
- }));
- Object.defineProperty(cue, "align", extend({}, baseObj, {
- get: function() {
- return _align;
- },
- set: function(value) {
- const setting = findAlignSetting(value);
- if (!setting) {
- throw new SyntaxError("An invalid or illegal string was specified.");
- }
- _align = setting;
- this.hasBeenReset = true;
- }
- }));
- cue.displayState = void 0;
- }
- VTTCue2.prototype.getCueAsHTML = function() {
- const WebVTT = self.WebVTT;
- return WebVTT.convertCueToDOMTree(self, this.text);
- };
- return VTTCue2;
- }();
- var StringDecoder = class {
- decode(data, options) {
- if (!data) {
- return "";
- }
- if (typeof data !== "string") {
- throw new Error("Error - expected string data.");
- }
- return decodeURIComponent(encodeURIComponent(data));
- }
- };
- function parseTimeStamp(input) {
- function computeSeconds(h, m2, s, f) {
- return (h | 0) * 3600 + (m2 | 0) * 60 + (s | 0) + parseFloat(f || 0);
- }
- const m = input.match(/^(?:(\d+):)?(\d{2}):(\d{2})(\.\d+)?/);
- if (!m) {
- return null;
- }
- if (parseFloat(m[2]) > 59) {
- return computeSeconds(m[2], m[3], 0, m[4]);
- }
- return computeSeconds(m[1], m[2], m[3], m[4]);
- }
- var Settings = class {
- constructor() {
- this.values = /* @__PURE__ */ Object.create(null);
- }
- // Only accept the first assignment to any key.
- set(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(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(k) {
- return k in this.values;
- }
- // Accept a setting if its one of the given alternatives.
- alt(k, v, a) {
- for (let 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(k, v) {
- if (/^-?\d+$/.test(v)) {
- this.set(k, parseInt(v, 10));
- }
- }
- // Accept a setting if its a valid percentage.
- percent(k, v) {
- if (/^([\d]{1,3})(\.[\d]*)?%$/.test(v)) {
- const percent = parseFloat(v);
- if (percent >= 0 && percent <= 100) {
- this.set(k, percent);
- return true;
- }
- }
- return false;
- }
- };
- function parseOptions(input, callback, keyValueDelim, groupDelim) {
- const groups = groupDelim ? input.split(groupDelim) : [input];
- for (const i in groups) {
- if (typeof groups[i] !== "string") {
- continue;
- }
- const kv = groups[i].split(keyValueDelim);
- if (kv.length !== 2) {
- continue;
- }
- const k = kv[0];
- const v = kv[1];
- callback(k, v);
- }
- }
- var defaults = new VTTCue(0, 0, "");
- var center = defaults.align === "middle" ? "middle" : "center";
- function parseCue(input, cue, regionList) {
- const oInput = input;
- function consumeTimeStamp() {
- const ts = parseTimeStamp(input);
- if (ts === null) {
- throw new Error("Malformed timestamp: " + oInput);
- }
- input = input.replace(/^[^\sa-zA-Z-]+/, "");
- return ts;
- }
- function consumeCueSettings(input2, cue2) {
- const settings = new Settings();
- parseOptions(input2, function(k, v) {
- let vals;
- switch (k) {
- case "region":
- for (let 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":
- vals = v.split(",");
- settings.integer(k, vals[0]);
- if (settings.percent(k, vals[0])) {
- settings.set("snapToLines", false);
- }
- settings.alt(k, vals[0], ["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", "line-left", "line-right", "auto"]);
- }
- break;
- case "size":
- settings.percent(k, v);
- break;
- case "align":
- settings.alt(k, v, ["start", center, "end", "left", "right"]);
- break;
- }
- }, /:/, /\s/);
- cue2.region = settings.get("region", null);
- cue2.vertical = settings.get("vertical", "");
- let line = settings.get("line", "auto");
- if (line === "auto" && defaults.line === -1) {
- line = -1;
- }
- cue2.line = line;
- cue2.lineAlign = settings.get("lineAlign", "start");
- cue2.snapToLines = settings.get("snapToLines", true);
- cue2.size = settings.get("size", 100);
- cue2.align = settings.get("align", center);
- let position = settings.get("position", "auto");
- if (position === "auto" && defaults.position === 50) {
- position = cue2.align === "start" || cue2.align === "left" ? 0 : cue2.align === "end" || cue2.align === "right" ? 100 : 50;
- }
- cue2.position = position;
- }
- function skipWhitespace() {
- input = input.replace(/^\s+/, "");
- }
- skipWhitespace();
- cue.startTime = consumeTimeStamp();
- skipWhitespace();
- if (input.slice(0, 3) !== "-->") {
- throw new Error("Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
- }
- input = input.slice(3);
- skipWhitespace();
- cue.endTime = consumeTimeStamp();
- skipWhitespace();
- consumeCueSettings(input, cue);
- }
- function fixLineBreaks(input) {
- return input.replace(/<br(?: \/)?>/gi, "\n");
- }
- var VTTParser = class {
- constructor() {
- this.state = "INITIAL";
- this.buffer = "";
- this.decoder = new StringDecoder();
- this.regionList = [];
- this.cue = null;
- this.oncue = void 0;
- this.onparsingerror = void 0;
- this.onflush = void 0;
- }
- parse(data) {
- const _this = this;
- if (data) {
- _this.buffer += _this.decoder.decode(data, {
- stream: true
- });
- }
- function collectNextLine() {
- let buffer = _this.buffer;
- let pos = 0;
- buffer = fixLineBreaks(buffer);
- while (pos < buffer.length && buffer[pos] !== "\r" && buffer[pos] !== "\n") {
- ++pos;
- }
- const line = buffer.slice(0, pos);
- if (buffer[pos] === "\r") {
- ++pos;
- }
- if (buffer[pos] === "\n") {
- ++pos;
- }
- _this.buffer = buffer.slice(pos);
- return line;
- }
- function parseHeader2(input) {
- parseOptions(input, function(k, v) {
- }, /:/);
- }
- try {
- let line = "";
- if (_this.state === "INITIAL") {
- if (!/\r\n|\n/.test(_this.buffer)) {
- return this;
- }
- line = collectNextLine();
- const m = line.match(/^()?WEBVTT([ \t].*)?$/);
- if (!(m != null && m[0])) {
- throw new Error("Malformed WebVTT signature.");
- }
- _this.state = "HEADER";
- }
- let alreadyCollectedLine = false;
- while (_this.buffer) {
- if (!/\r\n|\n/.test(_this.buffer)) {
- return this;
- }
- if (!alreadyCollectedLine) {
- line = collectNextLine();
- } else {
- alreadyCollectedLine = false;
- }
- switch (_this.state) {
- case "HEADER":
- if (/:/.test(line)) {
- parseHeader2(line);
- } else if (!line) {
- _this.state = "ID";
- }
- continue;
- case "NOTE":
- if (!line) {
- _this.state = "ID";
- }
- continue;
- case "ID":
- if (/^NOTE($|[ \t])/.test(line)) {
- _this.state = "NOTE";
- break;
- }
- if (!line) {
- continue;
- }
- _this.cue = new VTTCue(0, 0, "");
- _this.state = "CUE";
- if (line.indexOf("-->") === -1) {
- _this.cue.id = line;
- continue;
- }
- case "CUE":
- if (!_this.cue) {
- _this.state = "BADCUE";
- continue;
- }
- try {
- parseCue(line, _this.cue, _this.regionList);
- } catch (e) {
- _this.cue = null;
- _this.state = "BADCUE";
- continue;
- }
- _this.state = "CUETEXT";
- continue;
- case "CUETEXT":
- {
- const hasSubstring = line.indexOf("-->") !== -1;
- if (!line || hasSubstring && (alreadyCollectedLine = true)) {
- if (_this.oncue && _this.cue) {
- _this.oncue(_this.cue);
- }
- _this.cue = null;
- _this.state = "ID";
- continue;
- }
- if (_this.cue === null) {
- continue;
- }
- if (_this.cue.text) {
- _this.cue.text += "\n";
- }
- _this.cue.text += line;
- }
- continue;
- case "BADCUE":
- if (!line) {
- _this.state = "ID";
- }
- }
- }
- } catch (e) {
- if (_this.state === "CUETEXT" && _this.cue && _this.oncue) {
- _this.oncue(_this.cue);
- }
- _this.cue = null;
- _this.state = _this.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
- }
- return this;
- }
- flush() {
- const _this = this;
- try {
- if (_this.cue || _this.state === "HEADER") {
- _this.buffer += "\n\n";
- _this.parse();
- }
- if (_this.state === "INITIAL" || _this.state === "BADWEBVTT") {
- throw new Error("Malformed WebVTT signature.");
- }
- } catch (e) {
- if (_this.onparsingerror) {
- _this.onparsingerror(e);
- }
- }
- if (_this.onflush) {
- _this.onflush();
- }
- return this;
- }
- };
- var LINEBREAKS = /\r\n|\n\r|\n|\r/g;
- var startsWith = function startsWith2(inputString, searchString, position = 0) {
- return inputString.slice(position, position + searchString.length) === searchString;
- };
- var cueString2millis = function cueString2millis2(timeString) {
- let ts = parseInt(timeString.slice(-3));
- const secs = parseInt(timeString.slice(-6, -4));
- const mins = parseInt(timeString.slice(-9, -7));
- const hours = timeString.length > 9 ? parseInt(timeString.substring(0, timeString.indexOf(":"))) : 0;
- if (!isFiniteNumber(ts) || !isFiniteNumber(secs) || !isFiniteNumber(mins) || !isFiniteNumber(hours)) {
- throw Error(`Malformed X-TIMESTAMP-MAP: Local:${timeString}`);
- }
- ts += 1e3 * secs;
- ts += 60 * 1e3 * mins;
- ts += 60 * 60 * 1e3 * hours;
- return ts;
- };
- function generateCueId(startTime, endTime, text) {
- return hash(startTime.toString()) + hash(endTime.toString()) + hash(text);
- }
- var calculateOffset = function calculateOffset2(vttCCs, cc, presentationTime) {
- let currCC = vttCCs[cc];
- let prevCC = vttCCs[currCC.prevCC];
- if (!prevCC || !prevCC.new && currCC.new) {
- vttCCs.ccOffset = vttCCs.presentationOffset = currCC.start;
- currCC.new = false;
- return;
- }
- while ((_prevCC = prevCC) != null && _prevCC.new) {
- var _prevCC;
- vttCCs.ccOffset += currCC.start - prevCC.start;
- currCC.new = false;
- currCC = prevCC;
- prevCC = vttCCs[currCC.prevCC];
- }
- vttCCs.presentationOffset = presentationTime;
- };
- function parseWebVTT(vttByteArray, initPTS, vttCCs, cc, timeOffset, callBack, errorCallBack) {
- const parser = new VTTParser();
- const vttLines = utf8ArrayToStr(new Uint8Array(vttByteArray)).trim().replace(LINEBREAKS, "\n").split("\n");
- const cues = [];
- const init90kHz = initPTS ? toMpegTsClockFromTimescale(initPTS.baseTime, initPTS.timescale) : 0;
- let cueTime = "00:00.000";
- let timestampMapMPEGTS = 0;
- let timestampMapLOCAL = 0;
- let parsingError;
- let inHeader = true;
- parser.oncue = function(cue) {
- const currCC = vttCCs[cc];
- let cueOffset = vttCCs.ccOffset;
- const webVttMpegTsMapOffset = (timestampMapMPEGTS - init90kHz) / 9e4;
- if (currCC != null && currCC.new) {
- if (timestampMapLOCAL !== void 0) {
- cueOffset = vttCCs.ccOffset = currCC.start;
- } else {
- calculateOffset(vttCCs, cc, webVttMpegTsMapOffset);
- }
- }
- if (webVttMpegTsMapOffset) {
- if (!initPTS) {
- parsingError = new Error("Missing initPTS for VTT MPEGTS");
- return;
- }
- cueOffset = webVttMpegTsMapOffset - vttCCs.presentationOffset;
- }
- const duration = cue.endTime - cue.startTime;
- const startTime = normalizePts((cue.startTime + cueOffset - timestampMapLOCAL) * 9e4, timeOffset * 9e4) / 9e4;
- cue.startTime = Math.max(startTime, 0);
- cue.endTime = Math.max(startTime + duration, 0);
- const text = cue.text.trim();
- cue.text = decodeURIComponent(encodeURIComponent(text));
- if (!cue.id) {
- cue.id = generateCueId(cue.startTime, cue.endTime, text);
- }
- if (cue.endTime > 0) {
- cues.push(cue);
- }
- };
- parser.onparsingerror = function(error) {
- parsingError = error;
- };
- parser.onflush = function() {
- if (parsingError) {
- errorCallBack(parsingError);
- return;
- }
- callBack(cues);
- };
- vttLines.forEach((line) => {
- if (inHeader) {
- if (startsWith(line, "X-TIMESTAMP-MAP=")) {
- inHeader = false;
- line.slice(16).split(",").forEach((timestamp) => {
- if (startsWith(timestamp, "LOCAL:")) {
- cueTime = timestamp.slice(6);
- } else if (startsWith(timestamp, "MPEGTS:")) {
- timestampMapMPEGTS = parseInt(timestamp.slice(7));
- }
- });
- try {
- timestampMapLOCAL = cueString2millis(cueTime) / 1e3;
- } catch (error) {
- parsingError = error;
- }
- return;
- } else if (line === "") {
- inHeader = false;
- }
- }
- parser.parse(line + "\n");
- });
- parser.flush();
- }
- var IMSC1_CODEC = "stpp.ttml.im1t";
- var HMSF_REGEX = /^(\d{2,}):(\d{2}):(\d{2}):(\d{2})\.?(\d+)?$/;
- var TIME_UNIT_REGEX = /^(\d*(?:\.\d*)?)(h|m|s|ms|f|t)$/;
- var textAlignToLineAlign = {
- left: "start",
- center: "center",
- right: "end",
- start: "start",
- end: "end"
- };
- function parseIMSC1(payload, initPTS, callBack, errorCallBack) {
- const results = findBox(new Uint8Array(payload), ["mdat"]);
- if (results.length === 0) {
- errorCallBack(new Error("Could not parse IMSC1 mdat"));
- return;
- }
- const ttmlList = results.map((mdat) => utf8ArrayToStr(mdat));
- const syncTime = toTimescaleFromScale(initPTS.baseTime, 1, initPTS.timescale);
- try {
- ttmlList.forEach((ttml) => callBack(parseTTML(ttml, syncTime)));
- } catch (error) {
- errorCallBack(error);
- }
- }
- function parseTTML(ttml, syncTime) {
- const parser = new DOMParser();
- const xmlDoc = parser.parseFromString(ttml, "text/xml");
- const tt = xmlDoc.getElementsByTagName("tt")[0];
- if (!tt) {
- throw new Error("Invalid ttml");
- }
- const defaultRateInfo = {
- frameRate: 30,
- subFrameRate: 1,
- frameRateMultiplier: 0,
- tickRate: 0
- };
- const rateInfo = Object.keys(defaultRateInfo).reduce((result, key) => {
- result[key] = tt.getAttribute(`ttp:${key}`) || defaultRateInfo[key];
- return result;
- }, {});
- const trim = tt.getAttribute("xml:space") !== "preserve";
- const styleElements = collectionToDictionary(getElementCollection(tt, "styling", "style"));
- const regionElements = collectionToDictionary(getElementCollection(tt, "layout", "region"));
- const cueElements = getElementCollection(tt, "body", "[begin]");
- return [].map.call(cueElements, (cueElement) => {
- const cueText = getTextContent(cueElement, trim);
- if (!cueText || !cueElement.hasAttribute("begin")) {
- return null;
- }
- const startTime = parseTtmlTime(cueElement.getAttribute("begin"), rateInfo);
- const duration = parseTtmlTime(cueElement.getAttribute("dur"), rateInfo);
- let endTime = parseTtmlTime(cueElement.getAttribute("end"), rateInfo);
- if (startTime === null) {
- throw timestampParsingError(cueElement);
- }
- if (endTime === null) {
- if (duration === null) {
- throw timestampParsingError(cueElement);
- }
- endTime = startTime + duration;
- }
- const cue = new VTTCue(startTime - syncTime, endTime - syncTime, cueText);
- cue.id = generateCueId(cue.startTime, cue.endTime, cue.text);
- const region = regionElements[cueElement.getAttribute("region")];
- const style = styleElements[cueElement.getAttribute("style")];
- const styles = getTtmlStyles(region, style, styleElements);
- const {
- textAlign
- } = styles;
- if (textAlign) {
- const lineAlign = textAlignToLineAlign[textAlign];
- if (lineAlign) {
- cue.lineAlign = lineAlign;
- }
- cue.align = textAlign;
- }
- _extends(cue, styles);
- return cue;
- }).filter((cue) => cue !== null);
- }
- function getElementCollection(fromElement, parentName, childName) {
- const parent = fromElement.getElementsByTagName(parentName)[0];
- if (parent) {
- return [].slice.call(parent.querySelectorAll(childName));
- }
- return [];
- }
- function collectionToDictionary(elementsWithId) {
- return elementsWithId.reduce((dict, element) => {
- const id = element.getAttribute("xml:id");
- if (id) {
- dict[id] = element;
- }
- return dict;
- }, {});
- }
- function getTextContent(element, trim) {
- return [].slice.call(element.childNodes).reduce((str, node, i) => {
- var _node$childNodes;
- if (node.nodeName === "br" && i) {
- return str + "\n";
- }
- if ((_node$childNodes = node.childNodes) != null && _node$childNodes.length) {
- return getTextContent(node, trim);
- } else if (trim) {
- return str + node.textContent.trim().replace(/\s+/g, " ");
- }
- return str + node.textContent;
- }, "");
- }
- function getTtmlStyles(region, style, styleElements) {
- const ttsNs = "http://www.w3.org/ns/ttml#styling";
- let regionStyle = null;
- const styleAttributes = [
- "displayAlign",
- "textAlign",
- "color",
- "backgroundColor",
- "fontSize",
- "fontFamily"
- // 'fontWeight',
- // 'lineHeight',
- // 'wrapOption',
- // 'fontStyle',
- // 'direction',
- // 'writingMode'
- ];
- const regionStyleName = region != null && region.hasAttribute("style") ? region.getAttribute("style") : null;
- if (regionStyleName && styleElements.hasOwnProperty(regionStyleName)) {
- regionStyle = styleElements[regionStyleName];
- }
- return styleAttributes.reduce((styles, name) => {
- const value = getAttributeNS(style, ttsNs, name) || getAttributeNS(region, ttsNs, name) || getAttributeNS(regionStyle, ttsNs, name);
- if (value) {
- styles[name] = value;
- }
- return styles;
- }, {});
- }
- function getAttributeNS(element, ns, name) {
- if (!element) {
- return null;
- }
- return element.hasAttributeNS(ns, name) ? element.getAttributeNS(ns, name) : null;
- }
- function timestampParsingError(node) {
- return new Error(`Could not parse ttml timestamp ${node}`);
- }
- function parseTtmlTime(timeAttributeValue, rateInfo) {
- if (!timeAttributeValue) {
- return null;
- }
- let seconds = parseTimeStamp(timeAttributeValue);
- if (seconds === null) {
- if (HMSF_REGEX.test(timeAttributeValue)) {
- seconds = parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo);
- } else if (TIME_UNIT_REGEX.test(timeAttributeValue)) {
- seconds = parseTimeUnits(timeAttributeValue, rateInfo);
- }
- }
- return seconds;
- }
- function parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo) {
- const m = HMSF_REGEX.exec(timeAttributeValue);
- const frames = (m[4] | 0) + (m[5] | 0) / rateInfo.subFrameRate;
- return (m[1] | 0) * 3600 + (m[2] | 0) * 60 + (m[3] | 0) + frames / rateInfo.frameRate;
- }
- function parseTimeUnits(timeAttributeValue, rateInfo) {
- const m = TIME_UNIT_REGEX.exec(timeAttributeValue);
- const value = Number(m[1]);
- const unit = m[2];
- switch (unit) {
- case "h":
- return value * 3600;
- case "m":
- return value * 60;
- case "ms":
- return value * 1e3;
- case "f":
- return value / rateInfo.frameRate;
- case "t":
- return value / rateInfo.tickRate;
- }
- return value;
- }
- var OutputFilter = class {
- constructor(timelineController, trackName) {
- this.timelineController = void 0;
- this.cueRanges = [];
- this.trackName = void 0;
- this.startTime = null;
- this.endTime = null;
- this.screen = null;
- this.timelineController = timelineController;
- this.trackName = trackName;
- }
- dispatchCue() {
- if (this.startTime === null) {
- return;
- }
- this.timelineController.addCues(this.trackName, this.startTime, this.endTime, this.screen, this.cueRanges);
- this.startTime = null;
- }
- newCue(startTime, endTime, screen) {
- if (this.startTime === null || this.startTime > startTime) {
- this.startTime = startTime;
- }
- this.endTime = endTime;
- this.screen = screen;
- this.timelineController.createCaptionsTrack(this.trackName);
- }
- reset() {
- this.cueRanges = [];
- this.startTime = null;
- }
- };
- var TimelineController = class {
- constructor(hls) {
- this.hls = void 0;
- this.media = null;
- this.config = void 0;
- this.enabled = true;
- this.Cues = void 0;
- this.textTracks = [];
- this.tracks = [];
- this.initPTS = [];
- this.unparsedVttFrags = [];
- this.captionsTracks = {};
- this.nonNativeCaptionsTracks = {};
- this.cea608Parser1 = void 0;
- this.cea608Parser2 = void 0;
- this.lastCc = -1;
- this.lastSn = -1;
- this.lastPartIndex = -1;
- this.prevCC = -1;
- this.vttCCs = newVTTCCs();
- this.captionsProperties = void 0;
- this.hls = hls;
- this.config = hls.config;
- this.Cues = hls.config.cueHandler;
- this.captionsProperties = {
- textTrack1: {
- label: this.config.captionsTextTrack1Label,
- languageCode: this.config.captionsTextTrack1LanguageCode
- },
- textTrack2: {
- label: this.config.captionsTextTrack2Label,
- languageCode: this.config.captionsTextTrack2LanguageCode
- },
- textTrack3: {
- label: this.config.captionsTextTrack3Label,
- languageCode: this.config.captionsTextTrack3LanguageCode
- },
- textTrack4: {
- label: this.config.captionsTextTrack4Label,
- languageCode: this.config.captionsTextTrack4LanguageCode
- }
- };
- hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
- hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
- hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
- hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
- hls.on(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this);
- hls.on(Events.FRAG_DECRYPTED, this.onFragDecrypted, this);
- hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
- hls.on(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);
- hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
- }
- destroy() {
- const {
- hls
- } = this;
- hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
- hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
- hls.off(Events.FRAG_LOADING, this.onFragLoading, this);
- hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
- hls.off(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this);
- hls.off(Events.FRAG_DECRYPTED, this.onFragDecrypted, this);
- hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
- hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);
- hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
- this.hls = this.config = this.media = null;
- this.cea608Parser1 = this.cea608Parser2 = void 0;
- }
- initCea608Parsers() {
- const channel1 = new OutputFilter(this, "textTrack1");
- const channel2 = new OutputFilter(this, "textTrack2");
- const channel3 = new OutputFilter(this, "textTrack3");
- const channel4 = new OutputFilter(this, "textTrack4");
- this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
- this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
- }
- addCues(trackName, startTime, endTime, screen, cueRanges) {
- let merged = false;
- for (let i = cueRanges.length; i--; ) {
- const cueRange = cueRanges[i];
- const overlap = intersection(cueRange[0], cueRange[1], startTime, endTime);
- if (overlap >= 0) {
- cueRange[0] = Math.min(cueRange[0], startTime);
- cueRange[1] = Math.max(cueRange[1], endTime);
- merged = true;
- if (overlap / (endTime - startTime) > 0.5) {
- return;
- }
- }
- }
- if (!merged) {
- cueRanges.push([startTime, endTime]);
- }
- if (this.config.renderTextTracksNatively) {
- const track = this.captionsTracks[trackName];
- this.Cues.newCue(track, startTime, endTime, screen);
- } else {
- const cues = this.Cues.newCue(null, startTime, endTime, screen);
- this.hls.trigger(Events.CUES_PARSED, {
- type: "captions",
- cues,
- track: trackName
- });
- }
- }
- // Triggered when an initial PTS is found; used for synchronisation of WebVTT.
- onInitPtsFound(event, {
- frag,
- id,
- initPTS,
- timescale,
- trackId
- }) {
- const {
- unparsedVttFrags
- } = this;
- if (id === PlaylistLevelType.MAIN) {
- this.initPTS[frag.cc] = {
- baseTime: initPTS,
- timescale,
- trackId
- };
- }
- if (unparsedVttFrags.length) {
- this.unparsedVttFrags = [];
- unparsedVttFrags.forEach((data) => {
- if (this.initPTS[data.frag.cc]) {
- this.onFragLoaded(Events.FRAG_LOADED, data);
- } else {
- this.hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
- success: false,
- frag: data.frag,
- error: new Error("Subtitle discontinuity domain does not match main")
- });
- }
- });
- }
- }
- getExistingTrack(label, language) {
- const {
- media
- } = this;
- if (media) {
- for (let i = 0; i < media.textTracks.length; i++) {
- const textTrack = media.textTracks[i];
- if (canReuseVttTextTrack(textTrack, {
- name: label,
- lang: language,
- characteristics: "transcribes-spoken-dialog,describes-music-and-sound"
- })) {
- return textTrack;
- }
- }
- }
- return null;
- }
- createCaptionsTrack(trackName) {
- if (this.config.renderTextTracksNatively) {
- this.createNativeTrack(trackName);
- } else {
- this.createNonNativeTrack(trackName);
- }
- }
- createNativeTrack(trackName) {
- if (this.captionsTracks[trackName]) {
- return;
- }
- const {
- captionsProperties,
- captionsTracks,
- media
- } = this;
- const {
- label,
- languageCode
- } = captionsProperties[trackName];
- const existingTrack = this.getExistingTrack(label, languageCode);
- if (!existingTrack) {
- const textTrack = this.createTextTrack("captions", label, languageCode);
- if (textTrack) {
- textTrack[trackName] = true;
- captionsTracks[trackName] = textTrack;
- }
- } else {
- captionsTracks[trackName] = existingTrack;
- clearCurrentCues(captionsTracks[trackName]);
- sendAddTrackEvent(captionsTracks[trackName], media);
- }
- }
- createNonNativeTrack(trackName) {
- if (this.nonNativeCaptionsTracks[trackName]) {
- return;
- }
- const trackProperties = this.captionsProperties[trackName];
- if (!trackProperties) {
- return;
- }
- const label = trackProperties.label;
- const track = {
- _id: trackName,
- label,
- kind: "captions",
- default: trackProperties.media ? !!trackProperties.media.default : false,
- closedCaptions: trackProperties.media
- };
- this.nonNativeCaptionsTracks[trackName] = track;
- this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, {
- tracks: [track]
- });
- }
- createTextTrack(kind, label, lang) {
- const media = this.media;
- if (!media) {
- return;
- }
- return media.addTextTrack(kind, label, lang);
- }
- onMediaAttaching(event, data) {
- this.media = data.media;
- if (!data.mediaSource) {
- this._cleanTracks();
- }
- }
- onMediaDetaching(event, data) {
- const transferringMedia = !!data.transferMedia;
- this.media = null;
- if (transferringMedia) {
- return;
- }
- const {
- captionsTracks
- } = this;
- Object.keys(captionsTracks).forEach((trackName) => {
- clearCurrentCues(captionsTracks[trackName]);
- delete captionsTracks[trackName];
- });
- this.nonNativeCaptionsTracks = {};
- }
- onManifestLoading() {
- this.lastCc = -1;
- this.lastSn = -1;
- this.lastPartIndex = -1;
- this.prevCC = -1;
- this.vttCCs = newVTTCCs();
- this._cleanTracks();
- this.tracks = [];
- this.captionsTracks = {};
- this.nonNativeCaptionsTracks = {};
- this.textTracks = [];
- this.unparsedVttFrags = [];
- this.initPTS = [];
- if (this.cea608Parser1 && this.cea608Parser2) {
- this.cea608Parser1.reset();
- this.cea608Parser2.reset();
- }
- }
- _cleanTracks() {
- const {
- media
- } = this;
- if (!media) {
- return;
- }
- const textTracks = media.textTracks;
- if (textTracks) {
- for (let i = 0; i < textTracks.length; i++) {
- clearCurrentCues(textTracks[i]);
- }
- }
- }
- onSubtitleTracksUpdated(event, data) {
- const tracks = data.subtitleTracks || [];
- const hasIMSC1 = tracks.some((track) => track.textCodec === IMSC1_CODEC);
- if (this.config.enableWebVTT || hasIMSC1 && this.config.enableIMSC1) {
- const listIsIdentical = subtitleOptionsIdentical(this.tracks, tracks);
- if (listIsIdentical) {
- this.tracks = tracks;
- return;
- }
- this.textTracks = [];
- this.tracks = tracks;
- if (this.config.renderTextTracksNatively) {
- const media = this.media;
- const inUseTracks = media ? filterSubtitleTracks(media.textTracks) : null;
- this.tracks.forEach((track, index) => {
- let textTrack;
- if (inUseTracks) {
- let inUseTrack = null;
- for (let i = 0; i < inUseTracks.length; i++) {
- if (inUseTracks[i] && canReuseVttTextTrack(inUseTracks[i], track)) {
- inUseTrack = inUseTracks[i];
- inUseTracks[i] = null;
- break;
- }
- }
- if (inUseTrack) {
- textTrack = inUseTrack;
- }
- }
- if (textTrack) {
- clearCurrentCues(textTrack);
- } else {
- const textTrackKind = captionsOrSubtitlesFromCharacteristics(track);
- textTrack = this.createTextTrack(textTrackKind, track.name, track.lang);
- if (textTrack) {
- textTrack.mode = "disabled";
- }
- }
- if (textTrack) {
- this.textTracks.push(textTrack);
- }
- });
- if (inUseTracks != null && inUseTracks.length) {
- const unusedTextTracks = inUseTracks.filter((t) => t !== null).map((t) => t.label);
- if (unusedTextTracks.length) {
- this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(", ")}. Replace media element for each source to clear TextTracks and captions menu.`);
- }
- }
- } else if (this.tracks.length) {
- const tracksList = this.tracks.map((track) => {
- return {
- label: track.name,
- kind: track.type.toLowerCase(),
- default: track.default,
- subtitleTrack: track
- };
- });
- this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, {
- tracks: tracksList
- });
- }
- }
- }
- onManifestLoaded(event, data) {
- if (this.config.enableCEA708Captions && data.captions) {
- data.captions.forEach((captionsTrack) => {
- const instreamIdMatch = /(?:CC|SERVICE)([1-4])/.exec(captionsTrack.instreamId);
- if (!instreamIdMatch) {
- return;
- }
- const trackName = `textTrack${instreamIdMatch[1]}`;
- const trackProperties = this.captionsProperties[trackName];
- if (!trackProperties) {
- return;
- }
- trackProperties.label = captionsTrack.name;
- if (captionsTrack.lang) {
- trackProperties.languageCode = captionsTrack.lang;
- }
- trackProperties.media = captionsTrack;
- });
- }
- }
- closedCaptionsForLevel(frag) {
- const level = this.hls.levels[frag.level];
- return level == null ? void 0 : level.attrs["CLOSED-CAPTIONS"];
- }
- onFragLoading(event, data) {
- if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
- var _data$part$index, _data$part;
- const {
- cea608Parser1,
- cea608Parser2,
- lastSn
- } = this;
- const {
- cc,
- sn
- } = data.frag;
- const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
- if (cea608Parser1 && cea608Parser2) {
- if (sn !== lastSn + 1 || sn === lastSn && partIndex !== this.lastPartIndex + 1 || cc !== this.lastCc) {
- cea608Parser1.reset();
- cea608Parser2.reset();
- }
- }
- this.lastCc = cc;
- this.lastSn = sn;
- this.lastPartIndex = partIndex;
- }
- }
- onFragLoaded(event, data) {
- const {
- frag,
- payload
- } = data;
- if (frag.type === PlaylistLevelType.SUBTITLE) {
- if (payload.byteLength) {
- const decryptData = frag.decryptdata;
- const decrypted = "stats" in data;
- if (decryptData == null || !decryptData.encrypted || decrypted) {
- const trackPlaylistMedia = this.tracks[frag.level];
- const vttCCs = this.vttCCs;
- if (!vttCCs[frag.cc]) {
- vttCCs[frag.cc] = {
- start: frag.start,
- prevCC: this.prevCC,
- new: true
- };
- this.prevCC = frag.cc;
- }
- if (trackPlaylistMedia && trackPlaylistMedia.textCodec === IMSC1_CODEC) {
- this._parseIMSC1(frag, payload);
- } else {
- this._parseVTTs(data);
- }
- }
- } else {
- this.hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
- success: false,
- frag,
- error: new Error("Empty subtitle payload")
- });
- }
- }
- }
- _parseIMSC1(frag, payload) {
- const hls = this.hls;
- parseIMSC1(payload, this.initPTS[frag.cc], (cues) => {
- this._appendCues(cues, frag.level);
- hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
- success: true,
- frag
- });
- }, (error) => {
- hls.logger.log(`Failed to parse IMSC1: ${error}`);
- hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
- success: false,
- frag,
- error
- });
- });
- }
- _parseVTTs(data) {
- var _frag$initSegment;
- const {
- frag,
- payload
- } = data;
- const {
- initPTS,
- unparsedVttFrags
- } = this;
- const maxAvCC = initPTS.length - 1;
- if (!initPTS[frag.cc] && maxAvCC === -1) {
- unparsedVttFrags.push(data);
- return;
- }
- const hls = this.hls;
- const payloadWebVTT = (_frag$initSegment = frag.initSegment) != null && _frag$initSegment.data ? appendUint8Array(frag.initSegment.data, new Uint8Array(payload)).buffer : payload;
- parseWebVTT(payloadWebVTT, this.initPTS[frag.cc], this.vttCCs, frag.cc, frag.start, (cues) => {
- this._appendCues(cues, frag.level);
- hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
- success: true,
- frag
- });
- }, (error) => {
- const missingInitPTS = error.message === "Missing initPTS for VTT MPEGTS";
- if (missingInitPTS) {
- unparsedVttFrags.push(data);
- } else {
- this._fallbackToIMSC1(frag, payload);
- }
- hls.logger.log(`Failed to parse VTT cue: ${error}`);
- if (missingInitPTS && maxAvCC > frag.cc) {
- return;
- }
- hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
- success: false,
- frag,
- error
- });
- });
- }
- _fallbackToIMSC1(frag, payload) {
- const trackPlaylistMedia = this.tracks[frag.level];
- if (!trackPlaylistMedia.textCodec) {
- parseIMSC1(payload, this.initPTS[frag.cc], () => {
- trackPlaylistMedia.textCodec = IMSC1_CODEC;
- this._parseIMSC1(frag, payload);
- }, () => {
- trackPlaylistMedia.textCodec = "wvtt";
- });
- }
- }
- _appendCues(cues, fragLevel) {
- const hls = this.hls;
- if (this.config.renderTextTracksNatively) {
- const textTrack = this.textTracks[fragLevel];
- if (!textTrack || textTrack.mode === "disabled") {
- return;
- }
- cues.forEach((cue) => addCueToTrack(textTrack, cue));
- } else {
- const currentTrack = this.tracks[fragLevel];
- if (!currentTrack) {
- return;
- }
- const track = currentTrack.default ? "default" : "subtitles" + fragLevel;
- hls.trigger(Events.CUES_PARSED, {
- type: "subtitles",
- cues,
- track
- });
- }
- }
- onFragDecrypted(event, data) {
- const {
- frag
- } = data;
- if (frag.type === PlaylistLevelType.SUBTITLE) {
- this.onFragLoaded(Events.FRAG_LOADED, data);
- }
- }
- onSubtitleTracksCleared() {
- this.tracks = [];
- this.captionsTracks = {};
- }
- onFragParsingUserdata(event, data) {
- if (!this.enabled || !this.config.enableCEA708Captions) {
- return;
- }
- const {
- frag,
- samples
- } = data;
- if (frag.type === PlaylistLevelType.MAIN && this.closedCaptionsForLevel(frag) === "NONE") {
- return;
- }
- for (let i = 0; i < samples.length; i++) {
- const ccBytes = samples[i].bytes;
- if (ccBytes) {
- if (!this.cea608Parser1) {
- this.initCea608Parsers();
- }
- const ccdatas = this.extractCea608Data(ccBytes);
- this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
- this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
- }
- }
- }
- onBufferFlushing(event, {
- startOffset,
- endOffset,
- endOffsetSubtitles,
- type
- }) {
- const {
- media
- } = this;
- if (!media || media.currentTime < endOffset) {
- return;
- }
- if (!type || type === "video") {
- const {
- captionsTracks
- } = this;
- Object.keys(captionsTracks).forEach((trackName) => removeCuesInRange(captionsTracks[trackName], startOffset, endOffset));
- }
- if (this.config.renderTextTracksNatively) {
- if (startOffset === 0 && endOffsetSubtitles !== void 0) {
- const {
- textTracks
- } = this;
- Object.keys(textTracks).forEach((trackName) => removeCuesInRange(textTracks[trackName], startOffset, endOffsetSubtitles));
- }
- }
- }
- extractCea608Data(byteArray) {
- const actualCCBytes = [[], []];
- const count = byteArray[0] & 31;
- let position = 2;
- for (let j = 0; j < count; j++) {
- const tmpByte = byteArray[position++];
- const ccbyte1 = 127 & byteArray[position++];
- const ccbyte2 = 127 & byteArray[position++];
- if (ccbyte1 === 0 && ccbyte2 === 0) {
- continue;
- }
- const ccValid = (4 & tmpByte) !== 0;
- if (ccValid) {
- const ccType = 3 & tmpByte;
- if (0 === ccType || 1 === ccType) {
- actualCCBytes[ccType].push(ccbyte1);
- actualCCBytes[ccType].push(ccbyte2);
- }
- }
- }
- return actualCCBytes;
- }
- };
- function captionsOrSubtitlesFromCharacteristics(track) {
- if (track.characteristics) {
- if (/transcribes-spoken-dialog/gi.test(track.characteristics) && /describes-music-and-sound/gi.test(track.characteristics)) {
- return "captions";
- }
- }
- return "subtitles";
- }
- function canReuseVttTextTrack(inUseTrack, manifestTrack) {
- return !!inUseTrack && inUseTrack.kind === captionsOrSubtitlesFromCharacteristics(manifestTrack) && subtitleTrackMatchesTextTrack(manifestTrack, inUseTrack);
- }
- function intersection(x1, x2, y1, y2) {
- return Math.min(x2, y2) - Math.max(x1, y1);
- }
- function newVTTCCs() {
- return {
- ccOffset: 0,
- presentationOffset: 0,
- 0: {
- start: 0,
- prevCC: -1,
- new: true
- }
- };
- }
- var WHITESPACE_CHAR = /\s/;
- var Cues = {
- newCue(track, startTime, endTime, captionScreen) {
- const result = [];
- let row;
- let cue;
- let indenting;
- let indent;
- let text;
- const Cue = self.VTTCue || self.TextTrackCue;
- for (let r = 0; r < captionScreen.rows.length; r++) {
- row = captionScreen.rows[r];
- indenting = true;
- indent = 0;
- text = "";
- if (!row.isEmpty()) {
- var _track$cues;
- for (let c = 0; c < row.chars.length; c++) {
- if (WHITESPACE_CHAR.test(row.chars[c].uchar) && indenting) {
- indent++;
- } else {
- text += row.chars[c].uchar;
- indenting = false;
- }
- }
- row.cueStartTime = startTime;
- if (startTime === endTime) {
- endTime += 1e-4;
- }
- if (indent >= 16) {
- indent--;
- } else {
- indent++;
- }
- const cueText = fixLineBreaks(text.trim());
- const id = generateCueId(startTime, endTime, cueText);
- if (!(track != null && (_track$cues = track.cues) != null && _track$cues.getCueById(id))) {
- cue = new Cue(startTime, endTime, cueText);
- cue.id = id;
- cue.line = r + 1;
- cue.align = "left";
- cue.position = 10 + Math.min(80, Math.floor(indent * 8 / 32) * 10);
- result.push(cue);
- }
- }
- }
- if (track && result.length) {
- result.sort((cueA, cueB) => {
- if (cueA.line === "auto" || cueB.line === "auto") {
- return 0;
- }
- if (cueA.line > 8 && cueB.line > 8) {
- return cueB.line - cueA.line;
- }
- return cueA.line - cueB.line;
- });
- result.forEach((cue2) => addCueToTrack(track, cue2));
- }
- return result;
- }
- };
- function fetchSupported() {
- if (
- // @ts-ignore
- self.fetch && self.AbortController && self.ReadableStream && self.Request
- ) {
- try {
- new self.ReadableStream({});
- return true;
- } catch (e) {
- }
- }
- return false;
- }
- var BYTERANGE = /(\d+)-(\d+)\/(\d+)/;
- var FetchLoader = class {
- constructor(config) {
- this.fetchSetup = void 0;
- this.requestTimeout = void 0;
- this.request = null;
- this.response = null;
- this.controller = void 0;
- this.context = null;
- this.config = null;
- this.callbacks = null;
- this.stats = void 0;
- this.loader = null;
- this.fetchSetup = config.fetchSetup || getRequest;
- this.controller = new self.AbortController();
- this.stats = new LoadStats();
- }
- destroy() {
- this.loader = this.callbacks = this.context = this.config = this.request = null;
- this.abortInternal();
- this.response = null;
- this.fetchSetup = this.controller = this.stats = null;
- }
- abortInternal() {
- if (this.controller && !this.stats.loading.end) {
- this.stats.aborted = true;
- this.controller.abort();
- }
- }
- abort() {
- var _this$callbacks;
- this.abortInternal();
- if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) {
- this.callbacks.onAbort(this.stats, this.context, this.response);
- }
- }
- load(context, config, callbacks) {
- const stats = this.stats;
- if (stats.loading.start) {
- throw new Error("Loader can only be used once.");
- }
- stats.loading.start = self.performance.now();
- const initParams = getRequestParameters(context, this.controller.signal);
- const isArrayBuffer = context.responseType === "arraybuffer";
- const LENGTH = isArrayBuffer ? "byteLength" : "length";
- const {
- maxTimeToFirstByteMs,
- maxLoadTimeMs
- } = config.loadPolicy;
- this.context = context;
- this.config = config;
- this.callbacks = callbacks;
- this.request = this.fetchSetup(context, initParams);
- self.clearTimeout(this.requestTimeout);
- config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs;
- this.requestTimeout = self.setTimeout(() => {
- if (this.callbacks) {
- this.abortInternal();
- this.callbacks.onTimeout(stats, context, this.response);
- }
- }, config.timeout);
- const fetchPromise = isPromise(this.request) ? this.request.then(self.fetch) : self.fetch(this.request);
- fetchPromise.then((response) => {
- var _this$callbacks2;
- this.response = this.loader = response;
- const first = Math.max(self.performance.now(), stats.loading.start);
- self.clearTimeout(this.requestTimeout);
- config.timeout = maxLoadTimeMs;
- this.requestTimeout = self.setTimeout(() => {
- if (this.callbacks) {
- this.abortInternal();
- this.callbacks.onTimeout(stats, context, this.response);
- }
- }, maxLoadTimeMs - (first - stats.loading.start));
- if (!response.ok) {
- const {
- status: status2,
- statusText
- } = response;
- throw new FetchError(statusText || "fetch, bad network response", status2, response);
- }
- stats.loading.first = first;
- stats.total = getContentLength(response.headers) || stats.total;
- const onProgress = (_this$callbacks2 = this.callbacks) == null ? void 0 : _this$callbacks2.onProgress;
- if (onProgress && isFiniteNumber(config.highWaterMark)) {
- return this.loadProgressively(response, stats, context, config.highWaterMark, onProgress);
- }
- if (isArrayBuffer) {
- return response.arrayBuffer();
- }
- if (context.responseType === "json") {
- return response.json();
- }
- return response.text();
- }).then((responseData) => {
- var _this$callbacks3, _this$callbacks4;
- const response = this.response;
- if (!response) {
- throw new Error("loader destroyed");
- }
- self.clearTimeout(this.requestTimeout);
- stats.loading.end = Math.max(self.performance.now(), stats.loading.first);
- const total = responseData[LENGTH];
- if (total) {
- stats.loaded = stats.total = total;
- }
- const loaderResponse = {
- url: response.url,
- data: responseData,
- code: response.status
- };
- const onProgress = (_this$callbacks3 = this.callbacks) == null ? void 0 : _this$callbacks3.onProgress;
- if (onProgress && !isFiniteNumber(config.highWaterMark)) {
- onProgress(stats, context, responseData, response);
- }
- (_this$callbacks4 = this.callbacks) == null || _this$callbacks4.onSuccess(loaderResponse, stats, context, response);
- }).catch((error) => {
- var _this$callbacks5;
- self.clearTimeout(this.requestTimeout);
- if (stats.aborted) {
- return;
- }
- const code = !error ? 0 : error.code || 0;
- const text = !error ? null : error.message;
- (_this$callbacks5 = this.callbacks) == null || _this$callbacks5.onError({
- code,
- text
- }, context, error ? error.details : null, stats);
- });
- }
- getCacheAge() {
- let result = null;
- if (this.response) {
- const ageHeader = this.response.headers.get("age");
- result = ageHeader ? parseFloat(ageHeader) : null;
- }
- return result;
- }
- getResponseHeader(name) {
- return this.response ? this.response.headers.get(name) : null;
- }
- loadProgressively(response, stats, context, highWaterMark = 0, onProgress) {
- const chunkCache = new ChunkCache();
- const reader = response.body.getReader();
- const pump = () => {
- return reader.read().then((data) => {
- if (data.done) {
- if (chunkCache.dataLength) {
- onProgress(stats, context, chunkCache.flush().buffer, response);
- }
- return Promise.resolve(new ArrayBuffer(0));
- }
- const chunk = data.value;
- const len = chunk.length;
- stats.loaded += len;
- if (len < highWaterMark || chunkCache.dataLength) {
- chunkCache.push(chunk);
- if (chunkCache.dataLength >= highWaterMark) {
- onProgress(stats, context, chunkCache.flush().buffer, response);
- }
- } else {
- onProgress(stats, context, chunk.buffer, response);
- }
- return pump();
- }).catch(() => {
- return Promise.reject();
- });
- };
- return pump();
- }
- };
- function getRequestParameters(context, signal) {
- const initParams = {
- method: "GET",
- mode: "cors",
- credentials: "same-origin",
- signal,
- headers: new self.Headers(_extends({}, context.headers))
- };
- if (context.rangeEnd) {
- initParams.headers.set("Range", "bytes=" + context.rangeStart + "-" + String(context.rangeEnd - 1));
- }
- return initParams;
- }
- function getByteRangeLength(byteRangeHeader) {
- const result = BYTERANGE.exec(byteRangeHeader);
- if (result) {
- return parseInt(result[2]) - parseInt(result[1]) + 1;
- }
- }
- function getContentLength(headers) {
- const contentRange = headers.get("Content-Range");
- if (contentRange) {
- const byteRangeLength = getByteRangeLength(contentRange);
- if (isFiniteNumber(byteRangeLength)) {
- return byteRangeLength;
- }
- }
- const contentLength = headers.get("Content-Length");
- if (contentLength) {
- return parseInt(contentLength);
- }
- }
- function getRequest(context, initParams) {
- return new self.Request(context.url, initParams);
- }
- var FetchError = class extends Error {
- constructor(message, code, details) {
- super(message);
- this.code = void 0;
- this.details = void 0;
- this.code = code;
- this.details = details;
- }
- };
- var AGE_HEADER_LINE_REGEX = /^age:\s*[\d.]+\s*$/im;
- var XhrLoader = class {
- constructor(config) {
- this.xhrSetup = void 0;
- this.requestTimeout = void 0;
- this.retryTimeout = void 0;
- this.retryDelay = void 0;
- this.config = null;
- this.callbacks = null;
- this.context = null;
- this.loader = null;
- this.stats = void 0;
- this.xhrSetup = config ? config.xhrSetup || null : null;
- this.stats = new LoadStats();
- this.retryDelay = 0;
- }
- destroy() {
- this.callbacks = null;
- this.abortInternal();
- this.loader = null;
- this.config = null;
- this.context = null;
- this.xhrSetup = null;
- }
- abortInternal() {
- const loader = this.loader;
- self.clearTimeout(this.requestTimeout);
- self.clearTimeout(this.retryTimeout);
- if (loader) {
- loader.onreadystatechange = null;
- loader.onprogress = null;
- if (loader.readyState !== 4) {
- this.stats.aborted = true;
- loader.abort();
- }
- }
- }
- abort() {
- var _this$callbacks;
- this.abortInternal();
- if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) {
- this.callbacks.onAbort(this.stats, this.context, this.loader);
- }
- }
- load(context, config, callbacks) {
- if (this.stats.loading.start) {
- throw new Error("Loader can only be used once.");
- }
- this.stats.loading.start = self.performance.now();
- this.context = context;
- this.config = config;
- this.callbacks = callbacks;
- this.loadInternal();
- }
- loadInternal() {
- const {
- config,
- context
- } = this;
- if (!config || !context) {
- return;
- }
- const xhr = this.loader = new self.XMLHttpRequest();
- const stats = this.stats;
- stats.loading.first = 0;
- stats.loaded = 0;
- stats.aborted = false;
- const xhrSetup = this.xhrSetup;
- if (xhrSetup) {
- Promise.resolve().then(() => {
- if (this.loader !== xhr || this.stats.aborted)
- return;
- return xhrSetup(xhr, context.url);
- }).catch((error) => {
- if (this.loader !== xhr || this.stats.aborted)
- return;
- xhr.open("GET", context.url, true);
- return xhrSetup(xhr, context.url);
- }).then(() => {
- if (this.loader !== xhr || this.stats.aborted)
- return;
- this.openAndSendXhr(xhr, context, config);
- }).catch((error) => {
- var _this$callbacks2;
- (_this$callbacks2 = this.callbacks) == null || _this$callbacks2.onError({
- code: xhr.status,
- text: error.message
- }, context, xhr, stats);
- return;
- });
- } else {
- this.openAndSendXhr(xhr, context, config);
- }
- }
- openAndSendXhr(xhr, context, config) {
- if (!xhr.readyState) {
- xhr.open("GET", context.url, true);
- }
- const headers = context.headers;
- const {
- maxTimeToFirstByteMs,
- maxLoadTimeMs
- } = config.loadPolicy;
- if (headers) {
- for (const header in headers) {
- xhr.setRequestHeader(header, headers[header]);
- }
- }
- if (context.rangeEnd) {
- xhr.setRequestHeader("Range", "bytes=" + context.rangeStart + "-" + (context.rangeEnd - 1));
- }
- xhr.onreadystatechange = this.readystatechange.bind(this);
- xhr.onprogress = this.loadprogress.bind(this);
- xhr.responseType = context.responseType;
- self.clearTimeout(this.requestTimeout);
- config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs;
- this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.timeout);
- xhr.send();
- }
- readystatechange() {
- const {
- context,
- loader: xhr,
- stats
- } = this;
- if (!context || !xhr) {
- return;
- }
- const readyState = xhr.readyState;
- const config = this.config;
- if (stats.aborted) {
- return;
- }
- if (readyState >= 2) {
- if (stats.loading.first === 0) {
- stats.loading.first = Math.max(self.performance.now(), stats.loading.start);
- if (config.timeout !== config.loadPolicy.maxLoadTimeMs) {
- self.clearTimeout(this.requestTimeout);
- config.timeout = config.loadPolicy.maxLoadTimeMs;
- this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.loadPolicy.maxLoadTimeMs - (stats.loading.first - stats.loading.start));
- }
- }
- if (readyState === 4) {
- self.clearTimeout(this.requestTimeout);
- xhr.onreadystatechange = null;
- xhr.onprogress = null;
- const status2 = xhr.status;
- const useResponseText = xhr.responseType === "text" ? xhr.responseText : null;
- if (status2 >= 200 && status2 < 300) {
- const data = useResponseText != null ? useResponseText : xhr.response;
- if (data != null) {
- var _this$callbacks3, _this$callbacks4;
- stats.loading.end = Math.max(self.performance.now(), stats.loading.first);
- const len = xhr.responseType === "arraybuffer" ? data.byteLength : data.length;
- stats.loaded = stats.total = len;
- stats.bwEstimate = stats.total * 8e3 / (stats.loading.end - stats.loading.first);
- const onProgress = (_this$callbacks3 = this.callbacks) == null ? void 0 : _this$callbacks3.onProgress;
- if (onProgress) {
- onProgress(stats, context, data, xhr);
- }
- const _response = {
- url: xhr.responseURL,
- data,
- code: status2
- };
- (_this$callbacks4 = this.callbacks) == null || _this$callbacks4.onSuccess(_response, stats, context, xhr);
- return;
- }
- }
- const retryConfig = config.loadPolicy.errorRetry;
- const retryCount = stats.retry;
- const response = {
- url: context.url,
- data: void 0,
- code: status2
- };
- if (shouldRetry(retryConfig, retryCount, false, response)) {
- this.retry(retryConfig);
- } else {
- var _this$callbacks5;
- logger.error(`${status2} while loading ${context.url}`);
- (_this$callbacks5 = this.callbacks) == null || _this$callbacks5.onError({
- code: status2,
- text: xhr.statusText
- }, context, xhr, stats);
- }
- }
- }
- }
- loadtimeout() {
- if (!this.config)
- return;
- const retryConfig = this.config.loadPolicy.timeoutRetry;
- const retryCount = this.stats.retry;
- if (shouldRetry(retryConfig, retryCount, true)) {
- this.retry(retryConfig);
- } else {
- var _this$context;
- logger.warn(`timeout while loading ${(_this$context = this.context) == null ? void 0 : _this$context.url}`);
- const callbacks = this.callbacks;
- if (callbacks) {
- this.abortInternal();
- callbacks.onTimeout(this.stats, this.context, this.loader);
- }
- }
- }
- retry(retryConfig) {
- const {
- context,
- stats
- } = this;
- this.retryDelay = getRetryDelay(retryConfig, stats.retry);
- stats.retry++;
- logger.warn(`${status ? "HTTP Status " + status : "Timeout"} while loading ${context == null ? void 0 : context.url}, retrying ${stats.retry}/${retryConfig.maxNumRetry} in ${this.retryDelay}ms`);
- this.abortInternal();
- this.loader = null;
- self.clearTimeout(this.retryTimeout);
- this.retryTimeout = self.setTimeout(this.loadInternal.bind(this), this.retryDelay);
- }
- loadprogress(event) {
- const stats = this.stats;
- stats.loaded = event.loaded;
- if (event.lengthComputable) {
- stats.total = event.total;
- }
- }
- getCacheAge() {
- let result = null;
- if (this.loader && AGE_HEADER_LINE_REGEX.test(this.loader.getAllResponseHeaders())) {
- const ageHeader = this.loader.getResponseHeader("age");
- result = ageHeader ? parseFloat(ageHeader) : null;
- }
- return result;
- }
- getResponseHeader(name) {
- if (this.loader && new RegExp(`^${name}:\\s*[\\d.]+\\s*$`, "im").test(this.loader.getAllResponseHeaders())) {
- return this.loader.getResponseHeader(name);
- }
- return null;
- }
- };
- var defaultLoadPolicy = {
- maxTimeToFirstByteMs: 8e3,
- maxLoadTimeMs: 2e4,
- timeoutRetry: null,
- errorRetry: null
- };
- var hlsDefaultConfig = _objectSpread2(_objectSpread2({
- autoStartLoad: true,
- // used by stream-controller
- startPosition: -1,
- // used by stream-controller
- defaultAudioCodec: void 0,
- // used by stream-controller
- debug: false,
- // used by logger
- capLevelOnFPSDrop: false,
- // used by fps-controller
- capLevelToPlayerSize: false,
- // used by cap-level-controller
- ignoreDevicePixelRatio: false,
- // used by cap-level-controller
- maxDevicePixelRatio: Number.POSITIVE_INFINITY,
- // used by cap-level-controller
- preferManagedMediaSource: true,
- initialLiveManifestSize: 1,
- // used by stream-controller
- maxBufferLength: 30,
- // used by stream-controller
- backBufferLength: Infinity,
- // used by buffer-controller
- frontBufferFlushThreshold: Infinity,
- startOnSegmentBoundary: false,
- // used by stream-controller
- maxBufferSize: 60 * 1e3 * 1e3,
- // used by stream-controller
- maxFragLookUpTolerance: 0.25,
- // used by stream-controller
- maxBufferHole: 0.1,
- // used by stream-controller and gap-controller
- detectStallWithCurrentTimeMs: 1250,
- // used by gap-controller
- highBufferWatchdogPeriod: 2,
- // used by gap-controller
- nudgeOffset: 0.1,
- // used by gap-controller
- nudgeMaxRetry: 3,
- // used by gap-controller
- nudgeOnVideoHole: true,
- // used by gap-controller
- liveSyncMode: "edge",
- // used by stream-controller
- liveSyncDurationCount: 3,
- // used by latency-controller
- liveSyncOnStallIncrease: 1,
- // used by latency-controller
- liveMaxLatencyDurationCount: Infinity,
- // used by latency-controller
- liveSyncDuration: void 0,
- // used by latency-controller
- liveMaxLatencyDuration: void 0,
- // used by latency-controller
- maxLiveSyncPlaybackRate: 1,
- // used by latency-controller
- liveDurationInfinity: false,
- // used by buffer-controller
- /**
- * @deprecated use backBufferLength
- */
- liveBackBufferLength: null,
- // used by buffer-controller
- maxMaxBufferLength: 600,
- // used by stream-controller
- enableWorker: true,
- // used by transmuxer
- workerPath: null,
- // used by transmuxer
- enableSoftwareAES: true,
- // used by decrypter
- startLevel: void 0,
- // used by level-controller
- startFragPrefetch: false,
- // used by stream-controller
- fpsDroppedMonitoringPeriod: 5e3,
- // used by fps-controller
- fpsDroppedMonitoringThreshold: 0.2,
- // used by fps-controller
- appendErrorMaxRetry: 3,
- // used by buffer-controller
- ignorePlaylistParsingErrors: false,
- loader: XhrLoader,
- // loader: FetchLoader,
- fLoader: void 0,
- // used by fragment-loader
- pLoader: void 0,
- // used by playlist-loader
- xhrSetup: void 0,
- // used by xhr-loader
- licenseXhrSetup: void 0,
- // used by eme-controller
- licenseResponseCallback: void 0,
- // used by eme-controller
- abrController: AbrController,
- bufferController: BufferController,
- capLevelController: CapLevelController,
- errorController: ErrorController,
- fpsController: FPSController,
- stretchShortVideoTrack: false,
- // used by mp4-remuxer
- maxAudioFramesDrift: 1,
- // used by mp4-remuxer
- forceKeyFrameOnDiscontinuity: true,
- // used by ts-demuxer
- abrEwmaFastLive: 3,
- // used by abr-controller
- abrEwmaSlowLive: 9,
- // used by abr-controller
- abrEwmaFastVoD: 3,
- // used by abr-controller
- abrEwmaSlowVoD: 9,
- // used by abr-controller
- abrEwmaDefaultEstimate: 5e5,
- // 500 kbps // used by abr-controller
- abrEwmaDefaultEstimateMax: 5e6,
- // 5 mbps
- abrBandWidthFactor: 0.95,
- // used by abr-controller
- abrBandWidthUpFactor: 0.7,
- // used by abr-controller
- abrMaxWithRealBitrate: false,
- // used by abr-controller
- maxStarvationDelay: 4,
- // used by abr-controller
- maxLoadingDelay: 4,
- // used by abr-controller
- minAutoBitrate: 0,
- // used by hls
- emeEnabled: false,
- // used by eme-controller
- widevineLicenseUrl: void 0,
- // used by eme-controller
- drmSystems: {},
- // used by eme-controller
- drmSystemOptions: {},
- // used by eme-controller
- requestMediaKeySystemAccessFunc: requestMediaKeySystemAccess,
- // used by eme-controller
- requireKeySystemAccessOnStart: false,
- // used by eme-controller
- testBandwidth: true,
- progressive: false,
- lowLatencyMode: true,
- cmcd: void 0,
- enableDateRangeMetadataCues: true,
- enableEmsgMetadataCues: true,
- enableEmsgKLVMetadata: false,
- enableID3MetadataCues: true,
- enableInterstitialPlayback: true,
- interstitialAppendInPlace: true,
- interstitialLiveLookAhead: 10,
- useMediaCapabilities: true,
- preserveManualLevelOnError: false,
- certLoadPolicy: {
- default: defaultLoadPolicy
- },
- keyLoadPolicy: {
- default: {
- maxTimeToFirstByteMs: 8e3,
- maxLoadTimeMs: 2e4,
- timeoutRetry: {
- maxNumRetry: 1,
- retryDelayMs: 1e3,
- maxRetryDelayMs: 2e4,
- backoff: "linear"
- },
- errorRetry: {
- maxNumRetry: 8,
- retryDelayMs: 1e3,
- maxRetryDelayMs: 2e4,
- backoff: "linear"
- }
- }
- },
- manifestLoadPolicy: {
- default: {
- maxTimeToFirstByteMs: Infinity,
- maxLoadTimeMs: 2e4,
- timeoutRetry: {
- maxNumRetry: 2,
- retryDelayMs: 0,
- maxRetryDelayMs: 0
- },
- errorRetry: {
- maxNumRetry: 1,
- retryDelayMs: 1e3,
- maxRetryDelayMs: 8e3
- }
- }
- },
- playlistLoadPolicy: {
- default: {
- maxTimeToFirstByteMs: 1e4,
- maxLoadTimeMs: 2e4,
- timeoutRetry: {
- maxNumRetry: 2,
- retryDelayMs: 0,
- maxRetryDelayMs: 0
- },
- errorRetry: {
- maxNumRetry: 2,
- retryDelayMs: 1e3,
- maxRetryDelayMs: 8e3
- }
- }
- },
- fragLoadPolicy: {
- default: {
- maxTimeToFirstByteMs: 1e4,
- maxLoadTimeMs: 12e4,
- timeoutRetry: {
- maxNumRetry: 4,
- retryDelayMs: 0,
- maxRetryDelayMs: 0
- },
- errorRetry: {
- maxNumRetry: 6,
- retryDelayMs: 1e3,
- maxRetryDelayMs: 8e3
- }
- }
- },
- steeringManifestLoadPolicy: {
- default: {
- maxTimeToFirstByteMs: 1e4,
- maxLoadTimeMs: 2e4,
- timeoutRetry: {
- maxNumRetry: 2,
- retryDelayMs: 0,
- maxRetryDelayMs: 0
- },
- errorRetry: {
- maxNumRetry: 1,
- retryDelayMs: 1e3,
- maxRetryDelayMs: 8e3
- }
- }
- },
- interstitialAssetListLoadPolicy: {
- default: {
- maxTimeToFirstByteMs: 1e4,
- maxLoadTimeMs: 3e4,
- timeoutRetry: {
- maxNumRetry: 0,
- retryDelayMs: 0,
- maxRetryDelayMs: 0
- },
- errorRetry: {
- maxNumRetry: 0,
- retryDelayMs: 1e3,
- maxRetryDelayMs: 8e3
- }
- }
- },
- // These default settings are deprecated in favor of the above policies
- // and are maintained for backwards compatibility
- manifestLoadingTimeOut: 1e4,
- manifestLoadingMaxRetry: 1,
- manifestLoadingRetryDelay: 1e3,
- manifestLoadingMaxRetryTimeout: 64e3,
- levelLoadingTimeOut: 1e4,
- levelLoadingMaxRetry: 4,
- levelLoadingRetryDelay: 1e3,
- levelLoadingMaxRetryTimeout: 64e3,
- fragLoadingTimeOut: 2e4,
- fragLoadingMaxRetry: 6,
- fragLoadingRetryDelay: 1e3,
- fragLoadingMaxRetryTimeout: 64e3
- }, timelineConfig()), {}, {
- subtitleStreamController: SubtitleStreamController,
- subtitleTrackController: SubtitleTrackController,
- timelineController: TimelineController,
- audioStreamController: AudioStreamController,
- audioTrackController: AudioTrackController,
- emeController: EMEController,
- cmcdController: CMCDController,
- contentSteeringController: ContentSteeringController,
- interstitialsController: InterstitialsController
- });
- function timelineConfig() {
- return {
- cueHandler: Cues,
- // used by timeline-controller
- enableWebVTT: true,
- // used by timeline-controller
- enableIMSC1: true,
- // used by timeline-controller
- enableCEA708Captions: true,
- // used by timeline-controller
- captionsTextTrack1Label: "English",
- // used by timeline-controller
- captionsTextTrack1LanguageCode: "en",
- // used by timeline-controller
- captionsTextTrack2Label: "Spanish",
- // used by timeline-controller
- captionsTextTrack2LanguageCode: "es",
- // used by timeline-controller
- captionsTextTrack3Label: "Unknown CC",
- // used by timeline-controller
- captionsTextTrack3LanguageCode: "",
- // used by timeline-controller
- captionsTextTrack4Label: "Unknown CC",
- // used by timeline-controller
- captionsTextTrack4LanguageCode: "",
- // used by timeline-controller
- renderTextTracksNatively: true
- };
- }
- function mergeConfig(defaultConfig, userConfig, logger2) {
- if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
- throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
- }
- if (userConfig.liveMaxLatencyDurationCount !== void 0 && (userConfig.liveSyncDurationCount === void 0 || userConfig.liveMaxLatencyDurationCount <= userConfig.liveSyncDurationCount)) {
- throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"');
- }
- if (userConfig.liveMaxLatencyDuration !== void 0 && (userConfig.liveSyncDuration === void 0 || userConfig.liveMaxLatencyDuration <= userConfig.liveSyncDuration)) {
- throw new Error('Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"');
- }
- const defaultsCopy = deepCpy(defaultConfig);
- const deprecatedSettingTypes = ["manifest", "level", "frag"];
- const deprecatedSettings = ["TimeOut", "MaxRetry", "RetryDelay", "MaxRetryTimeout"];
- deprecatedSettingTypes.forEach((type) => {
- const policyName = `${type === "level" ? "playlist" : type}LoadPolicy`;
- const policyNotSet = userConfig[policyName] === void 0;
- const report = [];
- deprecatedSettings.forEach((setting) => {
- const deprecatedSetting = `${type}Loading${setting}`;
- const value = userConfig[deprecatedSetting];
- if (value !== void 0 && policyNotSet) {
- report.push(deprecatedSetting);
- const settings = defaultsCopy[policyName].default;
- userConfig[policyName] = {
- default: settings
- };
- switch (setting) {
- case "TimeOut":
- settings.maxLoadTimeMs = value;
- settings.maxTimeToFirstByteMs = value;
- break;
- case "MaxRetry":
- settings.errorRetry.maxNumRetry = value;
- settings.timeoutRetry.maxNumRetry = value;
- break;
- case "RetryDelay":
- settings.errorRetry.retryDelayMs = value;
- settings.timeoutRetry.retryDelayMs = value;
- break;
- case "MaxRetryTimeout":
- settings.errorRetry.maxRetryDelayMs = value;
- settings.timeoutRetry.maxRetryDelayMs = value;
- break;
- }
- }
- });
- if (report.length) {
- logger2.warn(`hls.js config: "${report.join('", "')}" setting(s) are deprecated, use "${policyName}": ${stringify(userConfig[policyName])}`);
- }
- });
- return _objectSpread2(_objectSpread2({}, defaultsCopy), userConfig);
- }
- function deepCpy(obj) {
- if (obj && typeof obj === "object") {
- if (Array.isArray(obj)) {
- return obj.map(deepCpy);
- }
- return Object.keys(obj).reduce((result, key) => {
- result[key] = deepCpy(obj[key]);
- return result;
- }, {});
- }
- return obj;
- }
- function enableStreamingMode(config, logger2) {
- const currentLoader = config.loader;
- if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
- logger2.log("[config]: Custom loader detected, cannot enable progressive streaming");
- config.progressive = false;
- } else {
- const canStreamProgressively = fetchSupported();
- if (canStreamProgressively) {
- config.loader = FetchLoader;
- config.progressive = true;
- config.enableSoftwareAES = true;
- logger2.log("[config]: Progressive streaming enabled, using FetchLoader");
- }
- }
- }
- var MAX_START_GAP_JUMP = 2;
- var SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
- var SKIP_BUFFER_RANGE_START = 0.05;
- var TICK_INTERVAL$1 = 100;
- var GapController = class extends TaskLoop {
- constructor(hls, fragmentTracker) {
- super("gap-controller", hls.logger);
- this.hls = void 0;
- this.fragmentTracker = void 0;
- this.media = null;
- this.mediaSource = void 0;
- this.nudgeRetry = 0;
- this.stallReported = false;
- this.stalled = null;
- this.moved = false;
- this.seeking = false;
- this.buffered = {};
- this.lastCurrentTime = 0;
- this.ended = 0;
- this.waiting = 0;
- this.onMediaPlaying = () => {
- this.ended = 0;
- this.waiting = 0;
- };
- this.onMediaWaiting = () => {
- var _this$media;
- if ((_this$media = this.media) != null && _this$media.seeking) {
- return;
- }
- this.waiting = self.performance.now();
- this.tick();
- };
- this.onMediaEnded = () => {
- if (this.hls) {
- var _this$media2;
- this.ended = ((_this$media2 = this.media) == null ? void 0 : _this$media2.currentTime) || 1;
- this.hls.trigger(Events.MEDIA_ENDED, {
- stalled: false
- });
- }
- };
- this.hls = hls;
- this.fragmentTracker = fragmentTracker;
- this.registerListeners();
- }
- registerListeners() {
- const {
- hls
- } = this;
- if (hls) {
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
- }
- }
- unregisterListeners() {
- const {
- hls
- } = this;
- if (hls) {
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
- }
- }
- destroy() {
- super.destroy();
- this.unregisterListeners();
- this.media = this.hls = this.fragmentTracker = null;
- this.mediaSource = void 0;
- }
- onMediaAttached(event, data) {
- this.setInterval(TICK_INTERVAL$1);
- this.mediaSource = data.mediaSource;
- const media = this.media = data.media;
- addEventListener(media, "playing", this.onMediaPlaying);
- addEventListener(media, "waiting", this.onMediaWaiting);
- addEventListener(media, "ended", this.onMediaEnded);
- }
- onMediaDetaching(event, data) {
- this.clearInterval();
- const {
- media
- } = this;
- if (media) {
- removeEventListener(media, "playing", this.onMediaPlaying);
- removeEventListener(media, "waiting", this.onMediaWaiting);
- removeEventListener(media, "ended", this.onMediaEnded);
- this.media = null;
- }
- this.mediaSource = void 0;
- }
- onBufferAppended(event, data) {
- this.buffered = data.timeRanges;
- }
- get hasBuffered() {
- return Object.keys(this.buffered).length > 0;
- }
- tick() {
- var _this$media3;
- if (!((_this$media3 = this.media) != null && _this$media3.readyState) || !this.hasBuffered) {
- return;
- }
- const currentTime = this.media.currentTime;
- this.poll(currentTime, this.lastCurrentTime);
- this.lastCurrentTime = currentTime;
- }
- /**
- * Checks if the playhead is stuck within a gap, and if so, attempts to free it.
- * A gap is an unbuffered range between two buffered ranges (or the start and the first buffered range).
- *
- * @param lastCurrentTime - Previously read playhead position
- */
- poll(currentTime, lastCurrentTime) {
- var _this$hls, _this$hls2;
- const config = (_this$hls = this.hls) == null ? void 0 : _this$hls.config;
- if (!config) {
- return;
- }
- const media = this.media;
- if (!media) {
- return;
- }
- const {
- seeking
- } = media;
- const seeked = this.seeking && !seeking;
- const beginSeek = !this.seeking && seeking;
- const pausedEndedOrHalted = media.paused && !seeking || media.ended || media.playbackRate === 0;
- this.seeking = seeking;
- if (currentTime !== lastCurrentTime) {
- if (lastCurrentTime) {
- this.ended = 0;
- }
- this.moved = true;
- if (!seeking) {
- this.nudgeRetry = 0;
- if (config.nudgeOnVideoHole && !pausedEndedOrHalted && currentTime > lastCurrentTime) {
- this.nudgeOnVideoHole(currentTime, lastCurrentTime);
- }
- }
- if (this.waiting === 0) {
- this.stallResolved(currentTime);
- }
- return;
- }
- if (beginSeek || seeked) {
- if (seeked) {
- this.stallResolved(currentTime);
- }
- return;
- }
- if (pausedEndedOrHalted) {
- this.nudgeRetry = 0;
- this.stallResolved(currentTime);
- if (!this.ended && media.ended && this.hls) {
- this.ended = currentTime || 1;
- this.hls.trigger(Events.MEDIA_ENDED, {
- stalled: false
- });
- }
- return;
- }
- if (!BufferHelper.getBuffered(media).length) {
- this.nudgeRetry = 0;
- return;
- }
- const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
- const nextStart = bufferInfo.nextStart || 0;
- const fragmentTracker = this.fragmentTracker;
- if (seeking && fragmentTracker && this.hls) {
- const inFlightDependency = getInFlightDependency(this.hls.inFlightFragments, currentTime);
- const hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
- const noBufferHole = !nextStart || inFlightDependency || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime);
- if (hasEnoughBuffer || noBufferHole) {
- return;
- }
- this.moved = false;
- }
- const levelDetails = (_this$hls2 = this.hls) == null ? void 0 : _this$hls2.latestLevelDetails;
- if (!this.moved && this.stalled !== null && fragmentTracker) {
- const isBuffered = bufferInfo.len > 0;
- if (!isBuffered && !nextStart) {
- return;
- }
- const startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime;
- const isLive = !!(levelDetails != null && levelDetails.live);
- const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
- const appended = appendedFragAtPosition(currentTime, fragmentTracker);
- if (startJump > 0 && (startJump <= maxStartGapJump || appended)) {
- if (!media.paused) {
- this._trySkipBufferHole(appended);
- }
- return;
- }
- }
- const detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
- const tnow = self.performance.now();
- const tWaiting = this.waiting;
- let stalled = this.stalled;
- if (stalled === null) {
- if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
- stalled = this.stalled = tWaiting;
- } else {
- this.stalled = tnow;
- return;
- }
- }
- const stalledDuration = tnow - stalled;
- if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) {
- var _this$mediaSource;
- if (((_this$mediaSource = this.mediaSource) == null ? void 0 : _this$mediaSource.readyState) === "ended" && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
- if (this.ended) {
- return;
- }
- this.ended = currentTime || 1;
- this.hls.trigger(Events.MEDIA_ENDED, {
- stalled: true
- });
- return;
- }
- this._reportStall(bufferInfo);
- if (!this.media || !this.hls) {
- return;
- }
- }
- const bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
- this._tryFixBufferStall(bufferedWithHoles, stalledDuration, currentTime);
- }
- stallResolved(currentTime) {
- const stalled = this.stalled;
- if (stalled && this.hls) {
- this.stalled = null;
- if (this.stallReported) {
- const stalledDuration = self.performance.now() - stalled;
- this.log(`playback not stuck anymore @${currentTime}, after ${Math.round(stalledDuration)}ms`);
- this.stallReported = false;
- this.waiting = 0;
- this.hls.trigger(Events.STALL_RESOLVED, {});
- }
- }
- }
- nudgeOnVideoHole(currentTime, lastCurrentTime) {
- var _this$buffered$audio;
- const videoSourceBuffered = this.buffered.video;
- if (this.hls && this.media && this.fragmentTracker && (_this$buffered$audio = this.buffered.audio) != null && _this$buffered$audio.length && videoSourceBuffered && videoSourceBuffered.length > 1 && currentTime > videoSourceBuffered.end(0)) {
- const audioBufferInfo = BufferHelper.bufferedInfo(BufferHelper.timeRangesToArray(this.buffered.audio), currentTime, 0);
- if (audioBufferInfo.len > 1 && lastCurrentTime >= audioBufferInfo.start) {
- const videoTimes = BufferHelper.timeRangesToArray(videoSourceBuffered);
- const lastBufferedIndex = BufferHelper.bufferedInfo(videoTimes, lastCurrentTime, 0).bufferedIndex;
- if (lastBufferedIndex > -1 && lastBufferedIndex < videoTimes.length - 1) {
- const bufferedIndex = BufferHelper.bufferedInfo(videoTimes, currentTime, 0).bufferedIndex;
- const holeStart = videoTimes[lastBufferedIndex].end;
- const holeEnd = videoTimes[lastBufferedIndex + 1].start;
- if ((bufferedIndex === -1 || bufferedIndex > lastBufferedIndex) && holeEnd - holeStart < 1 && // `maxBufferHole` may be too small and setting it to 0 should not disable this feature
- currentTime - holeStart < 2) {
- const error = new Error(`nudging playhead to flush pipeline after video hole. currentTime: ${currentTime} hole: ${holeStart} -> ${holeEnd} buffered index: ${bufferedIndex}`);
- this.warn(error.message);
- this.media.currentTime += 1e-6;
- let frag = appendedFragAtPosition(currentTime, this.fragmentTracker);
- if (frag && "fragment" in frag) {
- frag = frag.fragment;
- } else if (!frag) {
- frag = void 0;
- }
- const bufferInfo = BufferHelper.bufferInfo(this.media, currentTime, 0);
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
- fatal: false,
- error,
- reason: error.message,
- frag,
- buffer: bufferInfo.len,
- bufferInfo
- });
- }
- }
- }
- }
- }
- /**
- * Detects and attempts to fix known buffer stalling issues.
- * @param bufferInfo - The properties of the current buffer.
- * @param stalledDurationMs - The amount of time Hls.js has been stalling for.
- * @private
- */
- _tryFixBufferStall(bufferInfo, stalledDurationMs, currentTime) {
- var _this$hls3, _this$hls4;
- const {
- fragmentTracker,
- media
- } = this;
- const config = (_this$hls3 = this.hls) == null ? void 0 : _this$hls3.config;
- if (!media || !fragmentTracker || !config) {
- return;
- }
- const levelDetails = (_this$hls4 = this.hls) == null ? void 0 : _this$hls4.latestLevelDetails;
- const appended = appendedFragAtPosition(currentTime, fragmentTracker);
- if (appended || levelDetails != null && levelDetails.live && currentTime < levelDetails.fragmentStart) {
- const targetTime = this._trySkipBufferHole(appended);
- if (targetTime || !this.media) {
- return;
- }
- }
- const bufferedRanges = bufferInfo.buffered;
- const adjacentTraversal = this.adjacentTraversal(bufferInfo, currentTime);
- if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && (bufferInfo.nextStart - currentTime < config.maxBufferHole || adjacentTraversal)) && (stalledDurationMs > config.highBufferWatchdogPeriod * 1e3 || this.waiting)) {
- this.warn("Trying to nudge playhead over buffer-hole");
- this._tryNudgeBuffer(bufferInfo);
- }
- }
- adjacentTraversal(bufferInfo, currentTime) {
- const fragmentTracker = this.fragmentTracker;
- const nextStart = bufferInfo.nextStart;
- if (fragmentTracker && nextStart) {
- const current = fragmentTracker.getFragAtPos(currentTime, PlaylistLevelType.MAIN);
- const next = fragmentTracker.getFragAtPos(nextStart, PlaylistLevelType.MAIN);
- if (current && next) {
- return next.sn - current.sn < 2;
- }
- }
- return false;
- }
- /**
- * Triggers a BUFFER_STALLED_ERROR event, but only once per stall period.
- * @param bufferLen - The playhead distance from the end of the current buffer segment.
- * @private
- */
- _reportStall(bufferInfo) {
- const {
- hls,
- media,
- stallReported,
- stalled
- } = this;
- if (!stallReported && stalled !== null && media && hls) {
- this.stallReported = true;
- const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${stringify(bufferInfo)})`);
- this.warn(error.message);
- hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.BUFFER_STALLED_ERROR,
- fatal: false,
- error,
- buffer: bufferInfo.len,
- bufferInfo,
- stalled: {
- start: stalled
- }
- });
- }
- }
- /**
- * Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments
- * @param appended - The fragment or part found at the current time (where playback is stalling).
- * @private
- */
- _trySkipBufferHole(appended) {
- var _this$hls5;
- const {
- fragmentTracker,
- media
- } = this;
- const config = (_this$hls5 = this.hls) == null ? void 0 : _this$hls5.config;
- if (!media || !fragmentTracker || !config) {
- return 0;
- }
- const currentTime = media.currentTime;
- const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
- const startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart;
- if (startTime && this.hls) {
- const bufferStarved = bufferInfo.len <= config.maxBufferHole;
- const waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3;
- const gapLength = startTime - currentTime;
- if (gapLength > 0 && (bufferStarved || waiting)) {
- if (gapLength > config.maxBufferHole) {
- let startGap = false;
- if (currentTime === 0) {
- const startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
- if (startFrag && startTime < startFrag.end) {
- startGap = true;
- }
- }
- if (!startGap && appended) {
- var _this$hls$loadLevelOb;
- if (!((_this$hls$loadLevelOb = this.hls.loadLevelObj) != null && _this$hls$loadLevelOb.details)) {
- return 0;
- }
- const inFlightDependency = getInFlightDependency(this.hls.inFlightFragments, startTime);
- if (inFlightDependency) {
- return 0;
- }
- let moreToLoad = false;
- let pos = appended.end;
- while (pos < startTime) {
- const provisioned = appendedFragAtPosition(pos, fragmentTracker);
- if (provisioned) {
- pos += provisioned.duration;
- } else {
- moreToLoad = true;
- break;
- }
- }
- if (moreToLoad) {
- return 0;
- }
- }
- }
- const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
- this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
- this.moved = true;
- media.currentTime = targetTime;
- if (!(appended != null && appended.gap)) {
- const error = new Error(`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`);
- const errorData = {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
- fatal: false,
- error,
- reason: error.message,
- buffer: bufferInfo.len,
- bufferInfo
- };
- if (appended) {
- if ("fragment" in appended) {
- errorData.part = appended;
- } else {
- errorData.frag = appended;
- }
- }
- this.hls.trigger(Events.ERROR, errorData);
- }
- return targetTime;
- }
- }
- return 0;
- }
- /**
- * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
- * @private
- */
- _tryNudgeBuffer(bufferInfo) {
- const {
- hls,
- media,
- nudgeRetry
- } = this;
- const config = hls == null ? void 0 : hls.config;
- if (!media || !config) {
- return 0;
- }
- const currentTime = media.currentTime;
- this.nudgeRetry++;
- if (nudgeRetry < config.nudgeMaxRetry) {
- const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
- const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
- this.warn(error.message);
- media.currentTime = targetTime;
- hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
- error,
- fatal: false,
- buffer: bufferInfo.len,
- bufferInfo
- });
- } else {
- const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
- this.error(error.message);
- hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.BUFFER_STALLED_ERROR,
- error,
- fatal: true,
- buffer: bufferInfo.len,
- bufferInfo
- });
- }
- }
- };
- function getInFlightDependency(inFlightFragments, currentTime) {
- const main = inFlight(inFlightFragments.main);
- if (main && main.start <= currentTime) {
- return main;
- }
- const audio = inFlight(inFlightFragments.audio);
- if (audio && audio.start <= currentTime) {
- return audio;
- }
- return null;
- }
- function inFlight(inFlightData) {
- if (!inFlightData) {
- return null;
- }
- switch (inFlightData.state) {
- case State.IDLE:
- case State.STOPPED:
- case State.ENDED:
- case State.ERROR:
- return null;
- }
- return inFlightData.frag;
- }
- function appendedFragAtPosition(pos, fragmentTracker) {
- return fragmentTracker.getAppendedFrag(pos, PlaylistLevelType.MAIN) || fragmentTracker.getPartialFragment(pos);
- }
- var MIN_CUE_DURATION = 0.25;
- function getCueClass() {
- if (typeof self === "undefined")
- return void 0;
- return self.VTTCue || self.TextTrackCue;
- }
- function createCueWithDataFields(Cue, startTime, endTime, data, type) {
- let cue = new Cue(startTime, endTime, "");
- try {
- cue.value = data;
- if (type) {
- cue.type = type;
- }
- } catch (e) {
- cue = new Cue(startTime, endTime, stringify(type ? _objectSpread2({
- type
- }, data) : data));
- }
- return cue;
- }
- var MAX_CUE_ENDTIME = (() => {
- const Cue = getCueClass();
- try {
- Cue && new Cue(0, Number.POSITIVE_INFINITY, "");
- } catch (e) {
- return Number.MAX_VALUE;
- }
- return Number.POSITIVE_INFINITY;
- })();
- var ID3TrackController = class {
- constructor(hls) {
- this.hls = void 0;
- this.id3Track = null;
- this.media = null;
- this.dateRangeCuesAppended = {};
- this.removeCues = true;
- this.assetCue = void 0;
- this.onEventCueEnter = () => {
- if (!this.hls) {
- return;
- }
- this.hls.trigger(Events.EVENT_CUE_ENTER, {});
- };
- this.hls = hls;
- this._registerListeners();
- }
- destroy() {
- this._unregisterListeners();
- this.id3Track = null;
- this.media = null;
- this.dateRangeCuesAppended = {};
- this.hls = this.onEventCueEnter = null;
- }
- _registerListeners() {
- const {
- hls
- } = this;
- if (hls) {
- hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
- hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
- hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
- hls.on(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
- }
- }
- _unregisterListeners() {
- const {
- hls
- } = this;
- if (hls) {
- hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
- hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
- hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
- hls.off(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
- }
- }
- // Add ID3 metatadata text track.
- onMediaAttaching(event, data) {
- var _data$overrides;
- this.media = data.media;
- if (((_data$overrides = data.overrides) == null ? void 0 : _data$overrides.cueRemoval) === false) {
- this.removeCues = false;
- }
- }
- onMediaAttached() {
- var _this$hls;
- const details = (_this$hls = this.hls) == null ? void 0 : _this$hls.latestLevelDetails;
- if (details) {
- this.updateDateRangeCues(details);
- }
- }
- onMediaDetaching(event, data) {
- this.media = null;
- const transferringMedia = !!data.transferMedia;
- if (transferringMedia) {
- return;
- }
- if (this.id3Track) {
- if (this.removeCues) {
- clearCurrentCues(this.id3Track, this.onEventCueEnter);
- }
- this.id3Track = null;
- }
- this.dateRangeCuesAppended = {};
- }
- onManifestLoading() {
- this.dateRangeCuesAppended = {};
- }
- createTrack(media) {
- const track = this.getID3Track(media.textTracks);
- track.mode = "hidden";
- return track;
- }
- getID3Track(textTracks) {
- if (!this.media) {
- return;
- }
- for (let i = 0; i < textTracks.length; i++) {
- const textTrack = textTracks[i];
- if (textTrack.kind === "metadata" && textTrack.label === "id3") {
- sendAddTrackEvent(textTrack, this.media);
- return textTrack;
- }
- }
- return this.media.addTextTrack("metadata", "id3");
- }
- onFragParsingMetadata(event, data) {
- if (!this.media || !this.hls) {
- return;
- }
- const {
- enableEmsgMetadataCues,
- enableID3MetadataCues
- } = this.hls.config;
- if (!enableEmsgMetadataCues && !enableID3MetadataCues) {
- return;
- }
- const {
- samples
- } = data;
- if (!this.id3Track) {
- this.id3Track = this.createTrack(this.media);
- }
- const Cue = getCueClass();
- if (!Cue) {
- return;
- }
- for (let i = 0; i < samples.length; i++) {
- const type = samples[i].type;
- if (type === MetadataSchema.emsg && !enableEmsgMetadataCues || !enableID3MetadataCues) {
- continue;
- }
- const frames = getId3Frames(samples[i].data);
- const startTime = samples[i].pts;
- let endTime = startTime + samples[i].duration;
- if (endTime > MAX_CUE_ENDTIME) {
- endTime = MAX_CUE_ENDTIME;
- }
- const timeDiff = endTime - startTime;
- if (timeDiff <= 0) {
- endTime = startTime + MIN_CUE_DURATION;
- }
- for (let j = 0; j < frames.length; j++) {
- const frame = frames[j];
- if (!isId3TimestampFrame(frame)) {
- this.updateId3CueEnds(startTime, type);
- const cue = createCueWithDataFields(Cue, startTime, endTime, frame, type);
- if (cue) {
- this.id3Track.addCue(cue);
- }
- }
- }
- }
- }
- updateId3CueEnds(startTime, type) {
- var _this$id3Track;
- const cues = (_this$id3Track = this.id3Track) == null ? void 0 : _this$id3Track.cues;
- if (cues) {
- for (let i = cues.length; i--; ) {
- const cue = cues[i];
- if (cue.type === type && cue.startTime < startTime && cue.endTime === MAX_CUE_ENDTIME) {
- cue.endTime = startTime;
- }
- }
- }
- }
- onBufferFlushing(event, {
- startOffset,
- endOffset,
- type
- }) {
- const {
- id3Track,
- hls
- } = this;
- if (!hls) {
- return;
- }
- const {
- config: {
- enableEmsgMetadataCues,
- enableID3MetadataCues
- }
- } = hls;
- if (id3Track && (enableEmsgMetadataCues || enableID3MetadataCues)) {
- let predicate;
- if (type === "audio") {
- predicate = (cue) => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues;
- } else if (type === "video") {
- predicate = (cue) => cue.type === MetadataSchema.emsg && enableEmsgMetadataCues;
- } else {
- predicate = (cue) => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues || cue.type === MetadataSchema.emsg && enableEmsgMetadataCues;
- }
- removeCuesInRange(id3Track, startOffset, endOffset, predicate);
- }
- }
- onLevelUpdated(event, {
- details
- }) {
- this.updateDateRangeCues(details, true);
- }
- onLevelPtsUpdated(event, data) {
- if (Math.abs(data.drift) > 0.01) {
- this.updateDateRangeCues(data.details);
- }
- }
- updateDateRangeCues(details, removeOldCues) {
- if (!this.hls || !this.media) {
- return;
- }
- const {
- assetPlayerId,
- timelineOffset,
- enableDateRangeMetadataCues,
- interstitialsController
- } = this.hls.config;
- if (!enableDateRangeMetadataCues) {
- return;
- }
- const Cue = getCueClass();
- if (assetPlayerId && timelineOffset && !interstitialsController) {
- const {
- fragmentStart,
- fragmentEnd
- } = details;
- let cue = this.assetCue;
- if (cue) {
- cue.startTime = fragmentStart;
- cue.endTime = fragmentEnd;
- } else if (Cue) {
- cue = this.assetCue = createCueWithDataFields(Cue, fragmentStart, fragmentEnd, {
- assetPlayerId: this.hls.config.assetPlayerId
- }, "hlsjs.interstitial.asset");
- if (cue) {
- cue.id = assetPlayerId;
- this.id3Track || (this.id3Track = this.createTrack(this.media));
- this.id3Track.addCue(cue);
- cue.addEventListener("enter", this.onEventCueEnter);
- }
- }
- }
- if (!details.hasProgramDateTime) {
- return;
- }
- const {
- id3Track
- } = this;
- const {
- dateRanges
- } = details;
- const ids = Object.keys(dateRanges);
- let dateRangeCuesAppended = this.dateRangeCuesAppended;
- if (id3Track && removeOldCues) {
- var _id3Track$cues;
- if ((_id3Track$cues = id3Track.cues) != null && _id3Track$cues.length) {
- const idsToRemove = Object.keys(dateRangeCuesAppended).filter((id) => !ids.includes(id));
- for (let i = idsToRemove.length; i--; ) {
- var _dateRangeCuesAppende;
- const id = idsToRemove[i];
- const cues = (_dateRangeCuesAppende = dateRangeCuesAppended[id]) == null ? void 0 : _dateRangeCuesAppende.cues;
- delete dateRangeCuesAppended[id];
- if (cues) {
- Object.keys(cues).forEach((key) => {
- const cue = cues[key];
- if (cue) {
- cue.removeEventListener("enter", this.onEventCueEnter);
- try {
- id3Track.removeCue(cue);
- } catch (e) {
- }
- }
- });
- }
- }
- } else {
- dateRangeCuesAppended = this.dateRangeCuesAppended = {};
- }
- }
- const lastFragment = details.fragments[details.fragments.length - 1];
- if (ids.length === 0 || !isFiniteNumber(lastFragment == null ? void 0 : lastFragment.programDateTime)) {
- return;
- }
- this.id3Track || (this.id3Track = this.createTrack(this.media));
- for (let i = 0; i < ids.length; i++) {
- const id = ids[i];
- const dateRange = dateRanges[id];
- const startTime = dateRange.startTime;
- const appendedDateRangeCues = dateRangeCuesAppended[id];
- const cues = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.cues) || {};
- let durationKnown = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.durationKnown) || false;
- let endTime = MAX_CUE_ENDTIME;
- const {
- duration,
- endDate
- } = dateRange;
- if (endDate && duration !== null) {
- endTime = startTime + duration;
- durationKnown = true;
- } else if (dateRange.endOnNext && !durationKnown) {
- const nextDateRangeWithSameClass = ids.reduce((candidateDateRange, id2) => {
- if (id2 !== dateRange.id) {
- const otherDateRange = dateRanges[id2];
- if (otherDateRange.class === dateRange.class && otherDateRange.startDate > dateRange.startDate && (!candidateDateRange || dateRange.startDate < candidateDateRange.startDate)) {
- return otherDateRange;
- }
- }
- return candidateDateRange;
- }, null);
- if (nextDateRangeWithSameClass) {
- endTime = nextDateRangeWithSameClass.startTime;
- durationKnown = true;
- }
- }
- const attributes = Object.keys(dateRange.attr);
- for (let j = 0; j < attributes.length; j++) {
- const key = attributes[j];
- if (!isDateRangeCueAttribute(key)) {
- continue;
- }
- const cue = cues[key];
- if (cue) {
- if (durationKnown && !(appendedDateRangeCues != null && appendedDateRangeCues.durationKnown)) {
- cue.endTime = endTime;
- } else if (Math.abs(cue.startTime - startTime) > 0.01) {
- cue.startTime = startTime;
- cue.endTime = endTime;
- }
- } else if (Cue) {
- let data = dateRange.attr[key];
- if (isSCTE35Attribute(key)) {
- data = hexToArrayBuffer(data);
- }
- const payload = {
- key,
- data
- };
- const _cue = createCueWithDataFields(Cue, startTime, endTime, payload, MetadataSchema.dateRange);
- if (_cue) {
- _cue.id = id;
- this.id3Track.addCue(_cue);
- cues[key] = _cue;
- if (interstitialsController) {
- if (key === "X-ASSET-LIST" || key === "X-ASSET-URL") {
- _cue.addEventListener("enter", this.onEventCueEnter);
- }
- }
- }
- }
- }
- dateRangeCuesAppended[id] = {
- cues,
- dateRange,
- durationKnown
- };
- }
- }
- };
- var LatencyController = class {
- constructor(hls) {
- this.hls = void 0;
- this.config = void 0;
- this.media = null;
- this.currentTime = 0;
- this.stallCount = 0;
- this._latency = null;
- this._targetLatencyUpdated = false;
- this.onTimeupdate = () => {
- const {
- media
- } = this;
- const levelDetails = this.levelDetails;
- if (!media || !levelDetails) {
- return;
- }
- this.currentTime = media.currentTime;
- const latency = this.computeLatency();
- if (latency === null) {
- return;
- }
- this._latency = latency;
- const {
- lowLatencyMode,
- maxLiveSyncPlaybackRate
- } = this.config;
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
- return;
- }
- const targetLatency = this.targetLatency;
- if (targetLatency === null) {
- return;
- }
- const distanceFromTarget = latency - targetLatency;
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
- const max = Math.min(2, Math.max(1, maxLiveSyncPlaybackRate));
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
- const playbackRate = Math.min(max, Math.max(1, rate));
- this.changeMediaPlaybackRate(media, playbackRate);
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
- this.changeMediaPlaybackRate(media, 1);
- }
- };
- this.hls = hls;
- this.config = hls.config;
- this.registerListeners();
- }
- get levelDetails() {
- var _this$hls;
- return ((_this$hls = this.hls) == null ? void 0 : _this$hls.latestLevelDetails) || null;
- }
- get latency() {
- return this._latency || 0;
- }
- get maxLatency() {
- const {
- config
- } = this;
- if (config.liveMaxLatencyDuration !== void 0) {
- return config.liveMaxLatencyDuration;
- }
- const levelDetails = this.levelDetails;
- return levelDetails ? config.liveMaxLatencyDurationCount * levelDetails.targetduration : 0;
- }
- get targetLatency() {
- const levelDetails = this.levelDetails;
- if (levelDetails === null || this.hls === null) {
- return null;
- }
- const {
- holdBack,
- partHoldBack,
- targetduration
- } = levelDetails;
- const {
- liveSyncDuration,
- liveSyncDurationCount,
- lowLatencyMode
- } = this.config;
- const userConfig = this.hls.userConfig;
- let targetLatency = lowLatencyMode ? partHoldBack || holdBack : holdBack;
- if (this._targetLatencyUpdated || userConfig.liveSyncDuration || userConfig.liveSyncDurationCount || targetLatency === 0) {
- targetLatency = liveSyncDuration !== void 0 ? liveSyncDuration : liveSyncDurationCount * targetduration;
- }
- const maxLiveSyncOnStallIncrease = targetduration;
- return targetLatency + Math.min(this.stallCount * this.config.liveSyncOnStallIncrease, maxLiveSyncOnStallIncrease);
- }
- set targetLatency(latency) {
- this.stallCount = 0;
- this.config.liveSyncDuration = latency;
- this._targetLatencyUpdated = true;
- }
- get liveSyncPosition() {
- const liveEdge = this.estimateLiveEdge();
- const targetLatency = this.targetLatency;
- if (liveEdge === null || targetLatency === null) {
- return null;
- }
- const levelDetails = this.levelDetails;
- if (levelDetails === null) {
- return null;
- }
- const edge = levelDetails.edge;
- const syncPosition = liveEdge - targetLatency - this.edgeStalled;
- const min = edge - levelDetails.totalduration;
- const max = edge - (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration);
- return Math.min(Math.max(min, syncPosition), max);
- }
- get drift() {
- const levelDetails = this.levelDetails;
- if (levelDetails === null) {
- return 1;
- }
- return levelDetails.drift;
- }
- get edgeStalled() {
- const levelDetails = this.levelDetails;
- if (levelDetails === null) {
- return 0;
- }
- const maxLevelUpdateAge = (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration) * 3;
- return Math.max(levelDetails.age - maxLevelUpdateAge, 0);
- }
- get forwardBufferLength() {
- const {
- media
- } = this;
- const levelDetails = this.levelDetails;
- if (!media || !levelDetails) {
- return 0;
- }
- const bufferedRanges = media.buffered.length;
- return (bufferedRanges ? media.buffered.end(bufferedRanges - 1) : levelDetails.edge) - this.currentTime;
- }
- destroy() {
- this.unregisterListeners();
- this.onMediaDetaching();
- this.hls = null;
- }
- registerListeners() {
- const {
- hls
- } = this;
- if (!hls) {
- return;
- }
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
- hls.on(Events.ERROR, this.onError, this);
- }
- unregisterListeners() {
- const {
- hls
- } = this;
- if (!hls) {
- return;
- }
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
- hls.off(Events.ERROR, this.onError, this);
- }
- onMediaAttached(event, data) {
- this.media = data.media;
- this.media.addEventListener("timeupdate", this.onTimeupdate);
- }
- onMediaDetaching() {
- if (this.media) {
- this.media.removeEventListener("timeupdate", this.onTimeupdate);
- this.media = null;
- }
- }
- onManifestLoading() {
- this._latency = null;
- this.stallCount = 0;
- }
- onLevelUpdated(event, {
- details
- }) {
- if (details.advanced) {
- this.onTimeupdate();
- }
- if (!details.live && this.media) {
- this.media.removeEventListener("timeupdate", this.onTimeupdate);
- }
- }
- onError(event, data) {
- var _this$levelDetails;
- if (data.details !== ErrorDetails.BUFFER_STALLED_ERROR) {
- return;
- }
- this.stallCount++;
- if (this.hls && (_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
- this.hls.logger.warn("[latency-controller]: Stall detected, adjusting target latency");
- }
- }
- changeMediaPlaybackRate(media, playbackRate) {
- var _this$hls2, _this$targetLatency;
- if (media.playbackRate === playbackRate) {
- return;
- }
- (_this$hls2 = this.hls) == null || _this$hls2.logger.debug(`[latency-controller]: latency=${this.latency.toFixed(3)}, targetLatency=${(_this$targetLatency = this.targetLatency) == null ? void 0 : _this$targetLatency.toFixed(3)}, forwardBufferLength=${this.forwardBufferLength.toFixed(3)}: adjusting playback rate from ${media.playbackRate} to ${playbackRate}`);
- media.playbackRate = playbackRate;
- }
- estimateLiveEdge() {
- const levelDetails = this.levelDetails;
- if (levelDetails === null) {
- return null;
- }
- return levelDetails.edge + levelDetails.age;
- }
- computeLatency() {
- const liveEdge = this.estimateLiveEdge();
- if (liveEdge === null) {
- return null;
- }
- return liveEdge - this.currentTime;
- }
- };
- var LevelController = class extends BasePlaylistController {
- constructor(hls, contentSteeringController) {
- super(hls, "level-controller");
- this._levels = [];
- this._firstLevel = -1;
- this._maxAutoLevel = -1;
- this._startLevel = void 0;
- this.currentLevel = null;
- this.currentLevelIndex = -1;
- this.manualLevelIndex = -1;
- this.steering = void 0;
- this.onParsedComplete = void 0;
- this.steering = contentSteeringController;
- this._registerListeners();
- }
- _registerListeners() {
- const {
- hls
- } = this;
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
- hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
- hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
- hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
- hls.on(Events.ERROR, this.onError, this);
- }
- _unregisterListeners() {
- const {
- hls
- } = this;
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
- hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
- hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
- hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
- hls.off(Events.ERROR, this.onError, this);
- }
- destroy() {
- this._unregisterListeners();
- this.steering = null;
- this.resetLevels();
- super.destroy();
- }
- stopLoad() {
- const levels = this._levels;
- levels.forEach((level) => {
- level.loadError = 0;
- level.fragmentError = 0;
- });
- super.stopLoad();
- }
- resetLevels() {
- this._startLevel = void 0;
- this.manualLevelIndex = -1;
- this.currentLevelIndex = -1;
- this.currentLevel = null;
- this._levels = [];
- this._maxAutoLevel = -1;
- }
- onManifestLoading(event, data) {
- this.resetLevels();
- }
- onManifestLoaded(event, data) {
- const preferManagedMediaSource = this.hls.config.preferManagedMediaSource;
- const levels = [];
- const redundantSet = {};
- const generatePathwaySet = {};
- let resolutionFound = false;
- let videoCodecFound = false;
- let audioCodecFound = false;
- data.levels.forEach((levelParsed) => {
- const attributes = levelParsed.attrs;
- let {
- audioCodec,
- videoCodec
- } = levelParsed;
- if (audioCodec) {
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || void 0;
- }
- if (videoCodec) {
- videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
- }
- const {
- width,
- height,
- unknownCodecs
- } = levelParsed;
- const unknownUnsupportedCodecCount = (unknownCodecs == null ? void 0 : unknownCodecs.length) || 0;
- resolutionFound || (resolutionFound = !!(width && height));
- videoCodecFound || (videoCodecFound = !!videoCodec);
- audioCodecFound || (audioCodecFound = !!audioCodec);
- if (unknownUnsupportedCodecCount || audioCodec && !this.isAudioSupported(audioCodec) || videoCodec && !this.isVideoSupported(videoCodec)) {
- this.log(`Some or all CODECS not supported "${attributes.CODECS}"`);
- return;
- }
- const {
- CODECS,
- "FRAME-RATE": FRAMERATE,
- "HDCP-LEVEL": HDCP,
- "PATHWAY-ID": PATHWAY,
- RESOLUTION,
- "VIDEO-RANGE": VIDEO_RANGE
- } = attributes;
- const contentSteeringPrefix = `${PATHWAY || "."}-`;
- const levelKey = `${contentSteeringPrefix}${levelParsed.bitrate}-${RESOLUTION}-${FRAMERATE}-${CODECS}-${VIDEO_RANGE}-${HDCP}`;
- if (!redundantSet[levelKey]) {
- const level = this.createLevel(levelParsed);
- redundantSet[levelKey] = level;
- generatePathwaySet[levelKey] = 1;
- levels.push(level);
- } else if (redundantSet[levelKey].uri !== levelParsed.url && !levelParsed.attrs["PATHWAY-ID"]) {
- const pathwayCount = generatePathwaySet[levelKey] += 1;
- levelParsed.attrs["PATHWAY-ID"] = new Array(pathwayCount + 1).join(".");
- const level = this.createLevel(levelParsed);
- redundantSet[levelKey] = level;
- levels.push(level);
- } else {
- redundantSet[levelKey].addGroupId("audio", attributes.AUDIO);
- redundantSet[levelKey].addGroupId("text", attributes.SUBTITLES);
- }
- });
- this.filterAndSortMediaOptions(levels, data, resolutionFound, videoCodecFound, audioCodecFound);
- }
- createLevel(levelParsed) {
- const level = new Level(levelParsed);
- const supplemental = levelParsed.supplemental;
- if (supplemental != null && supplemental.videoCodec && !this.isVideoSupported(supplemental.videoCodec)) {
- const error = new Error(`SUPPLEMENTAL-CODECS not supported "${supplemental.videoCodec}"`);
- this.log(error.message);
- level.supportedResult = getUnsupportedResult(error, []);
- }
- return level;
- }
- isAudioSupported(codec) {
- return areCodecsMediaSourceSupported(codec, "audio", this.hls.config.preferManagedMediaSource);
- }
- isVideoSupported(codec) {
- return areCodecsMediaSourceSupported(codec, "video", this.hls.config.preferManagedMediaSource);
- }
- filterAndSortMediaOptions(filteredLevels, data, resolutionFound, videoCodecFound, audioCodecFound) {
- var _data$stats;
- let audioTracks = [];
- let subtitleTracks = [];
- let levels = filteredLevels;
- const statsParsing = ((_data$stats = data.stats) == null ? void 0 : _data$stats.parsing) || {};
- if ((resolutionFound || videoCodecFound) && audioCodecFound) {
- levels = levels.filter(({
- videoCodec,
- videoRange,
- width,
- height
- }) => (!!videoCodec || !!(width && height)) && isVideoRange(videoRange));
- }
- if (levels.length === 0) {
- Promise.resolve().then(() => {
- if (this.hls) {
- let message = "no level with compatible codecs found in manifest";
- let reason = message;
- if (data.levels.length) {
- reason = `one or more CODECS in variant not supported: ${stringify(data.levels.map((level) => level.attrs.CODECS).filter((value, index, array) => array.indexOf(value) === index))}`;
- this.warn(reason);
- message += ` (${reason})`;
- }
- const error = new Error(message);
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.MEDIA_ERROR,
- details: ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR,
- fatal: true,
- url: data.url,
- error,
- reason
- });
- }
- });
- statsParsing.end = performance.now();
- return;
- }
- if (data.audioTracks) {
- audioTracks = data.audioTracks.filter((track) => !track.audioCodec || this.isAudioSupported(track.audioCodec));
- assignTrackIdsByGroup(audioTracks);
- }
- if (data.subtitles) {
- subtitleTracks = data.subtitles;
- assignTrackIdsByGroup(subtitleTracks);
- }
- const unsortedLevels = levels.slice(0);
- levels.sort((a, b) => {
- if (a.attrs["HDCP-LEVEL"] !== b.attrs["HDCP-LEVEL"]) {
- return (a.attrs["HDCP-LEVEL"] || "") > (b.attrs["HDCP-LEVEL"] || "") ? 1 : -1;
- }
- if (resolutionFound && a.height !== b.height) {
- return a.height - b.height;
- }
- if (a.frameRate !== b.frameRate) {
- return a.frameRate - b.frameRate;
- }
- if (a.videoRange !== b.videoRange) {
- return VideoRangeValues.indexOf(a.videoRange) - VideoRangeValues.indexOf(b.videoRange);
- }
- if (a.videoCodec !== b.videoCodec) {
- const valueA = videoCodecPreferenceValue(a.videoCodec);
- const valueB = videoCodecPreferenceValue(b.videoCodec);
- if (valueA !== valueB) {
- return valueB - valueA;
- }
- }
- if (a.uri === b.uri && a.codecSet !== b.codecSet) {
- const valueA = codecsSetSelectionPreferenceValue(a.codecSet);
- const valueB = codecsSetSelectionPreferenceValue(b.codecSet);
- if (valueA !== valueB) {
- return valueB - valueA;
- }
- }
- if (a.averageBitrate !== b.averageBitrate) {
- return a.averageBitrate - b.averageBitrate;
- }
- return 0;
- });
- let firstLevelInPlaylist = unsortedLevels[0];
- if (this.steering) {
- levels = this.steering.filterParsedLevels(levels);
- if (levels.length !== unsortedLevels.length) {
- for (let i = 0; i < unsortedLevels.length; i++) {
- if (unsortedLevels[i].pathwayId === levels[0].pathwayId) {
- firstLevelInPlaylist = unsortedLevels[i];
- break;
- }
- }
- }
- }
- this._levels = levels;
- for (let i = 0; i < levels.length; i++) {
- if (levels[i] === firstLevelInPlaylist) {
- var _this$hls$userConfig;
- this._firstLevel = i;
- const firstLevelBitrate = firstLevelInPlaylist.bitrate;
- const bandwidthEstimate = this.hls.bandwidthEstimate;
- this.log(`manifest loaded, ${levels.length} level(s) found, first bitrate: ${firstLevelBitrate}`);
- if (((_this$hls$userConfig = this.hls.userConfig) == null ? void 0 : _this$hls$userConfig.abrEwmaDefaultEstimate) === void 0) {
- const startingBwEstimate = Math.min(firstLevelBitrate, this.hls.config.abrEwmaDefaultEstimateMax);
- if (startingBwEstimate > bandwidthEstimate && bandwidthEstimate === this.hls.abrEwmaDefaultEstimate) {
- this.hls.bandwidthEstimate = startingBwEstimate;
- }
- }
- break;
- }
- }
- const audioOnly = audioCodecFound && !videoCodecFound;
- const config = this.hls.config;
- const altAudioEnabled = !!(config.audioStreamController && config.audioTrackController);
- const edata = {
- levels,
- audioTracks,
- subtitleTracks,
- sessionData: data.sessionData,
- sessionKeys: data.sessionKeys,
- firstLevel: this._firstLevel,
- stats: data.stats,
- audio: audioCodecFound,
- video: videoCodecFound,
- altAudio: altAudioEnabled && !audioOnly && audioTracks.some((t) => !!t.url)
- };
- statsParsing.end = performance.now();
- this.hls.trigger(Events.MANIFEST_PARSED, edata);
- }
- get levels() {
- if (this._levels.length === 0) {
- return null;
- }
- return this._levels;
- }
- get loadLevelObj() {
- return this.currentLevel;
- }
- get level() {
- return this.currentLevelIndex;
- }
- set level(newLevel) {
- const levels = this._levels;
- if (levels.length === 0) {
- return;
- }
- if (newLevel < 0 || newLevel >= levels.length) {
- const error = new Error("invalid level idx");
- const fatal = newLevel < 0;
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.LEVEL_SWITCH_ERROR,
- level: newLevel,
- fatal,
- error,
- reason: error.message
- });
- if (fatal) {
- return;
- }
- newLevel = Math.min(newLevel, levels.length - 1);
- }
- const lastLevelIndex = this.currentLevelIndex;
- const lastLevel = this.currentLevel;
- const lastPathwayId = lastLevel ? lastLevel.attrs["PATHWAY-ID"] : void 0;
- const level = levels[newLevel];
- const pathwayId = level.attrs["PATHWAY-ID"];
- this.currentLevelIndex = newLevel;
- this.currentLevel = level;
- if (lastLevelIndex === newLevel && lastLevel && lastPathwayId === pathwayId) {
- return;
- }
- this.log(`Switching to level ${newLevel} (${level.height ? level.height + "p " : ""}${level.videoRange ? level.videoRange + " " : ""}${level.codecSet ? level.codecSet + " " : ""}@${level.bitrate})${pathwayId ? " with Pathway " + pathwayId : ""} from level ${lastLevelIndex}${lastPathwayId ? " with Pathway " + lastPathwayId : ""}`);
- const levelSwitchingData = {
- level: newLevel,
- attrs: level.attrs,
- details: level.details,
- bitrate: level.bitrate,
- averageBitrate: level.averageBitrate,
- maxBitrate: level.maxBitrate,
- realBitrate: level.realBitrate,
- width: level.width,
- height: level.height,
- codecSet: level.codecSet,
- audioCodec: level.audioCodec,
- videoCodec: level.videoCodec,
- audioGroups: level.audioGroups,
- subtitleGroups: level.subtitleGroups,
- loaded: level.loaded,
- loadError: level.loadError,
- fragmentError: level.fragmentError,
- name: level.name,
- id: level.id,
- uri: level.uri,
- url: level.url,
- urlId: 0,
- audioGroupIds: level.audioGroupIds,
- textGroupIds: level.textGroupIds
- };
- this.hls.trigger(Events.LEVEL_SWITCHING, levelSwitchingData);
- const levelDetails = level.details;
- if (!levelDetails || levelDetails.live) {
- const hlsUrlParameters = this.switchParams(level.uri, lastLevel == null ? void 0 : lastLevel.details, levelDetails);
- this.loadPlaylist(hlsUrlParameters);
- }
- }
- get manualLevel() {
- return this.manualLevelIndex;
- }
- set manualLevel(newLevel) {
- this.manualLevelIndex = newLevel;
- if (this._startLevel === void 0) {
- this._startLevel = newLevel;
- }
- if (newLevel !== -1) {
- this.level = newLevel;
- }
- }
- get firstLevel() {
- return this._firstLevel;
- }
- set firstLevel(newLevel) {
- this._firstLevel = newLevel;
- }
- get startLevel() {
- if (this._startLevel === void 0) {
- const configStartLevel = this.hls.config.startLevel;
- if (configStartLevel !== void 0) {
- return configStartLevel;
- }
- return this.hls.firstAutoLevel;
- }
- return this._startLevel;
- }
- set startLevel(newLevel) {
- this._startLevel = newLevel;
- }
- get pathways() {
- if (this.steering) {
- return this.steering.pathways();
- }
- return [];
- }
- get pathwayPriority() {
- if (this.steering) {
- return this.steering.pathwayPriority;
- }
- return null;
- }
- set pathwayPriority(pathwayPriority) {
- if (this.steering) {
- const pathwaysList = this.steering.pathways();
- const filteredPathwayPriority = pathwayPriority.filter((pathwayId) => {
- return pathwaysList.indexOf(pathwayId) !== -1;
- });
- if (pathwayPriority.length < 1) {
- this.warn(`pathwayPriority ${pathwayPriority} should contain at least one pathway from list: ${pathwaysList}`);
- return;
- }
- this.steering.pathwayPriority = filteredPathwayPriority;
- }
- }
- onError(event, data) {
- if (data.fatal || !data.context) {
- return;
- }
- if (data.context.type === PlaylistContextType.LEVEL && data.context.level === this.level) {
- this.checkRetry(data);
- }
- }
- // reset errors on the successful load of a fragment
- onFragBuffered(event, {
- frag
- }) {
- if (frag !== void 0 && frag.type === PlaylistLevelType.MAIN) {
- const el = frag.elementaryStreams;
- if (!Object.keys(el).some((type) => !!el[type])) {
- return;
- }
- const level = this._levels[frag.level];
- if (level != null && level.loadError) {
- this.log(`Resetting level error count of ${level.loadError} on frag buffered`);
- level.loadError = 0;
- }
- }
- }
- onLevelLoaded(event, data) {
- var _data$deliveryDirecti2;
- const {
- level,
- details
- } = data;
- const curLevel = data.levelInfo;
- if (!curLevel) {
- var _data$deliveryDirecti;
- this.warn(`Invalid level index ${level}`);
- if ((_data$deliveryDirecti = data.deliveryDirectives) != null && _data$deliveryDirecti.skip) {
- details.deltaUpdateFailed = true;
- }
- return;
- }
- if (curLevel === this.currentLevel || data.withoutMultiVariant) {
- if (curLevel.fragmentError === 0) {
- curLevel.loadError = 0;
- }
- let previousDetails = curLevel.details;
- if (previousDetails === data.details && previousDetails.advanced) {
- previousDetails = void 0;
- }
- this.playlistLoaded(level, data, previousDetails);
- } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
- details.deltaUpdateFailed = true;
- }
- }
- loadPlaylist(hlsUrlParameters) {
- super.loadPlaylist();
- if (this.shouldLoadPlaylist(this.currentLevel)) {
- this.scheduleLoading(this.currentLevel, hlsUrlParameters);
- }
- }
- loadingPlaylist(currentLevel, hlsUrlParameters) {
- super.loadingPlaylist(currentLevel, hlsUrlParameters);
- const url = this.getUrlWithDirectives(currentLevel.uri, hlsUrlParameters);
- const currentLevelIndex = this.currentLevelIndex;
- const pathwayId = currentLevel.attrs["PATHWAY-ID"];
- const details = currentLevel.details;
- const age = details == null ? void 0 : details.age;
- this.log(`Loading level index ${currentLevelIndex}${(hlsUrlParameters == null ? void 0 : hlsUrlParameters.msn) !== void 0 ? " at sn " + hlsUrlParameters.msn + " part " + hlsUrlParameters.part : ""}${pathwayId ? " Pathway " + pathwayId : ""}${age && details.live ? " age " + age.toFixed(1) + (details.type ? " " + details.type || "" : "") : ""} ${url}`);
- this.hls.trigger(Events.LEVEL_LOADING, {
- url,
- level: currentLevelIndex,
- levelInfo: currentLevel,
- pathwayId: currentLevel.attrs["PATHWAY-ID"],
- id: 0,
- // Deprecated Level urlId
- deliveryDirectives: hlsUrlParameters || null
- });
- }
- get nextLoadLevel() {
- if (this.manualLevelIndex !== -1) {
- return this.manualLevelIndex;
- } else {
- return this.hls.nextAutoLevel;
- }
- }
- set nextLoadLevel(nextLevel) {
- this.level = nextLevel;
- if (this.manualLevelIndex === -1) {
- this.hls.nextAutoLevel = nextLevel;
- }
- }
- removeLevel(levelIndex) {
- var _this$currentLevel;
- if (this._levels.length === 1) {
- return;
- }
- const levels = this._levels.filter((level, index) => {
- if (index !== levelIndex) {
- return true;
- }
- if (this.steering) {
- this.steering.removeLevel(level);
- }
- if (level === this.currentLevel) {
- this.currentLevel = null;
- this.currentLevelIndex = -1;
- if (level.details) {
- level.details.fragments.forEach((f) => f.level = -1);
- }
- }
- return false;
- });
- reassignFragmentLevelIndexes(levels);
- this._levels = levels;
- if (this.currentLevelIndex > -1 && (_this$currentLevel = this.currentLevel) != null && _this$currentLevel.details) {
- this.currentLevelIndex = this.currentLevel.details.fragments[0].level;
- }
- if (this.manualLevelIndex > -1) {
- this.manualLevelIndex = this.currentLevelIndex;
- }
- const maxLevel = levels.length - 1;
- this._firstLevel = Math.min(this._firstLevel, maxLevel);
- if (this._startLevel) {
- this._startLevel = Math.min(this._startLevel, maxLevel);
- }
- this.hls.trigger(Events.LEVELS_UPDATED, {
- levels
- });
- }
- onLevelsUpdated(event, {
- levels
- }) {
- this._levels = levels;
- }
- checkMaxAutoUpdated() {
- const {
- autoLevelCapping,
- maxAutoLevel,
- maxHdcpLevel
- } = this.hls;
- if (this._maxAutoLevel !== maxAutoLevel) {
- this._maxAutoLevel = maxAutoLevel;
- this.hls.trigger(Events.MAX_AUTO_LEVEL_UPDATED, {
- autoLevelCapping,
- levels: this.levels,
- maxAutoLevel,
- minAutoLevel: this.hls.minAutoLevel,
- maxHdcpLevel
- });
- }
- }
- };
- function assignTrackIdsByGroup(tracks) {
- const groups = {};
- tracks.forEach((track) => {
- const groupId = track.groupId || "";
- track.id = groups[groupId] = groups[groupId] || 0;
- groups[groupId]++;
- });
- }
- function getSourceBuffer() {
- return self.SourceBuffer || self.WebKitSourceBuffer;
- }
- function isMSESupported() {
- const mediaSource = getMediaSource();
- if (!mediaSource) {
- return false;
- }
- const sourceBuffer = getSourceBuffer();
- return !sourceBuffer || sourceBuffer.prototype && typeof sourceBuffer.prototype.appendBuffer === "function" && typeof sourceBuffer.prototype.remove === "function";
- }
- function isSupported() {
- if (!isMSESupported()) {
- return false;
- }
- const mediaSource = getMediaSource();
- return typeof (mediaSource == null ? void 0 : mediaSource.isTypeSupported) === "function" && (["avc1.42E01E,mp4a.40.2", "av01.0.01M.08", "vp09.00.50.08"].some((codecsForVideoContainer) => mediaSource.isTypeSupported(mimeTypeForCodec(codecsForVideoContainer, "video"))) || ["mp4a.40.2", "fLaC"].some((codecForAudioContainer) => mediaSource.isTypeSupported(mimeTypeForCodec(codecForAudioContainer, "audio"))));
- }
- function changeTypeSupported() {
- var _sourceBuffer$prototy;
- const sourceBuffer = getSourceBuffer();
- return typeof (sourceBuffer == null || (_sourceBuffer$prototy = sourceBuffer.prototype) == null ? void 0 : _sourceBuffer$prototy.changeType) === "function";
- }
- var TICK_INTERVAL = 100;
- var StreamController = class extends BaseStreamController {
- constructor(hls, fragmentTracker, keyLoader) {
- super(hls, fragmentTracker, keyLoader, "stream-controller", PlaylistLevelType.MAIN);
- this.audioCodecSwap = false;
- this.level = -1;
- this._forceStartLoad = false;
- this._hasEnoughToStart = false;
- this.altAudio = 0;
- this.audioOnly = false;
- this.fragPlaying = null;
- this.fragLastKbps = 0;
- this.couldBacktrack = false;
- this.backtrackFragment = null;
- this.audioCodecSwitch = false;
- this.videoBuffer = null;
- this.onMediaPlaying = () => {
- this.tick();
- };
- this.onMediaSeeked = () => {
- const media = this.media;
- const currentTime = media ? media.currentTime : null;
- if (currentTime === null || !isFiniteNumber(currentTime)) {
- return;
- }
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
- if (!this.getBufferedFrag(currentTime)) {
- return;
- }
- const bufferInfo = this.getFwdBufferInfoAtPos(media, currentTime, PlaylistLevelType.MAIN, 0);
- if (bufferInfo === null || bufferInfo.len === 0) {
- this.warn(`Main forward buffer length at ${currentTime} on "seeked" event ${bufferInfo ? bufferInfo.len : "empty"})`);
- return;
- }
- this.tick();
- };
- this.registerListeners();
- }
- registerListeners() {
- super.registerListeners();
- const {
- hls
- } = this;
- hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
- hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
- hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
- hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
- hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
- hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
- hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
- hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
- hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
- }
- unregisterListeners() {
- super.unregisterListeners();
- const {
- hls
- } = this;
- hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
- hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
- hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
- hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
- hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
- hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
- hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
- hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
- hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
- }
- onHandlerDestroying() {
- this.onMediaPlaying = this.onMediaSeeked = null;
- this.unregisterListeners();
- super.onHandlerDestroying();
- }
- startLoad(startPosition, skipSeekToStartPosition) {
- if (this.levels) {
- const {
- lastCurrentTime,
- hls
- } = this;
- this.stopLoad();
- this.setInterval(TICK_INTERVAL);
- this.level = -1;
- if (!this.startFragRequested) {
- let startLevel = hls.startLevel;
- if (startLevel === -1) {
- if (hls.config.testBandwidth && this.levels.length > 1) {
- startLevel = 0;
- this.bitrateTest = true;
- } else {
- startLevel = hls.firstAutoLevel;
- }
- }
- hls.nextLoadLevel = startLevel;
- this.level = hls.loadLevel;
- this._hasEnoughToStart = !!skipSeekToStartPosition;
- }
- if (lastCurrentTime > 0 && startPosition === -1 && !skipSeekToStartPosition) {
- this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`);
- startPosition = lastCurrentTime;
- }
- this.state = State.IDLE;
- this.nextLoadPosition = this.lastCurrentTime = startPosition + this.timelineOffset;
- this.startPosition = skipSeekToStartPosition ? -1 : startPosition;
- this.tick();
- } else {
- this._forceStartLoad = true;
- this.state = State.STOPPED;
- }
- }
- stopLoad() {
- this._forceStartLoad = false;
- super.stopLoad();
- }
- doTick() {
- switch (this.state) {
- case State.WAITING_LEVEL: {
- const {
- levels,
- level
- } = this;
- const currentLevel = levels == null ? void 0 : levels[level];
- const details = currentLevel == null ? void 0 : currentLevel.details;
- if (details && (!details.live || this.levelLastLoaded === currentLevel && !this.waitForLive(currentLevel))) {
- if (this.waitForCdnTuneIn(details)) {
- break;
- }
- this.state = State.IDLE;
- break;
- } else if (this.hls.nextLoadLevel !== this.level) {
- this.state = State.IDLE;
- break;
- }
- break;
- }
- case State.FRAG_LOADING_WAITING_RETRY:
- this.checkRetryDate();
- break;
- }
- if (this.state === State.IDLE) {
- this.doTickIdle();
- }
- this.onTickEnd();
- }
- onTickEnd() {
- var _this$media;
- super.onTickEnd();
- if ((_this$media = this.media) != null && _this$media.readyState && this.media.seeking === false) {
- this.lastCurrentTime = this.media.currentTime;
- }
- this.checkFragmentChanged();
- }
- doTickIdle() {
- const {
- hls,
- levelLastLoaded,
- levels,
- media
- } = this;
- if (levelLastLoaded === null || !media && !this.primaryPrefetch && (this.startFragRequested || !hls.config.startFragPrefetch)) {
- return;
- }
- if (this.altAudio && this.audioOnly) {
- return;
- }
- const level = this.buffering ? hls.nextLoadLevel : hls.loadLevel;
- if (!(levels != null && levels[level])) {
- return;
- }
- const levelInfo = levels[level];
- const bufferInfo = this.getMainFwdBufferInfo();
- if (bufferInfo === null) {
- return;
- }
- const lastDetails = this.getLevelDetails();
- if (lastDetails && this._streamEnded(bufferInfo, lastDetails)) {
- const data = {};
- if (this.altAudio === 2) {
- data.type = "video";
- }
- this.hls.trigger(Events.BUFFER_EOS, data);
- this.state = State.ENDED;
- return;
- }
- if (!this.buffering) {
- return;
- }
- if (hls.loadLevel !== level && hls.manualLevel === -1) {
- this.log(`Adapting to level ${level} from level ${this.level}`);
- }
- this.level = hls.nextLoadLevel = level;
- const levelDetails = levelInfo.details;
- if (!levelDetails || this.state === State.WAITING_LEVEL || this.waitForLive(levelInfo)) {
- this.level = level;
- this.state = State.WAITING_LEVEL;
- this.startFragRequested = false;
- return;
- }
- const bufferLen = bufferInfo.len;
- const maxBufLen = this.getMaxBufferLength(levelInfo.maxBitrate);
- if (bufferLen >= maxBufLen) {
- return;
- }
- if (this.backtrackFragment && this.backtrackFragment.start > bufferInfo.end) {
- this.backtrackFragment = null;
- }
- const targetBufferTime = this.backtrackFragment ? this.backtrackFragment.start : bufferInfo.end;
- let frag = this.getNextFragment(targetBufferTime, levelDetails);
- if (this.couldBacktrack && !this.fragPrevious && frag && isMediaFragment(frag) && this.fragmentTracker.getState(frag) !== FragmentState.OK) {
- var _this$backtrackFragme;
- const backtrackSn = ((_this$backtrackFragme = this.backtrackFragment) != null ? _this$backtrackFragme : frag).sn;
- const fragIdx = backtrackSn - levelDetails.startSN;
- const backtrackFrag = levelDetails.fragments[fragIdx - 1];
- if (backtrackFrag && frag.cc === backtrackFrag.cc) {
- frag = backtrackFrag;
- this.fragmentTracker.removeFragment(backtrackFrag);
- }
- } else if (this.backtrackFragment && bufferInfo.len) {
- this.backtrackFragment = null;
- }
- if (frag && this.isLoopLoading(frag, targetBufferTime)) {
- const gapStart = frag.gap;
- if (!gapStart) {
- const type = this.audioOnly && !this.altAudio ? ElementaryStreamTypes.AUDIO : ElementaryStreamTypes.VIDEO;
- const mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media;
- if (mediaBuffer) {
- this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);
- }
- }
- frag = this.getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen);
- }
- if (!frag) {
- return;
- }
- if (frag.initSegment && !frag.initSegment.data && !this.bitrateTest) {
- frag = frag.initSegment;
- }
- this.loadFragment(frag, levelInfo, targetBufferTime);
- }
- loadFragment(frag, level, targetBufferTime) {
- const fragState = this.fragmentTracker.getState(frag);
- if (fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
- if (!isMediaFragment(frag)) {
- this._loadInitSegment(frag, level);
- } else if (this.bitrateTest) {
- this.log(`Fragment ${frag.sn} of level ${frag.level} is being downloaded to test bitrate and will not be buffered`);
- this._loadBitrateTestFrag(frag, level);
- } else {
- super.loadFragment(frag, level, targetBufferTime);
- }
- } else {
- this.clearTrackerIfNeeded(frag);
- }
- }
- getBufferedFrag(position) {
- return this.fragmentTracker.getBufferedFrag(position, PlaylistLevelType.MAIN);
- }
- followingBufferedFrag(frag) {
- if (frag) {
- return this.getBufferedFrag(frag.end + 0.5);
- }
- return null;
- }
- /*
- on immediate level switch :
- - pause playback if playing
- - cancel any pending load request
- - and trigger a buffer flush
- */
- immediateLevelSwitch() {
- this.abortCurrentFrag();
- this.flushMainBuffer(0, Number.POSITIVE_INFINITY);
- }
- /**
- * try to switch ASAP without breaking video playback:
- * in order to ensure smooth but quick level switching,
- * we need to find the next flushable buffer range
- * we should take into account new segment fetch time
- */
- nextLevelSwitch() {
- const {
- levels,
- media
- } = this;
- if (media != null && media.readyState) {
- let fetchdelay;
- const fragPlayingCurrent = this.getAppendedFrag(media.currentTime);
- if (fragPlayingCurrent && fragPlayingCurrent.start > 1) {
- this.flushMainBuffer(0, fragPlayingCurrent.start - 1);
- }
- const levelDetails = this.getLevelDetails();
- if (levelDetails != null && levelDetails.live) {
- const bufferInfo = this.getMainFwdBufferInfo();
- if (!bufferInfo || bufferInfo.len < levelDetails.targetduration * 2) {
- return;
- }
- }
- if (!media.paused && levels) {
- const nextLevelId = this.hls.nextLoadLevel;
- const nextLevel = levels[nextLevelId];
- const fragLastKbps = this.fragLastKbps;
- if (fragLastKbps && this.fragCurrent) {
- fetchdelay = this.fragCurrent.duration * nextLevel.maxBitrate / (1e3 * fragLastKbps) + 1;
- } else {
- fetchdelay = 0;
- }
- } else {
- fetchdelay = 0;
- }
- const bufferedFrag = this.getBufferedFrag(media.currentTime + fetchdelay);
- if (bufferedFrag) {
- const nextBufferedFrag = this.followingBufferedFrag(bufferedFrag);
- if (nextBufferedFrag) {
- this.abortCurrentFrag();
- const maxStart = nextBufferedFrag.maxStartPTS ? nextBufferedFrag.maxStartPTS : nextBufferedFrag.start;
- const fragDuration = nextBufferedFrag.duration;
- const startPts = Math.max(bufferedFrag.end, maxStart + Math.min(Math.max(fragDuration - this.config.maxFragLookUpTolerance, fragDuration * (this.couldBacktrack ? 0.5 : 0.125)), fragDuration * (this.couldBacktrack ? 0.75 : 0.25)));
- this.flushMainBuffer(startPts, Number.POSITIVE_INFINITY);
- }
- }
- }
- }
- abortCurrentFrag() {
- const fragCurrent = this.fragCurrent;
- this.fragCurrent = null;
- this.backtrackFragment = null;
- if (fragCurrent) {
- fragCurrent.abortRequests();
- this.fragmentTracker.removeFragment(fragCurrent);
- }
- switch (this.state) {
- case State.KEY_LOADING:
- case State.FRAG_LOADING:
- case State.FRAG_LOADING_WAITING_RETRY:
- case State.PARSING:
- case State.PARSED:
- this.state = State.IDLE;
- break;
- }
- this.nextLoadPosition = this.getLoadPosition();
- }
- flushMainBuffer(startOffset, endOffset) {
- super.flushMainBuffer(startOffset, endOffset, this.altAudio === 2 ? "video" : null);
- }
- onMediaAttached(event, data) {
- super.onMediaAttached(event, data);
- const media = data.media;
- addEventListener(media, "playing", this.onMediaPlaying);
- addEventListener(media, "seeked", this.onMediaSeeked);
- }
- onMediaDetaching(event, data) {
- const {
- media
- } = this;
- if (media) {
- removeEventListener(media, "playing", this.onMediaPlaying);
- removeEventListener(media, "seeked", this.onMediaSeeked);
- }
- this.videoBuffer = null;
- this.fragPlaying = null;
- super.onMediaDetaching(event, data);
- const transferringMedia = !!data.transferMedia;
- if (transferringMedia) {
- return;
- }
- this._hasEnoughToStart = false;
- }
- onManifestLoading() {
- super.onManifestLoading();
- this.log("Trigger BUFFER_RESET");
- this.hls.trigger(Events.BUFFER_RESET, void 0);
- this.couldBacktrack = false;
- this.fragLastKbps = 0;
- this.fragPlaying = this.backtrackFragment = null;
- this.altAudio = 0;
- this.audioOnly = false;
- }
- onManifestParsed(event, data) {
- let aac = false;
- let heaac = false;
- for (let i = 0; i < data.levels.length; i++) {
- const codec = data.levels[i].audioCodec;
- if (codec) {
- aac = aac || codec.indexOf("mp4a.40.2") !== -1;
- heaac = heaac || codec.indexOf("mp4a.40.5") !== -1;
- }
- }
- this.audioCodecSwitch = aac && heaac && !changeTypeSupported();
- if (this.audioCodecSwitch) {
- this.log("Both AAC/HE-AAC audio found in levels; declaring level codec as HE-AAC");
- }
- this.levels = data.levels;
- this.startFragRequested = false;
- }
- onLevelLoading(event, data) {
- const {
- levels
- } = this;
- if (!levels || this.state !== State.IDLE) {
- return;
- }
- const level = data.levelInfo;
- if (!level.details || level.details.live && (this.levelLastLoaded !== level || level.details.expired) || this.waitForCdnTuneIn(level.details)) {
- this.state = State.WAITING_LEVEL;
- }
- }
- onLevelLoaded(event, data) {
- var _curLevel$details;
- const {
- levels,
- startFragRequested
- } = this;
- const newLevelId = data.level;
- const newDetails = data.details;
- const duration = newDetails.totalduration;
- if (!levels) {
- this.warn(`Levels were reset while loading level ${newLevelId}`);
- return;
- }
- this.log(`Level ${newLevelId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ""}, cc [${newDetails.startCC}, ${newDetails.endCC}] duration:${duration}`);
- const curLevel = data.levelInfo;
- const fragCurrent = this.fragCurrent;
- if (fragCurrent && (this.state === State.FRAG_LOADING || this.state === State.FRAG_LOADING_WAITING_RETRY)) {
- if (fragCurrent.level !== data.level && fragCurrent.loader) {
- this.abortCurrentFrag();
- }
- }
- let sliding = 0;
- if (newDetails.live || (_curLevel$details = curLevel.details) != null && _curLevel$details.live) {
- var _this$levelLastLoaded;
- this.checkLiveUpdate(newDetails);
- if (newDetails.deltaUpdateFailed) {
- return;
- }
- sliding = this.alignPlaylists(newDetails, curLevel.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details);
- }
- curLevel.details = newDetails;
- this.levelLastLoaded = curLevel;
- if (!startFragRequested) {
- this.setStartPosition(newDetails, sliding);
- }
- this.hls.trigger(Events.LEVEL_UPDATED, {
- details: newDetails,
- level: newLevelId
- });
- if (this.state === State.WAITING_LEVEL) {
- if (this.waitForCdnTuneIn(newDetails)) {
- return;
- }
- this.state = State.IDLE;
- }
- if (startFragRequested && newDetails.live) {
- this.synchronizeToLiveEdge(newDetails);
- }
- this.tick();
- }
- synchronizeToLiveEdge(levelDetails) {
- const {
- config,
- media
- } = this;
- if (!media) {
- return;
- }
- const liveSyncPosition = this.hls.liveSyncPosition;
- const currentTime = this.getLoadPosition();
- const start = levelDetails.fragmentStart;
- const end = levelDetails.edge;
- const withinSlidingWindow = currentTime >= start - config.maxFragLookUpTolerance && currentTime <= end;
- if (liveSyncPosition !== null && media.duration > liveSyncPosition && (currentTime < liveSyncPosition || !withinSlidingWindow)) {
- const maxLatency = config.liveMaxLatencyDuration !== void 0 ? config.liveMaxLatencyDuration : config.liveMaxLatencyDurationCount * levelDetails.targetduration;
- if (!withinSlidingWindow && media.readyState < 4 || currentTime < end - maxLatency) {
- if (!this._hasEnoughToStart) {
- this.nextLoadPosition = liveSyncPosition;
- }
- if (media.readyState) {
- this.warn(`Playback: ${currentTime.toFixed(3)} is located too far from the end of live sliding playlist: ${end}, reset currentTime to : ${liveSyncPosition.toFixed(3)}`);
- if (this.config.liveSyncMode === "buffered") {
- var _bufferInfo$buffered;
- const bufferInfo = BufferHelper.bufferInfo(media, liveSyncPosition, 0);
- if (!((_bufferInfo$buffered = bufferInfo.buffered) != null && _bufferInfo$buffered.length)) {
- media.currentTime = liveSyncPosition;
- return;
- }
- const isLiveSyncInBuffer = bufferInfo.start <= currentTime;
- if (isLiveSyncInBuffer) {
- media.currentTime = liveSyncPosition;
- return;
- }
- const {
- nextStart
- } = BufferHelper.bufferedInfo(bufferInfo.buffered, currentTime, 0);
- if (nextStart) {
- media.currentTime = nextStart;
- }
- } else {
- media.currentTime = liveSyncPosition;
- }
- }
- }
- }
- }
- _handleFragmentLoadProgress(data) {
- var _frag$initSegment;
- const frag = data.frag;
- const {
- part,
- payload
- } = data;
- const {
- levels
- } = this;
- if (!levels) {
- this.warn(`Levels were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`);
- return;
- }
- const currentLevel = levels[frag.level];
- if (!currentLevel) {
- this.warn(`Level ${frag.level} not found on progress`);
- return;
- }
- const details = currentLevel.details;
- if (!details) {
- this.warn(`Dropping fragment ${frag.sn} of level ${frag.level} after level details were reset`);
- this.fragmentTracker.removeFragment(frag);
- return;
- }
- const videoCodec = currentLevel.videoCodec;
- const accurateTimeOffset = details.PTSKnown || !details.live;
- const initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data;
- const audioCodec = this._getAudioCodec(currentLevel);
- const transmuxer = this.transmuxer = this.transmuxer || new TransmuxerInterface(this.hls, PlaylistLevelType.MAIN, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this));
- const partIndex = part ? part.index : -1;
- const partial = partIndex !== -1;
- const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial);
- const initPTS = this.initPTS[frag.cc];
- transmuxer.push(payload, initSegmentData, audioCodec, videoCodec, frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS);
- }
- onAudioTrackSwitching(event, data) {
- const hls = this.hls;
- const fromAltAudio = this.altAudio === 2;
- const altAudio = useAlternateAudio(data.url, hls);
- if (!altAudio) {
- if (this.mediaBuffer !== this.media) {
- this.log("Switching on main audio, use media.buffered to schedule main fragment loading");
- this.mediaBuffer = this.media;
- const fragCurrent = this.fragCurrent;
- if (fragCurrent) {
- this.log("Switching to main audio track, cancel main fragment load");
- fragCurrent.abortRequests();
- this.fragmentTracker.removeFragment(fragCurrent);
- }
- this.resetTransmuxer();
- this.resetLoadingState();
- } else if (this.audioOnly) {
- this.resetTransmuxer();
- }
- if (fromAltAudio) {
- this.fragmentTracker.removeAllFragments();
- hls.once(Events.BUFFER_FLUSHED, () => {
- if (!this.hls) {
- return;
- }
- this.hls.trigger(Events.AUDIO_TRACK_SWITCHED, data);
- });
- hls.trigger(Events.BUFFER_FLUSHING, {
- startOffset: 0,
- endOffset: Number.POSITIVE_INFINITY,
- type: null
- });
- return;
- }
- hls.trigger(Events.AUDIO_TRACK_SWITCHED, data);
- } else {
- this.altAudio = 1;
- }
- }
- onAudioTrackSwitched(event, data) {
- const altAudio = useAlternateAudio(data.url, this.hls);
- if (altAudio) {
- const videoBuffer = this.videoBuffer;
- if (videoBuffer && this.mediaBuffer !== videoBuffer) {
- this.log("Switching on alternate audio, use video.buffered to schedule main fragment loading");
- this.mediaBuffer = videoBuffer;
- }
- }
- this.altAudio = altAudio ? 2 : 0;
- this.tick();
- }
- onBufferCreated(event, data) {
- const tracks = data.tracks;
- let mediaTrack;
- let name;
- let alternate = false;
- for (const type in tracks) {
- const track = tracks[type];
- if (track.id === "main") {
- name = type;
- mediaTrack = track;
- if (type === "video") {
- const videoTrack = tracks[type];
- if (videoTrack) {
- this.videoBuffer = videoTrack.buffer;
- }
- }
- } else {
- alternate = true;
- }
- }
- if (alternate && mediaTrack) {
- this.log(`Alternate track found, use ${name}.buffered to schedule main fragment loading`);
- this.mediaBuffer = mediaTrack.buffer;
- } else {
- this.mediaBuffer = this.media;
- }
- }
- onFragBuffered(event, data) {
- const {
- frag,
- part
- } = data;
- const bufferedMainFragment = frag.type === PlaylistLevelType.MAIN;
- if (bufferedMainFragment) {
- if (this.fragContextChanged(frag)) {
- this.warn(`Fragment ${frag.sn}${part ? " p: " + part.index : ""} of level ${frag.level} finished buffering, but was aborted. state: ${this.state}`);
- if (this.state === State.PARSED) {
- this.state = State.IDLE;
- }
- return;
- }
- const stats = part ? part.stats : frag.stats;
- this.fragLastKbps = Math.round(8 * stats.total / (stats.buffering.end - stats.loading.first));
- if (isMediaFragment(frag)) {
- this.fragPrevious = frag;
- }
- this.fragBufferedComplete(frag, part);
- }
- const media = this.media;
- if (!media) {
- return;
- }
- if (!this._hasEnoughToStart && BufferHelper.getBuffered(media).length) {
- this._hasEnoughToStart = true;
- this.seekToStartPos();
- }
- if (bufferedMainFragment) {
- this.tick();
- }
- }
- get hasEnoughToStart() {
- return this._hasEnoughToStart;
- }
- onError(event, data) {
- var _data$context;
- if (data.fatal) {
- this.state = State.ERROR;
- return;
- }
- switch (data.details) {
- case ErrorDetails.FRAG_GAP:
- case ErrorDetails.FRAG_PARSING_ERROR:
- case ErrorDetails.FRAG_DECRYPT_ERROR:
- case ErrorDetails.FRAG_LOAD_ERROR:
- case ErrorDetails.FRAG_LOAD_TIMEOUT:
- case ErrorDetails.KEY_LOAD_ERROR:
- case ErrorDetails.KEY_LOAD_TIMEOUT:
- this.onFragmentOrKeyLoadError(PlaylistLevelType.MAIN, data);
- break;
- case ErrorDetails.LEVEL_LOAD_ERROR:
- case ErrorDetails.LEVEL_LOAD_TIMEOUT:
- case ErrorDetails.LEVEL_PARSING_ERROR:
- if (!data.levelRetry && this.state === State.WAITING_LEVEL && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.LEVEL) {
- this.state = State.IDLE;
- }
- break;
- case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
- case ErrorDetails.BUFFER_APPEND_ERROR:
- if (data.parent !== "main") {
- return;
- }
- if (this.reduceLengthAndFlushBuffer(data)) {
- this.resetLoadingState();
- }
- break;
- case ErrorDetails.BUFFER_FULL_ERROR:
- if (data.parent !== "main") {
- return;
- }
- if (this.reduceLengthAndFlushBuffer(data)) {
- const isAssetPlayer = !this.config.interstitialsController && this.config.assetPlayerId;
- if (isAssetPlayer) {
- this._hasEnoughToStart = true;
- } else {
- this.flushMainBuffer(0, Number.POSITIVE_INFINITY);
- }
- }
- break;
- case ErrorDetails.INTERNAL_EXCEPTION:
- this.recoverWorkerError(data);
- break;
- }
- }
- onFragLoadEmergencyAborted() {
- this.state = State.IDLE;
- if (!this._hasEnoughToStart) {
- this.startFragRequested = false;
- this.nextLoadPosition = this.lastCurrentTime;
- }
- this.tickImmediate();
- }
- onBufferFlushed(event, {
- type
- }) {
- if (type !== ElementaryStreamTypes.AUDIO || !this.altAudio) {
- const mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media;
- if (mediaBuffer) {
- this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);
- this.tick();
- }
- }
- }
- onLevelsUpdated(event, data) {
- if (this.level > -1 && this.fragCurrent) {
- this.level = this.fragCurrent.level;
- if (this.level === -1) {
- this.resetWhenMissingContext(this.fragCurrent);
- }
- }
- this.levels = data.levels;
- }
- swapAudioCodec() {
- this.audioCodecSwap = !this.audioCodecSwap;
- }
- /**
- * Seeks to the set startPosition if not equal to the mediaElement's current time.
- */
- seekToStartPos() {
- const {
- media
- } = this;
- if (!media) {
- return;
- }
- const currentTime = media.currentTime;
- let startPosition = this.startPosition;
- if (startPosition >= 0 && currentTime < startPosition) {
- if (media.seeking) {
- this.log(`could not seek to ${startPosition}, already seeking at ${currentTime}`);
- return;
- }
- const timelineOffset = this.timelineOffset;
- if (timelineOffset && startPosition) {
- startPosition += timelineOffset;
- }
- const details = this.getLevelDetails();
- const buffered = BufferHelper.getBuffered(media);
- const bufferStart = buffered.length ? buffered.start(0) : 0;
- const delta = bufferStart - startPosition;
- const skipTolerance = Math.max(this.config.maxBufferHole, this.config.maxFragLookUpTolerance);
- if (this.config.startOnSegmentBoundary || delta > 0 && (delta < skipTolerance || this.loadingParts && delta < 2 * ((details == null ? void 0 : details.partTarget) || 0))) {
- this.log(`adjusting start position by ${delta} to match buffer start`);
- startPosition += delta;
- this.startPosition = startPosition;
- }
- if (currentTime < startPosition) {
- this.log(`seek to target start position ${startPosition} from current time ${currentTime} buffer start ${bufferStart}`);
- media.currentTime = startPosition;
- }
- }
- }
- _getAudioCodec(currentLevel) {
- let audioCodec = this.config.defaultAudioCodec || currentLevel.audioCodec;
- if (this.audioCodecSwap && audioCodec) {
- this.log("Swapping audio codec");
- if (audioCodec.indexOf("mp4a.40.5") !== -1) {
- audioCodec = "mp4a.40.2";
- } else {
- audioCodec = "mp4a.40.5";
- }
- }
- return audioCodec;
- }
- _loadBitrateTestFrag(fragment, level) {
- fragment.bitrateTest = true;
- this._doFragLoad(fragment, level).then((data) => {
- const {
- hls
- } = this;
- const frag = data == null ? void 0 : data.frag;
- if (!frag || this.fragContextChanged(frag)) {
- return;
- }
- level.fragmentError = 0;
- this.state = State.IDLE;
- this.startFragRequested = false;
- this.bitrateTest = false;
- const stats = frag.stats;
- stats.parsing.start = stats.parsing.end = stats.buffering.start = stats.buffering.end = self.performance.now();
- hls.trigger(Events.FRAG_LOADED, data);
- frag.bitrateTest = false;
- }).catch((reason) => {
- if (this.state === State.STOPPED || this.state === State.ERROR) {
- return;
- }
- this.warn(reason);
- this.resetFragmentLoading(fragment);
- });
- }
- _handleTransmuxComplete(transmuxResult) {
- const id = this.playlistType;
- const {
- hls
- } = this;
- const {
- remuxResult,
- chunkMeta
- } = transmuxResult;
- const context = this.getCurrentContext(chunkMeta);
- if (!context) {
- this.resetWhenMissingContext(chunkMeta);
- return;
- }
- const {
- frag,
- part,
- level
- } = context;
- const {
- video,
- text,
- id3,
- initSegment
- } = remuxResult;
- const {
- details
- } = level;
- const audio = this.altAudio ? void 0 : remuxResult.audio;
- if (this.fragContextChanged(frag)) {
- this.fragmentTracker.removeFragment(frag);
- return;
- }
- this.state = State.PARSING;
- if (initSegment) {
- const tracks = initSegment.tracks;
- if (tracks) {
- const mapFragment = frag.initSegment || frag;
- if (this.unhandledEncryptionError(initSegment, frag)) {
- return;
- }
- this._bufferInitSegment(level, tracks, mapFragment, chunkMeta);
- hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, {
- frag: mapFragment,
- id,
- tracks
- });
- }
- const baseTime = initSegment.initPTS;
- const timescale = initSegment.timescale;
- const initPTS = this.initPTS[frag.cc];
- if (isFiniteNumber(baseTime) && (!initPTS || initPTS.baseTime !== baseTime || initPTS.timescale !== timescale)) {
- const trackId = initSegment.trackId;
- this.initPTS[frag.cc] = {
- baseTime,
- timescale,
- trackId
- };
- hls.trigger(Events.INIT_PTS_FOUND, {
- frag,
- id,
- initPTS: baseTime,
- timescale,
- trackId
- });
- }
- }
- if (video && details) {
- if (audio && video.type === "audiovideo") {
- this.logMuxedErr(frag);
- }
- const prevFrag = details.fragments[frag.sn - 1 - details.startSN];
- const isFirstFragment = frag.sn === details.startSN;
- const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc;
- if (remuxResult.independent !== false) {
- const {
- startPTS,
- endPTS,
- startDTS,
- endDTS
- } = video;
- if (part) {
- part.elementaryStreams[video.type] = {
- startPTS,
- endPTS,
- startDTS,
- endDTS
- };
- } else {
- if (video.firstKeyFrame && video.independent && chunkMeta.id === 1 && !isFirstInDiscontinuity) {
- this.couldBacktrack = true;
- }
- if (video.dropped && video.independent) {
- const bufferInfo = this.getMainFwdBufferInfo();
- const targetBufferTime = (bufferInfo ? bufferInfo.end : this.getLoadPosition()) + this.config.maxBufferHole;
- const startTime = video.firstKeyFramePTS ? video.firstKeyFramePTS : startPTS;
- if (!isFirstFragment && targetBufferTime < startTime - this.config.maxBufferHole && !isFirstInDiscontinuity) {
- this.backtrack(frag);
- return;
- } else if (isFirstInDiscontinuity) {
- frag.gap = true;
- }
- frag.setElementaryStreamInfo(video.type, frag.start, endPTS, frag.start, endDTS, true);
- } else if (isFirstFragment && startPTS - (details.appliedTimelineOffset || 0) > MAX_START_GAP_JUMP) {
- frag.gap = true;
- }
- }
- frag.setElementaryStreamInfo(video.type, startPTS, endPTS, startDTS, endDTS);
- if (this.backtrackFragment) {
- this.backtrackFragment = frag;
- }
- this.bufferFragmentData(video, frag, part, chunkMeta, isFirstFragment || isFirstInDiscontinuity);
- } else if (isFirstFragment || isFirstInDiscontinuity) {
- frag.gap = true;
- } else {
- this.backtrack(frag);
- return;
- }
- }
- if (audio) {
- const {
- startPTS,
- endPTS,
- startDTS,
- endDTS
- } = audio;
- if (part) {
- part.elementaryStreams[ElementaryStreamTypes.AUDIO] = {
- startPTS,
- endPTS,
- startDTS,
- endDTS
- };
- }
- frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS);
- this.bufferFragmentData(audio, frag, part, chunkMeta);
- }
- if (details && id3 != null && id3.samples.length) {
- const emittedID3 = {
- id,
- frag,
- details,
- samples: id3.samples
- };
- hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3);
- }
- if (details && text) {
- const emittedText = {
- id,
- frag,
- details,
- samples: text.samples
- };
- hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText);
- }
- }
- logMuxedErr(frag) {
- this.warn(`${isMediaFragment(frag) ? "Media" : "Init"} segment with muxed audiovideo where only video expected: ${frag.url}`);
- }
- _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) {
- if (this.state !== State.PARSING) {
- return;
- }
- this.audioOnly = !!tracks.audio && !tracks.video;
- if (this.altAudio && !this.audioOnly) {
- delete tracks.audio;
- if (tracks.audiovideo) {
- this.logMuxedErr(frag);
- }
- }
- const {
- audio,
- video,
- audiovideo
- } = tracks;
- if (audio) {
- const levelCodec = currentLevel.audioCodec;
- let audioCodec = pickMostCompleteCodecName(audio.codec, levelCodec);
- if (audioCodec === "mp4a") {
- audioCodec = "mp4a.40.5";
- }
- const ua = navigator.userAgent.toLowerCase();
- if (this.audioCodecSwitch) {
- if (audioCodec) {
- if (audioCodec.indexOf("mp4a.40.5") !== -1) {
- audioCodec = "mp4a.40.2";
- } else {
- audioCodec = "mp4a.40.5";
- }
- }
- const audioMetadata = audio.metadata;
- if (audioMetadata && "channelCount" in audioMetadata && (audioMetadata.channelCount || 1) !== 1 && ua.indexOf("firefox") === -1) {
- audioCodec = "mp4a.40.5";
- }
- }
- if (audioCodec && audioCodec.indexOf("mp4a.40.5") !== -1 && ua.indexOf("android") !== -1 && audio.container !== "audio/mpeg") {
- audioCodec = "mp4a.40.2";
- this.log(`Android: force audio codec to ${audioCodec}`);
- }
- if (levelCodec && levelCodec !== audioCodec) {
- this.log(`Swapping manifest audio codec "${levelCodec}" for "${audioCodec}"`);
- }
- audio.levelCodec = audioCodec;
- audio.id = PlaylistLevelType.MAIN;
- this.log(`Init audio buffer, container:${audio.container}, codecs[selected/level/parsed]=[${audioCodec || ""}/${levelCodec || ""}/${audio.codec}]`);
- delete tracks.audiovideo;
- }
- if (video) {
- video.levelCodec = currentLevel.videoCodec;
- video.id = PlaylistLevelType.MAIN;
- const parsedVideoCodec = video.codec;
- if ((parsedVideoCodec == null ? void 0 : parsedVideoCodec.length) === 4) {
- switch (parsedVideoCodec) {
- case "hvc1":
- case "hev1":
- video.codec = "hvc1.1.6.L120.90";
- break;
- case "av01":
- video.codec = "av01.0.04M.08";
- break;
- case "avc1":
- video.codec = "avc1.42e01e";
- break;
- }
- }
- this.log(`Init video buffer, container:${video.container}, codecs[level/parsed]=[${currentLevel.videoCodec || ""}/${parsedVideoCodec}]${video.codec !== parsedVideoCodec ? " parsed-corrected=" + video.codec : ""}${video.supplemental ? " supplemental=" + video.supplemental : ""}`);
- delete tracks.audiovideo;
- }
- if (audiovideo) {
- this.log(`Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.codecs}/${audiovideo.codec}]`);
- delete tracks.video;
- delete tracks.audio;
- }
- const trackTypes = Object.keys(tracks);
- if (trackTypes.length) {
- this.hls.trigger(Events.BUFFER_CODECS, tracks);
- if (!this.hls) {
- return;
- }
- trackTypes.forEach((trackName) => {
- const track = tracks[trackName];
- const initSegment = track.initSegment;
- if (initSegment != null && initSegment.byteLength) {
- this.hls.trigger(Events.BUFFER_APPENDING, {
- type: trackName,
- data: initSegment,
- frag,
- part: null,
- chunkMeta,
- parent: frag.type
- });
- }
- });
- }
- this.tickImmediate();
- }
- getMainFwdBufferInfo() {
- const bufferOutput = this.mediaBuffer && this.altAudio === 2 ? this.mediaBuffer : this.media;
- return this.getFwdBufferInfo(bufferOutput, PlaylistLevelType.MAIN);
- }
- get maxBufferLength() {
- const {
- levels,
- level
- } = this;
- const levelInfo = levels == null ? void 0 : levels[level];
- if (!levelInfo) {
- return this.config.maxBufferLength;
- }
- return this.getMaxBufferLength(levelInfo.maxBitrate);
- }
- backtrack(frag) {
- this.couldBacktrack = true;
- this.backtrackFragment = frag;
- this.resetTransmuxer();
- this.flushBufferGap(frag);
- this.fragmentTracker.removeFragment(frag);
- this.fragPrevious = null;
- this.nextLoadPosition = frag.start;
- this.state = State.IDLE;
- }
- checkFragmentChanged() {
- const video = this.media;
- let fragPlayingCurrent = null;
- if (video && video.readyState > 1 && video.seeking === false) {
- const currentTime = video.currentTime;
- if (BufferHelper.isBuffered(video, currentTime)) {
- fragPlayingCurrent = this.getAppendedFrag(currentTime);
- } else if (BufferHelper.isBuffered(video, currentTime + 0.1)) {
- fragPlayingCurrent = this.getAppendedFrag(currentTime + 0.1);
- }
- if (fragPlayingCurrent) {
- this.backtrackFragment = null;
- const fragPlaying = this.fragPlaying;
- const fragCurrentLevel = fragPlayingCurrent.level;
- if (!fragPlaying || fragPlayingCurrent.sn !== fragPlaying.sn || fragPlaying.level !== fragCurrentLevel) {
- this.fragPlaying = fragPlayingCurrent;
- this.hls.trigger(Events.FRAG_CHANGED, {
- frag: fragPlayingCurrent
- });
- if (!fragPlaying || fragPlaying.level !== fragCurrentLevel) {
- this.hls.trigger(Events.LEVEL_SWITCHED, {
- level: fragCurrentLevel
- });
- }
- }
- }
- }
- }
- get nextLevel() {
- const frag = this.nextBufferedFrag;
- if (frag) {
- return frag.level;
- }
- return -1;
- }
- get currentFrag() {
- var _this$media2;
- if (this.fragPlaying) {
- return this.fragPlaying;
- }
- const currentTime = ((_this$media2 = this.media) == null ? void 0 : _this$media2.currentTime) || this.lastCurrentTime;
- if (isFiniteNumber(currentTime)) {
- return this.getAppendedFrag(currentTime);
- }
- return null;
- }
- get currentProgramDateTime() {
- var _this$media3;
- const currentTime = ((_this$media3 = this.media) == null ? void 0 : _this$media3.currentTime) || this.lastCurrentTime;
- if (isFiniteNumber(currentTime)) {
- const details = this.getLevelDetails();
- const frag = this.currentFrag || (details ? findFragmentByPTS(null, details.fragments, currentTime) : null);
- if (frag) {
- const programDateTime = frag.programDateTime;
- if (programDateTime !== null) {
- const epocMs = programDateTime + (currentTime - frag.start) * 1e3;
- return new Date(epocMs);
- }
- }
- }
- return null;
- }
- get currentLevel() {
- const frag = this.currentFrag;
- if (frag) {
- return frag.level;
- }
- return -1;
- }
- get nextBufferedFrag() {
- const frag = this.currentFrag;
- if (frag) {
- return this.followingBufferedFrag(frag);
- }
- return null;
- }
- get forceStartLoad() {
- return this._forceStartLoad;
- }
- };
- var KeyLoader = class extends Logger {
- constructor(config, logger2) {
- super("key-loader", logger2);
- this.config = void 0;
- this.keyIdToKeyInfo = {};
- this.emeController = null;
- this.config = config;
- }
- abort(type) {
- for (const id in this.keyIdToKeyInfo) {
- const loader = this.keyIdToKeyInfo[id].loader;
- if (loader) {
- var _loader$context;
- if (type && type !== ((_loader$context = loader.context) == null ? void 0 : _loader$context.frag.type)) {
- return;
- }
- loader.abort();
- }
- }
- }
- detach() {
- for (const id in this.keyIdToKeyInfo) {
- const keyInfo = this.keyIdToKeyInfo[id];
- if (keyInfo.mediaKeySessionContext || keyInfo.decryptdata.isCommonEncryption) {
- delete this.keyIdToKeyInfo[id];
- }
- }
- }
- destroy() {
- this.detach();
- for (const id in this.keyIdToKeyInfo) {
- const loader = this.keyIdToKeyInfo[id].loader;
- if (loader) {
- loader.destroy();
- }
- }
- this.keyIdToKeyInfo = {};
- }
- createKeyLoadError(frag, details = ErrorDetails.KEY_LOAD_ERROR, error, networkDetails, response) {
- return new LoadError({
- type: ErrorTypes.NETWORK_ERROR,
- details,
- fatal: false,
- frag,
- response,
- error,
- networkDetails
- });
- }
- loadClear(loadingFrag, encryptedFragments, startFragRequested) {
- if (this.emeController && this.config.emeEnabled && !this.emeController.getSelectedKeySystemFormats().length) {
- if (encryptedFragments.length) {
- for (let i = 0, l = encryptedFragments.length; i < l; i++) {
- const frag = encryptedFragments[i];
- if (loadingFrag.cc <= frag.cc && (!isMediaFragment(loadingFrag) || !isMediaFragment(frag) || loadingFrag.sn < frag.sn) || !startFragRequested && i == l - 1) {
- return this.emeController.selectKeySystemFormat(frag).then((keySystemFormat) => {
- if (!this.emeController) {
- return;
- }
- frag.setKeyFormat(keySystemFormat);
- const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat);
- if (keySystem) {
- return this.emeController.getKeySystemAccess([keySystem]);
- }
- });
- }
- }
- }
- if (this.config.requireKeySystemAccessOnStart) {
- const keySystemsInConfig = getKeySystemsForConfig(this.config);
- if (keySystemsInConfig.length) {
- return this.emeController.getKeySystemAccess(keySystemsInConfig);
- }
- }
- }
- return null;
- }
- load(frag) {
- if (!frag.decryptdata && frag.encrypted && this.emeController && this.config.emeEnabled) {
- return this.emeController.selectKeySystemFormat(frag).then((keySystemFormat) => {
- return this.loadInternal(frag, keySystemFormat);
- });
- }
- return this.loadInternal(frag);
- }
- loadInternal(frag, keySystemFormat) {
- var _keyInfo, _keyInfo2;
- if (keySystemFormat) {
- frag.setKeyFormat(keySystemFormat);
- }
- const decryptdata = frag.decryptdata;
- if (!decryptdata) {
- const error = new Error(keySystemFormat ? `Expected frag.decryptdata to be defined after setting format ${keySystemFormat}` : `Missing decryption data on fragment in onKeyLoading (emeEnabled with controller: ${this.emeController && this.config.emeEnabled})`);
- return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, error));
- }
- const uri = decryptdata.uri;
- if (!uri) {
- return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Invalid key URI: "${uri}"`)));
- }
- const id = getKeyId(decryptdata);
- let keyInfo = this.keyIdToKeyInfo[id];
- if ((_keyInfo = keyInfo) != null && _keyInfo.decryptdata.key) {
- decryptdata.key = keyInfo.decryptdata.key;
- return Promise.resolve({
- frag,
- keyInfo
- });
- }
- if (this.emeController && (_keyInfo2 = keyInfo) != null && _keyInfo2.keyLoadPromise) {
- const keyStatus = this.emeController.getKeyStatus(keyInfo.decryptdata);
- switch (keyStatus) {
- case "usable":
- case "usable-in-future":
- return keyInfo.keyLoadPromise.then((keyLoadedData) => {
- const {
- keyInfo: keyInfo2
- } = keyLoadedData;
- decryptdata.key = keyInfo2.decryptdata.key;
- return {
- frag,
- keyInfo: keyInfo2
- };
- });
- }
- }
- this.log(`${this.keyIdToKeyInfo[id] ? "Rel" : "L"}oading${decryptdata.keyId ? " keyId: " + arrayToHex(decryptdata.keyId) : ""} URI: ${decryptdata.uri} from ${frag.type} ${frag.level}`);
- keyInfo = this.keyIdToKeyInfo[id] = {
- decryptdata,
- keyLoadPromise: null,
- loader: null,
- mediaKeySessionContext: null
- };
- switch (decryptdata.method) {
- case "SAMPLE-AES":
- case "SAMPLE-AES-CENC":
- case "SAMPLE-AES-CTR":
- if (decryptdata.keyFormat === "identity") {
- return this.loadKeyHTTP(keyInfo, frag);
- }
- return this.loadKeyEME(keyInfo, frag);
- case "AES-128":
- case "AES-256":
- case "AES-256-CTR":
- return this.loadKeyHTTP(keyInfo, frag);
- default:
- return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
- }
- }
- loadKeyEME(keyInfo, frag) {
- const keyLoadedData = {
- frag,
- keyInfo
- };
- if (this.emeController && this.config.emeEnabled) {
- var _frag$initSegment;
- if (!keyInfo.decryptdata.keyId && (_frag$initSegment = frag.initSegment) != null && _frag$initSegment.data) {
- const keyIds = parseKeyIdsFromTenc(frag.initSegment.data);
- if (keyIds.length) {
- const keyId = keyIds[0];
- if (keyId.some((b) => b !== 0)) {
- this.log(`Using keyId found in init segment ${arrayToHex(keyId)}`);
- keyInfo.decryptdata.keyId = keyId;
- LevelKey.setKeyIdForUri(keyInfo.decryptdata.uri, keyId);
- }
- }
- }
- const keySessionContextPromise = this.emeController.loadKey(keyLoadedData);
- return (keyInfo.keyLoadPromise = keySessionContextPromise.then((keySessionContext) => {
- keyInfo.mediaKeySessionContext = keySessionContext;
- return keyLoadedData;
- })).catch((error) => {
- keyInfo.keyLoadPromise = null;
- if ("data" in error) {
- error.data.frag = frag;
- }
- throw error;
- });
- }
- return Promise.resolve(keyLoadedData);
- }
- loadKeyHTTP(keyInfo, frag) {
- const config = this.config;
- const Loader = config.loader;
- const keyLoader = new Loader(config);
- frag.keyLoader = keyInfo.loader = keyLoader;
- return keyInfo.keyLoadPromise = new Promise((resolve, reject) => {
- const loaderContext = {
- keyInfo,
- frag,
- responseType: "arraybuffer",
- url: keyInfo.decryptdata.uri
- };
- const loadPolicy = config.keyLoadPolicy.default;
- const loaderConfig = {
- loadPolicy,
- timeout: loadPolicy.maxLoadTimeMs,
- maxRetry: 0,
- retryDelay: 0,
- maxRetryDelay: 0
- };
- const loaderCallbacks = {
- onSuccess: (response, stats, context, networkDetails) => {
- const {
- frag: frag2,
- keyInfo: keyInfo2
- } = context;
- const id = getKeyId(keyInfo2.decryptdata);
- if (!frag2.decryptdata || keyInfo2 !== this.keyIdToKeyInfo[id]) {
- return reject(this.createKeyLoadError(frag2, ErrorDetails.KEY_LOAD_ERROR, new Error("after key load, decryptdata unset or changed"), networkDetails));
- }
- keyInfo2.decryptdata.key = frag2.decryptdata.key = new Uint8Array(response.data);
- frag2.keyLoader = null;
- keyInfo2.loader = null;
- resolve({
- frag: frag2,
- keyInfo: keyInfo2
- });
- },
- onError: (response, context, networkDetails, stats) => {
- this.resetLoader(context);
- reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`HTTP Error ${response.code} loading key ${response.text}`), networkDetails, _objectSpread2({
- url: loaderContext.url,
- data: void 0
- }, response)));
- },
- onTimeout: (stats, context, networkDetails) => {
- this.resetLoader(context);
- reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_TIMEOUT, new Error("key loading timed out"), networkDetails));
- },
- onAbort: (stats, context, networkDetails) => {
- this.resetLoader(context);
- reject(this.createKeyLoadError(frag, ErrorDetails.INTERNAL_ABORTED, new Error("key loading aborted"), networkDetails));
- }
- };
- keyLoader.load(loaderContext, loaderConfig, loaderCallbacks);
- });
- }
- resetLoader(context) {
- const {
- frag,
- keyInfo,
- url: uri
- } = context;
- const loader = keyInfo.loader;
- if (frag.keyLoader === loader) {
- frag.keyLoader = null;
- keyInfo.loader = null;
- }
- const id = getKeyId(keyInfo.decryptdata) || uri;
- delete this.keyIdToKeyInfo[id];
- if (loader) {
- loader.destroy();
- }
- }
- };
- function getKeyId(decryptdata) {
- if (decryptdata.keyFormat !== KeySystemFormats.FAIRPLAY) {
- const keyId = decryptdata.keyId;
- if (keyId) {
- return arrayToHex(keyId);
- }
- }
- return decryptdata.uri;
- }
- function mapContextToLevelType(context) {
- const {
- type
- } = context;
- switch (type) {
- case PlaylistContextType.AUDIO_TRACK:
- return PlaylistLevelType.AUDIO;
- case PlaylistContextType.SUBTITLE_TRACK:
- return PlaylistLevelType.SUBTITLE;
- default:
- return PlaylistLevelType.MAIN;
- }
- }
- function getResponseUrl(response, context) {
- let url = response.url;
- if (url === void 0 || url.indexOf("data:") === 0) {
- url = context.url;
- }
- return url;
- }
- var PlaylistLoader = class {
- constructor(hls) {
- this.hls = void 0;
- this.loaders = /* @__PURE__ */ Object.create(null);
- this.variableList = null;
- this.onManifestLoaded = this.checkAutostartLoad;
- this.hls = hls;
- this.registerListeners();
- }
- startLoad(startPosition) {
- }
- stopLoad() {
- this.destroyInternalLoaders();
- }
- registerListeners() {
- const {
- hls
- } = this;
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
- hls.on(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
- hls.on(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
- hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
- }
- unregisterListeners() {
- const {
- hls
- } = this;
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
- hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
- hls.off(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
- hls.off(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
- hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
- }
- /**
- * Returns defaults or configured loader-type overloads (pLoader and loader config params)
- */
- createInternalLoader(context) {
- const config = this.hls.config;
- const PLoader = config.pLoader;
- const Loader = config.loader;
- const InternalLoader = PLoader || Loader;
- const loader = new InternalLoader(config);
- this.loaders[context.type] = loader;
- return loader;
- }
- getInternalLoader(context) {
- return this.loaders[context.type];
- }
- resetInternalLoader(contextType) {
- if (this.loaders[contextType]) {
- delete this.loaders[contextType];
- }
- }
- /**
- * Call `destroy` on all internal loader instances mapped (one per context type)
- */
- destroyInternalLoaders() {
- for (const contextType in this.loaders) {
- const loader = this.loaders[contextType];
- if (loader) {
- loader.destroy();
- }
- this.resetInternalLoader(contextType);
- }
- }
- destroy() {
- this.variableList = null;
- this.unregisterListeners();
- this.destroyInternalLoaders();
- }
- onManifestLoading(event, data) {
- const {
- url
- } = data;
- this.variableList = null;
- this.load({
- id: null,
- level: 0,
- responseType: "text",
- type: PlaylistContextType.MANIFEST,
- url,
- deliveryDirectives: null,
- levelOrTrack: null
- });
- }
- onLevelLoading(event, data) {
- const {
- id,
- level,
- pathwayId,
- url,
- deliveryDirectives,
- levelInfo
- } = data;
- this.load({
- id,
- level,
- pathwayId,
- responseType: "text",
- type: PlaylistContextType.LEVEL,
- url,
- deliveryDirectives,
- levelOrTrack: levelInfo
- });
- }
- onAudioTrackLoading(event, data) {
- const {
- id,
- groupId,
- url,
- deliveryDirectives,
- track
- } = data;
- this.load({
- id,
- groupId,
- level: null,
- responseType: "text",
- type: PlaylistContextType.AUDIO_TRACK,
- url,
- deliveryDirectives,
- levelOrTrack: track
- });
- }
- onSubtitleTrackLoading(event, data) {
- const {
- id,
- groupId,
- url,
- deliveryDirectives,
- track
- } = data;
- this.load({
- id,
- groupId,
- level: null,
- responseType: "text",
- type: PlaylistContextType.SUBTITLE_TRACK,
- url,
- deliveryDirectives,
- levelOrTrack: track
- });
- }
- onLevelsUpdated(event, data) {
- const loader = this.loaders[PlaylistContextType.LEVEL];
- if (loader) {
- const context = loader.context;
- if (context && !data.levels.some((lvl) => lvl === context.levelOrTrack)) {
- loader.abort();
- delete this.loaders[PlaylistContextType.LEVEL];
- }
- }
- }
- load(context) {
- var _context$deliveryDire;
- const config = this.hls.config;
- let loader = this.getInternalLoader(context);
- if (loader) {
- const logger2 = this.hls.logger;
- const loaderContext = loader.context;
- if (loaderContext && loaderContext.levelOrTrack === context.levelOrTrack && (loaderContext.url === context.url || loaderContext.deliveryDirectives && !context.deliveryDirectives)) {
- if (loaderContext.url === context.url) {
- logger2.log(`[playlist-loader]: ignore ${context.url} ongoing request`);
- } else {
- logger2.log(`[playlist-loader]: ignore ${context.url} in favor of ${loaderContext.url}`);
- }
- return;
- }
- logger2.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
- loader.abort();
- }
- let loadPolicy;
- if (context.type === PlaylistContextType.MANIFEST) {
- loadPolicy = config.manifestLoadPolicy.default;
- } else {
- loadPolicy = _extends({}, config.playlistLoadPolicy.default, {
- timeoutRetry: null,
- errorRetry: null
- });
- }
- loader = this.createInternalLoader(context);
- if (isFiniteNumber((_context$deliveryDire = context.deliveryDirectives) == null ? void 0 : _context$deliveryDire.part)) {
- let levelDetails;
- if (context.type === PlaylistContextType.LEVEL && context.level !== null) {
- levelDetails = this.hls.levels[context.level].details;
- } else if (context.type === PlaylistContextType.AUDIO_TRACK && context.id !== null) {
- levelDetails = this.hls.audioTracks[context.id].details;
- } else if (context.type === PlaylistContextType.SUBTITLE_TRACK && context.id !== null) {
- levelDetails = this.hls.subtitleTracks[context.id].details;
- }
- if (levelDetails) {
- const partTarget = levelDetails.partTarget;
- const targetDuration = levelDetails.targetduration;
- if (partTarget && targetDuration) {
- const maxLowLatencyPlaylistRefresh = Math.max(partTarget * 3, targetDuration * 0.8) * 1e3;
- loadPolicy = _extends({}, loadPolicy, {
- maxTimeToFirstByteMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs),
- maxLoadTimeMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs)
- });
- }
- }
- }
- const legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {};
- const loaderConfig = {
- loadPolicy,
- timeout: loadPolicy.maxLoadTimeMs,
- maxRetry: legacyRetryCompatibility.maxNumRetry || 0,
- retryDelay: legacyRetryCompatibility.retryDelayMs || 0,
- maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0
- };
- const loaderCallbacks = {
- onSuccess: (response, stats, context2, networkDetails) => {
- const loader2 = this.getInternalLoader(context2);
- this.resetInternalLoader(context2.type);
- const string = response.data;
- stats.parsing.start = performance.now();
- if (M3U8Parser.isMediaPlaylist(string) || context2.type !== PlaylistContextType.MANIFEST) {
- this.handleTrackOrLevelPlaylist(response, stats, context2, networkDetails || null, loader2);
- } else {
- this.handleMasterPlaylist(response, stats, context2, networkDetails);
- }
- },
- onError: (response, context2, networkDetails, stats) => {
- this.handleNetworkError(context2, networkDetails, false, response, stats);
- },
- onTimeout: (stats, context2, networkDetails) => {
- this.handleNetworkError(context2, networkDetails, true, void 0, stats);
- }
- };
- loader.load(context, loaderConfig, loaderCallbacks);
- }
- checkAutostartLoad() {
- if (!this.hls) {
- return;
- }
- const {
- config: {
- autoStartLoad,
- startPosition
- },
- forceStartLoad
- } = this.hls;
- if (autoStartLoad || forceStartLoad) {
- this.hls.logger.log(`${autoStartLoad ? "auto" : "force"} startLoad with configured startPosition ${startPosition}`);
- this.hls.startLoad(startPosition);
- }
- }
- handleMasterPlaylist(response, stats, context, networkDetails) {
- const hls = this.hls;
- const string = response.data;
- const url = getResponseUrl(response, context);
- const parsedResult = M3U8Parser.parseMasterPlaylist(string, url);
- if (parsedResult.playlistParsingError) {
- stats.parsing.end = performance.now();
- this.handleManifestParsingError(response, context, parsedResult.playlistParsingError, networkDetails, stats);
- return;
- }
- const {
- contentSteering,
- levels,
- sessionData,
- sessionKeys,
- startTimeOffset,
- variableList
- } = parsedResult;
- this.variableList = variableList;
- levels.forEach((levelParsed) => {
- const {
- unknownCodecs
- } = levelParsed;
- if (unknownCodecs) {
- const {
- preferManagedMediaSource
- } = this.hls.config;
- let {
- audioCodec,
- videoCodec
- } = levelParsed;
- for (let i = unknownCodecs.length; i--; ) {
- const unknownCodec = unknownCodecs[i];
- if (areCodecsMediaSourceSupported(unknownCodec, "audio", preferManagedMediaSource)) {
- levelParsed.audioCodec = audioCodec = audioCodec ? `${audioCodec},${unknownCodec}` : unknownCodec;
- sampleEntryCodesISO.audio[audioCodec.substring(0, 4)] = 2;
- unknownCodecs.splice(i, 1);
- } else if (areCodecsMediaSourceSupported(unknownCodec, "video", preferManagedMediaSource)) {
- levelParsed.videoCodec = videoCodec = videoCodec ? `${videoCodec},${unknownCodec}` : unknownCodec;
- sampleEntryCodesISO.video[videoCodec.substring(0, 4)] = 2;
- unknownCodecs.splice(i, 1);
- }
- }
- }
- });
- const {
- AUDIO: audioTracks = [],
- SUBTITLES: subtitles,
- "CLOSED-CAPTIONS": captions
- } = M3U8Parser.parseMasterPlaylistMedia(string, url, parsedResult);
- if (audioTracks.length) {
- const embeddedAudioFound = audioTracks.some((audioTrack) => !audioTrack.url);
- if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
- this.hls.logger.log("[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one");
- audioTracks.unshift({
- type: "main",
- name: "main",
- groupId: "main",
- default: false,
- autoselect: false,
- forced: false,
- id: -1,
- attrs: new AttrList({}),
- bitrate: 0,
- url: ""
- });
- }
- }
- hls.trigger(Events.MANIFEST_LOADED, {
- levels,
- audioTracks,
- subtitles,
- captions,
- contentSteering,
- url,
- stats,
- networkDetails,
- sessionData,
- sessionKeys,
- startTimeOffset,
- variableList
- });
- }
- handleTrackOrLevelPlaylist(response, stats, context, networkDetails, loader) {
- const hls = this.hls;
- const {
- id,
- level,
- type
- } = context;
- const url = getResponseUrl(response, context);
- const levelId = isFiniteNumber(level) ? level : isFiniteNumber(id) ? id : 0;
- const levelType = mapContextToLevelType(context);
- const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, 0, this.variableList);
- if (type === PlaylistContextType.MANIFEST) {
- const singleLevel = {
- attrs: new AttrList({}),
- bitrate: 0,
- details: levelDetails,
- name: "",
- url
- };
- levelDetails.requestScheduled = stats.loading.start + computeReloadInterval(levelDetails, 0);
- hls.trigger(Events.MANIFEST_LOADED, {
- levels: [singleLevel],
- audioTracks: [],
- url,
- stats,
- networkDetails,
- sessionData: null,
- sessionKeys: null,
- contentSteering: null,
- startTimeOffset: null,
- variableList: null
- });
- }
- stats.parsing.end = performance.now();
- context.levelDetails = levelDetails;
- this.handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader);
- }
- handleManifestParsingError(response, context, error, networkDetails, stats) {
- this.hls.trigger(Events.ERROR, {
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.MANIFEST_PARSING_ERROR,
- fatal: context.type === PlaylistContextType.MANIFEST,
- url: response.url,
- err: error,
- error,
- reason: error.message,
- response,
- context,
- networkDetails,
- stats
- });
- }
- handleNetworkError(context, networkDetails, timeout = false, response, stats) {
- let message = `A network ${timeout ? "timeout" : "error" + (response ? " (status " + response.code + ")" : "")} occurred while loading ${context.type}`;
- if (context.type === PlaylistContextType.LEVEL) {
- message += `: ${context.level} id: ${context.id}`;
- } else if (context.type === PlaylistContextType.AUDIO_TRACK || context.type === PlaylistContextType.SUBTITLE_TRACK) {
- message += ` id: ${context.id} group-id: "${context.groupId}"`;
- }
- const error = new Error(message);
- this.hls.logger.warn(`[playlist-loader]: ${message}`);
- let details = ErrorDetails.UNKNOWN;
- let fatal = false;
- const loader = this.getInternalLoader(context);
- switch (context.type) {
- case PlaylistContextType.MANIFEST:
- details = timeout ? ErrorDetails.MANIFEST_LOAD_TIMEOUT : ErrorDetails.MANIFEST_LOAD_ERROR;
- fatal = true;
- break;
- case PlaylistContextType.LEVEL:
- details = timeout ? ErrorDetails.LEVEL_LOAD_TIMEOUT : ErrorDetails.LEVEL_LOAD_ERROR;
- fatal = false;
- break;
- case PlaylistContextType.AUDIO_TRACK:
- details = timeout ? ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT : ErrorDetails.AUDIO_TRACK_LOAD_ERROR;
- fatal = false;
- break;
- case PlaylistContextType.SUBTITLE_TRACK:
- details = timeout ? ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT : ErrorDetails.SUBTITLE_LOAD_ERROR;
- fatal = false;
- break;
- }
- if (loader) {
- this.resetInternalLoader(context.type);
- }
- const errorData = {
- type: ErrorTypes.NETWORK_ERROR,
- details,
- fatal,
- url: context.url,
- loader,
- context,
- error,
- networkDetails,
- stats
- };
- if (response) {
- const url = (networkDetails == null ? void 0 : networkDetails.url) || context.url;
- errorData.response = _objectSpread2({
- url,
- data: void 0
- }, response);
- }
- this.hls.trigger(Events.ERROR, errorData);
- }
- handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader) {
- const hls = this.hls;
- const {
- type,
- level,
- levelOrTrack,
- id,
- groupId,
- deliveryDirectives
- } = context;
- const url = getResponseUrl(response, context);
- const parent = mapContextToLevelType(context);
- let levelIndex = typeof context.level === "number" && parent === PlaylistLevelType.MAIN ? level : void 0;
- const error = levelDetails.playlistParsingError;
- if (error) {
- this.hls.logger.warn(`${error} ${levelDetails.url}`);
- if (!hls.config.ignorePlaylistParsingErrors) {
- hls.trigger(Events.ERROR, {
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.LEVEL_PARSING_ERROR,
- fatal: false,
- url,
- error,
- reason: error.message,
- response,
- context,
- level: levelIndex,
- parent,
- networkDetails,
- stats
- });
- return;
- }
- levelDetails.playlistParsingError = null;
- }
- if (!levelDetails.fragments.length) {
- const _error = levelDetails.playlistParsingError = new Error("No Segments found in Playlist");
- hls.trigger(Events.ERROR, {
- type: ErrorTypes.NETWORK_ERROR,
- details: ErrorDetails.LEVEL_EMPTY_ERROR,
- fatal: false,
- url,
- error: _error,
- reason: _error.message,
- response,
- context,
- level: levelIndex,
- parent,
- networkDetails,
- stats
- });
- return;
- }
- if (levelDetails.live && loader) {
- if (loader.getCacheAge) {
- levelDetails.ageHeader = loader.getCacheAge() || 0;
- }
- if (!loader.getCacheAge || isNaN(levelDetails.ageHeader)) {
- levelDetails.ageHeader = 0;
- }
- }
- switch (type) {
- case PlaylistContextType.MANIFEST:
- case PlaylistContextType.LEVEL:
- if (levelIndex) {
- if (!levelOrTrack) {
- levelIndex = 0;
- } else {
- if (levelOrTrack !== hls.levels[levelIndex]) {
- const updatedIndex = hls.levels.indexOf(levelOrTrack);
- if (updatedIndex > -1) {
- levelIndex = updatedIndex;
- }
- }
- }
- }
- hls.trigger(Events.LEVEL_LOADED, {
- details: levelDetails,
- levelInfo: levelOrTrack || hls.levels[0],
- level: levelIndex || 0,
- id: id || 0,
- stats,
- networkDetails,
- deliveryDirectives,
- withoutMultiVariant: type === PlaylistContextType.MANIFEST
- });
- break;
- case PlaylistContextType.AUDIO_TRACK:
- hls.trigger(Events.AUDIO_TRACK_LOADED, {
- details: levelDetails,
- track: levelOrTrack,
- id: id || 0,
- groupId: groupId || "",
- stats,
- networkDetails,
- deliveryDirectives
- });
- break;
- case PlaylistContextType.SUBTITLE_TRACK:
- hls.trigger(Events.SUBTITLE_TRACK_LOADED, {
- details: levelDetails,
- track: levelOrTrack,
- id: id || 0,
- groupId: groupId || "",
- stats,
- networkDetails,
- deliveryDirectives
- });
- break;
- }
- }
- };
- var Hls = class _Hls {
- /**
- * Get the video-dev/hls.js package version.
- */
- static get version() {
- return version;
- }
- /**
- * Check if the required MediaSource Extensions are available.
- */
- static isMSESupported() {
- return isMSESupported();
- }
- /**
- * Check if MediaSource Extensions are available and isTypeSupported checks pass for any baseline codecs.
- */
- static isSupported() {
- return isSupported();
- }
- /**
- * Get the MediaSource global used for MSE playback (ManagedMediaSource, MediaSource, or WebKitMediaSource).
- */
- static getMediaSource() {
- return getMediaSource();
- }
- static get Events() {
- return Events;
- }
- static get MetadataSchema() {
- return MetadataSchema;
- }
- static get ErrorTypes() {
- return ErrorTypes;
- }
- static get ErrorDetails() {
- return ErrorDetails;
- }
- /**
- * Get the default configuration applied to new instances.
- */
- static get DefaultConfig() {
- if (!_Hls.defaultConfig) {
- return hlsDefaultConfig;
- }
- return _Hls.defaultConfig;
- }
- /**
- * Replace the default configuration applied to new instances.
- */
- static set DefaultConfig(defaultConfig) {
- _Hls.defaultConfig = defaultConfig;
- }
- /**
- * Creates an instance of an HLS client that can attach to exactly one `HTMLMediaElement`.
- * @param userConfig - Configuration options applied over `Hls.DefaultConfig`
- */
- constructor(userConfig = {}) {
- this.config = void 0;
- this.userConfig = void 0;
- this.logger = void 0;
- this.coreComponents = void 0;
- this.networkControllers = void 0;
- this._emitter = new EventEmitter();
- this._autoLevelCapping = -1;
- this._maxHdcpLevel = null;
- this.abrController = void 0;
- this.bufferController = void 0;
- this.capLevelController = void 0;
- this.latencyController = void 0;
- this.levelController = void 0;
- this.streamController = void 0;
- this.audioStreamController = void 0;
- this.subtititleStreamController = void 0;
- this.audioTrackController = void 0;
- this.subtitleTrackController = void 0;
- this.interstitialsController = void 0;
- this.gapController = void 0;
- this.emeController = void 0;
- this.cmcdController = void 0;
- this._media = null;
- this._url = null;
- this._sessionId = void 0;
- this.triggeringException = void 0;
- this.started = false;
- const logger2 = this.logger = enableLogs(userConfig.debug || false, "Hls instance", userConfig.assetPlayerId);
- const config = this.config = mergeConfig(_Hls.DefaultConfig, userConfig, logger2);
- this.userConfig = userConfig;
- if (config.progressive) {
- enableStreamingMode(config, logger2);
- }
- const {
- abrController: _AbrController,
- bufferController: _BufferController,
- capLevelController: _CapLevelController,
- errorController: _ErrorController,
- fpsController: _FpsController
- } = config;
- const errorController = new _ErrorController(this);
- const abrController = this.abrController = new _AbrController(this);
- const fragmentTracker = new FragmentTracker(this);
- const _InterstitialsController = config.interstitialsController;
- const interstitialsController = _InterstitialsController ? this.interstitialsController = new _InterstitialsController(this, _Hls) : null;
- const bufferController = this.bufferController = new _BufferController(this, fragmentTracker);
- const capLevelController = this.capLevelController = new _CapLevelController(this);
- const fpsController = new _FpsController(this);
- const playListLoader = new PlaylistLoader(this);
- const _ContentSteeringController = config.contentSteeringController;
- const contentSteering = _ContentSteeringController ? new _ContentSteeringController(this) : null;
- const levelController = this.levelController = new LevelController(this, contentSteering);
- const id3TrackController = new ID3TrackController(this);
- const keyLoader = new KeyLoader(this.config, this.logger);
- const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
- const gapController = this.gapController = new GapController(this, fragmentTracker);
- capLevelController.setStreamController(streamController);
- fpsController.setStreamController(streamController);
- const networkControllers = [playListLoader, levelController, streamController];
- if (interstitialsController) {
- networkControllers.splice(1, 0, interstitialsController);
- }
- if (contentSteering) {
- networkControllers.splice(1, 0, contentSteering);
- }
- this.networkControllers = networkControllers;
- const coreComponents = [abrController, bufferController, gapController, capLevelController, fpsController, id3TrackController, fragmentTracker];
- this.audioTrackController = this.createController(config.audioTrackController, networkControllers);
- const AudioStreamControllerClass = config.audioStreamController;
- if (AudioStreamControllerClass) {
- networkControllers.push(this.audioStreamController = new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
- }
- this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers);
- const SubtitleStreamControllerClass = config.subtitleStreamController;
- if (SubtitleStreamControllerClass) {
- networkControllers.push(this.subtititleStreamController = new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader));
- }
- this.createController(config.timelineController, coreComponents);
- keyLoader.emeController = this.emeController = this.createController(config.emeController, coreComponents);
- this.cmcdController = this.createController(config.cmcdController, coreComponents);
- this.latencyController = this.createController(LatencyController, coreComponents);
- this.coreComponents = coreComponents;
- networkControllers.push(errorController);
- const onErrorOut = errorController.onErrorOut;
- if (typeof onErrorOut === "function") {
- this.on(Events.ERROR, onErrorOut, errorController);
- }
- this.on(Events.MANIFEST_LOADED, playListLoader.onManifestLoaded, playListLoader);
- }
- createController(ControllerClass, components) {
- if (ControllerClass) {
- const controllerInstance = new ControllerClass(this);
- if (components) {
- components.push(controllerInstance);
- }
- return controllerInstance;
- }
- return null;
- }
- // Delegate the EventEmitter through the public API of Hls.js
- on(event, listener, context = this) {
- this._emitter.on(event, listener, context);
- }
- once(event, listener, context = this) {
- this._emitter.once(event, listener, context);
- }
- removeAllListeners(event) {
- this._emitter.removeAllListeners(event);
- }
- off(event, listener, context = this, once) {
- this._emitter.off(event, listener, context, once);
- }
- listeners(event) {
- return this._emitter.listeners(event);
- }
- emit(event, name, eventObject) {
- return this._emitter.emit(event, name, eventObject);
- }
- trigger(event, eventObject) {
- if (this.config.debug) {
- return this.emit(event, event, eventObject);
- } else {
- try {
- return this.emit(event, event, eventObject);
- } catch (error) {
- this.logger.error("An internal error happened while handling event " + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
- if (!this.triggeringException) {
- this.triggeringException = true;
- const fatal = event === Events.ERROR;
- this.trigger(Events.ERROR, {
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.INTERNAL_EXCEPTION,
- fatal,
- event,
- error
- });
- this.triggeringException = false;
- }
- }
- }
- return false;
- }
- listenerCount(event) {
- return this._emitter.listenerCount(event);
- }
- /**
- * Dispose of the instance
- */
- destroy() {
- this.logger.log("destroy");
- this.trigger(Events.DESTROYING, void 0);
- this.detachMedia();
- this.removeAllListeners();
- this._autoLevelCapping = -1;
- this._url = null;
- this.networkControllers.forEach((component) => component.destroy());
- this.networkControllers.length = 0;
- this.coreComponents.forEach((component) => component.destroy());
- this.coreComponents.length = 0;
- const config = this.config;
- config.xhrSetup = config.fetchSetup = void 0;
- this.userConfig = null;
- }
- /**
- * Attaches Hls.js to a media element
- */
- attachMedia(data) {
- if (!data || "media" in data && !data.media) {
- const error = new Error(`attachMedia failed: invalid argument (${data})`);
- this.trigger(Events.ERROR, {
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.ATTACH_MEDIA_ERROR,
- fatal: true,
- error
- });
- return;
- }
- this.logger.log(`attachMedia`);
- if (this._media) {
- this.logger.warn(`media must be detached before attaching`);
- this.detachMedia();
- }
- const attachMediaSource = "media" in data;
- const media = attachMediaSource ? data.media : data;
- const attachingData = attachMediaSource ? data : {
- media
- };
- this._media = media;
- this.trigger(Events.MEDIA_ATTACHING, attachingData);
- }
- /**
- * Detach Hls.js from the media
- */
- detachMedia() {
- this.logger.log("detachMedia");
- this.trigger(Events.MEDIA_DETACHING, {});
- this._media = null;
- }
- /**
- * Detach HTMLMediaElement, MediaSource, and SourceBuffers without reset, for attaching to another instance
- */
- transferMedia() {
- this._media = null;
- const transferMedia = this.bufferController.transferMedia();
- this.trigger(Events.MEDIA_DETACHING, {
- transferMedia
- });
- return transferMedia;
- }
- /**
- * Set the source URL. Can be relative or absolute.
- */
- loadSource(url) {
- this.stopLoad();
- const media = this.media;
- const loadedSource = this._url;
- const loadingSource = this._url = urlToolkitExports.buildAbsoluteURL(self.location.href, url, {
- alwaysNormalize: true
- });
- this._autoLevelCapping = -1;
- this._maxHdcpLevel = null;
- this.logger.log(`loadSource:${loadingSource}`);
- if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
- this.detachMedia();
- this.attachMedia(media);
- }
- this.trigger(Events.MANIFEST_LOADING, {
- url
- });
- }
- /**
- * Gets the currently loaded URL
- */
- get url() {
- return this._url;
- }
- /**
- * Whether or not enough has been buffered to seek to start position or use `media.currentTime` to determine next load position
- */
- get hasEnoughToStart() {
- return this.streamController.hasEnoughToStart;
- }
- /**
- * Get the startPosition set on startLoad(position) or on autostart with config.startPosition
- */
- get startPosition() {
- return this.streamController.startPositionValue;
- }
- /**
- * Start loading data from the stream source.
- * Depending on default config, client starts loading automatically when a source is set.
- *
- * @param startPosition - Set the start position to stream from.
- * Defaults to -1 (None: starts from earliest point)
- */
- startLoad(startPosition = -1, skipSeekToStartPosition) {
- this.logger.log(`startLoad(${startPosition + (skipSeekToStartPosition ? ", <skip seek to start>" : "")})`);
- this.started = true;
- this.resumeBuffering();
- for (let i = 0; i < this.networkControllers.length; i++) {
- this.networkControllers[i].startLoad(startPosition, skipSeekToStartPosition);
- if (!this.started || !this.networkControllers) {
- break;
- }
- }
- }
- /**
- * Stop loading of any stream data.
- */
- stopLoad() {
- this.logger.log("stopLoad");
- this.started = false;
- for (let i = 0; i < this.networkControllers.length; i++) {
- this.networkControllers[i].stopLoad();
- if (this.started || !this.networkControllers) {
- break;
- }
- }
- }
- /**
- * Returns whether loading, toggled with `startLoad()` and `stopLoad()`, is active or not`.
- */
- get loadingEnabled() {
- return this.started;
- }
- /**
- * Returns state of fragment loading toggled by calling `pauseBuffering()` and `resumeBuffering()`.
- */
- get bufferingEnabled() {
- return this.streamController.bufferingEnabled;
- }
- /**
- * Resumes stream controller segment loading after `pauseBuffering` has been called.
- */
- resumeBuffering() {
- if (!this.bufferingEnabled) {
- this.logger.log(`resume buffering`);
- this.networkControllers.forEach((controller) => {
- if (controller.resumeBuffering) {
- controller.resumeBuffering();
- }
- });
- }
- }
- /**
- * Prevents stream controller from loading new segments until `resumeBuffering` is called.
- * This allows for media buffering to be paused without interupting playlist loading.
- */
- pauseBuffering() {
- if (this.bufferingEnabled) {
- this.logger.log(`pause buffering`);
- this.networkControllers.forEach((controller) => {
- if (controller.pauseBuffering) {
- controller.pauseBuffering();
- }
- });
- }
- }
- get inFlightFragments() {
- const inFlightData = {
- [PlaylistLevelType.MAIN]: this.streamController.inFlightFrag
- };
- if (this.audioStreamController) {
- inFlightData[PlaylistLevelType.AUDIO] = this.audioStreamController.inFlightFrag;
- }
- if (this.subtititleStreamController) {
- inFlightData[PlaylistLevelType.SUBTITLE] = this.subtititleStreamController.inFlightFrag;
- }
- return inFlightData;
- }
- /**
- * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
- */
- swapAudioCodec() {
- this.logger.log("swapAudioCodec");
- this.streamController.swapAudioCodec();
- }
- /**
- * When the media-element fails, this allows to detach and then re-attach it
- * as one call (convenience method).
- *
- * Automatic recovery of media-errors by this process is configurable.
- */
- recoverMediaError() {
- this.logger.log("recoverMediaError");
- const media = this._media;
- const time = media == null ? void 0 : media.currentTime;
- this.detachMedia();
- if (media) {
- this.attachMedia(media);
- if (time) {
- this.startLoad(time);
- }
- }
- }
- removeLevel(levelIndex) {
- this.levelController.removeLevel(levelIndex);
- }
- /**
- * @returns a UUID for this player instance
- */
- get sessionId() {
- let _sessionId = this._sessionId;
- if (!_sessionId) {
- _sessionId = this._sessionId = uuid();
- }
- return _sessionId;
- }
- /**
- * @returns an array of levels (variants) sorted by HDCP-LEVEL, RESOLUTION (height), FRAME-RATE, CODECS, VIDEO-RANGE, and BANDWIDTH
- */
- get levels() {
- const levels = this.levelController.levels;
- return levels ? levels : [];
- }
- /**
- * @returns LevelDetails of last loaded level (variant) or `null` prior to loading a media playlist.
- */
- get latestLevelDetails() {
- return this.streamController.getLevelDetails() || null;
- }
- /**
- * @returns Level object of selected level (variant) or `null` prior to selecting a level or once the level is removed.
- */
- get loadLevelObj() {
- return this.levelController.loadLevelObj;
- }
- /**
- * Index of quality level (variant) currently played
- */
- get currentLevel() {
- return this.streamController.currentLevel;
- }
- /**
- * Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection.
- */
- set currentLevel(newLevel) {
- this.logger.log(`set currentLevel:${newLevel}`);
- this.levelController.manualLevel = newLevel;
- this.streamController.immediateLevelSwitch();
- }
- /**
- * Index of next quality level loaded as scheduled by stream controller.
- */
- get nextLevel() {
- return this.streamController.nextLevel;
- }
- /**
- * Set quality level index for next loaded data.
- * This will switch the video quality asap, without interrupting playback.
- * May abort current loading of data, and flush parts of buffer (outside currently played fragment region).
- * @param newLevel - Pass -1 for automatic level selection
- */
- set nextLevel(newLevel) {
- this.logger.log(`set nextLevel:${newLevel}`);
- this.levelController.manualLevel = newLevel;
- this.streamController.nextLevelSwitch();
- }
- /**
- * Return the quality level of the currently or last (of none is loaded currently) segment
- */
- get loadLevel() {
- return this.levelController.level;
- }
- /**
- * Set quality level index for next loaded data in a conservative way.
- * This will switch the quality without flushing, but interrupt current loading.
- * Thus the moment when the quality switch will appear in effect will only be after the already existing buffer.
- * @param newLevel - Pass -1 for automatic level selection
- */
- set loadLevel(newLevel) {
- this.logger.log(`set loadLevel:${newLevel}`);
- this.levelController.manualLevel = newLevel;
- }
- /**
- * get next quality level loaded
- */
- get nextLoadLevel() {
- return this.levelController.nextLoadLevel;
- }
- /**
- * Set quality level of next loaded segment in a fully "non-destructive" way.
- * Same as `loadLevel` but will wait for next switch (until current loading is done).
- */
- set nextLoadLevel(level) {
- this.levelController.nextLoadLevel = level;
- }
- /**
- * Return "first level": like a default level, if not set,
- * falls back to index of first level referenced in manifest
- */
- get firstLevel() {
- return Math.max(this.levelController.firstLevel, this.minAutoLevel);
- }
- /**
- * Sets "first-level", see getter.
- */
- set firstLevel(newLevel) {
- this.logger.log(`set firstLevel:${newLevel}`);
- this.levelController.firstLevel = newLevel;
- }
- /**
- * Return the desired start level for the first fragment that will be loaded.
- * The default value of -1 indicates automatic start level selection.
- * Setting hls.nextAutoLevel without setting a startLevel will result in
- * the nextAutoLevel value being used for one fragment load.
- */
- get startLevel() {
- const startLevel = this.levelController.startLevel;
- if (startLevel === -1 && this.abrController.forcedAutoLevel > -1) {
- return this.abrController.forcedAutoLevel;
- }
- return startLevel;
- }
- /**
- * set start level (level of first fragment that will be played back)
- * if not overrided by user, first level appearing in manifest will be used as start level
- * if -1 : automatic start level selection, playback will start from level matching download bandwidth
- * (determined from download of first segment)
- */
- set startLevel(newLevel) {
- this.logger.log(`set startLevel:${newLevel}`);
- if (newLevel !== -1) {
- newLevel = Math.max(newLevel, this.minAutoLevel);
- }
- this.levelController.startLevel = newLevel;
- }
- /**
- * Whether level capping is enabled.
- * Default value is set via `config.capLevelToPlayerSize`.
- */
- get capLevelToPlayerSize() {
- return this.config.capLevelToPlayerSize;
- }
- /**
- * Enables or disables level capping. If disabled after previously enabled, `nextLevelSwitch` will be immediately called.
- */
- set capLevelToPlayerSize(shouldStartCapping) {
- const newCapLevelToPlayerSize = !!shouldStartCapping;
- if (newCapLevelToPlayerSize !== this.config.capLevelToPlayerSize) {
- if (newCapLevelToPlayerSize) {
- this.capLevelController.startCapping();
- } else {
- this.capLevelController.stopCapping();
- this.autoLevelCapping = -1;
- this.streamController.nextLevelSwitch();
- }
- this.config.capLevelToPlayerSize = newCapLevelToPlayerSize;
- }
- }
- /**
- * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`)
- */
- get autoLevelCapping() {
- return this._autoLevelCapping;
- }
- /**
- * Returns the current bandwidth estimate in bits per second, when available. Otherwise, `NaN` is returned.
- */
- get bandwidthEstimate() {
- const {
- bwEstimator
- } = this.abrController;
- if (!bwEstimator) {
- return NaN;
- }
- return bwEstimator.getEstimate();
- }
- set bandwidthEstimate(abrEwmaDefaultEstimate) {
- this.abrController.resetEstimator(abrEwmaDefaultEstimate);
- }
- get abrEwmaDefaultEstimate() {
- const {
- bwEstimator
- } = this.abrController;
- if (!bwEstimator) {
- return NaN;
- }
- return bwEstimator.defaultEstimate;
- }
- /**
- * get time to first byte estimate
- * @type {number}
- */
- get ttfbEstimate() {
- const {
- bwEstimator
- } = this.abrController;
- if (!bwEstimator) {
- return NaN;
- }
- return bwEstimator.getEstimateTTFB();
- }
- /**
- * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`)
- */
- set autoLevelCapping(newLevel) {
- if (this._autoLevelCapping !== newLevel) {
- this.logger.log(`set autoLevelCapping:${newLevel}`);
- this._autoLevelCapping = newLevel;
- this.levelController.checkMaxAutoUpdated();
- }
- }
- get maxHdcpLevel() {
- return this._maxHdcpLevel;
- }
- set maxHdcpLevel(value) {
- if (isHdcpLevel(value) && this._maxHdcpLevel !== value) {
- this._maxHdcpLevel = value;
- this.levelController.checkMaxAutoUpdated();
- }
- }
- /**
- * True when automatic level selection enabled
- */
- get autoLevelEnabled() {
- return this.levelController.manualLevel === -1;
- }
- /**
- * Level set manually (if any)
- */
- get manualLevel() {
- return this.levelController.manualLevel;
- }
- /**
- * min level selectable in auto mode according to config.minAutoBitrate
- */
- get minAutoLevel() {
- const {
- levels,
- config: {
- minAutoBitrate
- }
- } = this;
- if (!levels)
- return 0;
- const len = levels.length;
- for (let i = 0; i < len; i++) {
- if (levels[i].maxBitrate >= minAutoBitrate) {
- return i;
- }
- }
- return 0;
- }
- /**
- * max level selectable in auto mode according to autoLevelCapping
- */
- get maxAutoLevel() {
- const {
- levels,
- autoLevelCapping,
- maxHdcpLevel
- } = this;
- let maxAutoLevel;
- if (autoLevelCapping === -1 && levels != null && levels.length) {
- maxAutoLevel = levels.length - 1;
- } else {
- maxAutoLevel = autoLevelCapping;
- }
- if (maxHdcpLevel) {
- for (let i = maxAutoLevel; i--; ) {
- const hdcpLevel = levels[i].attrs["HDCP-LEVEL"];
- if (hdcpLevel && hdcpLevel <= maxHdcpLevel) {
- return i;
- }
- }
- }
- return maxAutoLevel;
- }
- get firstAutoLevel() {
- return this.abrController.firstAutoLevel;
- }
- /**
- * next automatically selected quality level
- */
- get nextAutoLevel() {
- return this.abrController.nextAutoLevel;
- }
- /**
- * this setter is used to force next auto level.
- * this is useful to force a switch down in auto mode:
- * in case of load error on level N, hls.js can set nextAutoLevel to N-1 for example)
- * forced value is valid for one fragment. upon successful frag loading at forced level,
- * this value will be resetted to -1 by ABR controller.
- */
- set nextAutoLevel(nextLevel) {
- this.abrController.nextAutoLevel = nextLevel;
- }
- /**
- * get the datetime value relative to media.currentTime for the active level Program Date Time if present
- */
- get playingDate() {
- return this.streamController.currentProgramDateTime;
- }
- get mainForwardBufferInfo() {
- return this.streamController.getMainFwdBufferInfo();
- }
- get maxBufferLength() {
- return this.streamController.maxBufferLength;
- }
- /**
- * Find and select the best matching audio track, making a level switch when a Group change is necessary.
- * Updates `hls.config.audioPreference`. Returns the selected track, or null when no matching track is found.
- */
- setAudioOption(audioOption) {
- var _this$audioTrackContr;
- return ((_this$audioTrackContr = this.audioTrackController) == null ? void 0 : _this$audioTrackContr.setAudioOption(audioOption)) || null;
- }
- /**
- * Find and select the best matching subtitle track, making a level switch when a Group change is necessary.
- * Updates `hls.config.subtitlePreference`. Returns the selected track, or null when no matching track is found.
- */
- setSubtitleOption(subtitleOption) {
- var _this$subtitleTrackCo;
- return ((_this$subtitleTrackCo = this.subtitleTrackController) == null ? void 0 : _this$subtitleTrackCo.setSubtitleOption(subtitleOption)) || null;
- }
- /**
- * Get the complete list of audio tracks across all media groups
- */
- get allAudioTracks() {
- const audioTrackController = this.audioTrackController;
- return audioTrackController ? audioTrackController.allAudioTracks : [];
- }
- /**
- * Get the list of selectable audio tracks
- */
- get audioTracks() {
- const audioTrackController = this.audioTrackController;
- return audioTrackController ? audioTrackController.audioTracks : [];
- }
- /**
- * index of the selected audio track (index in audio track lists)
- */
- get audioTrack() {
- const audioTrackController = this.audioTrackController;
- return audioTrackController ? audioTrackController.audioTrack : -1;
- }
- /**
- * selects an audio track, based on its index in audio track lists
- */
- set audioTrack(audioTrackId) {
- const audioTrackController = this.audioTrackController;
- if (audioTrackController) {
- audioTrackController.audioTrack = audioTrackId;
- }
- }
- /**
- * get the complete list of subtitle tracks across all media groups
- */
- get allSubtitleTracks() {
- const subtitleTrackController = this.subtitleTrackController;
- return subtitleTrackController ? subtitleTrackController.allSubtitleTracks : [];
- }
- /**
- * get alternate subtitle tracks list from playlist
- */
- get subtitleTracks() {
- const subtitleTrackController = this.subtitleTrackController;
- return subtitleTrackController ? subtitleTrackController.subtitleTracks : [];
- }
- /**
- * index of the selected subtitle track (index in subtitle track lists)
- */
- get subtitleTrack() {
- const subtitleTrackController = this.subtitleTrackController;
- return subtitleTrackController ? subtitleTrackController.subtitleTrack : -1;
- }
- get media() {
- return this._media;
- }
- /**
- * select an subtitle track, based on its index in subtitle track lists
- */
- set subtitleTrack(subtitleTrackId) {
- const subtitleTrackController = this.subtitleTrackController;
- if (subtitleTrackController) {
- subtitleTrackController.subtitleTrack = subtitleTrackId;
- }
- }
- /**
- * Whether subtitle display is enabled or not
- */
- get subtitleDisplay() {
- const subtitleTrackController = this.subtitleTrackController;
- return subtitleTrackController ? subtitleTrackController.subtitleDisplay : false;
- }
- /**
- * Enable/disable subtitle display rendering
- */
- set subtitleDisplay(value) {
- const subtitleTrackController = this.subtitleTrackController;
- if (subtitleTrackController) {
- subtitleTrackController.subtitleDisplay = value;
- }
- }
- /**
- * get mode for Low-Latency HLS loading
- */
- get lowLatencyMode() {
- return this.config.lowLatencyMode;
- }
- /**
- * Enable/disable Low-Latency HLS part playlist and segment loading, and start live streams at playlist PART-HOLD-BACK rather than HOLD-BACK.
- */
- set lowLatencyMode(mode) {
- this.config.lowLatencyMode = mode;
- }
- /**
- * Position (in seconds) of live sync point (ie edge of live position minus safety delay defined by ```hls.config.liveSyncDuration```)
- * @returns null prior to loading live Playlist
- */
- get liveSyncPosition() {
- return this.latencyController.liveSyncPosition;
- }
- /**
- * Estimated position (in seconds) of live edge (ie edge of live playlist plus time sync playlist advanced)
- * @returns 0 before first playlist is loaded
- */
- get latency() {
- return this.latencyController.latency;
- }
- /**
- * maximum distance from the edge before the player seeks forward to ```hls.liveSyncPosition```
- * configured using ```liveMaxLatencyDurationCount``` (multiple of target duration) or ```liveMaxLatencyDuration```
- * @returns 0 before first playlist is loaded
- */
- get maxLatency() {
- return this.latencyController.maxLatency;
- }
- /**
- * target distance from the edge as calculated by the latency controller
- */
- get targetLatency() {
- return this.latencyController.targetLatency;
- }
- set targetLatency(latency) {
- this.latencyController.targetLatency = latency;
- }
- /**
- * the rate at which the edge of the current live playlist is advancing or 1 if there is none
- */
- get drift() {
- return this.latencyController.drift;
- }
- /**
- * set to true when startLoad is called before MANIFEST_PARSED event
- */
- get forceStartLoad() {
- return this.streamController.forceStartLoad;
- }
- /**
- * ContentSteering pathways getter
- */
- get pathways() {
- return this.levelController.pathways;
- }
- /**
- * ContentSteering pathwayPriority getter/setter
- */
- get pathwayPriority() {
- return this.levelController.pathwayPriority;
- }
- set pathwayPriority(pathwayPriority) {
- this.levelController.pathwayPriority = pathwayPriority;
- }
- /**
- * returns true when all SourceBuffers are buffered to the end
- */
- get bufferedToEnd() {
- var _this$bufferControlle;
- return !!((_this$bufferControlle = this.bufferController) != null && _this$bufferControlle.bufferedToEnd);
- }
- /**
- * returns Interstitials Program Manager
- */
- get interstitialsManager() {
- var _this$interstitialsCo;
- return ((_this$interstitialsCo = this.interstitialsController) == null ? void 0 : _this$interstitialsCo.interstitialsManager) || null;
- }
- /**
- * returns mediaCapabilities.decodingInfo for a variant/rendition
- */
- getMediaDecodingInfo(level, audioTracks = this.allAudioTracks) {
- const audioTracksByGroup = getAudioTracksByGroup(audioTracks);
- return getMediaDecodingInfoPromise(level, audioTracksByGroup, navigator.mediaCapabilities);
- }
- };
- Hls.defaultConfig = void 0;
- export {
- AbrController,
- AttrList,
- AudioStreamController,
- AudioTrackController,
- BasePlaylistController,
- BaseSegment,
- BaseStreamController,
- BufferController,
- CMCDController,
- CapLevelController,
- ChunkMetadata,
- ContentSteeringController,
- Cues,
- DateRange,
- EMEController,
- ErrorActionFlags,
- ErrorController,
- ErrorDetails,
- ErrorTypes,
- Events,
- FPSController,
- FetchLoader,
- Fragment,
- Hls,
- HlsSkip,
- HlsUrlParameters,
- KeySystemFormats,
- KeySystems,
- Level,
- LevelDetails,
- LevelKey,
- LoadStats,
- M3U8Parser,
- MetadataSchema,
- NetworkErrorAction,
- Part,
- PlaylistLevelType,
- SubtitleStreamController,
- SubtitleTrackController,
- TimelineController,
- XhrLoader,
- Hls as default,
- fetchSupported,
- getMediaSource,
- isMSESupported,
- isSupported,
- requestMediaKeySystemAccess
- };
- //# sourceMappingURL=hls__js.js.map
|