video.es.js 1.6 MB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182351833518435185351863518735188351893519035191351923519335194351953519635197351983519935200352013520235203352043520535206352073520835209352103521135212352133521435215352163521735218352193522035221352223522335224352253522635227352283522935230352313523235233352343523535236352373523835239352403524135242352433524435245352463524735248352493525035251352523525335254352553525635257352583525935260352613526235263352643526535266352673526835269352703527135272352733527435275352763527735278352793528035281352823528335284352853528635287352883528935290352913529235293352943529535296352973529835299353003530135302353033530435305353063530735308353093531035311353123531335314353153531635317353183531935320353213532235323353243532535326353273532835329353303533135332353333533435335353363533735338353393534035341353423534335344353453534635347353483534935350353513535235353353543535535356353573535835359353603536135362353633536435365353663536735368353693537035371353723537335374353753537635377353783537935380353813538235383353843538535386353873538835389353903539135392353933539435395353963539735398353993540035401354023540335404354053540635407354083540935410354113541235413354143541535416354173541835419354203542135422354233542435425354263542735428354293543035431354323543335434354353543635437354383543935440354413544235443354443544535446354473544835449354503545135452354533545435455354563545735458354593546035461354623546335464354653546635467354683546935470354713547235473354743547535476354773547835479354803548135482354833548435485354863548735488354893549035491354923549335494354953549635497354983549935500355013550235503355043550535506355073550835509355103551135512355133551435515355163551735518355193552035521355223552335524355253552635527355283552935530355313553235533355343553535536355373553835539355403554135542355433554435545355463554735548355493555035551355523555335554355553555635557355583555935560355613556235563355643556535566355673556835569355703557135572355733557435575355763557735578355793558035581355823558335584355853558635587355883558935590355913559235593355943559535596355973559835599356003560135602356033560435605356063560735608356093561035611356123561335614356153561635617356183561935620356213562235623356243562535626356273562835629356303563135632356333563435635356363563735638356393564035641356423564335644356453564635647356483564935650356513565235653356543565535656356573565835659356603566135662356633566435665356663566735668356693567035671356723567335674356753567635677356783567935680356813568235683356843568535686356873568835689356903569135692356933569435695356963569735698356993570035701357023570335704357053570635707357083570935710357113571235713357143571535716357173571835719357203572135722357233572435725357263572735728357293573035731357323573335734357353573635737357383573935740357413574235743357443574535746357473574835749357503575135752357533575435755357563575735758357593576035761357623576335764357653576635767357683576935770357713577235773357743577535776357773577835779357803578135782357833578435785357863578735788357893579035791357923579335794357953579635797357983579935800358013580235803358043580535806358073580835809358103581135812358133581435815358163581735818358193582035821358223582335824358253582635827358283582935830358313583235833358343583535836358373583835839358403584135842358433584435845358463584735848358493585035851358523585335854358553585635857358583585935860358613586235863358643586535866358673586835869358703587135872358733587435875358763587735878358793588035881358823588335884358853588635887358883588935890358913589235893358943589535896358973589835899359003590135902359033590435905359063590735908359093591035911359123591335914359153591635917359183591935920359213592235923359243592535926359273592835929359303593135932359333593435935359363593735938359393594035941359423594335944359453594635947359483594935950359513595235953359543595535956359573595835959359603596135962359633596435965359663596735968359693597035971359723597335974359753597635977359783597935980359813598235983359843598535986359873598835989359903599135992359933599435995359963599735998359993600036001360023600336004360053600636007360083600936010360113601236013360143601536016360173601836019360203602136022360233602436025360263602736028360293603036031360323603336034360353603636037360383603936040360413604236043360443604536046360473604836049360503605136052360533605436055360563605736058360593606036061360623606336064360653606636067360683606936070360713607236073360743607536076360773607836079360803608136082360833608436085360863608736088360893609036091360923609336094360953609636097360983609936100361013610236103361043610536106361073610836109361103611136112361133611436115361163611736118361193612036121361223612336124361253612636127361283612936130361313613236133361343613536136361373613836139361403614136142361433614436145361463614736148361493615036151361523615336154361553615636157361583615936160361613616236163361643616536166361673616836169361703617136172361733617436175361763617736178361793618036181361823618336184361853618636187361883618936190361913619236193361943619536196361973619836199362003620136202362033620436205362063620736208362093621036211362123621336214362153621636217362183621936220362213622236223362243622536226362273622836229362303623136232362333623436235362363623736238362393624036241362423624336244362453624636247362483624936250362513625236253362543625536256362573625836259362603626136262362633626436265362663626736268362693627036271362723627336274362753627636277362783627936280362813628236283362843628536286362873628836289362903629136292362933629436295362963629736298362993630036301363023630336304363053630636307363083630936310363113631236313363143631536316363173631836319363203632136322363233632436325363263632736328363293633036331363323633336334363353633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353383543835538356383573835838359383603836138362383633836438365383663836738368383693837038371383723837338374383753837638377383783837938380383813838238383383843838538386383873838838389383903839138392383933839438395383963839738398383993840038401384023840338404384053840638407384083840938410384113841238413384143841538416384173841838419384203842138422384233842438425384263842738428384293843038431384323843338434384353843638437384383843938440384413844238443384443844538446384473844838449384503845138452384533845438455384563845738458384593846038461384623846338464384653846638467384683846938470384713847238473384743847538476384773847838479384803848138482384833848438485384863848738488384893849038491384923849338494384953849638497384983849938500385013850238503385043850538506385073850838509385103851138512385133851438515385163851738518385193852038521385223852338524385253852638527385283852938530385313853238533385343853538536385373853838539385403854138542385433854438545385463854738548385493855038551385523855338554385553855638557385583855938560385613856238563385643856538566385673856838569385703857138572385733857438575385763857738578385793858038581385823858338584385853858638587385883858938590385913859238593385943859538596385973859838599386003860138602386033860438605386063860738608386093861038611386123861338614386153861638617386183861938620386213862238623386243862538626386273862838629386303863138632386333863438635386363863738638386393864038641386423864338644386453864638647386483864938650386513865238653386543865538656386573865838659386603866138662386633866438665386663866738668386693867038671386723867338674386753867638677386783867938680386813868238683386843868538686386873868838689386903869138692386933869438695386963869738698386993870038701387023870338704387053870638707387083870938710387113871238713387143871538716387173871838719387203872138722387233872438725387263872738728387293873038731387323873338734387353873638737387383873938740387413874238743387443874538746387473874838749387503875138752387533875438755387563875738758387593876038761387623876338764387653876638767387683876938770387713877238773387743877538776387773877838779387803878138782387833878438785387863878738788387893879038791387923879338794387953879638797387983879938800388013880238803388043880538806388073880838809388103881138812388133881438815388163881738818388193882038821388223882338824388253882638827388283882938830388313883238833388343883538836388373883838839388403884138842388433884438845388463884738848388493885038851388523885338854388553885638857388583885938860388613886238863388643886538866388673886838869388703887138872388733887438875388763887738878388793888038881388823888338884388853888638887388883888938890388913889238893388943889538896388973889838899389003890138902389033890438905389063890738908389093891038911389123891338914389153891638917389183891938920389213892238923389243892538926389273892838929389303893138932389333893438935389363893738938389393894038941389423894338944389453894638947389483894938950389513895238953389543895538956389573895838959389603896138962389633896438965389663896738968389693897038971389723897338974389753897638977389783897938980389813898238983389843898538986389873898838989389903899138992389933899438995389963899738998389993900039001390023900339004390053900639007390083900939010390113901239013390143901539016390173901839019390203902139022390233902439025390263902739028390293903039031390323903339034390353903639037390383903939040390413904239043390443904539046390473904839049390503905139052390533905439055390563905739058390593906039061390623906339064390653906639067390683906939070390713907239073390743907539076390773907839079390803908139082390833908439085390863908739088390893909039091390923909339094390953909639097390983909939100391013910239103391043910539106391073910839109391103911139112391133911439115391163911739118391193912039121391223912339124391253912639127391283912939130391313913239133391343913539136391373913839139391403914139142391433914439145391463914739148391493915039151391523915339154391553915639157391583915939160391613916239163391643916539166391673916839169391703917139172391733917439175391763917739178391793918039181391823918339184391853918639187391883918939190391913919239193391943919539196391973919839199392003920139202392033920439205392063920739208392093921039211392123921339214392153921639217392183921939220392213922239223392243922539226392273922839229392303923139232392333923439235392363923739238392393924039241392423924339244392453924639247392483924939250392513925239253392543925539256392573925839259392603926139262392633926439265392663926739268392693927039271392723927339274392753927639277392783927939280392813928239283392843928539286392873928839289392903929139292392933929439295392963929739298392993930039301393023930339304393053930639307393083930939310393113931239313393143931539316393173931839319393203932139322393233932439325393263932739328393293933039331393323933339334393353933639337393383933939340393413934239343393443934539346393473934839349393503935139352393533935439355393563935739358393593936039361393623936339364393653936639367393683936939370393713937239373393743937539376393773937839379393803938139382393833938439385393863938739388393893939039391393923939339394393953939639397393983939939400394013940239403394043940539406394073940839409394103941139412394133941439415394163941739418394193942039421394223942339424394253942639427394283942939430394313943239433394343943539436394373943839439394403944139442394433944439445394463944739448394493945039451394523945339454394553945639457394583945939460394613946239463394643946539466394673946839469394703947139472394733947439475394763947739478394793948039481394823948339484394853948639487394883948939490394913949239493394943949539496394973949839499395003950139502395033950439505395063950739508395093951039511395123951339514395153951639517395183951939520395213952239523395243952539526395273952839529395303953139532395333953439535395363953739538395393954039541395423954339544395453954639547395483954939550395513955239553395543955539556395573955839559395603956139562395633956439565395663956739568395693957039571395723957339574395753957639577395783957939580395813958239583395843958539586395873958839589395903959139592395933959439595395963959739598395993960039601396023960339604396053960639607396083960939610396113961239613396143961539616396173961839619396203962139622396233962439625396263962739628396293963039631396323963339634396353963639637396383963939640396413964239643396443964539646396473964839649396503965139652396533965439655396563965739658396593966039661396623966339664396653966639667396683966939670396713967239673396743967539676396773967839679396803968139682396833968439685396863968739688396893969039691396923969339694396953969639697396983969939700397013970239703397043970539706397073970839709397103971139712397133971439715397163971739718397193972039721397223972339724397253972639727397283972939730397313973239733397343973539736397373973839739397403974139742397433974439745397463974739748397493975039751397523975339754397553975639757397583975939760397613976239763397643976539766397673976839769397703977139772397733977439775397763977739778397793978039781397823978339784397853978639787397883978939790397913979239793397943979539796397973979839799398003980139802398033980439805398063980739808398093981039811398123981339814398153981639817398183981939820398213982239823398243982539826398273982839829398303983139832398333983439835398363983739838398393984039841398423984339844398453984639847398483984939850398513985239853398543985539856398573985839859398603986139862398633986439865398663986739868398693987039871398723987339874398753987639877398783987939880398813988239883398843988539886398873988839889398903989139892398933989439895398963989739898398993990039901399023990339904399053990639907399083990939910399113991239913399143991539916399173991839919399203992139922399233992439925399263992739928399293993039931399323993339934399353993639937399383993939940399413994239943399443994539946399473994839949399503995139952399533995439955399563995739958399593996039961399623996339964399653996639967399683996939970399713997239973399743997539976399773997839979399803998139982399833998439985399863998739988399893999039991399923999339994399953999639997399983999940000400014000240003400044000540006400074000840009400104001140012400134001440015400164001740018400194002040021400224002340024400254002640027400284002940030400314003240033400344003540036400374003840039400404004140042400434004440045400464004740048400494005040051400524005340054400554005640057400584005940060400614006240063400644006540066400674006840069400704007140072400734007440075400764007740078400794008040081400824008340084400854008640087400884008940090400914009240093400944009540096400974009840099401004010140102401034010440105401064010740108401094011040111401124011340114401154011640117401184011940120401214012240123401244012540126401274012840129401304013140132401334013440135401364013740138401394014040141401424014340144401454014640147401484014940150401514015240153401544015540156401574015840159401604016140162401634016440165401664016740168401694017040171401724017340174401754017640177401784017940180401814018240183401844018540186401874018840189401904019140192401934019440195401964019740198401994020040201402024020340204402054020640207402084020940210402114021240213402144021540216402174021840219402204022140222402234022440225402264022740228402294023040231402324023340234402354023640237402384023940240402414024240243402444024540246402474024840249402504025140252402534025440255402564025740258402594026040261402624026340264402654026640267402684026940270402714027240273402744027540276402774027840279402804028140282402834028440285402864028740288402894029040291402924029340294402954029640297402984029940300403014030240303403044030540306403074030840309403104031140312403134031440315403164031740318403194032040321403224032340324403254032640327403284032940330403314033240333403344033540336403374033840339403404034140342403434034440345403464034740348403494035040351403524035340354403554035640357403584035940360403614036240363403644036540366403674036840369403704037140372403734037440375403764037740378403794038040381403824038340384403854038640387403884038940390403914039240393403944039540396403974039840399404004040140402404034040440405404064040740408404094041040411404124041340414404154041640417404184041940420404214042240423404244042540426404274042840429404304043140432404334043440435404364043740438404394044040441404424044340444404454044640447404484044940450404514045240453404544045540456404574045840459404604046140462404634046440465404664046740468404694047040471404724047340474404754047640477404784047940480404814048240483404844048540486404874048840489404904049140492404934049440495404964049740498404994050040501405024050340504405054050640507405084050940510405114051240513405144051540516405174051840519405204052140522405234052440525405264052740528405294053040531405324053340534405354053640537405384053940540405414054240543405444054540546405474054840549405504055140552405534055440555405564055740558405594056040561405624056340564405654056640567405684056940570405714057240573405744057540576405774057840579405804058140582405834058440585405864058740588405894059040591405924059340594405954059640597405984059940600406014060240603406044060540606406074060840609406104061140612406134061440615406164061740618406194062040621406224062340624406254062640627406284062940630406314063240633406344063540636406374063840639406404064140642406434064440645406464064740648406494065040651406524065340654406554065640657406584065940660406614066240663406644066540666406674066840669406704067140672406734067440675406764067740678406794068040681406824068340684406854068640687406884068940690406914069240693406944069540696406974069840699407004070140702407034070440705407064070740708407094071040711407124071340714407154071640717407184071940720407214072240723407244072540726407274072840729407304073140732407334073440735407364073740738407394074040741407424074340744407454074640747407484074940750407514075240753407544075540756407574075840759407604076140762407634076440765407664076740768407694077040771407724077340774407754077640777407784077940780407814078240783407844078540786407874078840789407904079140792407934079440795407964079740798407994080040801408024080340804408054080640807408084080940810408114081240813408144081540816408174081840819408204082140822408234082440825408264082740828408294083040831408324083340834408354083640837408384083940840408414084240843408444084540846408474084840849408504085140852408534085440855408564085740858408594086040861408624086340864408654086640867408684086940870408714087240873408744087540876408774087840879408804088140882408834088440885408864088740888408894089040891408924089340894408954089640897408984089940900409014090240903409044090540906409074090840909409104091140912409134091440915409164091740918409194092040921409224092340924409254092640927409284092940930409314093240933409344093540936409374093840939409404094140942409434094440945409464094740948409494095040951409524095340954409554095640957409584095940960409614096240963409644096540966409674096840969409704097140972409734097440975409764097740978409794098040981409824098340984409854098640987409884098940990409914099240993409944099540996409974099840999410004100141002410034100441005410064100741008410094101041011410124101341014410154101641017410184101941020410214102241023410244102541026410274102841029410304103141032410334103441035410364103741038410394104041041410424104341044410454104641047410484104941050410514105241053410544105541056410574105841059410604106141062410634106441065410664106741068410694107041071410724107341074410754107641077410784107941080410814108241083410844108541086410874108841089410904109141092410934109441095410964109741098410994110041101411024110341104411054110641107411084110941110411114111241113411144111541116411174111841119411204112141122411234112441125411264112741128411294113041131411324113341134411354113641137411384113941140411414114241143411444114541146411474114841149411504115141152411534115441155411564115741158411594116041161411624116341164411654116641167411684116941170411714117241173411744117541176411774117841179411804118141182411834118441185411864118741188411894119041191411924119341194411954119641197411984119941200412014120241203412044120541206412074120841209412104121141212412134121441215412164121741218412194122041221412224122341224412254122641227412284122941230412314123241233412344123541236412374123841239412404124141242412434124441245412464124741248412494125041251412524125341254412554125641257412584125941260412614126241263412644126541266412674126841269412704127141272412734127441275412764127741278412794128041281412824128341284412854128641287412884128941290412914129241293412944129541296412974129841299413004130141302413034130441305413064130741308413094131041311413124131341314413154131641317413184131941320413214132241323413244132541326413274132841329413304133141332413334133441335413364133741338413394134041341413424134341344413454134641347413484134941350413514135241353413544135541356413574135841359413604136141362413634136441365413664136741368413694137041371413724137341374413754137641377413784137941380413814138241383413844138541386413874138841389413904139141392413934139441395413964139741398413994140041401414024140341404414054140641407414084140941410414114141241413414144141541416414174141841419414204142141422414234142441425414264142741428414294143041431414324143341434414354143641437414384143941440414414144241443414444144541446414474144841449414504145141452414534145441455414564145741458414594146041461414624146341464414654146641467414684146941470414714147241473414744147541476414774147841479414804148141482414834148441485414864148741488414894149041491414924149341494414954149641497414984149941500415014150241503415044150541506415074150841509415104151141512415134151441515415164151741518415194152041521415224152341524415254152641527415284152941530415314153241533415344153541536415374153841539415404154141542415434154441545415464154741548415494155041551415524155341554415554155641557415584155941560415614156241563415644156541566415674156841569415704157141572415734157441575415764157741578415794158041581415824158341584415854158641587415884158941590415914159241593415944159541596415974159841599416004160141602416034160441605416064160741608416094161041611416124161341614416154161641617416184161941620416214162241623416244162541626416274162841629416304163141632416334163441635416364163741638416394164041641416424164341644416454164641647416484164941650416514165241653416544165541656416574165841659416604166141662416634166441665416664166741668416694167041671416724167341674416754167641677416784167941680416814168241683416844168541686416874168841689416904169141692416934169441695416964169741698416994170041701417024170341704417054170641707417084170941710417114171241713417144171541716417174171841719417204172141722417234172441725417264172741728417294173041731417324173341734417354173641737417384173941740417414174241743417444174541746417474174841749417504175141752417534175441755417564175741758417594176041761417624176341764417654176641767417684176941770417714177241773417744177541776417774177841779417804178141782417834178441785417864178741788417894179041791417924179341794417954179641797417984179941800418014180241803418044180541806418074180841809418104181141812418134181441815418164181741818418194182041821418224182341824418254182641827418284182941830418314183241833418344183541836418374183841839418404184141842418434184441845418464184741848418494185041851418524185341854418554185641857418584185941860418614186241863418644186541866418674186841869418704187141872418734187441875418764187741878418794188041881418824188341884418854188641887418884188941890418914189241893418944189541896418974189841899419004190141902419034190441905419064190741908419094191041911419124191341914419154191641917419184191941920419214192241923419244192541926419274192841929419304193141932419334193441935419364193741938419394194041941419424194341944419454194641947419484194941950419514195241953419544195541956419574195841959419604196141962419634196441965419664196741968419694197041971419724197341974419754197641977419784197941980419814198241983419844198541986419874198841989419904199141992419934199441995419964199741998419994200042001420024200342004420054200642007420084200942010420114201242013420144201542016420174201842019420204202142022420234202442025420264202742028420294203042031420324203342034420354203642037420384203942040420414204242043420444204542046420474204842049420504205142052420534205442055420564205742058420594206042061420624206342064420654206642067420684206942070420714207242073420744207542076420774207842079420804208142082420834208442085420864208742088420894209042091420924209342094420954209642097420984209942100421014210242103421044210542106421074210842109421104211142112421134211442115421164211742118421194212042121421224212342124421254212642127421284212942130421314213242133421344213542136421374213842139421404214142142421434214442145421464214742148421494215042151421524215342154421554215642157421584215942160421614216242163421644216542166421674216842169421704217142172421734217442175421764217742178421794218042181421824218342184421854218642187421884218942190421914219242193421944219542196421974219842199422004220142202422034220442205422064220742208422094221042211422124221342214422154221642217422184221942220422214222242223422244222542226422274222842229422304223142232422334223442235422364223742238422394224042241422424224342244422454224642247422484224942250422514225242253422544225542256422574225842259422604226142262422634226442265422664226742268422694227042271422724227342274422754227642277422784227942280422814228242283422844228542286422874228842289422904229142292422934229442295422964229742298422994230042301423024230342304423054230642307423084230942310423114231242313423144231542316423174231842319423204232142322423234232442325423264232742328423294233042331423324233342334423354233642337423384233942340423414234242343423444234542346423474234842349423504235142352423534235442355423564235742358423594236042361423624236342364423654236642367423684236942370423714237242373423744237542376423774237842379423804238142382423834238442385423864238742388423894239042391423924239342394423954239642397423984239942400424014240242403424044240542406424074240842409424104241142412424134241442415424164241742418424194242042421424224242342424424254242642427424284242942430424314243242433424344243542436424374243842439424404244142442424434244442445424464244742448424494245042451424524245342454424554245642457424584245942460424614246242463424644246542466424674246842469424704247142472424734247442475424764247742478424794248042481424824248342484424854248642487424884248942490424914249242493424944249542496424974249842499425004250142502425034250442505425064250742508425094251042511425124251342514425154251642517425184251942520425214252242523425244252542526425274252842529425304253142532425334253442535425364253742538425394254042541425424254342544425454254642547425484254942550425514255242553425544255542556425574255842559425604256142562425634256442565425664256742568425694257042571425724257342574425754257642577425784257942580425814258242583425844258542586425874258842589425904259142592425934259442595425964259742598425994260042601426024260342604426054260642607426084260942610426114261242613426144261542616426174261842619426204262142622426234262442625426264262742628426294263042631426324263342634426354263642637426384263942640426414264242643426444264542646426474264842649426504265142652426534265442655426564265742658426594266042661426624266342664426654266642667426684266942670426714267242673426744267542676426774267842679426804268142682426834268442685426864268742688426894269042691426924269342694426954269642697426984269942700427014270242703427044270542706427074270842709427104271142712427134271442715427164271742718427194272042721427224272342724427254272642727427284272942730427314273242733427344273542736427374273842739427404274142742427434274442745427464274742748427494275042751427524275342754427554275642757427584275942760427614276242763427644276542766427674276842769427704277142772427734277442775427764277742778427794278042781427824278342784427854278642787427884278942790427914279242793427944279542796427974279842799428004280142802428034280442805428064280742808428094281042811428124281342814428154281642817428184281942820428214282242823428244282542826428274282842829428304283142832428334283442835428364283742838428394284042841428424284342844428454284642847428484284942850428514285242853428544285542856428574285842859428604286142862428634286442865428664286742868428694287042871428724287342874428754287642877428784287942880428814288242883428844288542886428874288842889428904289142892428934289442895428964289742898428994290042901429024290342904429054290642907429084290942910429114291242913429144291542916429174291842919429204292142922429234292442925429264292742928429294293042931429324293342934429354293642937429384293942940429414294242943429444294542946429474294842949429504295142952429534295442955429564295742958429594296042961429624296342964429654296642967429684296942970429714297242973429744297542976429774297842979429804298142982429834298442985429864298742988429894299042991429924299342994429954299642997429984299943000430014300243003430044300543006430074300843009430104301143012430134301443015430164301743018430194302043021430224302343024430254302643027430284302943030430314303243033430344303543036430374303843039430404304143042430434304443045430464304743048430494305043051430524305343054430554305643057430584305943060430614306243063430644306543066430674306843069430704307143072430734307443075430764307743078430794308043081430824308343084430854308643087430884308943090430914309243093430944309543096430974309843099431004310143102431034310443105431064310743108431094311043111431124311343114431154311643117431184311943120431214312243123431244312543126431274312843129431304313143132431334313443135431364313743138431394314043141431424314343144431454314643147431484314943150431514315243153431544315543156431574315843159431604316143162431634316443165431664316743168431694317043171431724317343174431754317643177431784317943180431814318243183431844318543186431874318843189431904319143192431934319443195431964319743198431994320043201432024320343204432054320643207432084320943210432114321243213432144321543216432174321843219432204322143222432234322443225432264322743228432294323043231432324323343234432354323643237432384323943240432414324243243432444324543246432474324843249432504325143252432534325443255432564325743258432594326043261432624326343264432654326643267432684326943270432714327243273432744327543276432774327843279432804328143282432834328443285432864328743288432894329043291432924329343294432954329643297432984329943300433014330243303433044330543306433074330843309433104331143312433134331443315433164331743318433194332043321433224332343324433254332643327433284332943330433314333243333433344333543336433374333843339433404334143342433434334443345433464334743348433494335043351433524335343354433554335643357433584335943360433614336243363433644336543366433674336843369433704337143372433734337443375433764337743378433794338043381433824338343384433854338643387433884338943390433914339243393433944339543396433974339843399434004340143402434034340443405434064340743408434094341043411434124341343414434154341643417434184341943420434214342243423434244342543426434274342843429434304343143432434334343443435434364343743438434394344043441434424344343444434454344643447434484344943450434514345243453434544345543456434574345843459434604346143462434634346443465434664346743468434694347043471434724347343474434754347643477434784347943480434814348243483434844348543486434874348843489434904349143492434934349443495434964349743498434994350043501435024350343504435054350643507435084350943510435114351243513435144351543516435174351843519435204352143522435234352443525435264352743528435294353043531435324353343534435354353643537435384353943540435414354243543435444354543546435474354843549435504355143552435534355443555435564355743558435594356043561435624356343564435654356643567435684356943570435714357243573435744357543576435774357843579435804358143582435834358443585435864358743588435894359043591435924359343594435954359643597435984359943600436014360243603436044360543606436074360843609436104361143612436134361443615436164361743618436194362043621436224362343624436254362643627436284362943630436314363243633436344363543636436374363843639436404364143642436434364443645436464364743648436494365043651436524365343654436554365643657436584365943660436614366243663436644366543666436674366843669436704367143672436734367443675436764367743678436794368043681436824368343684436854368643687436884368943690436914369243693436944369543696436974369843699437004370143702437034370443705437064370743708437094371043711437124371343714437154371643717437184371943720437214372243723437244372543726437274372843729437304373143732437334373443735437364373743738437394374043741437424374343744437454374643747437484374943750437514375243753437544375543756437574375843759437604376143762437634376443765437664376743768437694377043771437724377343774437754377643777437784377943780437814378243783437844378543786437874378843789437904379143792437934379443795437964379743798437994380043801438024380343804438054380643807438084380943810438114381243813438144381543816438174381843819438204382143822438234382443825438264382743828438294383043831438324383343834438354383643837438384383943840438414384243843438444384543846438474384843849438504385143852438534385443855438564385743858438594386043861438624386343864438654386643867438684386943870438714387243873438744387543876438774387843879438804388143882438834388443885438864388743888438894389043891438924389343894438954389643897438984389943900439014390243903439044390543906439074390843909439104391143912439134391443915439164391743918439194392043921439224392343924439254392643927439284392943930439314393243933439344393543936439374393843939439404394143942439434394443945439464394743948439494395043951439524395343954439554395643957439584395943960439614396243963439644396543966439674396843969439704397143972439734397443975439764397743978439794398043981439824398343984439854398643987439884398943990439914399243993439944399543996439974399843999440004400144002440034400444005440064400744008440094401044011440124401344014440154401644017440184401944020440214402244023440244402544026440274402844029440304403144032440334403444035440364403744038440394404044041440424404344044440454404644047440484404944050440514405244053440544405544056440574405844059440604406144062440634406444065440664406744068440694407044071440724407344074440754407644077440784407944080440814408244083440844408544086440874408844089440904409144092440934409444095440964409744098440994410044101441024410344104441054410644107441084410944110441114411244113441144411544116441174411844119441204412144122441234412444125441264412744128441294413044131441324413344134441354413644137441384413944140441414414244143441444414544146441474414844149441504415144152441534415444155441564415744158441594416044161441624416344164441654416644167441684416944170441714417244173441744417544176441774417844179441804418144182441834418444185441864418744188441894419044191441924419344194441954419644197441984419944200442014420244203442044420544206442074420844209442104421144212442134421444215442164421744218442194422044221442224422344224442254422644227442284422944230442314423244233442344423544236442374423844239442404424144242442434424444245442464424744248442494425044251442524425344254442554425644257442584425944260442614426244263442644426544266442674426844269442704427144272442734427444275442764427744278442794428044281442824428344284442854428644287442884428944290442914429244293442944429544296442974429844299443004430144302443034430444305443064430744308443094431044311443124431344314443154431644317443184431944320443214432244323443244432544326443274432844329443304433144332443334433444335443364433744338443394434044341443424434344344443454434644347443484434944350443514435244353443544435544356443574435844359443604436144362443634436444365443664436744368443694437044371443724437344374443754437644377443784437944380443814438244383443844438544386443874438844389443904439144392443934439444395443964439744398443994440044401444024440344404444054440644407444084440944410444114441244413444144441544416444174441844419444204442144422444234442444425444264442744428444294443044431444324443344434444354443644437444384443944440444414444244443444444444544446444474444844449444504445144452444534445444455444564445744458444594446044461444624446344464444654446644467444684446944470444714447244473444744447544476444774447844479444804448144482444834448444485444864448744488444894449044491444924449344494444954449644497444984449944500445014450244503445044450544506445074450844509445104451144512445134451444515445164451744518445194452044521445224452344524445254452644527445284452944530445314453244533445344453544536445374453844539445404454144542445434454444545445464454744548445494455044551445524455344554445554455644557445584455944560445614456244563445644456544566445674456844569445704457144572445734457444575445764457744578445794458044581445824458344584445854458644587445884458944590445914459244593445944459544596445974459844599446004460144602446034460444605446064460744608446094461044611446124461344614446154461644617446184461944620446214462244623446244462544626446274462844629446304463144632446334463444635446364463744638446394464044641446424464344644446454464644647446484464944650446514465244653446544465544656446574465844659446604466144662446634466444665446664466744668446694467044671446724467344674446754467644677446784467944680446814468244683446844468544686446874468844689446904469144692446934469444695446964469744698446994470044701447024470344704447054470644707447084470944710447114471244713447144471544716447174471844719447204472144722447234472444725447264472744728447294473044731447324473344734447354473644737447384473944740447414474244743447444474544746447474474844749447504475144752447534475444755447564475744758447594476044761447624476344764447654476644767447684476944770447714477244773447744477544776447774477844779447804478144782447834478444785447864478744788447894479044791447924479344794447954479644797447984479944800448014480244803448044480544806448074480844809448104481144812448134481444815448164481744818448194482044821448224482344824448254482644827448284482944830448314483244833448344483544836448374483844839448404484144842448434484444845448464484744848448494485044851448524485344854448554485644857448584485944860448614486244863448644486544866448674486844869448704487144872448734487444875448764487744878448794488044881448824488344884448854488644887448884488944890448914489244893448944489544896448974489844899449004490144902449034490444905449064490744908449094491044911449124491344914449154491644917449184491944920449214492244923449244492544926449274492844929449304493144932449334493444935449364493744938449394494044941449424494344944449454494644947449484494944950449514495244953449544495544956449574495844959449604496144962449634496444965449664496744968449694497044971449724497344974449754497644977449784497944980449814498244983449844498544986449874498844989449904499144992449934499444995449964499744998449994500045001450024500345004450054500645007450084500945010450114501245013450144501545016450174501845019450204502145022450234502445025450264502745028450294503045031450324503345034450354503645037450384503945040450414504245043450444504545046450474504845049450504505145052450534505445055450564505745058450594506045061450624506345064450654506645067450684506945070450714507245073450744507545076450774507845079450804508145082450834508445085450864508745088450894509045091450924509345094450954509645097450984509945100451014510245103451044510545106451074510845109451104511145112451134511445115451164511745118451194512045121451224512345124451254512645127451284512945130451314513245133451344513545136451374513845139451404514145142451434514445145451464514745148451494515045151451524515345154451554515645157451584515945160451614516245163451644516545166451674516845169451704517145172451734517445175451764517745178451794518045181451824518345184451854518645187451884518945190451914519245193451944519545196451974519845199452004520145202452034520445205452064520745208452094521045211452124521345214452154521645217452184521945220452214522245223452244522545226452274522845229452304523145232452334523445235452364523745238452394524045241452424524345244452454524645247452484524945250452514525245253452544525545256452574525845259452604526145262452634526445265452664526745268452694527045271452724527345274452754527645277452784527945280452814528245283452844528545286452874528845289452904529145292452934529445295452964529745298452994530045301453024530345304453054530645307453084530945310453114531245313453144531545316453174531845319453204532145322453234532445325453264532745328453294533045331453324533345334453354533645337453384533945340453414534245343453444534545346453474534845349453504535145352453534535445355453564535745358453594536045361453624536345364453654536645367453684536945370453714537245373453744537545376453774537845379453804538145382453834538445385453864538745388453894539045391453924539345394453954539645397453984539945400454014540245403454044540545406454074540845409454104541145412454134541445415454164541745418454194542045421454224542345424454254542645427454284542945430454314543245433454344543545436454374543845439454404544145442454434544445445454464544745448454494545045451454524545345454454554545645457454584545945460454614546245463454644546545466454674546845469454704547145472454734547445475454764547745478454794548045481454824548345484454854548645487454884548945490454914549245493454944549545496454974549845499455004550145502455034550445505455064550745508455094551045511455124551345514455154551645517455184551945520455214552245523455244552545526455274552845529455304553145532455334553445535455364553745538455394554045541455424554345544455454554645547455484554945550455514555245553455544555545556455574555845559455604556145562455634556445565455664556745568455694557045571455724557345574455754557645577455784557945580455814558245583455844558545586455874558845589455904559145592455934559445595455964559745598455994560045601456024560345604456054560645607456084560945610456114561245613456144561545616456174561845619456204562145622456234562445625456264562745628456294563045631456324563345634456354563645637456384563945640456414564245643456444564545646456474564845649456504565145652456534565445655456564565745658456594566045661456624566345664456654566645667456684566945670456714567245673456744567545676456774567845679456804568145682456834568445685456864568745688456894569045691456924569345694456954569645697456984569945700457014570245703457044570545706457074570845709457104571145712457134571445715457164571745718457194572045721457224572345724457254572645727457284572945730457314573245733457344573545736457374573845739457404574145742457434574445745457464574745748457494575045751457524575345754457554575645757457584575945760457614576245763457644576545766457674576845769457704577145772457734577445775457764577745778457794578045781457824578345784457854578645787457884578945790457914579245793457944579545796457974579845799458004580145802458034580445805458064580745808458094581045811458124581345814458154581645817458184581945820458214582245823458244582545826458274582845829458304583145832458334583445835458364583745838458394584045841458424584345844458454584645847458484584945850458514585245853458544585545856458574585845859458604586145862458634586445865458664586745868458694587045871458724587345874458754587645877458784587945880458814588245883458844588545886458874588845889458904589145892458934589445895458964589745898458994590045901459024590345904459054590645907459084590945910459114591245913459144591545916459174591845919459204592145922459234592445925459264592745928459294593045931459324593345934459354593645937459384593945940459414594245943459444594545946459474594845949459504595145952459534595445955459564595745958459594596045961459624596345964459654596645967459684596945970459714597245973459744597545976459774597845979459804598145982459834598445985459864598745988459894599045991459924599345994459954599645997459984599946000460014600246003460044600546006460074600846009460104601146012460134601446015460164601746018460194602046021460224602346024460254602646027460284602946030460314603246033460344603546036460374603846039460404604146042460434604446045460464604746048460494605046051460524605346054460554605646057460584605946060460614606246063460644606546066460674606846069460704607146072460734607446075460764607746078460794608046081460824608346084460854608646087460884608946090460914609246093460944609546096460974609846099461004610146102461034610446105461064610746108461094611046111461124611346114461154611646117461184611946120461214612246123461244612546126461274612846129461304613146132461334613446135461364613746138461394614046141461424614346144461454614646147461484614946150461514615246153461544615546156461574615846159461604616146162461634616446165461664616746168461694617046171461724617346174461754617646177461784617946180461814618246183461844618546186461874618846189461904619146192461934619446195461964619746198461994620046201462024620346204462054620646207462084620946210462114621246213462144621546216462174621846219462204622146222462234622446225462264622746228462294623046231462324623346234462354623646237462384623946240462414624246243462444624546246462474624846249462504625146252462534625446255462564625746258462594626046261462624626346264462654626646267462684626946270462714627246273462744627546276462774627846279462804628146282462834628446285462864628746288462894629046291462924629346294462954629646297462984629946300463014630246303463044630546306463074630846309463104631146312463134631446315463164631746318463194632046321463224632346324463254632646327463284632946330463314633246333463344633546336463374633846339463404634146342463434634446345463464634746348463494635046351463524635346354463554635646357463584635946360463614636246363463644636546366463674636846369463704637146372463734637446375463764637746378463794638046381463824638346384463854638646387463884638946390463914639246393463944639546396463974639846399464004640146402464034640446405464064640746408464094641046411464124641346414464154641646417464184641946420464214642246423464244642546426464274642846429464304643146432464334643446435464364643746438464394644046441464424644346444464454644646447464484644946450464514645246453464544645546456464574645846459464604646146462464634646446465464664646746468464694647046471464724647346474464754647646477464784647946480464814648246483464844648546486464874648846489464904649146492464934649446495464964649746498464994650046501465024650346504465054650646507465084650946510465114651246513465144651546516465174651846519465204652146522465234652446525465264652746528465294653046531465324653346534465354653646537465384653946540465414654246543465444654546546465474654846549465504655146552465534655446555465564655746558465594656046561465624656346564465654656646567465684656946570465714657246573465744657546576465774657846579465804658146582465834658446585465864658746588465894659046591465924659346594465954659646597465984659946600466014660246603466044660546606466074660846609466104661146612466134661446615466164661746618466194662046621466224662346624466254662646627466284662946630466314663246633466344663546636466374663846639466404664146642466434664446645466464664746648466494665046651466524665346654466554665646657466584665946660466614666246663466644666546666466674666846669466704667146672466734667446675466764667746678466794668046681466824668346684466854668646687466884668946690466914669246693466944669546696466974669846699467004670146702467034670446705467064670746708467094671046711467124671346714467154671646717467184671946720467214672246723467244672546726467274672846729467304673146732467334673446735467364673746738467394674046741467424674346744467454674646747467484674946750467514675246753467544675546756467574675846759467604676146762467634676446765467664676746768467694677046771467724677346774467754677646777467784677946780467814678246783467844678546786467874678846789467904679146792467934679446795467964679746798467994680046801468024680346804468054680646807468084680946810468114681246813468144681546816468174681846819468204682146822468234682446825468264682746828468294683046831468324683346834468354683646837468384683946840468414684246843468444684546846468474684846849468504685146852468534685446855468564685746858468594686046861468624686346864468654686646867468684686946870468714687246873468744687546876468774687846879468804688146882468834688446885468864688746888468894689046891468924689346894468954689646897468984689946900469014690246903469044690546906469074690846909469104691146912469134691446915469164691746918469194692046921469224692346924469254692646927469284692946930469314693246933469344693546936469374693846939469404694146942469434694446945469464694746948469494695046951469524695346954469554695646957469584695946960469614696246963469644696546966469674696846969469704697146972469734697446975469764697746978469794698046981469824698346984469854698646987469884698946990469914699246993469944699546996469974699846999470004700147002470034700447005470064700747008470094701047011470124701347014470154701647017470184701947020470214702247023470244702547026470274702847029470304703147032470334703447035470364703747038470394704047041470424704347044470454704647047470484704947050470514705247053470544705547056470574705847059470604706147062470634706447065470664706747068470694707047071470724707347074470754707647077470784707947080470814708247083470844708547086470874708847089470904709147092470934709447095470964709747098470994710047101471024710347104471054710647107471084710947110471114711247113471144711547116471174711847119471204712147122471234712447125471264712747128471294713047131471324713347134471354713647137471384713947140471414714247143471444714547146471474714847149471504715147152471534715447155471564715747158471594716047161471624716347164471654716647167471684716947170471714717247173471744717547176471774717847179471804718147182471834718447185471864718747188471894719047191471924719347194471954719647197471984719947200472014720247203472044720547206472074720847209472104721147212472134721447215472164721747218472194722047221472224722347224472254722647227472284722947230472314723247233472344723547236472374723847239472404724147242472434724447245472464724747248472494725047251472524725347254472554725647257472584725947260472614726247263472644726547266472674726847269472704727147272472734727447275472764727747278472794728047281472824728347284472854728647287472884728947290472914729247293472944729547296472974729847299473004730147302473034730447305473064730747308473094731047311473124731347314473154731647317473184731947320473214732247323473244732547326473274732847329473304733147332473334733447335473364733747338473394734047341473424734347344473454734647347473484734947350473514735247353473544735547356473574735847359473604736147362473634736447365473664736747368473694737047371473724737347374473754737647377473784737947380473814738247383473844738547386473874738847389473904739147392473934739447395473964739747398473994740047401474024740347404474054740647407474084740947410474114741247413474144741547416474174741847419474204742147422474234742447425474264742747428474294743047431474324743347434474354743647437474384743947440474414744247443474444744547446474474744847449474504745147452474534745447455474564745747458474594746047461474624746347464474654746647467474684746947470474714747247473474744747547476474774747847479474804748147482474834748447485474864748747488474894749047491474924749347494474954749647497474984749947500475014750247503475044750547506475074750847509475104751147512475134751447515475164751747518475194752047521475224752347524475254752647527475284752947530475314753247533475344753547536475374753847539475404754147542475434754447545475464754747548475494755047551475524755347554475554755647557475584755947560475614756247563475644756547566475674756847569475704757147572475734757447575475764757747578475794758047581475824758347584475854758647587475884758947590475914759247593475944759547596475974759847599476004760147602476034760447605476064760747608476094761047611476124761347614476154761647617476184761947620476214762247623476244762547626476274762847629476304763147632476334763447635476364763747638476394764047641476424764347644476454764647647476484764947650476514765247653476544765547656476574765847659476604766147662476634766447665476664766747668476694767047671476724767347674476754767647677476784767947680476814768247683476844768547686476874768847689476904769147692476934769447695476964769747698476994770047701477024770347704477054770647707477084770947710477114771247713477144771547716477174771847719477204772147722477234772447725477264772747728477294773047731477324773347734477354773647737477384773947740477414774247743477444774547746477474774847749477504775147752477534775447755477564775747758477594776047761477624776347764477654776647767477684776947770477714777247773477744777547776477774777847779477804778147782477834778447785477864778747788477894779047791477924779347794477954779647797477984779947800478014780247803478044780547806478074780847809478104781147812478134781447815478164781747818478194782047821478224782347824478254782647827478284782947830478314783247833478344783547836478374783847839478404784147842478434784447845478464784747848478494785047851478524785347854478554785647857478584785947860478614786247863478644786547866478674786847869478704787147872478734787447875478764787747878478794788047881478824788347884478854788647887478884788947890478914789247893478944789547896478974789847899479004790147902479034790447905479064790747908479094791047911479124791347914479154791647917479184791947920479214792247923479244792547926479274792847929479304793147932479334793447935479364793747938479394794047941479424794347944479454794647947479484794947950479514795247953479544795547956479574795847959479604796147962479634796447965479664796747968479694797047971479724797347974479754797647977479784797947980479814798247983479844798547986479874798847989479904799147992479934799447995479964799747998479994800048001480024800348004480054800648007480084800948010480114801248013480144801548016480174801848019480204802148022480234802448025480264802748028480294803048031480324803348034480354803648037480384803948040480414804248043480444804548046480474804848049480504805148052480534805448055480564805748058480594806048061480624806348064480654806648067480684806948070480714807248073480744807548076480774807848079480804808148082480834808448085480864808748088480894809048091480924809348094480954809648097480984809948100481014810248103481044810548106481074810848109481104811148112481134811448115481164811748118481194812048121481224812348124481254812648127481284812948130481314813248133481344813548136481374813848139481404814148142481434814448145481464814748148481494815048151481524815348154481554815648157481584815948160481614816248163481644816548166481674816848169481704817148172481734817448175481764817748178481794818048181481824818348184481854818648187481884818948190481914819248193481944819548196481974819848199482004820148202482034820448205482064820748208482094821048211482124821348214482154821648217482184821948220482214822248223482244822548226482274822848229482304823148232482334823448235482364823748238482394824048241482424824348244482454824648247482484824948250482514825248253482544825548256482574825848259482604826148262482634826448265482664826748268482694827048271482724827348274482754827648277482784827948280482814828248283482844828548286482874828848289482904829148292482934829448295482964829748298482994830048301483024830348304483054830648307483084830948310483114831248313483144831548316483174831848319483204832148322483234832448325483264832748328483294833048331483324833348334483354833648337483384833948340483414834248343483444834548346483474834848349483504835148352483534835448355483564835748358483594836048361483624836348364483654836648367483684836948370483714837248373483744837548376483774837848379483804838148382483834838448385483864838748388483894839048391483924839348394483954839648397483984839948400484014840248403484044840548406484074840848409484104841148412484134841448415484164841748418484194842048421484224842348424484254842648427484284842948430484314843248433484344843548436484374843848439484404844148442484434844448445484464844748448484494845048451484524845348454484554845648457484584845948460484614846248463484644846548466484674846848469484704847148472484734847448475484764847748478484794848048481484824848348484484854848648487484884848948490484914849248493484944849548496484974849848499485004850148502485034850448505485064850748508485094851048511485124851348514485154851648517485184851948520485214852248523485244852548526485274852848529485304853148532485334853448535485364853748538485394854048541485424854348544485454854648547485484854948550485514855248553485544855548556485574855848559485604856148562485634856448565485664856748568485694857048571485724857348574485754857648577485784857948580485814858248583485844858548586485874858848589485904859148592485934859448595485964859748598485994860048601486024860348604486054860648607486084860948610486114861248613486144861548616486174861848619486204862148622486234862448625486264862748628486294863048631486324863348634486354863648637486384863948640486414864248643486444864548646486474864848649486504865148652486534865448655486564865748658486594866048661486624866348664486654866648667486684866948670486714867248673486744867548676486774867848679486804868148682486834868448685486864868748688486894869048691486924869348694486954869648697486984869948700487014870248703487044870548706487074870848709487104871148712487134871448715487164871748718487194872048721487224872348724487254872648727487284872948730487314873248733487344873548736487374873848739487404874148742487434874448745487464874748748487494875048751487524875348754487554875648757487584875948760487614876248763487644876548766487674876848769487704877148772487734877448775487764877748778487794878048781487824878348784487854878648787487884878948790487914879248793487944879548796487974879848799488004880148802488034880448805488064880748808488094881048811488124881348814488154881648817488184881948820488214882248823488244882548826488274882848829488304883148832488334883448835488364883748838488394884048841488424884348844488454884648847488484884948850488514885248853488544885548856488574885848859488604886148862488634886448865488664886748868488694887048871488724887348874488754887648877488784887948880488814888248883488844888548886488874888848889488904889148892488934889448895488964889748898488994890048901489024890348904489054890648907489084890948910489114891248913489144891548916489174891848919489204892148922489234892448925489264892748928489294893048931489324893348934489354893648937489384893948940489414894248943489444894548946489474894848949489504895148952489534895448955489564895748958489594896048961489624896348964489654896648967489684896948970489714897248973489744897548976489774897848979489804898148982489834898448985489864898748988489894899048991489924899348994489954899648997489984899949000490014900249003490044900549006490074900849009490104901149012490134901449015490164901749018490194902049021490224902349024490254902649027490284902949030490314903249033490344903549036490374903849039490404904149042490434904449045490464904749048490494905049051490524905349054490554905649057490584905949060490614906249063490644906549066490674906849069490704907149072490734907449075490764907749078490794908049081490824908349084490854908649087490884908949090490914909249093490944909549096490974909849099491004910149102491034910449105491064910749108491094911049111491124911349114491154911649117491184911949120491214912249123491244912549126491274912849129491304913149132491334913449135491364913749138491394914049141491424914349144491454914649147491484914949150491514915249153491544915549156491574915849159491604916149162491634916449165491664916749168491694917049171491724917349174491754917649177491784917949180491814918249183491844918549186491874918849189491904919149192491934919449195491964919749198491994920049201492024920349204492054920649207492084920949210492114921249213492144921549216492174921849219492204922149222492234922449225492264922749228492294923049231492324923349234492354923649237492384923949240492414924249243492444924549246492474924849249492504925149252492534925449255492564925749258492594926049261492624926349264492654926649267492684926949270492714927249273492744927549276492774927849279492804928149282492834928449285492864928749288492894929049291492924929349294492954929649297492984929949300493014930249303493044930549306493074930849309493104931149312493134931449315493164931749318493194932049321493224932349324493254932649327493284932949330493314933249333493344933549336493374933849339493404934149342493434934449345493464934749348493494935049351493524935349354493554935649357493584935949360493614936249363493644936549366493674936849369493704937149372493734937449375493764937749378493794938049381493824938349384493854938649387493884938949390493914939249393493944939549396493974939849399494004940149402494034940449405494064940749408494094941049411494124941349414494154941649417494184941949420494214942249423494244942549426494274942849429494304943149432494334943449435494364943749438494394944049441494424944349444494454944649447494484944949450494514945249453494544945549456494574945849459494604946149462494634946449465494664946749468494694947049471494724947349474494754947649477494784947949480494814948249483494844948549486494874948849489494904949149492494934949449495494964949749498494994950049501495024950349504495054950649507495084950949510495114951249513495144951549516495174951849519495204952149522495234952449525495264952749528495294953049531495324953349534495354953649537495384953949540495414954249543495444954549546495474954849549495504955149552495534955449555495564955749558495594956049561495624956349564495654956649567495684956949570495714957249573495744957549576495774957849579495804958149582495834958449585495864958749588495894959049591495924959349594495954959649597495984959949600496014960249603496044960549606496074960849609496104961149612496134961449615496164961749618496194962049621496224962349624496254962649627496284962949630496314963249633496344963549636496374963849639496404964149642496434964449645496464964749648496494965049651496524965349654496554965649657496584965949660496614966249663496644966549666496674966849669496704967149672496734967449675496764967749678496794968049681496824968349684496854968649687496884968949690496914969249693496944969549696496974969849699497004970149702497034970449705497064970749708497094971049711497124971349714497154971649717497184971949720497214972249723497244972549726497274972849729497304973149732497334973449735497364973749738497394974049741497424974349744497454974649747497484974949750497514975249753497544975549756497574975849759497604976149762497634976449765497664976749768497694977049771497724977349774497754977649777497784977949780497814978249783497844978549786497874978849789497904979149792497934979449795497964979749798497994980049801498024980349804498054980649807498084980949810498114981249813498144981549816498174981849819498204982149822498234982449825498264982749828498294983049831498324983349834498354983649837498384983949840498414984249843498444984549846498474984849849498504985149852498534985449855498564985749858498594986049861498624986349864498654986649867498684986949870498714987249873498744987549876498774987849879498804988149882498834988449885498864988749888498894989049891498924989349894498954989649897498984989949900499014990249903499044990549906499074990849909499104991149912499134991449915499164991749918499194992049921499224992349924499254992649927499284992949930499314993249933499344993549936499374993849939499404994149942499434994449945499464994749948499494995049951499524995349954499554995649957499584995949960499614996249963499644996549966499674996849969499704997149972499734997449975499764997749978499794998049981499824998349984499854998649987499884998949990499914999249993499944999549996499974999849999500005000150002500035000450005500065000750008500095001050011500125001350014500155001650017500185001950020500215002250023500245002550026500275002850029500305003150032500335003450035500365003750038500395004050041500425004350044500455004650047500485004950050500515005250053500545005550056500575005850059500605006150062500635006450065500665006750068500695007050071500725007350074500755007650077500785007950080500815008250083500845008550086500875008850089500905009150092500935009450095500965009750098500995010050101501025010350104501055010650107501085010950110501115011250113501145011550116501175011850119501205012150122501235012450125501265012750128501295013050131501325013350134501355013650137501385013950140501415014250143501445014550146501475014850149501505015150152501535015450155501565015750158501595016050161501625016350164501655016650167501685016950170501715017250173501745017550176501775017850179501805018150182501835018450185501865018750188501895019050191501925019350194501955019650197501985019950200502015020250203502045020550206502075020850209502105021150212502135021450215502165021750218502195022050221502225022350224502255022650227502285022950230502315023250233502345023550236502375023850239502405024150242502435024450245502465024750248502495025050251502525025350254502555025650257502585025950260502615026250263502645026550266502675026850269502705027150272502735027450275502765027750278502795028050281502825028350284502855028650287502885028950290502915029250293502945029550296502975029850299503005030150302503035030450305503065030750308503095031050311503125031350314503155031650317503185031950320503215032250323503245032550326503275032850329503305033150332503335033450335503365033750338503395034050341503425034350344503455034650347503485034950350503515035250353503545035550356503575035850359503605036150362503635036450365503665036750368503695037050371503725037350374503755037650377503785037950380503815038250383503845038550386503875038850389503905039150392503935039450395503965039750398503995040050401504025040350404504055040650407504085040950410504115041250413504145041550416504175041850419504205042150422504235042450425504265042750428504295043050431504325043350434504355043650437504385043950440504415044250443504445044550446504475044850449504505045150452504535045450455504565045750458504595046050461504625046350464504655046650467504685046950470504715047250473504745047550476504775047850479504805048150482504835048450485504865048750488504895049050491504925049350494504955049650497504985049950500505015050250503505045050550506505075050850509505105051150512505135051450515505165051750518505195052050521505225052350524505255052650527505285052950530505315053250533505345053550536505375053850539505405054150542505435054450545505465054750548505495055050551505525055350554505555055650557505585055950560505615056250563505645056550566505675056850569505705057150572505735057450575505765057750578505795058050581505825058350584505855058650587505885058950590505915059250593505945059550596505975059850599506005060150602506035060450605506065060750608506095061050611506125061350614506155061650617506185061950620506215062250623506245062550626506275062850629506305063150632506335063450635506365063750638506395064050641506425064350644506455064650647506485064950650506515065250653506545065550656506575065850659506605066150662506635066450665506665066750668506695067050671506725067350674506755067650677506785067950680506815068250683506845068550686506875068850689506905069150692506935069450695506965069750698506995070050701507025070350704507055070650707507085070950710507115071250713507145071550716507175071850719507205072150722507235072450725507265072750728507295073050731507325073350734507355073650737507385073950740507415074250743507445074550746507475074850749507505075150752507535075450755507565075750758507595076050761507625076350764507655076650767507685076950770507715077250773507745077550776507775077850779507805078150782507835078450785507865078750788507895079050791507925079350794507955079650797507985079950800508015080250803508045080550806508075080850809508105081150812508135081450815508165081750818508195082050821508225082350824508255082650827508285082950830508315083250833508345083550836508375083850839508405084150842508435084450845508465084750848508495085050851508525085350854508555085650857508585085950860508615086250863508645086550866508675086850869508705087150872508735087450875508765087750878508795088050881508825088350884508855088650887508885088950890508915089250893508945089550896508975089850899509005090150902509035090450905509065090750908509095091050911509125091350914509155091650917509185091950920509215092250923509245092550926509275092850929509305093150932509335093450935509365093750938509395094050941509425094350944509455094650947509485094950950509515095250953509545095550956509575095850959509605096150962509635096450965509665096750968509695097050971509725097350974509755097650977509785097950980509815098250983509845098550986509875098850989509905099150992509935099450995509965099750998509995100051001510025100351004510055100651007510085100951010510115101251013510145101551016510175101851019510205102151022510235102451025510265102751028510295103051031510325103351034510355103651037510385103951040510415104251043510445104551046510475104851049510505105151052510535105451055510565105751058510595106051061510625106351064510655106651067510685106951070510715107251073510745107551076510775107851079510805108151082510835108451085510865108751088510895109051091510925109351094510955109651097510985109951100511015110251103511045110551106511075110851109511105111151112511135111451115511165111751118511195112051121511225112351124511255112651127511285112951130511315113251133511345113551136511375113851139511405114151142511435114451145511465114751148511495115051151511525115351154511555115651157511585115951160511615116251163511645116551166511675116851169511705117151172511735117451175511765117751178511795118051181511825118351184511855118651187511885118951190511915119251193511945119551196511975119851199512005120151202512035120451205512065120751208512095121051211512125121351214512155121651217512185121951220512215122251223512245122551226512275122851229512305123151232512335123451235512365123751238512395124051241512425124351244512455124651247512485124951250512515125251253512545125551256512575125851259512605126151262512635126451265512665126751268512695127051271512725127351274512755127651277512785127951280512815128251283512845128551286512875128851289512905129151292512935129451295512965129751298512995130051301513025130351304513055130651307513085130951310513115131251313513145131551316513175131851319513205132151322513235132451325513265132751328513295133051331513325133351334513355133651337513385133951340513415134251343513445134551346513475134851349513505135151352513535135451355513565135751358513595136051361513625136351364513655136651367513685136951370513715137251373513745137551376513775137851379513805138151382513835138451385513865138751388513895139051391513925139351394513955139651397513985139951400514015140251403514045140551406514075140851409514105141151412514135141451415514165141751418514195142051421514225142351424514255142651427514285142951430514315143251433514345143551436514375143851439514405144151442514435144451445514465144751448514495145051451514525145351454514555145651457514585145951460514615146251463514645146551466514675146851469514705147151472514735147451475514765147751478514795148051481514825148351484514855148651487514885148951490514915149251493514945149551496514975149851499515005150151502515035150451505515065150751508515095151051511515125151351514515155151651517515185151951520515215152251523515245152551526515275152851529515305153151532515335153451535515365153751538515395154051541515425154351544515455154651547515485154951550515515155251553515545155551556515575155851559515605156151562515635156451565515665156751568515695157051571515725157351574515755157651577515785157951580515815158251583515845158551586515875158851589515905159151592515935159451595515965159751598515995160051601516025160351604516055160651607516085160951610516115161251613516145161551616516175161851619516205162151622516235162451625516265162751628516295163051631516325163351634516355163651637516385163951640516415164251643516445164551646516475164851649516505165151652516535165451655516565165751658516595166051661516625166351664516655166651667516685166951670516715167251673516745167551676516775167851679516805168151682516835168451685516865168751688516895169051691516925169351694516955169651697516985169951700517015170251703517045170551706517075170851709517105171151712517135171451715517165171751718517195172051721517225172351724517255172651727517285172951730517315173251733517345173551736517375173851739517405174151742517435174451745517465174751748517495175051751517525175351754517555175651757517585175951760517615176251763517645176551766517675176851769517705177151772517735177451775517765177751778517795178051781517825178351784517855178651787517885178951790517915179251793517945179551796517975179851799518005180151802518035180451805518065180751808518095181051811518125181351814518155181651817518185181951820518215182251823518245182551826518275182851829518305183151832518335183451835518365183751838518395184051841518425184351844518455184651847518485184951850518515185251853518545185551856518575185851859518605186151862518635186451865518665186751868518695187051871518725187351874518755187651877518785187951880518815188251883518845188551886518875188851889518905189151892518935189451895518965189751898518995190051901519025190351904519055190651907519085190951910519115191251913519145191551916519175191851919519205192151922519235192451925519265192751928519295193051931519325193351934519355193651937519385193951940519415194251943519445194551946519475194851949519505195151952519535195451955519565195751958519595196051961519625196351964519655196651967519685196951970519715197251973519745197551976519775197851979519805198151982519835198451985519865198751988519895199051991519925199351994519955199651997519985199952000520015200252003520045200552006520075200852009520105201152012520135201452015520165201752018520195202052021520225202352024520255202652027520285202952030520315203252033520345203552036520375203852039520405204152042520435204452045520465204752048520495205052051520525205352054520555205652057520585205952060520615206252063520645206552066520675206852069520705207152072520735207452075520765207752078520795208052081520825208352084520855208652087520885208952090520915209252093520945209552096520975209852099521005210152102521035210452105521065210752108521095211052111521125211352114521155211652117521185211952120521215212252123521245212552126521275212852129521305213152132521335213452135521365213752138521395214052141521425214352144521455214652147521485214952150521515215252153521545215552156521575215852159521605216152162521635216452165521665216752168521695217052171521725217352174521755217652177521785217952180521815218252183521845218552186521875218852189521905219152192521935219452195521965219752198521995220052201522025220352204522055220652207522085220952210522115221252213522145221552216522175221852219522205222152222522235222452225522265222752228522295223052231522325223352234522355223652237522385223952240522415224252243522445224552246522475224852249522505225152252522535225452255522565225752258522595226052261522625226352264522655226652267522685226952270522715227252273522745227552276522775227852279522805228152282522835228452285522865228752288522895229052291522925229352294522955229652297522985229952300523015230252303523045230552306523075230852309523105231152312523135231452315523165231752318523195232052321523225232352324523255232652327523285232952330523315233252333523345233552336523375233852339523405234152342523435234452345523465234752348523495235052351523525235352354523555235652357523585235952360523615236252363523645236552366523675236852369523705237152372523735237452375523765237752378523795238052381523825238352384523855238652387523885238952390523915239252393523945239552396523975239852399524005240152402524035240452405524065240752408524095241052411524125241352414524155241652417524185241952420524215242252423524245242552426524275242852429524305243152432524335243452435524365243752438524395244052441524425244352444524455244652447524485244952450524515245252453524545245552456524575245852459524605246152462524635246452465524665246752468524695247052471524725247352474524755247652477524785247952480524815248252483524845248552486524875248852489524905249152492524935249452495524965249752498524995250052501525025250352504525055250652507525085250952510525115251252513525145251552516525175251852519525205252152522525235252452525525265252752528525295253052531525325253352534525355253652537525385253952540525415254252543525445254552546525475254852549525505255152552525535255452555525565255752558525595256052561525625256352564525655256652567525685256952570525715257252573525745257552576525775257852579525805258152582525835258452585525865258752588525895259052591525925259352594525955259652597525985259952600526015260252603526045260552606526075260852609526105261152612526135261452615526165261752618526195262052621526225262352624526255262652627526285262952630526315263252633526345263552636526375263852639526405264152642526435264452645526465264752648526495265052651526525265352654526555265652657526585265952660526615266252663526645266552666526675266852669526705267152672526735267452675526765267752678526795268052681526825268352684526855268652687526885268952690526915269252693526945269552696526975269852699527005270152702527035270452705527065270752708527095271052711527125271352714527155271652717527185271952720527215272252723527245272552726527275272852729527305273152732527335273452735527365273752738527395274052741527425274352744527455274652747527485274952750527515275252753527545275552756527575275852759527605276152762527635276452765527665276752768527695277052771527725277352774527755277652777527785277952780527815278252783527845278552786527875278852789527905279152792527935279452795527965279752798527995280052801528025280352804528055280652807528085280952810528115281252813528145281552816528175281852819528205282152822528235282452825528265282752828528295283052831528325283352834528355283652837528385283952840528415284252843528445284552846528475284852849528505285152852528535285452855528565285752858528595286052861528625286352864528655286652867528685286952870528715287252873528745287552876528775287852879528805288152882528835288452885528865288752888528895289052891528925289352894528955289652897528985289952900529015290252903529045290552906529075290852909529105291152912529135291452915529165291752918529195292052921529225292352924529255292652927529285292952930529315293252933529345293552936529375293852939529405294152942529435294452945529465294752948529495295052951529525295352954529555295652957529585295952960529615296252963529645296552966529675296852969529705297152972529735297452975529765297752978529795298052981529825298352984529855298652987529885298952990529915299252993529945299552996529975299852999530005300153002530035300453005530065300753008530095301053011530125301353014530155301653017530185301953020530215302253023530245302553026530275302853029530305303153032530335303453035530365303753038530395304053041530425304353044530455304653047530485304953050530515305253053530545305553056530575305853059530605306153062530635306453065530665306753068530695307053071530725307353074530755307653077530785307953080530815308253083530845308553086530875308853089530905309153092530935309453095530965309753098530995310053101531025310353104531055310653107531085310953110531115311253113531145311553116531175311853119531205312153122531235312453125531265312753128531295313053131531325313353134531355313653137531385313953140531415314253143531445314553146531475314853149531505315153152531535315453155531565315753158531595316053161531625316353164531655316653167531685316953170531715317253173531745317553176531775317853179531805318153182531835318453185531865318753188531895319053191531925319353194531955319653197531985319953200532015320253203532045320553206532075320853209532105321153212532135321453215532165321753218532195322053221532225322353224532255322653227532285322953230532315323253233532345323553236532375323853239532405324153242532435324453245532465324753248532495325053251532525325353254532555325653257532585325953260532615326253263532645326553266532675326853269532705327153272532735327453275532765327753278532795328053281532825328353284532855328653287532885328953290532915329253293532945329553296532975329853299533005330153302533035330453305533065330753308533095331053311533125331353314533155331653317533185331953320533215332253323533245332553326533275332853329533305333153332533335333453335533365333753338533395334053341533425334353344533455334653347533485334953350533515335253353533545335553356533575335853359533605336153362533635336453365533665336753368533695337053371533725337353374533755337653377533785337953380533815338253383533845338553386533875338853389533905339153392533935339453395533965339753398533995340053401534025340353404534055340653407534085340953410534115341253413534145341553416534175341853419534205342153422534235342453425534265342753428534295343053431534325343353434534355343653437534385343953440534415344253443534445344553446534475344853449534505345153452534535345453455534565345753458534595346053461534625346353464534655346653467534685346953470534715347253473534745347553476534775347853479534805348153482534835348453485534865348753488534895349053491534925349353494534955349653497534985349953500535015350253503535045350553506535075350853509535105351153512535135351453515535165351753518535195352053521535225352353524535255352653527535285352953530535315353253533535345353553536535375353853539535405354153542535435354453545535465354753548535495355053551535525355353554535555355653557535585355953560535615356253563535645356553566535675356853569535705357153572535735357453575535765357753578535795358053581535825358353584535855358653587535885358953590535915359253593535945359553596535975359853599536005360153602536035360453605536065360753608536095361053611536125361353614536155361653617536185361953620536215362253623536245362553626536275362853629536305363153632536335363453635536365363753638536395364053641536425364353644536455364653647536485364953650536515365253653536545365553656536575365853659536605366153662536635366453665536665366753668536695367053671536725367353674536755367653677536785367953680536815368253683536845368553686536875368853689536905369153692536935369453695536965369753698536995370053701537025370353704537055370653707537085370953710537115371253713537145371553716537175371853719537205372153722537235372453725537265372753728537295373053731537325373353734537355373653737537385373953740537415374253743537445374553746537475374853749537505375153752537535375453755537565375753758537595376053761537625376353764537655376653767537685376953770537715377253773537745377553776537775377853779537805378153782537835378453785537865378753788537895379053791537925379353794537955379653797537985379953800538015380253803538045380553806538075380853809538105381153812538135381453815538165381753818538195382053821538225382353824538255382653827538285382953830538315383253833538345383553836538375383853839538405384153842538435384453845538465384753848538495385053851538525385353854538555385653857538585385953860538615386253863538645386553866538675386853869538705387153872538735387453875538765387753878538795388053881538825388353884538855388653887538885388953890538915389253893538945389553896538975389853899539005390153902539035390453905539065390753908539095391053911539125391353914539155391653917539185391953920539215392253923539245392553926539275392853929539305393153932539335393453935539365393753938539395394053941539425394353944539455394653947539485394953950539515395253953539545395553956539575395853959539605396153962539635396453965539665396753968539695397053971539725397353974539755397653977539785397953980539815398253983539845398553986539875398853989539905399153992539935399453995539965399753998539995400054001540025400354004540055400654007540085400954010540115401254013540145401554016540175401854019540205402154022540235402454025540265402754028540295403054031540325403354034540355403654037540385403954040540415404254043540445404554046540475404854049540505405154052540535405454055540565405754058540595406054061540625406354064540655406654067540685406954070540715407254073540745407554076540775407854079540805408154082540835408454085540865408754088540895409054091540925409354094540955409654097540985409954100541015410254103541045410554106541075410854109541105411154112541135411454115541165411754118541195412054121541225412354124541255412654127541285412954130541315413254133541345413554136541375413854139541405414154142541435414454145541465414754148541495415054151541525415354154541555415654157541585415954160541615416254163541645416554166541675416854169541705417154172541735417454175541765417754178541795418054181541825418354184541855418654187541885418954190541915419254193541945419554196541975419854199542005420154202542035420454205542065420754208542095421054211542125421354214542155421654217542185421954220542215422254223542245422554226542275422854229542305423154232542335423454235542365423754238542395424054241542425424354244542455424654247542485424954250542515425254253542545425554256542575425854259542605426154262542635426454265542665426754268542695427054271542725427354274542755427654277542785427954280542815428254283542845428554286542875428854289542905429154292542935429454295542965429754298542995430054301543025430354304543055430654307543085430954310543115431254313543145431554316543175431854319543205432154322543235432454325543265432754328543295433054331543325433354334543355433654337543385433954340543415434254343543445434554346543475434854349543505435154352543535435454355543565435754358543595436054361543625436354364543655436654367543685436954370543715437254373543745437554376543775437854379543805438154382543835438454385543865438754388543895439054391543925439354394543955439654397543985439954400544015440254403544045440554406544075440854409544105441154412544135441454415544165441754418544195442054421544225442354424544255442654427544285442954430544315443254433544345443554436544375443854439544405444154442544435444454445544465444754448544495445054451544525445354454544555445654457544585445954460544615446254463544645446554466544675446854469544705447154472544735447454475544765447754478544795448054481544825448354484544855448654487544885448954490544915449254493544945449554496544975449854499545005450154502545035450454505545065450754508545095451054511545125451354514545155451654517545185451954520545215452254523545245452554526545275452854529545305453154532545335453454535545365453754538545395454054541545425454354544545455454654547545485454954550545515455254553545545455554556545575455854559545605456154562545635456454565545665456754568545695457054571545725457354574545755457654577545785457954580545815458254583545845458554586545875458854589545905459154592545935459454595545965459754598545995460054601546025460354604546055460654607546085460954610546115461254613546145461554616546175461854619546205462154622546235462454625546265462754628546295463054631546325463354634546355463654637546385463954640546415464254643546445464554646546475464854649546505465154652546535465454655546565465754658546595466054661546625466354664546655466654667546685466954670546715467254673546745467554676546775467854679546805468154682546835468454685546865468754688546895469054691546925469354694546955469654697546985469954700547015470254703547045470554706547075470854709547105471154712547135471454715547165471754718547195472054721547225472354724547255472654727547285472954730547315473254733547345473554736547375473854739547405474154742547435474454745547465474754748547495475054751547525475354754547555475654757547585475954760547615476254763547645476554766547675476854769547705477154772547735477454775547765477754778547795478054781547825478354784547855478654787547885478954790547915479254793547945479554796547975479854799548005480154802548035480454805548065480754808548095481054811548125481354814548155481654817548185481954820548215482254823548245482554826548275482854829548305483154832548335483454835548365483754838548395484054841548425484354844548455484654847548485484954850548515485254853548545485554856548575485854859548605486154862548635486454865548665486754868548695487054871548725487354874548755487654877548785487954880548815488254883548845488554886548875488854889548905489154892548935489454895548965489754898548995490054901549025490354904549055490654907549085490954910549115491254913549145491554916549175491854919549205492154922549235492454925549265492754928549295493054931549325493354934549355493654937549385493954940549415494254943549445494554946549475494854949549505495154952549535495454955549565495754958549595496054961549625496354964549655496654967549685496954970549715497254973549745497554976549775497854979549805498154982549835498454985549865498754988549895499054991549925499354994549955499654997549985499955000550015500255003550045500555006550075500855009550105501155012550135501455015550165501755018550195502055021550225502355024550255502655027550285502955030550315503255033550345503555036550375503855039550405504155042550435504455045550465504755048550495505055051550525505355054550555505655057550585505955060550615506255063550645506555066550675506855069550705507155072550735507455075550765507755078550795508055081550825508355084550855508655087550885508955090550915509255093550945509555096550975509855099551005510155102551035510455105551065510755108551095511055111551125511355114551155511655117551185511955120551215512255123551245512555126551275512855129551305513155132551335513455135551365513755138551395514055141551425514355144551455514655147551485514955150551515515255153551545515555156551575515855159551605516155162551635516455165551665516755168551695517055171551725517355174551755517655177551785517955180551815518255183551845518555186551875518855189551905519155192551935519455195551965519755198551995520055201552025520355204552055520655207552085520955210552115521255213552145521555216552175521855219552205522155222552235522455225552265522755228552295523055231552325523355234552355523655237552385523955240552415524255243552445524555246552475524855249552505525155252552535525455255552565525755258552595526055261552625526355264552655526655267552685526955270552715527255273552745527555276552775527855279552805528155282552835528455285552865528755288552895529055291552925529355294552955529655297552985529955300553015530255303553045530555306553075530855309553105531155312553135531455315553165531755318553195532055321553225532355324553255532655327553285532955330553315533255333553345533555336553375533855339553405534155342553435534455345553465534755348553495535055351553525535355354553555535655357553585535955360553615536255363553645536555366
  1. /**
  2. * @license
  3. * Video.js 7.21.4 <http://videojs.com/>
  4. * Copyright Brightcove, Inc. <https://www.brightcove.com/>
  5. * Available under Apache License Version 2.0
  6. * <https://github.com/videojs/video.js/blob/main/LICENSE>
  7. *
  8. * Includes vtt.js <https://github.com/mozilla/vtt.js>
  9. * Available under Apache License Version 2.0
  10. * <https://github.com/mozilla/vtt.js/blob/main/LICENSE>
  11. */
  12. import window$1 from 'global/window';
  13. import document from 'global/document';
  14. import _extends from '@babel/runtime/helpers/extends';
  15. import keycode from 'keycode';
  16. import _assertThisInitialized from '@babel/runtime/helpers/assertThisInitialized';
  17. import _inheritsLoose from '@babel/runtime/helpers/inheritsLoose';
  18. import safeParseTuple from 'safe-json-parse/tuple';
  19. import XHR from '@videojs/xhr';
  20. import vtt from 'videojs-vtt.js';
  21. import _construct from '@babel/runtime/helpers/construct';
  22. import _inherits from '@babel/runtime/helpers/inherits';
  23. import _resolveUrl from '@videojs/vhs-utils/es/resolve-url.js';
  24. import { Parser } from 'm3u8-parser';
  25. import { browserSupportsCodec, DEFAULT_VIDEO_CODEC, DEFAULT_AUDIO_CODEC, muxerSupportsCodec, parseCodecs, translateLegacyCodec, codecsFromDefault, getMimeForCodec, isAudioCodec } from '@videojs/vhs-utils/es/codecs.js';
  26. import { simpleTypeFromSourceType } from '@videojs/vhs-utils/es/media-types.js';
  27. import { isArrayBufferView, concatTypedArrays, stringToBytes, toUint8 } from '@videojs/vhs-utils/es/byte-helpers';
  28. import { generateSidxKey, parseUTCTiming, parse, addSidxSegmentsToPlaylist } from 'mpd-parser';
  29. import parseSidx from 'mux.js/lib/tools/parse-sidx';
  30. import { getId3Offset } from '@videojs/vhs-utils/es/id3-helpers';
  31. import { detectContainerForBytes, isLikelyFmp4MediaSegment } from '@videojs/vhs-utils/es/containers';
  32. import { ONE_SECOND_IN_TS } from 'mux.js/lib/utils/clock';
  33. import _wrapNativeSuper from '@babel/runtime/helpers/wrapNativeSuper';
  34. var version$5 = "7.21.4";
  35. /**
  36. * An Object that contains lifecycle hooks as keys which point to an array
  37. * of functions that are run when a lifecycle is triggered
  38. *
  39. * @private
  40. */
  41. var hooks_ = {};
  42. /**
  43. * Get a list of hooks for a specific lifecycle
  44. *
  45. * @param {string} type
  46. * the lifecyle to get hooks from
  47. *
  48. * @param {Function|Function[]} [fn]
  49. * Optionally add a hook (or hooks) to the lifecycle that your are getting.
  50. *
  51. * @return {Array}
  52. * an array of hooks, or an empty array if there are none.
  53. */
  54. var hooks = function hooks(type, fn) {
  55. hooks_[type] = hooks_[type] || [];
  56. if (fn) {
  57. hooks_[type] = hooks_[type].concat(fn);
  58. }
  59. return hooks_[type];
  60. };
  61. /**
  62. * Add a function hook to a specific videojs lifecycle.
  63. *
  64. * @param {string} type
  65. * the lifecycle to hook the function to.
  66. *
  67. * @param {Function|Function[]}
  68. * The function or array of functions to attach.
  69. */
  70. var hook = function hook(type, fn) {
  71. hooks(type, fn);
  72. };
  73. /**
  74. * Remove a hook from a specific videojs lifecycle.
  75. *
  76. * @param {string} type
  77. * the lifecycle that the function hooked to
  78. *
  79. * @param {Function} fn
  80. * The hooked function to remove
  81. *
  82. * @return {boolean}
  83. * The function that was removed or undef
  84. */
  85. var removeHook = function removeHook(type, fn) {
  86. var index = hooks(type).indexOf(fn);
  87. if (index <= -1) {
  88. return false;
  89. }
  90. hooks_[type] = hooks_[type].slice();
  91. hooks_[type].splice(index, 1);
  92. return true;
  93. };
  94. /**
  95. * Add a function hook that will only run once to a specific videojs lifecycle.
  96. *
  97. * @param {string} type
  98. * the lifecycle to hook the function to.
  99. *
  100. * @param {Function|Function[]}
  101. * The function or array of functions to attach.
  102. */
  103. var hookOnce = function hookOnce(type, fn) {
  104. hooks(type, [].concat(fn).map(function (original) {
  105. var wrapper = function wrapper() {
  106. removeHook(type, wrapper);
  107. return original.apply(void 0, arguments);
  108. };
  109. return wrapper;
  110. }));
  111. };
  112. /**
  113. * @file fullscreen-api.js
  114. * @module fullscreen-api
  115. * @private
  116. */
  117. /**
  118. * Store the browser-specific methods for the fullscreen API.
  119. *
  120. * @type {Object}
  121. * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
  122. * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
  123. */
  124. var FullscreenApi = {
  125. prefixed: true
  126. }; // browser API methods
  127. var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit
  128. ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla
  129. ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft
  130. ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']];
  131. var specApi = apiMap[0];
  132. var browserApi; // determine the supported set of functions
  133. for (var i = 0; i < apiMap.length; i++) {
  134. // check for exitFullscreen function
  135. if (apiMap[i][1] in document) {
  136. browserApi = apiMap[i];
  137. break;
  138. }
  139. } // map the browser API names to the spec API names
  140. if (browserApi) {
  141. for (var _i = 0; _i < browserApi.length; _i++) {
  142. FullscreenApi[specApi[_i]] = browserApi[_i];
  143. }
  144. FullscreenApi.prefixed = browserApi[0] !== specApi[0];
  145. }
  146. /**
  147. * @file create-logger.js
  148. * @module create-logger
  149. */
  150. var history = [];
  151. /**
  152. * Log messages to the console and history based on the type of message
  153. *
  154. * @private
  155. * @param {string} type
  156. * The name of the console method to use.
  157. *
  158. * @param {Array} args
  159. * The arguments to be passed to the matching console method.
  160. */
  161. var LogByTypeFactory = function LogByTypeFactory(name, log) {
  162. return function (type, level, args) {
  163. var lvl = log.levels[level];
  164. var lvlRegExp = new RegExp("^(" + lvl + ")$");
  165. if (type !== 'log') {
  166. // Add the type to the front of the message when it's not "log".
  167. args.unshift(type.toUpperCase() + ':');
  168. } // Add console prefix after adding to history.
  169. args.unshift(name + ':'); // Add a clone of the args at this point to history.
  170. if (history) {
  171. history.push([].concat(args)); // only store 1000 history entries
  172. var splice = history.length - 1000;
  173. history.splice(0, splice > 0 ? splice : 0);
  174. } // If there's no console then don't try to output messages, but they will
  175. // still be stored in history.
  176. if (!window$1.console) {
  177. return;
  178. } // Was setting these once outside of this function, but containing them
  179. // in the function makes it easier to test cases where console doesn't exist
  180. // when the module is executed.
  181. var fn = window$1.console[type];
  182. if (!fn && type === 'debug') {
  183. // Certain browsers don't have support for console.debug. For those, we
  184. // should default to the closest comparable log.
  185. fn = window$1.console.info || window$1.console.log;
  186. } // Bail out if there's no console or if this type is not allowed by the
  187. // current logging level.
  188. if (!fn || !lvl || !lvlRegExp.test(type)) {
  189. return;
  190. }
  191. fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args);
  192. };
  193. };
  194. function createLogger$1(name) {
  195. // This is the private tracking variable for logging level.
  196. var level = 'info'; // the curried logByType bound to the specific log and history
  197. var logByType;
  198. /**
  199. * Logs plain debug messages. Similar to `console.log`.
  200. *
  201. * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
  202. * of our JSDoc template, we cannot properly document this as both a function
  203. * and a namespace, so its function signature is documented here.
  204. *
  205. * #### Arguments
  206. * ##### *args
  207. * Mixed[]
  208. *
  209. * Any combination of values that could be passed to `console.log()`.
  210. *
  211. * #### Return Value
  212. *
  213. * `undefined`
  214. *
  215. * @namespace
  216. * @param {Mixed[]} args
  217. * One or more messages or objects that should be logged.
  218. */
  219. var log = function log() {
  220. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  221. args[_key] = arguments[_key];
  222. }
  223. logByType('log', level, args);
  224. }; // This is the logByType helper that the logging methods below use
  225. logByType = LogByTypeFactory(name, log);
  226. /**
  227. * Create a new sublogger which chains the old name to the new name.
  228. *
  229. * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
  230. * ```js
  231. * mylogger('foo');
  232. * // > VIDEOJS: player: foo
  233. * ```
  234. *
  235. * @param {string} name
  236. * The name to add call the new logger
  237. * @return {Object}
  238. */
  239. log.createLogger = function (subname) {
  240. return createLogger$1(name + ': ' + subname);
  241. };
  242. /**
  243. * Enumeration of available logging levels, where the keys are the level names
  244. * and the values are `|`-separated strings containing logging methods allowed
  245. * in that logging level. These strings are used to create a regular expression
  246. * matching the function name being called.
  247. *
  248. * Levels provided by Video.js are:
  249. *
  250. * - `off`: Matches no calls. Any value that can be cast to `false` will have
  251. * this effect. The most restrictive.
  252. * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
  253. * `log.warn`, and `log.error`).
  254. * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
  255. * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
  256. * - `warn`: Matches `log.warn` and `log.error` calls.
  257. * - `error`: Matches only `log.error` calls.
  258. *
  259. * @type {Object}
  260. */
  261. log.levels = {
  262. all: 'debug|log|warn|error',
  263. off: '',
  264. debug: 'debug|log|warn|error',
  265. info: 'log|warn|error',
  266. warn: 'warn|error',
  267. error: 'error',
  268. DEFAULT: level
  269. };
  270. /**
  271. * Get or set the current logging level.
  272. *
  273. * If a string matching a key from {@link module:log.levels} is provided, acts
  274. * as a setter.
  275. *
  276. * @param {string} [lvl]
  277. * Pass a valid level to set a new logging level.
  278. *
  279. * @return {string}
  280. * The current logging level.
  281. */
  282. log.level = function (lvl) {
  283. if (typeof lvl === 'string') {
  284. if (!log.levels.hasOwnProperty(lvl)) {
  285. throw new Error("\"" + lvl + "\" in not a valid log level");
  286. }
  287. level = lvl;
  288. }
  289. return level;
  290. };
  291. /**
  292. * Returns an array containing everything that has been logged to the history.
  293. *
  294. * This array is a shallow clone of the internal history record. However, its
  295. * contents are _not_ cloned; so, mutating objects inside this array will
  296. * mutate them in history.
  297. *
  298. * @return {Array}
  299. */
  300. log.history = function () {
  301. return history ? [].concat(history) : [];
  302. };
  303. /**
  304. * Allows you to filter the history by the given logger name
  305. *
  306. * @param {string} fname
  307. * The name to filter by
  308. *
  309. * @return {Array}
  310. * The filtered list to return
  311. */
  312. log.history.filter = function (fname) {
  313. return (history || []).filter(function (historyItem) {
  314. // if the first item in each historyItem includes `fname`, then it's a match
  315. return new RegExp(".*" + fname + ".*").test(historyItem[0]);
  316. });
  317. };
  318. /**
  319. * Clears the internal history tracking, but does not prevent further history
  320. * tracking.
  321. */
  322. log.history.clear = function () {
  323. if (history) {
  324. history.length = 0;
  325. }
  326. };
  327. /**
  328. * Disable history tracking if it is currently enabled.
  329. */
  330. log.history.disable = function () {
  331. if (history !== null) {
  332. history.length = 0;
  333. history = null;
  334. }
  335. };
  336. /**
  337. * Enable history tracking if it is currently disabled.
  338. */
  339. log.history.enable = function () {
  340. if (history === null) {
  341. history = [];
  342. }
  343. };
  344. /**
  345. * Logs error messages. Similar to `console.error`.
  346. *
  347. * @param {Mixed[]} args
  348. * One or more messages or objects that should be logged as an error
  349. */
  350. log.error = function () {
  351. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  352. args[_key2] = arguments[_key2];
  353. }
  354. return logByType('error', level, args);
  355. };
  356. /**
  357. * Logs warning messages. Similar to `console.warn`.
  358. *
  359. * @param {Mixed[]} args
  360. * One or more messages or objects that should be logged as a warning.
  361. */
  362. log.warn = function () {
  363. for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  364. args[_key3] = arguments[_key3];
  365. }
  366. return logByType('warn', level, args);
  367. };
  368. /**
  369. * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
  370. * log if `console.debug` is not available
  371. *
  372. * @param {Mixed[]} args
  373. * One or more messages or objects that should be logged as debug.
  374. */
  375. log.debug = function () {
  376. for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
  377. args[_key4] = arguments[_key4];
  378. }
  379. return logByType('debug', level, args);
  380. };
  381. return log;
  382. }
  383. /**
  384. * @file log.js
  385. * @module log
  386. */
  387. var log$1 = createLogger$1('VIDEOJS');
  388. var createLogger = log$1.createLogger;
  389. /**
  390. * @file obj.js
  391. * @module obj
  392. */
  393. /**
  394. * @callback obj:EachCallback
  395. *
  396. * @param {Mixed} value
  397. * The current key for the object that is being iterated over.
  398. *
  399. * @param {string} key
  400. * The current key-value for object that is being iterated over
  401. */
  402. /**
  403. * @callback obj:ReduceCallback
  404. *
  405. * @param {Mixed} accum
  406. * The value that is accumulating over the reduce loop.
  407. *
  408. * @param {Mixed} value
  409. * The current key for the object that is being iterated over.
  410. *
  411. * @param {string} key
  412. * The current key-value for object that is being iterated over
  413. *
  414. * @return {Mixed}
  415. * The new accumulated value.
  416. */
  417. var toString = Object.prototype.toString;
  418. /**
  419. * Get the keys of an Object
  420. *
  421. * @param {Object}
  422. * The Object to get the keys from
  423. *
  424. * @return {string[]}
  425. * An array of the keys from the object. Returns an empty array if the
  426. * object passed in was invalid or had no keys.
  427. *
  428. * @private
  429. */
  430. var keys = function keys(object) {
  431. return isObject(object) ? Object.keys(object) : [];
  432. };
  433. /**
  434. * Array-like iteration for objects.
  435. *
  436. * @param {Object} object
  437. * The object to iterate over
  438. *
  439. * @param {obj:EachCallback} fn
  440. * The callback function which is called for each key in the object.
  441. */
  442. function each(object, fn) {
  443. keys(object).forEach(function (key) {
  444. return fn(object[key], key);
  445. });
  446. }
  447. /**
  448. * Array-like reduce for objects.
  449. *
  450. * @param {Object} object
  451. * The Object that you want to reduce.
  452. *
  453. * @param {Function} fn
  454. * A callback function which is called for each key in the object. It
  455. * receives the accumulated value and the per-iteration value and key
  456. * as arguments.
  457. *
  458. * @param {Mixed} [initial = 0]
  459. * Starting value
  460. *
  461. * @return {Mixed}
  462. * The final accumulated value.
  463. */
  464. function reduce(object, fn, initial) {
  465. if (initial === void 0) {
  466. initial = 0;
  467. }
  468. return keys(object).reduce(function (accum, key) {
  469. return fn(accum, object[key], key);
  470. }, initial);
  471. }
  472. /**
  473. * Object.assign-style object shallow merge/extend.
  474. *
  475. * @param {Object} target
  476. * @param {Object} ...sources
  477. * @return {Object}
  478. */
  479. function assign(target) {
  480. for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  481. sources[_key - 1] = arguments[_key];
  482. }
  483. if (Object.assign) {
  484. return _extends.apply(void 0, [target].concat(sources));
  485. }
  486. sources.forEach(function (source) {
  487. if (!source) {
  488. return;
  489. }
  490. each(source, function (value, key) {
  491. target[key] = value;
  492. });
  493. });
  494. return target;
  495. }
  496. /**
  497. * Returns whether a value is an object of any kind - including DOM nodes,
  498. * arrays, regular expressions, etc. Not functions, though.
  499. *
  500. * This avoids the gotcha where using `typeof` on a `null` value
  501. * results in `'object'`.
  502. *
  503. * @param {Object} value
  504. * @return {boolean}
  505. */
  506. function isObject(value) {
  507. return !!value && typeof value === 'object';
  508. }
  509. /**
  510. * Returns whether an object appears to be a "plain" object - that is, a
  511. * direct instance of `Object`.
  512. *
  513. * @param {Object} value
  514. * @return {boolean}
  515. */
  516. function isPlain(value) {
  517. return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
  518. }
  519. /**
  520. * @file computed-style.js
  521. * @module computed-style
  522. */
  523. /**
  524. * A safe getComputedStyle.
  525. *
  526. * This is needed because in Firefox, if the player is loaded in an iframe with
  527. * `display:none`, then `getComputedStyle` returns `null`, so, we do a
  528. * null-check to make sure that the player doesn't break in these cases.
  529. *
  530. * @function
  531. * @param {Element} el
  532. * The element you want the computed style of
  533. *
  534. * @param {string} prop
  535. * The property name you want
  536. *
  537. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
  538. */
  539. function computedStyle(el, prop) {
  540. if (!el || !prop) {
  541. return '';
  542. }
  543. if (typeof window$1.getComputedStyle === 'function') {
  544. var computedStyleValue;
  545. try {
  546. computedStyleValue = window$1.getComputedStyle(el);
  547. } catch (e) {
  548. return '';
  549. }
  550. return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : '';
  551. }
  552. return '';
  553. }
  554. /**
  555. * @file browser.js
  556. * @module browser
  557. */
  558. var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || '';
  559. var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
  560. var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
  561. /**
  562. * Whether or not this device is an iPod.
  563. *
  564. * @static
  565. * @const
  566. * @type {Boolean}
  567. */
  568. var IS_IPOD = /iPod/i.test(USER_AGENT);
  569. /**
  570. * The detected iOS version - or `null`.
  571. *
  572. * @static
  573. * @const
  574. * @type {string|null}
  575. */
  576. var IOS_VERSION = function () {
  577. var match = USER_AGENT.match(/OS (\d+)_/i);
  578. if (match && match[1]) {
  579. return match[1];
  580. }
  581. return null;
  582. }();
  583. /**
  584. * Whether or not this is an Android device.
  585. *
  586. * @static
  587. * @const
  588. * @type {Boolean}
  589. */
  590. var IS_ANDROID = /Android/i.test(USER_AGENT);
  591. /**
  592. * The detected Android version - or `null`.
  593. *
  594. * @static
  595. * @const
  596. * @type {number|string|null}
  597. */
  598. var ANDROID_VERSION = function () {
  599. // This matches Android Major.Minor.Patch versions
  600. // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
  601. var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
  602. if (!match) {
  603. return null;
  604. }
  605. var major = match[1] && parseFloat(match[1]);
  606. var minor = match[2] && parseFloat(match[2]);
  607. if (major && minor) {
  608. return parseFloat(match[1] + '.' + match[2]);
  609. } else if (major) {
  610. return major;
  611. }
  612. return null;
  613. }();
  614. /**
  615. * Whether or not this is a native Android browser.
  616. *
  617. * @static
  618. * @const
  619. * @type {Boolean}
  620. */
  621. var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
  622. /**
  623. * Whether or not this is Mozilla Firefox.
  624. *
  625. * @static
  626. * @const
  627. * @type {Boolean}
  628. */
  629. var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
  630. /**
  631. * Whether or not this is Microsoft Edge.
  632. *
  633. * @static
  634. * @const
  635. * @type {Boolean}
  636. */
  637. var IS_EDGE = /Edg/i.test(USER_AGENT);
  638. /**
  639. * Whether or not this is Google Chrome.
  640. *
  641. * This will also be `true` for Chrome on iOS, which will have different support
  642. * as it is actually Safari under the hood.
  643. *
  644. * @static
  645. * @const
  646. * @type {Boolean}
  647. */
  648. var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
  649. /**
  650. * The detected Google Chrome version - or `null`.
  651. *
  652. * @static
  653. * @const
  654. * @type {number|null}
  655. */
  656. var CHROME_VERSION = function () {
  657. var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
  658. if (match && match[2]) {
  659. return parseFloat(match[2]);
  660. }
  661. return null;
  662. }();
  663. /**
  664. * The detected Internet Explorer version - or `null`.
  665. *
  666. * @static
  667. * @const
  668. * @type {number|null}
  669. */
  670. var IE_VERSION = function () {
  671. var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
  672. var version = result && parseFloat(result[1]);
  673. if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
  674. // IE 11 has a different user agent string than other IE versions
  675. version = 11.0;
  676. }
  677. return version;
  678. }();
  679. /**
  680. * Whether or not this is desktop Safari.
  681. *
  682. * @static
  683. * @const
  684. * @type {Boolean}
  685. */
  686. var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
  687. /**
  688. * Whether or not this is a Windows machine.
  689. *
  690. * @static
  691. * @const
  692. * @type {Boolean}
  693. */
  694. var IS_WINDOWS = /Windows/i.test(USER_AGENT);
  695. /**
  696. * Whether or not this device is touch-enabled.
  697. *
  698. * @static
  699. * @const
  700. * @type {Boolean}
  701. */
  702. var TOUCH_ENABLED = Boolean(isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch));
  703. /**
  704. * Whether or not this device is an iPad.
  705. *
  706. * @static
  707. * @const
  708. * @type {Boolean}
  709. */
  710. var IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT);
  711. /**
  712. * Whether or not this device is an iPhone.
  713. *
  714. * @static
  715. * @const
  716. * @type {Boolean}
  717. */
  718. // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
  719. // to identify iPhones, we need to exclude iPads.
  720. // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
  721. var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
  722. /**
  723. * Whether or not this is an iOS device.
  724. *
  725. * @static
  726. * @const
  727. * @type {Boolean}
  728. */
  729. var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
  730. /**
  731. * Whether or not this is any flavor of Safari - including iOS.
  732. *
  733. * @static
  734. * @const
  735. * @type {Boolean}
  736. */
  737. var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
  738. var browser = /*#__PURE__*/Object.freeze({
  739. __proto__: null,
  740. IS_IPOD: IS_IPOD,
  741. IOS_VERSION: IOS_VERSION,
  742. IS_ANDROID: IS_ANDROID,
  743. ANDROID_VERSION: ANDROID_VERSION,
  744. IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
  745. IS_FIREFOX: IS_FIREFOX,
  746. IS_EDGE: IS_EDGE,
  747. IS_CHROME: IS_CHROME,
  748. CHROME_VERSION: CHROME_VERSION,
  749. IE_VERSION: IE_VERSION,
  750. IS_SAFARI: IS_SAFARI,
  751. IS_WINDOWS: IS_WINDOWS,
  752. TOUCH_ENABLED: TOUCH_ENABLED,
  753. IS_IPAD: IS_IPAD,
  754. IS_IPHONE: IS_IPHONE,
  755. IS_IOS: IS_IOS,
  756. IS_ANY_SAFARI: IS_ANY_SAFARI
  757. });
  758. /**
  759. * @file dom.js
  760. * @module dom
  761. */
  762. /**
  763. * Detect if a value is a string with any non-whitespace characters.
  764. *
  765. * @private
  766. * @param {string} str
  767. * The string to check
  768. *
  769. * @return {boolean}
  770. * Will be `true` if the string is non-blank, `false` otherwise.
  771. *
  772. */
  773. function isNonBlankString(str) {
  774. // we use str.trim as it will trim any whitespace characters
  775. // from the front or back of non-whitespace characters. aka
  776. // Any string that contains non-whitespace characters will
  777. // still contain them after `trim` but whitespace only strings
  778. // will have a length of 0, failing this check.
  779. return typeof str === 'string' && Boolean(str.trim());
  780. }
  781. /**
  782. * Throws an error if the passed string has whitespace. This is used by
  783. * class methods to be relatively consistent with the classList API.
  784. *
  785. * @private
  786. * @param {string} str
  787. * The string to check for whitespace.
  788. *
  789. * @throws {Error}
  790. * Throws an error if there is whitespace in the string.
  791. */
  792. function throwIfWhitespace(str) {
  793. // str.indexOf instead of regex because str.indexOf is faster performance wise.
  794. if (str.indexOf(' ') >= 0) {
  795. throw new Error('class has illegal whitespace characters');
  796. }
  797. }
  798. /**
  799. * Produce a regular expression for matching a className within an elements className.
  800. *
  801. * @private
  802. * @param {string} className
  803. * The className to generate the RegExp for.
  804. *
  805. * @return {RegExp}
  806. * The RegExp that will check for a specific `className` in an elements
  807. * className.
  808. */
  809. function classRegExp(className) {
  810. return new RegExp('(^|\\s)' + className + '($|\\s)');
  811. }
  812. /**
  813. * Whether the current DOM interface appears to be real (i.e. not simulated).
  814. *
  815. * @return {boolean}
  816. * Will be `true` if the DOM appears to be real, `false` otherwise.
  817. */
  818. function isReal() {
  819. // Both document and window will never be undefined thanks to `global`.
  820. return document === window$1.document;
  821. }
  822. /**
  823. * Determines, via duck typing, whether or not a value is a DOM element.
  824. *
  825. * @param {Mixed} value
  826. * The value to check.
  827. *
  828. * @return {boolean}
  829. * Will be `true` if the value is a DOM element, `false` otherwise.
  830. */
  831. function isEl(value) {
  832. return isObject(value) && value.nodeType === 1;
  833. }
  834. /**
  835. * Determines if the current DOM is embedded in an iframe.
  836. *
  837. * @return {boolean}
  838. * Will be `true` if the DOM is embedded in an iframe, `false`
  839. * otherwise.
  840. */
  841. function isInFrame() {
  842. // We need a try/catch here because Safari will throw errors when attempting
  843. // to get either `parent` or `self`
  844. try {
  845. return window$1.parent !== window$1.self;
  846. } catch (x) {
  847. return true;
  848. }
  849. }
  850. /**
  851. * Creates functions to query the DOM using a given method.
  852. *
  853. * @private
  854. * @param {string} method
  855. * The method to create the query with.
  856. *
  857. * @return {Function}
  858. * The query method
  859. */
  860. function createQuerier(method) {
  861. return function (selector, context) {
  862. if (!isNonBlankString(selector)) {
  863. return document[method](null);
  864. }
  865. if (isNonBlankString(context)) {
  866. context = document.querySelector(context);
  867. }
  868. var ctx = isEl(context) ? context : document;
  869. return ctx[method] && ctx[method](selector);
  870. };
  871. }
  872. /**
  873. * Creates an element and applies properties, attributes, and inserts content.
  874. *
  875. * @param {string} [tagName='div']
  876. * Name of tag to be created.
  877. *
  878. * @param {Object} [properties={}]
  879. * Element properties to be applied.
  880. *
  881. * @param {Object} [attributes={}]
  882. * Element attributes to be applied.
  883. *
  884. * @param {module:dom~ContentDescriptor} content
  885. * A content descriptor object.
  886. *
  887. * @return {Element}
  888. * The element that was created.
  889. */
  890. function createEl(tagName, properties, attributes, content) {
  891. if (tagName === void 0) {
  892. tagName = 'div';
  893. }
  894. if (properties === void 0) {
  895. properties = {};
  896. }
  897. if (attributes === void 0) {
  898. attributes = {};
  899. }
  900. var el = document.createElement(tagName);
  901. Object.getOwnPropertyNames(properties).forEach(function (propName) {
  902. var val = properties[propName]; // See #2176
  903. // We originally were accepting both properties and attributes in the
  904. // same object, but that doesn't work so well.
  905. if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
  906. log$1.warn('Setting attributes in the second argument of createEl()\n' + 'has been deprecated. Use the third argument instead.\n' + ("createEl(type, properties, attributes). Attempting to set " + propName + " to " + val + "."));
  907. el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a
  908. // method for it.
  909. } else if (propName === 'textContent') {
  910. textContent(el, val);
  911. } else if (el[propName] !== val || propName === 'tabIndex') {
  912. el[propName] = val;
  913. }
  914. });
  915. Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
  916. el.setAttribute(attrName, attributes[attrName]);
  917. });
  918. if (content) {
  919. appendContent(el, content);
  920. }
  921. return el;
  922. }
  923. /**
  924. * Injects text into an element, replacing any existing contents entirely.
  925. *
  926. * @param {Element} el
  927. * The element to add text content into
  928. *
  929. * @param {string} text
  930. * The text content to add.
  931. *
  932. * @return {Element}
  933. * The element with added text content.
  934. */
  935. function textContent(el, text) {
  936. if (typeof el.textContent === 'undefined') {
  937. el.innerText = text;
  938. } else {
  939. el.textContent = text;
  940. }
  941. return el;
  942. }
  943. /**
  944. * Insert an element as the first child node of another
  945. *
  946. * @param {Element} child
  947. * Element to insert
  948. *
  949. * @param {Element} parent
  950. * Element to insert child into
  951. */
  952. function prependTo(child, parent) {
  953. if (parent.firstChild) {
  954. parent.insertBefore(child, parent.firstChild);
  955. } else {
  956. parent.appendChild(child);
  957. }
  958. }
  959. /**
  960. * Check if an element has a class name.
  961. *
  962. * @param {Element} element
  963. * Element to check
  964. *
  965. * @param {string} classToCheck
  966. * Class name to check for
  967. *
  968. * @return {boolean}
  969. * Will be `true` if the element has a class, `false` otherwise.
  970. *
  971. * @throws {Error}
  972. * Throws an error if `classToCheck` has white space.
  973. */
  974. function hasClass(element, classToCheck) {
  975. throwIfWhitespace(classToCheck);
  976. if (element.classList) {
  977. return element.classList.contains(classToCheck);
  978. }
  979. return classRegExp(classToCheck).test(element.className);
  980. }
  981. /**
  982. * Add a class name to an element.
  983. *
  984. * @param {Element} element
  985. * Element to add class name to.
  986. *
  987. * @param {string} classToAdd
  988. * Class name to add.
  989. *
  990. * @return {Element}
  991. * The DOM element with the added class name.
  992. */
  993. function addClass(element, classToAdd) {
  994. if (element.classList) {
  995. element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
  996. // in the case of classList not being supported.
  997. } else if (!hasClass(element, classToAdd)) {
  998. element.className = (element.className + ' ' + classToAdd).trim();
  999. }
  1000. return element;
  1001. }
  1002. /**
  1003. * Remove a class name from an element.
  1004. *
  1005. * @param {Element} element
  1006. * Element to remove a class name from.
  1007. *
  1008. * @param {string} classToRemove
  1009. * Class name to remove
  1010. *
  1011. * @return {Element}
  1012. * The DOM element with class name removed.
  1013. */
  1014. function removeClass(element, classToRemove) {
  1015. // Protect in case the player gets disposed
  1016. if (!element) {
  1017. log$1.warn("removeClass was called with an element that doesn't exist");
  1018. return null;
  1019. }
  1020. if (element.classList) {
  1021. element.classList.remove(classToRemove);
  1022. } else {
  1023. throwIfWhitespace(classToRemove);
  1024. element.className = element.className.split(/\s+/).filter(function (c) {
  1025. return c !== classToRemove;
  1026. }).join(' ');
  1027. }
  1028. return element;
  1029. }
  1030. /**
  1031. * The callback definition for toggleClass.
  1032. *
  1033. * @callback module:dom~PredicateCallback
  1034. * @param {Element} element
  1035. * The DOM element of the Component.
  1036. *
  1037. * @param {string} classToToggle
  1038. * The `className` that wants to be toggled
  1039. *
  1040. * @return {boolean|undefined}
  1041. * If `true` is returned, the `classToToggle` will be added to the
  1042. * `element`. If `false`, the `classToToggle` will be removed from
  1043. * the `element`. If `undefined`, the callback will be ignored.
  1044. */
  1045. /**
  1046. * Adds or removes a class name to/from an element depending on an optional
  1047. * condition or the presence/absence of the class name.
  1048. *
  1049. * @param {Element} element
  1050. * The element to toggle a class name on.
  1051. *
  1052. * @param {string} classToToggle
  1053. * The class that should be toggled.
  1054. *
  1055. * @param {boolean|module:dom~PredicateCallback} [predicate]
  1056. * See the return value for {@link module:dom~PredicateCallback}
  1057. *
  1058. * @return {Element}
  1059. * The element with a class that has been toggled.
  1060. */
  1061. function toggleClass(element, classToToggle, predicate) {
  1062. // This CANNOT use `classList` internally because IE11 does not support the
  1063. // second parameter to the `classList.toggle()` method! Which is fine because
  1064. // `classList` will be used by the add/remove functions.
  1065. var has = hasClass(element, classToToggle);
  1066. if (typeof predicate === 'function') {
  1067. predicate = predicate(element, classToToggle);
  1068. }
  1069. if (typeof predicate !== 'boolean') {
  1070. predicate = !has;
  1071. } // If the necessary class operation matches the current state of the
  1072. // element, no action is required.
  1073. if (predicate === has) {
  1074. return;
  1075. }
  1076. if (predicate) {
  1077. addClass(element, classToToggle);
  1078. } else {
  1079. removeClass(element, classToToggle);
  1080. }
  1081. return element;
  1082. }
  1083. /**
  1084. * Apply attributes to an HTML element.
  1085. *
  1086. * @param {Element} el
  1087. * Element to add attributes to.
  1088. *
  1089. * @param {Object} [attributes]
  1090. * Attributes to be applied.
  1091. */
  1092. function setAttributes(el, attributes) {
  1093. Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
  1094. var attrValue = attributes[attrName];
  1095. if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
  1096. el.removeAttribute(attrName);
  1097. } else {
  1098. el.setAttribute(attrName, attrValue === true ? '' : attrValue);
  1099. }
  1100. });
  1101. }
  1102. /**
  1103. * Get an element's attribute values, as defined on the HTML tag.
  1104. *
  1105. * Attributes are not the same as properties. They're defined on the tag
  1106. * or with setAttribute.
  1107. *
  1108. * @param {Element} tag
  1109. * Element from which to get tag attributes.
  1110. *
  1111. * @return {Object}
  1112. * All attributes of the element. Boolean attributes will be `true` or
  1113. * `false`, others will be strings.
  1114. */
  1115. function getAttributes(tag) {
  1116. var obj = {}; // known boolean attributes
  1117. // we can check for matching boolean properties, but not all browsers
  1118. // and not all tags know about these attributes, so, we still want to check them manually
  1119. var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
  1120. if (tag && tag.attributes && tag.attributes.length > 0) {
  1121. var attrs = tag.attributes;
  1122. for (var i = attrs.length - 1; i >= 0; i--) {
  1123. var attrName = attrs[i].name;
  1124. var attrVal = attrs[i].value; // check for known booleans
  1125. // the matching element property will return a value for typeof
  1126. if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
  1127. // the value of an included boolean attribute is typically an empty
  1128. // string ('') which would equal false if we just check for a false value.
  1129. // we also don't want support bad code like autoplay='false'
  1130. attrVal = attrVal !== null ? true : false;
  1131. }
  1132. obj[attrName] = attrVal;
  1133. }
  1134. }
  1135. return obj;
  1136. }
  1137. /**
  1138. * Get the value of an element's attribute.
  1139. *
  1140. * @param {Element} el
  1141. * A DOM element.
  1142. *
  1143. * @param {string} attribute
  1144. * Attribute to get the value of.
  1145. *
  1146. * @return {string}
  1147. * The value of the attribute.
  1148. */
  1149. function getAttribute(el, attribute) {
  1150. return el.getAttribute(attribute);
  1151. }
  1152. /**
  1153. * Set the value of an element's attribute.
  1154. *
  1155. * @param {Element} el
  1156. * A DOM element.
  1157. *
  1158. * @param {string} attribute
  1159. * Attribute to set.
  1160. *
  1161. * @param {string} value
  1162. * Value to set the attribute to.
  1163. */
  1164. function setAttribute(el, attribute, value) {
  1165. el.setAttribute(attribute, value);
  1166. }
  1167. /**
  1168. * Remove an element's attribute.
  1169. *
  1170. * @param {Element} el
  1171. * A DOM element.
  1172. *
  1173. * @param {string} attribute
  1174. * Attribute to remove.
  1175. */
  1176. function removeAttribute(el, attribute) {
  1177. el.removeAttribute(attribute);
  1178. }
  1179. /**
  1180. * Attempt to block the ability to select text.
  1181. */
  1182. function blockTextSelection() {
  1183. document.body.focus();
  1184. document.onselectstart = function () {
  1185. return false;
  1186. };
  1187. }
  1188. /**
  1189. * Turn off text selection blocking.
  1190. */
  1191. function unblockTextSelection() {
  1192. document.onselectstart = function () {
  1193. return true;
  1194. };
  1195. }
  1196. /**
  1197. * Identical to the native `getBoundingClientRect` function, but ensures that
  1198. * the method is supported at all (it is in all browsers we claim to support)
  1199. * and that the element is in the DOM before continuing.
  1200. *
  1201. * This wrapper function also shims properties which are not provided by some
  1202. * older browsers (namely, IE8).
  1203. *
  1204. * Additionally, some browsers do not support adding properties to a
  1205. * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
  1206. * properties (except `x` and `y` which are not widely supported). This helps
  1207. * avoid implementations where keys are non-enumerable.
  1208. *
  1209. * @param {Element} el
  1210. * Element whose `ClientRect` we want to calculate.
  1211. *
  1212. * @return {Object|undefined}
  1213. * Always returns a plain object - or `undefined` if it cannot.
  1214. */
  1215. function getBoundingClientRect(el) {
  1216. if (el && el.getBoundingClientRect && el.parentNode) {
  1217. var rect = el.getBoundingClientRect();
  1218. var result = {};
  1219. ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
  1220. if (rect[k] !== undefined) {
  1221. result[k] = rect[k];
  1222. }
  1223. });
  1224. if (!result.height) {
  1225. result.height = parseFloat(computedStyle(el, 'height'));
  1226. }
  1227. if (!result.width) {
  1228. result.width = parseFloat(computedStyle(el, 'width'));
  1229. }
  1230. return result;
  1231. }
  1232. }
  1233. /**
  1234. * Represents the position of a DOM element on the page.
  1235. *
  1236. * @typedef {Object} module:dom~Position
  1237. *
  1238. * @property {number} left
  1239. * Pixels to the left.
  1240. *
  1241. * @property {number} top
  1242. * Pixels from the top.
  1243. */
  1244. /**
  1245. * Get the position of an element in the DOM.
  1246. *
  1247. * Uses `getBoundingClientRect` technique from John Resig.
  1248. *
  1249. * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
  1250. *
  1251. * @param {Element} el
  1252. * Element from which to get offset.
  1253. *
  1254. * @return {module:dom~Position}
  1255. * The position of the element that was passed in.
  1256. */
  1257. function findPosition(el) {
  1258. if (!el || el && !el.offsetParent) {
  1259. return {
  1260. left: 0,
  1261. top: 0,
  1262. width: 0,
  1263. height: 0
  1264. };
  1265. }
  1266. var width = el.offsetWidth;
  1267. var height = el.offsetHeight;
  1268. var left = 0;
  1269. var top = 0;
  1270. while (el.offsetParent && el !== document[FullscreenApi.fullscreenElement]) {
  1271. left += el.offsetLeft;
  1272. top += el.offsetTop;
  1273. el = el.offsetParent;
  1274. }
  1275. return {
  1276. left: left,
  1277. top: top,
  1278. width: width,
  1279. height: height
  1280. };
  1281. }
  1282. /**
  1283. * Represents x and y coordinates for a DOM element or mouse pointer.
  1284. *
  1285. * @typedef {Object} module:dom~Coordinates
  1286. *
  1287. * @property {number} x
  1288. * x coordinate in pixels
  1289. *
  1290. * @property {number} y
  1291. * y coordinate in pixels
  1292. */
  1293. /**
  1294. * Get the pointer position within an element.
  1295. *
  1296. * The base on the coordinates are the bottom left of the element.
  1297. *
  1298. * @param {Element} el
  1299. * Element on which to get the pointer position on.
  1300. *
  1301. * @param {EventTarget~Event} event
  1302. * Event object.
  1303. *
  1304. * @return {module:dom~Coordinates}
  1305. * A coordinates object corresponding to the mouse position.
  1306. *
  1307. */
  1308. function getPointerPosition(el, event) {
  1309. var translated = {
  1310. x: 0,
  1311. y: 0
  1312. };
  1313. if (IS_IOS) {
  1314. var item = el;
  1315. while (item && item.nodeName.toLowerCase() !== 'html') {
  1316. var transform = computedStyle(item, 'transform');
  1317. if (/^matrix/.test(transform)) {
  1318. var values = transform.slice(7, -1).split(/,\s/).map(Number);
  1319. translated.x += values[4];
  1320. translated.y += values[5];
  1321. } else if (/^matrix3d/.test(transform)) {
  1322. var _values = transform.slice(9, -1).split(/,\s/).map(Number);
  1323. translated.x += _values[12];
  1324. translated.y += _values[13];
  1325. }
  1326. item = item.parentNode;
  1327. }
  1328. }
  1329. var position = {};
  1330. var boxTarget = findPosition(event.target);
  1331. var box = findPosition(el);
  1332. var boxW = box.width;
  1333. var boxH = box.height;
  1334. var offsetY = event.offsetY - (box.top - boxTarget.top);
  1335. var offsetX = event.offsetX - (box.left - boxTarget.left);
  1336. if (event.changedTouches) {
  1337. offsetX = event.changedTouches[0].pageX - box.left;
  1338. offsetY = event.changedTouches[0].pageY + box.top;
  1339. if (IS_IOS) {
  1340. offsetX -= translated.x;
  1341. offsetY -= translated.y;
  1342. }
  1343. }
  1344. position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH));
  1345. position.x = Math.max(0, Math.min(1, offsetX / boxW));
  1346. return position;
  1347. }
  1348. /**
  1349. * Determines, via duck typing, whether or not a value is a text node.
  1350. *
  1351. * @param {Mixed} value
  1352. * Check if this value is a text node.
  1353. *
  1354. * @return {boolean}
  1355. * Will be `true` if the value is a text node, `false` otherwise.
  1356. */
  1357. function isTextNode(value) {
  1358. return isObject(value) && value.nodeType === 3;
  1359. }
  1360. /**
  1361. * Empties the contents of an element.
  1362. *
  1363. * @param {Element} el
  1364. * The element to empty children from
  1365. *
  1366. * @return {Element}
  1367. * The element with no children
  1368. */
  1369. function emptyEl(el) {
  1370. while (el.firstChild) {
  1371. el.removeChild(el.firstChild);
  1372. }
  1373. return el;
  1374. }
  1375. /**
  1376. * This is a mixed value that describes content to be injected into the DOM
  1377. * via some method. It can be of the following types:
  1378. *
  1379. * Type | Description
  1380. * -----------|-------------
  1381. * `string` | The value will be normalized into a text node.
  1382. * `Element` | The value will be accepted as-is.
  1383. * `TextNode` | The value will be accepted as-is.
  1384. * `Array` | A one-dimensional array of strings, elements, text nodes, or functions. These functions should return a string, element, or text node (any other return value, like an array, will be ignored).
  1385. * `Function` | A function, which is expected to return a string, element, text node, or array - any of the other possible values described above. This means that a content descriptor could be a function that returns an array of functions, but those second-level functions must return strings, elements, or text nodes.
  1386. *
  1387. * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor
  1388. */
  1389. /**
  1390. * Normalizes content for eventual insertion into the DOM.
  1391. *
  1392. * This allows a wide range of content definition methods, but helps protect
  1393. * from falling into the trap of simply writing to `innerHTML`, which could
  1394. * be an XSS concern.
  1395. *
  1396. * The content for an element can be passed in multiple types and
  1397. * combinations, whose behavior is as follows:
  1398. *
  1399. * @param {module:dom~ContentDescriptor} content
  1400. * A content descriptor value.
  1401. *
  1402. * @return {Array}
  1403. * All of the content that was passed in, normalized to an array of
  1404. * elements or text nodes.
  1405. */
  1406. function normalizeContent(content) {
  1407. // First, invoke content if it is a function. If it produces an array,
  1408. // that needs to happen before normalization.
  1409. if (typeof content === 'function') {
  1410. content = content();
  1411. } // Next up, normalize to an array, so one or many items can be normalized,
  1412. // filtered, and returned.
  1413. return (Array.isArray(content) ? content : [content]).map(function (value) {
  1414. // First, invoke value if it is a function to produce a new value,
  1415. // which will be subsequently normalized to a Node of some kind.
  1416. if (typeof value === 'function') {
  1417. value = value();
  1418. }
  1419. if (isEl(value) || isTextNode(value)) {
  1420. return value;
  1421. }
  1422. if (typeof value === 'string' && /\S/.test(value)) {
  1423. return document.createTextNode(value);
  1424. }
  1425. }).filter(function (value) {
  1426. return value;
  1427. });
  1428. }
  1429. /**
  1430. * Normalizes and appends content to an element.
  1431. *
  1432. * @param {Element} el
  1433. * Element to append normalized content to.
  1434. *
  1435. * @param {module:dom~ContentDescriptor} content
  1436. * A content descriptor value.
  1437. *
  1438. * @return {Element}
  1439. * The element with appended normalized content.
  1440. */
  1441. function appendContent(el, content) {
  1442. normalizeContent(content).forEach(function (node) {
  1443. return el.appendChild(node);
  1444. });
  1445. return el;
  1446. }
  1447. /**
  1448. * Normalizes and inserts content into an element; this is identical to
  1449. * `appendContent()`, except it empties the element first.
  1450. *
  1451. * @param {Element} el
  1452. * Element to insert normalized content into.
  1453. *
  1454. * @param {module:dom~ContentDescriptor} content
  1455. * A content descriptor value.
  1456. *
  1457. * @return {Element}
  1458. * The element with inserted normalized content.
  1459. */
  1460. function insertContent(el, content) {
  1461. return appendContent(emptyEl(el), content);
  1462. }
  1463. /**
  1464. * Check if an event was a single left click.
  1465. *
  1466. * @param {EventTarget~Event} event
  1467. * Event object.
  1468. *
  1469. * @return {boolean}
  1470. * Will be `true` if a single left click, `false` otherwise.
  1471. */
  1472. function isSingleLeftClick(event) {
  1473. // Note: if you create something draggable, be sure to
  1474. // call it on both `mousedown` and `mousemove` event,
  1475. // otherwise `mousedown` should be enough for a button
  1476. if (event.button === undefined && event.buttons === undefined) {
  1477. // Why do we need `buttons` ?
  1478. // Because, middle mouse sometimes have this:
  1479. // e.button === 0 and e.buttons === 4
  1480. // Furthermore, we want to prevent combination click, something like
  1481. // HOLD middlemouse then left click, that would be
  1482. // e.button === 0, e.buttons === 5
  1483. // just `button` is not gonna work
  1484. // Alright, then what this block does ?
  1485. // this is for chrome `simulate mobile devices`
  1486. // I want to support this as well
  1487. return true;
  1488. }
  1489. if (event.button === 0 && event.buttons === undefined) {
  1490. // Touch screen, sometimes on some specific device, `buttons`
  1491. // doesn't have anything (safari on ios, blackberry...)
  1492. return true;
  1493. } // `mouseup` event on a single left click has
  1494. // `button` and `buttons` equal to 0
  1495. if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) {
  1496. return true;
  1497. }
  1498. if (event.button !== 0 || event.buttons !== 1) {
  1499. // This is the reason we have those if else block above
  1500. // if any special case we can catch and let it slide
  1501. // we do it above, when get to here, this definitely
  1502. // is-not-left-click
  1503. return false;
  1504. }
  1505. return true;
  1506. }
  1507. /**
  1508. * Finds a single DOM element matching `selector` within the optional
  1509. * `context` of another DOM element (defaulting to `document`).
  1510. *
  1511. * @param {string} selector
  1512. * A valid CSS selector, which will be passed to `querySelector`.
  1513. *
  1514. * @param {Element|String} [context=document]
  1515. * A DOM element within which to query. Can also be a selector
  1516. * string in which case the first matching element will be used
  1517. * as context. If missing (or no element matches selector), falls
  1518. * back to `document`.
  1519. *
  1520. * @return {Element|null}
  1521. * The element that was found or null.
  1522. */
  1523. var $ = createQuerier('querySelector');
  1524. /**
  1525. * Finds a all DOM elements matching `selector` within the optional
  1526. * `context` of another DOM element (defaulting to `document`).
  1527. *
  1528. * @param {string} selector
  1529. * A valid CSS selector, which will be passed to `querySelectorAll`.
  1530. *
  1531. * @param {Element|String} [context=document]
  1532. * A DOM element within which to query. Can also be a selector
  1533. * string in which case the first matching element will be used
  1534. * as context. If missing (or no element matches selector), falls
  1535. * back to `document`.
  1536. *
  1537. * @return {NodeList}
  1538. * A element list of elements that were found. Will be empty if none
  1539. * were found.
  1540. *
  1541. */
  1542. var $$ = createQuerier('querySelectorAll');
  1543. var Dom = /*#__PURE__*/Object.freeze({
  1544. __proto__: null,
  1545. isReal: isReal,
  1546. isEl: isEl,
  1547. isInFrame: isInFrame,
  1548. createEl: createEl,
  1549. textContent: textContent,
  1550. prependTo: prependTo,
  1551. hasClass: hasClass,
  1552. addClass: addClass,
  1553. removeClass: removeClass,
  1554. toggleClass: toggleClass,
  1555. setAttributes: setAttributes,
  1556. getAttributes: getAttributes,
  1557. getAttribute: getAttribute,
  1558. setAttribute: setAttribute,
  1559. removeAttribute: removeAttribute,
  1560. blockTextSelection: blockTextSelection,
  1561. unblockTextSelection: unblockTextSelection,
  1562. getBoundingClientRect: getBoundingClientRect,
  1563. findPosition: findPosition,
  1564. getPointerPosition: getPointerPosition,
  1565. isTextNode: isTextNode,
  1566. emptyEl: emptyEl,
  1567. normalizeContent: normalizeContent,
  1568. appendContent: appendContent,
  1569. insertContent: insertContent,
  1570. isSingleLeftClick: isSingleLeftClick,
  1571. $: $,
  1572. $$: $$
  1573. });
  1574. /**
  1575. * @file setup.js - Functions for setting up a player without
  1576. * user interaction based on the data-setup `attribute` of the video tag.
  1577. *
  1578. * @module setup
  1579. */
  1580. var _windowLoaded = false;
  1581. var videojs$1;
  1582. /**
  1583. * Set up any tags that have a data-setup `attribute` when the player is started.
  1584. */
  1585. var autoSetup = function autoSetup() {
  1586. if (videojs$1.options.autoSetup === false) {
  1587. return;
  1588. }
  1589. var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
  1590. var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
  1591. var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js'));
  1592. var mediaEls = vids.concat(audios, divs); // Check if any media elements exist
  1593. if (mediaEls && mediaEls.length > 0) {
  1594. for (var i = 0, e = mediaEls.length; i < e; i++) {
  1595. var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func.
  1596. if (mediaEl && mediaEl.getAttribute) {
  1597. // Make sure this player hasn't already been set up.
  1598. if (mediaEl.player === undefined) {
  1599. var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists.
  1600. // We only auto-setup if they've added the data-setup attr.
  1601. if (options !== null) {
  1602. // Create new video.js instance.
  1603. videojs$1(mediaEl);
  1604. }
  1605. } // If getAttribute isn't defined, we need to wait for the DOM.
  1606. } else {
  1607. autoSetupTimeout(1);
  1608. break;
  1609. }
  1610. } // No videos were found, so keep looping unless page is finished loading.
  1611. } else if (!_windowLoaded) {
  1612. autoSetupTimeout(1);
  1613. }
  1614. };
  1615. /**
  1616. * Wait until the page is loaded before running autoSetup. This will be called in
  1617. * autoSetup if `hasLoaded` returns false.
  1618. *
  1619. * @param {number} wait
  1620. * How long to wait in ms
  1621. *
  1622. * @param {module:videojs} [vjs]
  1623. * The videojs library function
  1624. */
  1625. function autoSetupTimeout(wait, vjs) {
  1626. // Protect against breakage in non-browser environments
  1627. if (!isReal()) {
  1628. return;
  1629. }
  1630. if (vjs) {
  1631. videojs$1 = vjs;
  1632. }
  1633. window$1.setTimeout(autoSetup, wait);
  1634. }
  1635. /**
  1636. * Used to set the internal tracking of window loaded state to true.
  1637. *
  1638. * @private
  1639. */
  1640. function setWindowLoaded() {
  1641. _windowLoaded = true;
  1642. window$1.removeEventListener('load', setWindowLoaded);
  1643. }
  1644. if (isReal()) {
  1645. if (document.readyState === 'complete') {
  1646. setWindowLoaded();
  1647. } else {
  1648. /**
  1649. * Listen for the load event on window, and set _windowLoaded to true.
  1650. *
  1651. * We use a standard event listener here to avoid incrementing the GUID
  1652. * before any players are created.
  1653. *
  1654. * @listens load
  1655. */
  1656. window$1.addEventListener('load', setWindowLoaded);
  1657. }
  1658. }
  1659. /**
  1660. * @file stylesheet.js
  1661. * @module stylesheet
  1662. */
  1663. /**
  1664. * Create a DOM syle element given a className for it.
  1665. *
  1666. * @param {string} className
  1667. * The className to add to the created style element.
  1668. *
  1669. * @return {Element}
  1670. * The element that was created.
  1671. */
  1672. var createStyleElement = function createStyleElement(className) {
  1673. var style = document.createElement('style');
  1674. style.className = className;
  1675. return style;
  1676. };
  1677. /**
  1678. * Add text to a DOM element.
  1679. *
  1680. * @param {Element} el
  1681. * The Element to add text content to.
  1682. *
  1683. * @param {string} content
  1684. * The text to add to the element.
  1685. */
  1686. var setTextContent = function setTextContent(el, content) {
  1687. if (el.styleSheet) {
  1688. el.styleSheet.cssText = content;
  1689. } else {
  1690. el.textContent = content;
  1691. }
  1692. };
  1693. /**
  1694. * @file guid.js
  1695. * @module guid
  1696. */
  1697. // Default value for GUIDs. This allows us to reset the GUID counter in tests.
  1698. //
  1699. // The initial GUID is 3 because some users have come to rely on the first
  1700. // default player ID ending up as `vjs_video_3`.
  1701. //
  1702. // See: https://github.com/videojs/video.js/pull/6216
  1703. var _initialGuid = 3;
  1704. /**
  1705. * Unique ID for an element or function
  1706. *
  1707. * @type {Number}
  1708. */
  1709. var _guid = _initialGuid;
  1710. /**
  1711. * Get a unique auto-incrementing ID by number that has not been returned before.
  1712. *
  1713. * @return {number}
  1714. * A new unique ID.
  1715. */
  1716. function newGUID() {
  1717. return _guid++;
  1718. }
  1719. /**
  1720. * @file dom-data.js
  1721. * @module dom-data
  1722. */
  1723. var FakeWeakMap;
  1724. if (!window$1.WeakMap) {
  1725. FakeWeakMap = /*#__PURE__*/function () {
  1726. function FakeWeakMap() {
  1727. this.vdata = 'vdata' + Math.floor(window$1.performance && window$1.performance.now() || Date.now());
  1728. this.data = {};
  1729. }
  1730. var _proto = FakeWeakMap.prototype;
  1731. _proto.set = function set(key, value) {
  1732. var access = key[this.vdata] || newGUID();
  1733. if (!key[this.vdata]) {
  1734. key[this.vdata] = access;
  1735. }
  1736. this.data[access] = value;
  1737. return this;
  1738. };
  1739. _proto.get = function get(key) {
  1740. var access = key[this.vdata]; // we have data, return it
  1741. if (access) {
  1742. return this.data[access];
  1743. } // we don't have data, return nothing.
  1744. // return undefined explicitly as that's the contract for this method
  1745. log$1('We have no data for this element', key);
  1746. return undefined;
  1747. };
  1748. _proto.has = function has(key) {
  1749. var access = key[this.vdata];
  1750. return access in this.data;
  1751. };
  1752. _proto["delete"] = function _delete(key) {
  1753. var access = key[this.vdata];
  1754. if (access) {
  1755. delete this.data[access];
  1756. delete key[this.vdata];
  1757. }
  1758. };
  1759. return FakeWeakMap;
  1760. }();
  1761. }
  1762. /**
  1763. * Element Data Store.
  1764. *
  1765. * Allows for binding data to an element without putting it directly on the
  1766. * element. Ex. Event listeners are stored here.
  1767. * (also from jsninja.com, slightly modified and updated for closure compiler)
  1768. *
  1769. * @type {Object}
  1770. * @private
  1771. */
  1772. var DomData = window$1.WeakMap ? new WeakMap() : new FakeWeakMap();
  1773. /**
  1774. * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
  1775. * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
  1776. * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
  1777. * robust as jquery's, so there's probably some differences.
  1778. *
  1779. * @file events.js
  1780. * @module events
  1781. */
  1782. /**
  1783. * Clean up the listener cache and dispatchers
  1784. *
  1785. * @param {Element|Object} elem
  1786. * Element to clean up
  1787. *
  1788. * @param {string} type
  1789. * Type of event to clean up
  1790. */
  1791. function _cleanUpEvents(elem, type) {
  1792. if (!DomData.has(elem)) {
  1793. return;
  1794. }
  1795. var data = DomData.get(elem); // Remove the events of a particular type if there are none left
  1796. if (data.handlers[type].length === 0) {
  1797. delete data.handlers[type]; // data.handlers[type] = null;
  1798. // Setting to null was causing an error with data.handlers
  1799. // Remove the meta-handler from the element
  1800. if (elem.removeEventListener) {
  1801. elem.removeEventListener(type, data.dispatcher, false);
  1802. } else if (elem.detachEvent) {
  1803. elem.detachEvent('on' + type, data.dispatcher);
  1804. }
  1805. } // Remove the events object if there are no types left
  1806. if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
  1807. delete data.handlers;
  1808. delete data.dispatcher;
  1809. delete data.disabled;
  1810. } // Finally remove the element data if there is no data left
  1811. if (Object.getOwnPropertyNames(data).length === 0) {
  1812. DomData["delete"](elem);
  1813. }
  1814. }
  1815. /**
  1816. * Loops through an array of event types and calls the requested method for each type.
  1817. *
  1818. * @param {Function} fn
  1819. * The event method we want to use.
  1820. *
  1821. * @param {Element|Object} elem
  1822. * Element or object to bind listeners to
  1823. *
  1824. * @param {string} type
  1825. * Type of event to bind to.
  1826. *
  1827. * @param {EventTarget~EventListener} callback
  1828. * Event listener.
  1829. */
  1830. function _handleMultipleEvents(fn, elem, types, callback) {
  1831. types.forEach(function (type) {
  1832. // Call the event method for each one of the types
  1833. fn(elem, type, callback);
  1834. });
  1835. }
  1836. /**
  1837. * Fix a native event to have standard property values
  1838. *
  1839. * @param {Object} event
  1840. * Event object to fix.
  1841. *
  1842. * @return {Object}
  1843. * Fixed event object.
  1844. */
  1845. function fixEvent(event) {
  1846. if (event.fixed_) {
  1847. return event;
  1848. }
  1849. function returnTrue() {
  1850. return true;
  1851. }
  1852. function returnFalse() {
  1853. return false;
  1854. } // Test if fixing up is needed
  1855. // Used to check if !event.stopPropagation instead of isPropagationStopped
  1856. // But native events return true for stopPropagation, but don't have
  1857. // other expected methods like isPropagationStopped. Seems to be a problem
  1858. // with the Javascript Ninja code. So we're just overriding all events now.
  1859. if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
  1860. var old = event || window$1.event;
  1861. event = {}; // Clone the old object so that we can modify the values event = {};
  1862. // IE8 Doesn't like when you mess with native event properties
  1863. // Firefox returns false for event.hasOwnProperty('type') and other props
  1864. // which makes copying more difficult.
  1865. // TODO: Probably best to create a whitelist of event props
  1866. for (var key in old) {
  1867. // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
  1868. // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
  1869. // and webkitMovementX/Y
  1870. // Lighthouse complains if Event.path is copied
  1871. if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY' && key !== 'path') {
  1872. // Chrome 32+ warns if you try to copy deprecated returnValue, but
  1873. // we still want to if preventDefault isn't supported (IE8).
  1874. if (!(key === 'returnValue' && old.preventDefault)) {
  1875. event[key] = old[key];
  1876. }
  1877. }
  1878. } // The event occurred on this element
  1879. if (!event.target) {
  1880. event.target = event.srcElement || document;
  1881. } // Handle which other element the event is related to
  1882. if (!event.relatedTarget) {
  1883. event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
  1884. } // Stop the default browser action
  1885. event.preventDefault = function () {
  1886. if (old.preventDefault) {
  1887. old.preventDefault();
  1888. }
  1889. event.returnValue = false;
  1890. old.returnValue = false;
  1891. event.defaultPrevented = true;
  1892. };
  1893. event.defaultPrevented = false; // Stop the event from bubbling
  1894. event.stopPropagation = function () {
  1895. if (old.stopPropagation) {
  1896. old.stopPropagation();
  1897. }
  1898. event.cancelBubble = true;
  1899. old.cancelBubble = true;
  1900. event.isPropagationStopped = returnTrue;
  1901. };
  1902. event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers
  1903. event.stopImmediatePropagation = function () {
  1904. if (old.stopImmediatePropagation) {
  1905. old.stopImmediatePropagation();
  1906. }
  1907. event.isImmediatePropagationStopped = returnTrue;
  1908. event.stopPropagation();
  1909. };
  1910. event.isImmediatePropagationStopped = returnFalse; // Handle mouse position
  1911. if (event.clientX !== null && event.clientX !== undefined) {
  1912. var doc = document.documentElement;
  1913. var body = document.body;
  1914. event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
  1915. event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
  1916. } // Handle key presses
  1917. event.which = event.charCode || event.keyCode; // Fix button for mouse clicks:
  1918. // 0 == left; 1 == middle; 2 == right
  1919. if (event.button !== null && event.button !== undefined) {
  1920. // The following is disabled because it does not pass videojs-standard
  1921. // and... yikes.
  1922. /* eslint-disable */
  1923. event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
  1924. /* eslint-enable */
  1925. }
  1926. }
  1927. event.fixed_ = true; // Returns fixed-up instance
  1928. return event;
  1929. }
  1930. /**
  1931. * Whether passive event listeners are supported
  1932. */
  1933. var _supportsPassive;
  1934. var supportsPassive = function supportsPassive() {
  1935. if (typeof _supportsPassive !== 'boolean') {
  1936. _supportsPassive = false;
  1937. try {
  1938. var opts = Object.defineProperty({}, 'passive', {
  1939. get: function get() {
  1940. _supportsPassive = true;
  1941. }
  1942. });
  1943. window$1.addEventListener('test', null, opts);
  1944. window$1.removeEventListener('test', null, opts);
  1945. } catch (e) {// disregard
  1946. }
  1947. }
  1948. return _supportsPassive;
  1949. };
  1950. /**
  1951. * Touch events Chrome expects to be passive
  1952. */
  1953. var passiveEvents = ['touchstart', 'touchmove'];
  1954. /**
  1955. * Add an event listener to element
  1956. * It stores the handler function in a separate cache object
  1957. * and adds a generic handler to the element's event,
  1958. * along with a unique id (guid) to the element.
  1959. *
  1960. * @param {Element|Object} elem
  1961. * Element or object to bind listeners to
  1962. *
  1963. * @param {string|string[]} type
  1964. * Type of event to bind to.
  1965. *
  1966. * @param {EventTarget~EventListener} fn
  1967. * Event listener.
  1968. */
  1969. function on(elem, type, fn) {
  1970. if (Array.isArray(type)) {
  1971. return _handleMultipleEvents(on, elem, type, fn);
  1972. }
  1973. if (!DomData.has(elem)) {
  1974. DomData.set(elem, {});
  1975. }
  1976. var data = DomData.get(elem); // We need a place to store all our handler data
  1977. if (!data.handlers) {
  1978. data.handlers = {};
  1979. }
  1980. if (!data.handlers[type]) {
  1981. data.handlers[type] = [];
  1982. }
  1983. if (!fn.guid) {
  1984. fn.guid = newGUID();
  1985. }
  1986. data.handlers[type].push(fn);
  1987. if (!data.dispatcher) {
  1988. data.disabled = false;
  1989. data.dispatcher = function (event, hash) {
  1990. if (data.disabled) {
  1991. return;
  1992. }
  1993. event = fixEvent(event);
  1994. var handlers = data.handlers[event.type];
  1995. if (handlers) {
  1996. // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
  1997. var handlersCopy = handlers.slice(0);
  1998. for (var m = 0, n = handlersCopy.length; m < n; m++) {
  1999. if (event.isImmediatePropagationStopped()) {
  2000. break;
  2001. } else {
  2002. try {
  2003. handlersCopy[m].call(elem, event, hash);
  2004. } catch (e) {
  2005. log$1.error(e);
  2006. }
  2007. }
  2008. }
  2009. }
  2010. };
  2011. }
  2012. if (data.handlers[type].length === 1) {
  2013. if (elem.addEventListener) {
  2014. var options = false;
  2015. if (supportsPassive() && passiveEvents.indexOf(type) > -1) {
  2016. options = {
  2017. passive: true
  2018. };
  2019. }
  2020. elem.addEventListener(type, data.dispatcher, options);
  2021. } else if (elem.attachEvent) {
  2022. elem.attachEvent('on' + type, data.dispatcher);
  2023. }
  2024. }
  2025. }
  2026. /**
  2027. * Removes event listeners from an element
  2028. *
  2029. * @param {Element|Object} elem
  2030. * Object to remove listeners from.
  2031. *
  2032. * @param {string|string[]} [type]
  2033. * Type of listener to remove. Don't include to remove all events from element.
  2034. *
  2035. * @param {EventTarget~EventListener} [fn]
  2036. * Specific listener to remove. Don't include to remove listeners for an event
  2037. * type.
  2038. */
  2039. function off(elem, type, fn) {
  2040. // Don't want to add a cache object through getElData if not needed
  2041. if (!DomData.has(elem)) {
  2042. return;
  2043. }
  2044. var data = DomData.get(elem); // If no events exist, nothing to unbind
  2045. if (!data.handlers) {
  2046. return;
  2047. }
  2048. if (Array.isArray(type)) {
  2049. return _handleMultipleEvents(off, elem, type, fn);
  2050. } // Utility function
  2051. var removeType = function removeType(el, t) {
  2052. data.handlers[t] = [];
  2053. _cleanUpEvents(el, t);
  2054. }; // Are we removing all bound events?
  2055. if (type === undefined) {
  2056. for (var t in data.handlers) {
  2057. if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
  2058. removeType(elem, t);
  2059. }
  2060. }
  2061. return;
  2062. }
  2063. var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind
  2064. if (!handlers) {
  2065. return;
  2066. } // If no listener was provided, remove all listeners for type
  2067. if (!fn) {
  2068. removeType(elem, type);
  2069. return;
  2070. } // We're only removing a single handler
  2071. if (fn.guid) {
  2072. for (var n = 0; n < handlers.length; n++) {
  2073. if (handlers[n].guid === fn.guid) {
  2074. handlers.splice(n--, 1);
  2075. }
  2076. }
  2077. }
  2078. _cleanUpEvents(elem, type);
  2079. }
  2080. /**
  2081. * Trigger an event for an element
  2082. *
  2083. * @param {Element|Object} elem
  2084. * Element to trigger an event on
  2085. *
  2086. * @param {EventTarget~Event|string} event
  2087. * A string (the type) or an event object with a type attribute
  2088. *
  2089. * @param {Object} [hash]
  2090. * data hash to pass along with the event
  2091. *
  2092. * @return {boolean|undefined}
  2093. * Returns the opposite of `defaultPrevented` if default was
  2094. * prevented. Otherwise, returns `undefined`
  2095. */
  2096. function trigger(elem, event, hash) {
  2097. // Fetches element data and a reference to the parent (for bubbling).
  2098. // Don't want to add a data object to cache for every parent,
  2099. // so checking hasElData first.
  2100. var elemData = DomData.has(elem) ? DomData.get(elem) : {};
  2101. var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event,
  2102. // handler;
  2103. // If an event name was passed as a string, creates an event out of it
  2104. if (typeof event === 'string') {
  2105. event = {
  2106. type: event,
  2107. target: elem
  2108. };
  2109. } else if (!event.target) {
  2110. event.target = elem;
  2111. } // Normalizes the event properties.
  2112. event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers.
  2113. if (elemData.dispatcher) {
  2114. elemData.dispatcher.call(elem, event, hash);
  2115. } // Unless explicitly stopped or the event does not bubble (e.g. media events)
  2116. // recursively calls this function to bubble the event up the DOM.
  2117. if (parent && !event.isPropagationStopped() && event.bubbles === true) {
  2118. trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled.
  2119. } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
  2120. if (!DomData.has(event.target)) {
  2121. DomData.set(event.target, {});
  2122. }
  2123. var targetData = DomData.get(event.target); // Checks if the target has a default action for this event.
  2124. if (event.target[event.type]) {
  2125. // Temporarily disables event dispatching on the target as we have already executed the handler.
  2126. targetData.disabled = true; // Executes the default action.
  2127. if (typeof event.target[event.type] === 'function') {
  2128. event.target[event.type]();
  2129. } // Re-enables event dispatching.
  2130. targetData.disabled = false;
  2131. }
  2132. } // Inform the triggerer if the default was prevented by returning false
  2133. return !event.defaultPrevented;
  2134. }
  2135. /**
  2136. * Trigger a listener only once for an event.
  2137. *
  2138. * @param {Element|Object} elem
  2139. * Element or object to bind to.
  2140. *
  2141. * @param {string|string[]} type
  2142. * Name/type of event
  2143. *
  2144. * @param {Event~EventListener} fn
  2145. * Event listener function
  2146. */
  2147. function one(elem, type, fn) {
  2148. if (Array.isArray(type)) {
  2149. return _handleMultipleEvents(one, elem, type, fn);
  2150. }
  2151. var func = function func() {
  2152. off(elem, type, func);
  2153. fn.apply(this, arguments);
  2154. }; // copy the guid to the new function so it can removed using the original function's ID
  2155. func.guid = fn.guid = fn.guid || newGUID();
  2156. on(elem, type, func);
  2157. }
  2158. /**
  2159. * Trigger a listener only once and then turn if off for all
  2160. * configured events
  2161. *
  2162. * @param {Element|Object} elem
  2163. * Element or object to bind to.
  2164. *
  2165. * @param {string|string[]} type
  2166. * Name/type of event
  2167. *
  2168. * @param {Event~EventListener} fn
  2169. * Event listener function
  2170. */
  2171. function any(elem, type, fn) {
  2172. var func = function func() {
  2173. off(elem, type, func);
  2174. fn.apply(this, arguments);
  2175. }; // copy the guid to the new function so it can removed using the original function's ID
  2176. func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything
  2177. on(elem, type, func);
  2178. }
  2179. var Events = /*#__PURE__*/Object.freeze({
  2180. __proto__: null,
  2181. fixEvent: fixEvent,
  2182. on: on,
  2183. off: off,
  2184. trigger: trigger,
  2185. one: one,
  2186. any: any
  2187. });
  2188. /**
  2189. * @file fn.js
  2190. * @module fn
  2191. */
  2192. var UPDATE_REFRESH_INTERVAL = 30;
  2193. /**
  2194. * Bind (a.k.a proxy or context). A simple method for changing the context of
  2195. * a function.
  2196. *
  2197. * It also stores a unique id on the function so it can be easily removed from
  2198. * events.
  2199. *
  2200. * @function
  2201. * @param {Mixed} context
  2202. * The object to bind as scope.
  2203. *
  2204. * @param {Function} fn
  2205. * The function to be bound to a scope.
  2206. *
  2207. * @param {number} [uid]
  2208. * An optional unique ID for the function to be set
  2209. *
  2210. * @return {Function}
  2211. * The new function that will be bound into the context given
  2212. */
  2213. var bind = function bind(context, fn, uid) {
  2214. // Make sure the function has a unique ID
  2215. if (!fn.guid) {
  2216. fn.guid = newGUID();
  2217. } // Create the new function that changes the context
  2218. var bound = fn.bind(context); // Allow for the ability to individualize this function
  2219. // Needed in the case where multiple objects might share the same prototype
  2220. // IF both items add an event listener with the same function, then you try to remove just one
  2221. // it will remove both because they both have the same guid.
  2222. // when using this, you need to use the bind method when you remove the listener as well.
  2223. // currently used in text tracks
  2224. bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
  2225. return bound;
  2226. };
  2227. /**
  2228. * Wraps the given function, `fn`, with a new function that only invokes `fn`
  2229. * at most once per every `wait` milliseconds.
  2230. *
  2231. * @function
  2232. * @param {Function} fn
  2233. * The function to be throttled.
  2234. *
  2235. * @param {number} wait
  2236. * The number of milliseconds by which to throttle.
  2237. *
  2238. * @return {Function}
  2239. */
  2240. var throttle = function throttle(fn, wait) {
  2241. var last = window$1.performance.now();
  2242. var throttled = function throttled() {
  2243. var now = window$1.performance.now();
  2244. if (now - last >= wait) {
  2245. fn.apply(void 0, arguments);
  2246. last = now;
  2247. }
  2248. };
  2249. return throttled;
  2250. };
  2251. /**
  2252. * Creates a debounced function that delays invoking `func` until after `wait`
  2253. * milliseconds have elapsed since the last time the debounced function was
  2254. * invoked.
  2255. *
  2256. * Inspired by lodash and underscore implementations.
  2257. *
  2258. * @function
  2259. * @param {Function} func
  2260. * The function to wrap with debounce behavior.
  2261. *
  2262. * @param {number} wait
  2263. * The number of milliseconds to wait after the last invocation.
  2264. *
  2265. * @param {boolean} [immediate]
  2266. * Whether or not to invoke the function immediately upon creation.
  2267. *
  2268. * @param {Object} [context=window]
  2269. * The "context" in which the debounced function should debounce. For
  2270. * example, if this function should be tied to a Video.js player,
  2271. * the player can be passed here. Alternatively, defaults to the
  2272. * global `window` object.
  2273. *
  2274. * @return {Function}
  2275. * A debounced function.
  2276. */
  2277. var debounce = function debounce(func, wait, immediate, context) {
  2278. if (context === void 0) {
  2279. context = window$1;
  2280. }
  2281. var timeout;
  2282. var cancel = function cancel() {
  2283. context.clearTimeout(timeout);
  2284. timeout = null;
  2285. };
  2286. /* eslint-disable consistent-this */
  2287. var debounced = function debounced() {
  2288. var self = this;
  2289. var args = arguments;
  2290. var _later = function later() {
  2291. timeout = null;
  2292. _later = null;
  2293. if (!immediate) {
  2294. func.apply(self, args);
  2295. }
  2296. };
  2297. if (!timeout && immediate) {
  2298. func.apply(self, args);
  2299. }
  2300. context.clearTimeout(timeout);
  2301. timeout = context.setTimeout(_later, wait);
  2302. };
  2303. /* eslint-enable consistent-this */
  2304. debounced.cancel = cancel;
  2305. return debounced;
  2306. };
  2307. /**
  2308. * @file src/js/event-target.js
  2309. */
  2310. /**
  2311. * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
  2312. * adds shorthand functions that wrap around lengthy functions. For example:
  2313. * the `on` function is a wrapper around `addEventListener`.
  2314. *
  2315. * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
  2316. * @class EventTarget
  2317. */
  2318. var EventTarget$2 = function EventTarget() {};
  2319. /**
  2320. * A Custom DOM event.
  2321. *
  2322. * @typedef {Object} EventTarget~Event
  2323. * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
  2324. */
  2325. /**
  2326. * All event listeners should follow the following format.
  2327. *
  2328. * @callback EventTarget~EventListener
  2329. * @this {EventTarget}
  2330. *
  2331. * @param {EventTarget~Event} event
  2332. * the event that triggered this function
  2333. *
  2334. * @param {Object} [hash]
  2335. * hash of data sent during the event
  2336. */
  2337. /**
  2338. * An object containing event names as keys and booleans as values.
  2339. *
  2340. * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
  2341. * will have extra functionality. See that function for more information.
  2342. *
  2343. * @property EventTarget.prototype.allowedEvents_
  2344. * @private
  2345. */
  2346. EventTarget$2.prototype.allowedEvents_ = {};
  2347. /**
  2348. * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
  2349. * function that will get called when an event with a certain name gets triggered.
  2350. *
  2351. * @param {string|string[]} type
  2352. * An event name or an array of event names.
  2353. *
  2354. * @param {EventTarget~EventListener} fn
  2355. * The function to call with `EventTarget`s
  2356. */
  2357. EventTarget$2.prototype.on = function (type, fn) {
  2358. // Remove the addEventListener alias before calling Events.on
  2359. // so we don't get into an infinite type loop
  2360. var ael = this.addEventListener;
  2361. this.addEventListener = function () {};
  2362. on(this, type, fn);
  2363. this.addEventListener = ael;
  2364. };
  2365. /**
  2366. * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
  2367. * the standard DOM API.
  2368. *
  2369. * @function
  2370. * @see {@link EventTarget#on}
  2371. */
  2372. EventTarget$2.prototype.addEventListener = EventTarget$2.prototype.on;
  2373. /**
  2374. * Removes an `event listener` for a specific event from an instance of `EventTarget`.
  2375. * This makes it so that the `event listener` will no longer get called when the
  2376. * named event happens.
  2377. *
  2378. * @param {string|string[]} type
  2379. * An event name or an array of event names.
  2380. *
  2381. * @param {EventTarget~EventListener} fn
  2382. * The function to remove.
  2383. */
  2384. EventTarget$2.prototype.off = function (type, fn) {
  2385. off(this, type, fn);
  2386. };
  2387. /**
  2388. * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
  2389. * the standard DOM API.
  2390. *
  2391. * @function
  2392. * @see {@link EventTarget#off}
  2393. */
  2394. EventTarget$2.prototype.removeEventListener = EventTarget$2.prototype.off;
  2395. /**
  2396. * This function will add an `event listener` that gets triggered only once. After the
  2397. * first trigger it will get removed. This is like adding an `event listener`
  2398. * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
  2399. *
  2400. * @param {string|string[]} type
  2401. * An event name or an array of event names.
  2402. *
  2403. * @param {EventTarget~EventListener} fn
  2404. * The function to be called once for each event name.
  2405. */
  2406. EventTarget$2.prototype.one = function (type, fn) {
  2407. // Remove the addEventListener aliasing Events.on
  2408. // so we don't get into an infinite type loop
  2409. var ael = this.addEventListener;
  2410. this.addEventListener = function () {};
  2411. one(this, type, fn);
  2412. this.addEventListener = ael;
  2413. };
  2414. EventTarget$2.prototype.any = function (type, fn) {
  2415. // Remove the addEventListener aliasing Events.on
  2416. // so we don't get into an infinite type loop
  2417. var ael = this.addEventListener;
  2418. this.addEventListener = function () {};
  2419. any(this, type, fn);
  2420. this.addEventListener = ael;
  2421. };
  2422. /**
  2423. * This function causes an event to happen. This will then cause any `event listeners`
  2424. * that are waiting for that event, to get called. If there are no `event listeners`
  2425. * for an event then nothing will happen.
  2426. *
  2427. * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
  2428. * Trigger will also call the `on` + `uppercaseEventName` function.
  2429. *
  2430. * Example:
  2431. * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
  2432. * `onClick` if it exists.
  2433. *
  2434. * @param {string|EventTarget~Event|Object} event
  2435. * The name of the event, an `Event`, or an object with a key of type set to
  2436. * an event name.
  2437. */
  2438. EventTarget$2.prototype.trigger = function (event) {
  2439. var type = event.type || event; // deprecation
  2440. // In a future version we should default target to `this`
  2441. // similar to how we default the target to `elem` in
  2442. // `Events.trigger`. Right now the default `target` will be
  2443. // `document` due to the `Event.fixEvent` call.
  2444. if (typeof event === 'string') {
  2445. event = {
  2446. type: type
  2447. };
  2448. }
  2449. event = fixEvent(event);
  2450. if (this.allowedEvents_[type] && this['on' + type]) {
  2451. this['on' + type](event);
  2452. }
  2453. trigger(this, event);
  2454. };
  2455. /**
  2456. * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
  2457. * the standard DOM API.
  2458. *
  2459. * @function
  2460. * @see {@link EventTarget#trigger}
  2461. */
  2462. EventTarget$2.prototype.dispatchEvent = EventTarget$2.prototype.trigger;
  2463. var EVENT_MAP;
  2464. EventTarget$2.prototype.queueTrigger = function (event) {
  2465. var _this = this;
  2466. // only set up EVENT_MAP if it'll be used
  2467. if (!EVENT_MAP) {
  2468. EVENT_MAP = new Map();
  2469. }
  2470. var type = event.type || event;
  2471. var map = EVENT_MAP.get(this);
  2472. if (!map) {
  2473. map = new Map();
  2474. EVENT_MAP.set(this, map);
  2475. }
  2476. var oldTimeout = map.get(type);
  2477. map["delete"](type);
  2478. window$1.clearTimeout(oldTimeout);
  2479. var timeout = window$1.setTimeout(function () {
  2480. map["delete"](type); // if we cleared out all timeouts for the current target, delete its map
  2481. if (map.size === 0) {
  2482. map = null;
  2483. EVENT_MAP["delete"](_this);
  2484. }
  2485. _this.trigger(event);
  2486. }, 0);
  2487. map.set(type, timeout);
  2488. };
  2489. /**
  2490. * @file mixins/evented.js
  2491. * @module evented
  2492. */
  2493. var objName = function objName(obj) {
  2494. if (typeof obj.name === 'function') {
  2495. return obj.name();
  2496. }
  2497. if (typeof obj.name === 'string') {
  2498. return obj.name;
  2499. }
  2500. if (obj.name_) {
  2501. return obj.name_;
  2502. }
  2503. if (obj.constructor && obj.constructor.name) {
  2504. return obj.constructor.name;
  2505. }
  2506. return typeof obj;
  2507. };
  2508. /**
  2509. * Returns whether or not an object has had the evented mixin applied.
  2510. *
  2511. * @param {Object} object
  2512. * An object to test.
  2513. *
  2514. * @return {boolean}
  2515. * Whether or not the object appears to be evented.
  2516. */
  2517. var isEvented = function isEvented(object) {
  2518. return object instanceof EventTarget$2 || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
  2519. return typeof object[k] === 'function';
  2520. });
  2521. };
  2522. /**
  2523. * Adds a callback to run after the evented mixin applied.
  2524. *
  2525. * @param {Object} object
  2526. * An object to Add
  2527. * @param {Function} callback
  2528. * The callback to run.
  2529. */
  2530. var addEventedCallback = function addEventedCallback(target, callback) {
  2531. if (isEvented(target)) {
  2532. callback();
  2533. } else {
  2534. if (!target.eventedCallbacks) {
  2535. target.eventedCallbacks = [];
  2536. }
  2537. target.eventedCallbacks.push(callback);
  2538. }
  2539. };
  2540. /**
  2541. * Whether a value is a valid event type - non-empty string or array.
  2542. *
  2543. * @private
  2544. * @param {string|Array} type
  2545. * The type value to test.
  2546. *
  2547. * @return {boolean}
  2548. * Whether or not the type is a valid event type.
  2549. */
  2550. var isValidEventType = function isValidEventType(type) {
  2551. return (// The regex here verifies that the `type` contains at least one non-
  2552. // whitespace character.
  2553. typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
  2554. );
  2555. };
  2556. /**
  2557. * Validates a value to determine if it is a valid event target. Throws if not.
  2558. *
  2559. * @private
  2560. * @throws {Error}
  2561. * If the target does not appear to be a valid event target.
  2562. *
  2563. * @param {Object} target
  2564. * The object to test.
  2565. *
  2566. * @param {Object} obj
  2567. * The evented object we are validating for
  2568. *
  2569. * @param {string} fnName
  2570. * The name of the evented mixin function that called this.
  2571. */
  2572. var validateTarget = function validateTarget(target, obj, fnName) {
  2573. if (!target || !target.nodeName && !isEvented(target)) {
  2574. throw new Error("Invalid target for " + objName(obj) + "#" + fnName + "; must be a DOM node or evented object.");
  2575. }
  2576. };
  2577. /**
  2578. * Validates a value to determine if it is a valid event target. Throws if not.
  2579. *
  2580. * @private
  2581. * @throws {Error}
  2582. * If the type does not appear to be a valid event type.
  2583. *
  2584. * @param {string|Array} type
  2585. * The type to test.
  2586. *
  2587. * @param {Object} obj
  2588. * The evented object we are validating for
  2589. *
  2590. * @param {string} fnName
  2591. * The name of the evented mixin function that called this.
  2592. */
  2593. var validateEventType = function validateEventType(type, obj, fnName) {
  2594. if (!isValidEventType(type)) {
  2595. throw new Error("Invalid event type for " + objName(obj) + "#" + fnName + "; must be a non-empty string or array.");
  2596. }
  2597. };
  2598. /**
  2599. * Validates a value to determine if it is a valid listener. Throws if not.
  2600. *
  2601. * @private
  2602. * @throws {Error}
  2603. * If the listener is not a function.
  2604. *
  2605. * @param {Function} listener
  2606. * The listener to test.
  2607. *
  2608. * @param {Object} obj
  2609. * The evented object we are validating for
  2610. *
  2611. * @param {string} fnName
  2612. * The name of the evented mixin function that called this.
  2613. */
  2614. var validateListener = function validateListener(listener, obj, fnName) {
  2615. if (typeof listener !== 'function') {
  2616. throw new Error("Invalid listener for " + objName(obj) + "#" + fnName + "; must be a function.");
  2617. }
  2618. };
  2619. /**
  2620. * Takes an array of arguments given to `on()` or `one()`, validates them, and
  2621. * normalizes them into an object.
  2622. *
  2623. * @private
  2624. * @param {Object} self
  2625. * The evented object on which `on()` or `one()` was called. This
  2626. * object will be bound as the `this` value for the listener.
  2627. *
  2628. * @param {Array} args
  2629. * An array of arguments passed to `on()` or `one()`.
  2630. *
  2631. * @param {string} fnName
  2632. * The name of the evented mixin function that called this.
  2633. *
  2634. * @return {Object}
  2635. * An object containing useful values for `on()` or `one()` calls.
  2636. */
  2637. var normalizeListenArgs = function normalizeListenArgs(self, args, fnName) {
  2638. // If the number of arguments is less than 3, the target is always the
  2639. // evented object itself.
  2640. var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
  2641. var target;
  2642. var type;
  2643. var listener;
  2644. if (isTargetingSelf) {
  2645. target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to
  2646. // the evented object itself.
  2647. if (args.length >= 3) {
  2648. args.shift();
  2649. }
  2650. type = args[0];
  2651. listener = args[1];
  2652. } else {
  2653. target = args[0];
  2654. type = args[1];
  2655. listener = args[2];
  2656. }
  2657. validateTarget(target, self, fnName);
  2658. validateEventType(type, self, fnName);
  2659. validateListener(listener, self, fnName);
  2660. listener = bind(self, listener);
  2661. return {
  2662. isTargetingSelf: isTargetingSelf,
  2663. target: target,
  2664. type: type,
  2665. listener: listener
  2666. };
  2667. };
  2668. /**
  2669. * Adds the listener to the event type(s) on the target, normalizing for
  2670. * the type of target.
  2671. *
  2672. * @private
  2673. * @param {Element|Object} target
  2674. * A DOM node or evented object.
  2675. *
  2676. * @param {string} method
  2677. * The event binding method to use ("on" or "one").
  2678. *
  2679. * @param {string|Array} type
  2680. * One or more event type(s).
  2681. *
  2682. * @param {Function} listener
  2683. * A listener function.
  2684. */
  2685. var listen = function listen(target, method, type, listener) {
  2686. validateTarget(target, target, method);
  2687. if (target.nodeName) {
  2688. Events[method](target, type, listener);
  2689. } else {
  2690. target[method](type, listener);
  2691. }
  2692. };
  2693. /**
  2694. * Contains methods that provide event capabilities to an object which is passed
  2695. * to {@link module:evented|evented}.
  2696. *
  2697. * @mixin EventedMixin
  2698. */
  2699. var EventedMixin = {
  2700. /**
  2701. * Add a listener to an event (or events) on this object or another evented
  2702. * object.
  2703. *
  2704. * @param {string|Array|Element|Object} targetOrType
  2705. * If this is a string or array, it represents the event type(s)
  2706. * that will trigger the listener.
  2707. *
  2708. * Another evented object can be passed here instead, which will
  2709. * cause the listener to listen for events on _that_ object.
  2710. *
  2711. * In either case, the listener's `this` value will be bound to
  2712. * this object.
  2713. *
  2714. * @param {string|Array|Function} typeOrListener
  2715. * If the first argument was a string or array, this should be the
  2716. * listener function. Otherwise, this is a string or array of event
  2717. * type(s).
  2718. *
  2719. * @param {Function} [listener]
  2720. * If the first argument was another evented object, this will be
  2721. * the listener function.
  2722. */
  2723. on: function on() {
  2724. var _this = this;
  2725. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  2726. args[_key] = arguments[_key];
  2727. }
  2728. var _normalizeListenArgs = normalizeListenArgs(this, args, 'on'),
  2729. isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
  2730. target = _normalizeListenArgs.target,
  2731. type = _normalizeListenArgs.type,
  2732. listener = _normalizeListenArgs.listener;
  2733. listen(target, 'on', type, listener); // If this object is listening to another evented object.
  2734. if (!isTargetingSelf) {
  2735. // If this object is disposed, remove the listener.
  2736. var removeListenerOnDispose = function removeListenerOnDispose() {
  2737. return _this.off(target, type, listener);
  2738. }; // Use the same function ID as the listener so we can remove it later it
  2739. // using the ID of the original listener.
  2740. removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures
  2741. // that if the target is disposed BEFORE this object, we remove the
  2742. // removal listener that was just added. Otherwise, we create a memory leak.
  2743. var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
  2744. return _this.off('dispose', removeListenerOnDispose);
  2745. }; // Use the same function ID as the listener so we can remove it later
  2746. // it using the ID of the original listener.
  2747. removeRemoverOnTargetDispose.guid = listener.guid;
  2748. listen(this, 'on', 'dispose', removeListenerOnDispose);
  2749. listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
  2750. }
  2751. },
  2752. /**
  2753. * Add a listener to an event (or events) on this object or another evented
  2754. * object. The listener will be called once per event and then removed.
  2755. *
  2756. * @param {string|Array|Element|Object} targetOrType
  2757. * If this is a string or array, it represents the event type(s)
  2758. * that will trigger the listener.
  2759. *
  2760. * Another evented object can be passed here instead, which will
  2761. * cause the listener to listen for events on _that_ object.
  2762. *
  2763. * In either case, the listener's `this` value will be bound to
  2764. * this object.
  2765. *
  2766. * @param {string|Array|Function} typeOrListener
  2767. * If the first argument was a string or array, this should be the
  2768. * listener function. Otherwise, this is a string or array of event
  2769. * type(s).
  2770. *
  2771. * @param {Function} [listener]
  2772. * If the first argument was another evented object, this will be
  2773. * the listener function.
  2774. */
  2775. one: function one() {
  2776. var _this2 = this;
  2777. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  2778. args[_key2] = arguments[_key2];
  2779. }
  2780. var _normalizeListenArgs2 = normalizeListenArgs(this, args, 'one'),
  2781. isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
  2782. target = _normalizeListenArgs2.target,
  2783. type = _normalizeListenArgs2.type,
  2784. listener = _normalizeListenArgs2.listener; // Targeting this evented object.
  2785. if (isTargetingSelf) {
  2786. listen(target, 'one', type, listener); // Targeting another evented object.
  2787. } else {
  2788. // TODO: This wrapper is incorrect! It should only
  2789. // remove the wrapper for the event type that called it.
  2790. // Instead all listners are removed on the first trigger!
  2791. // see https://github.com/videojs/video.js/issues/5962
  2792. var wrapper = function wrapper() {
  2793. _this2.off(target, type, wrapper);
  2794. for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  2795. largs[_key3] = arguments[_key3];
  2796. }
  2797. listener.apply(null, largs);
  2798. }; // Use the same function ID as the listener so we can remove it later
  2799. // it using the ID of the original listener.
  2800. wrapper.guid = listener.guid;
  2801. listen(target, 'one', type, wrapper);
  2802. }
  2803. },
  2804. /**
  2805. * Add a listener to an event (or events) on this object or another evented
  2806. * object. The listener will only be called once for the first event that is triggered
  2807. * then removed.
  2808. *
  2809. * @param {string|Array|Element|Object} targetOrType
  2810. * If this is a string or array, it represents the event type(s)
  2811. * that will trigger the listener.
  2812. *
  2813. * Another evented object can be passed here instead, which will
  2814. * cause the listener to listen for events on _that_ object.
  2815. *
  2816. * In either case, the listener's `this` value will be bound to
  2817. * this object.
  2818. *
  2819. * @param {string|Array|Function} typeOrListener
  2820. * If the first argument was a string or array, this should be the
  2821. * listener function. Otherwise, this is a string or array of event
  2822. * type(s).
  2823. *
  2824. * @param {Function} [listener]
  2825. * If the first argument was another evented object, this will be
  2826. * the listener function.
  2827. */
  2828. any: function any() {
  2829. var _this3 = this;
  2830. for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
  2831. args[_key4] = arguments[_key4];
  2832. }
  2833. var _normalizeListenArgs3 = normalizeListenArgs(this, args, 'any'),
  2834. isTargetingSelf = _normalizeListenArgs3.isTargetingSelf,
  2835. target = _normalizeListenArgs3.target,
  2836. type = _normalizeListenArgs3.type,
  2837. listener = _normalizeListenArgs3.listener; // Targeting this evented object.
  2838. if (isTargetingSelf) {
  2839. listen(target, 'any', type, listener); // Targeting another evented object.
  2840. } else {
  2841. var wrapper = function wrapper() {
  2842. _this3.off(target, type, wrapper);
  2843. for (var _len5 = arguments.length, largs = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
  2844. largs[_key5] = arguments[_key5];
  2845. }
  2846. listener.apply(null, largs);
  2847. }; // Use the same function ID as the listener so we can remove it later
  2848. // it using the ID of the original listener.
  2849. wrapper.guid = listener.guid;
  2850. listen(target, 'any', type, wrapper);
  2851. }
  2852. },
  2853. /**
  2854. * Removes listener(s) from event(s) on an evented object.
  2855. *
  2856. * @param {string|Array|Element|Object} [targetOrType]
  2857. * If this is a string or array, it represents the event type(s).
  2858. *
  2859. * Another evented object can be passed here instead, in which case
  2860. * ALL 3 arguments are _required_.
  2861. *
  2862. * @param {string|Array|Function} [typeOrListener]
  2863. * If the first argument was a string or array, this may be the
  2864. * listener function. Otherwise, this is a string or array of event
  2865. * type(s).
  2866. *
  2867. * @param {Function} [listener]
  2868. * If the first argument was another evented object, this will be
  2869. * the listener function; otherwise, _all_ listeners bound to the
  2870. * event type(s) will be removed.
  2871. */
  2872. off: function off$1(targetOrType, typeOrListener, listener) {
  2873. // Targeting this evented object.
  2874. if (!targetOrType || isValidEventType(targetOrType)) {
  2875. off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object.
  2876. } else {
  2877. var target = targetOrType;
  2878. var type = typeOrListener; // Fail fast and in a meaningful way!
  2879. validateTarget(target, this, 'off');
  2880. validateEventType(type, this, 'off');
  2881. validateListener(listener, this, 'off'); // Ensure there's at least a guid, even if the function hasn't been used
  2882. listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given
  2883. // the same guid as the event listener in on().
  2884. this.off('dispose', listener);
  2885. if (target.nodeName) {
  2886. off(target, type, listener);
  2887. off(target, 'dispose', listener);
  2888. } else if (isEvented(target)) {
  2889. target.off(type, listener);
  2890. target.off('dispose', listener);
  2891. }
  2892. }
  2893. },
  2894. /**
  2895. * Fire an event on this evented object, causing its listeners to be called.
  2896. *
  2897. * @param {string|Object} event
  2898. * An event type or an object with a type property.
  2899. *
  2900. * @param {Object} [hash]
  2901. * An additional object to pass along to listeners.
  2902. *
  2903. * @return {boolean}
  2904. * Whether or not the default behavior was prevented.
  2905. */
  2906. trigger: function trigger$1(event, hash) {
  2907. validateTarget(this.eventBusEl_, this, 'trigger');
  2908. var type = event && typeof event !== 'string' ? event.type : event;
  2909. if (!isValidEventType(type)) {
  2910. var error = "Invalid event type for " + objName(this) + "#trigger; " + 'must be a non-empty string or object with a type key that has a non-empty value.';
  2911. if (event) {
  2912. (this.log || log$1).error(error);
  2913. } else {
  2914. throw new Error(error);
  2915. }
  2916. }
  2917. return trigger(this.eventBusEl_, event, hash);
  2918. }
  2919. };
  2920. /**
  2921. * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
  2922. *
  2923. * @param {Object} target
  2924. * The object to which to add event methods.
  2925. *
  2926. * @param {Object} [options={}]
  2927. * Options for customizing the mixin behavior.
  2928. *
  2929. * @param {string} [options.eventBusKey]
  2930. * By default, adds a `eventBusEl_` DOM element to the target object,
  2931. * which is used as an event bus. If the target object already has a
  2932. * DOM element that should be used, pass its key here.
  2933. *
  2934. * @return {Object}
  2935. * The target object.
  2936. */
  2937. function evented(target, options) {
  2938. if (options === void 0) {
  2939. options = {};
  2940. }
  2941. var _options = options,
  2942. eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_.
  2943. if (eventBusKey) {
  2944. if (!target[eventBusKey].nodeName) {
  2945. throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element.");
  2946. }
  2947. target.eventBusEl_ = target[eventBusKey];
  2948. } else {
  2949. target.eventBusEl_ = createEl('span', {
  2950. className: 'vjs-event-bus'
  2951. });
  2952. }
  2953. assign(target, EventedMixin);
  2954. if (target.eventedCallbacks) {
  2955. target.eventedCallbacks.forEach(function (callback) {
  2956. callback();
  2957. });
  2958. } // When any evented object is disposed, it removes all its listeners.
  2959. target.on('dispose', function () {
  2960. target.off();
  2961. [target, target.el_, target.eventBusEl_].forEach(function (val) {
  2962. if (val && DomData.has(val)) {
  2963. DomData["delete"](val);
  2964. }
  2965. });
  2966. window$1.setTimeout(function () {
  2967. target.eventBusEl_ = null;
  2968. }, 0);
  2969. });
  2970. return target;
  2971. }
  2972. /**
  2973. * @file mixins/stateful.js
  2974. * @module stateful
  2975. */
  2976. /**
  2977. * Contains methods that provide statefulness to an object which is passed
  2978. * to {@link module:stateful}.
  2979. *
  2980. * @mixin StatefulMixin
  2981. */
  2982. var StatefulMixin = {
  2983. /**
  2984. * A hash containing arbitrary keys and values representing the state of
  2985. * the object.
  2986. *
  2987. * @type {Object}
  2988. */
  2989. state: {},
  2990. /**
  2991. * Set the state of an object by mutating its
  2992. * {@link module:stateful~StatefulMixin.state|state} object in place.
  2993. *
  2994. * @fires module:stateful~StatefulMixin#statechanged
  2995. * @param {Object|Function} stateUpdates
  2996. * A new set of properties to shallow-merge into the plugin state.
  2997. * Can be a plain object or a function returning a plain object.
  2998. *
  2999. * @return {Object|undefined}
  3000. * An object containing changes that occurred. If no changes
  3001. * occurred, returns `undefined`.
  3002. */
  3003. setState: function setState(stateUpdates) {
  3004. var _this = this;
  3005. // Support providing the `stateUpdates` state as a function.
  3006. if (typeof stateUpdates === 'function') {
  3007. stateUpdates = stateUpdates();
  3008. }
  3009. var changes;
  3010. each(stateUpdates, function (value, key) {
  3011. // Record the change if the value is different from what's in the
  3012. // current state.
  3013. if (_this.state[key] !== value) {
  3014. changes = changes || {};
  3015. changes[key] = {
  3016. from: _this.state[key],
  3017. to: value
  3018. };
  3019. }
  3020. _this.state[key] = value;
  3021. }); // Only trigger "statechange" if there were changes AND we have a trigger
  3022. // function. This allows us to not require that the target object be an
  3023. // evented object.
  3024. if (changes && isEvented(this)) {
  3025. /**
  3026. * An event triggered on an object that is both
  3027. * {@link module:stateful|stateful} and {@link module:evented|evented}
  3028. * indicating that its state has changed.
  3029. *
  3030. * @event module:stateful~StatefulMixin#statechanged
  3031. * @type {Object}
  3032. * @property {Object} changes
  3033. * A hash containing the properties that were changed and
  3034. * the values they were changed `from` and `to`.
  3035. */
  3036. this.trigger({
  3037. changes: changes,
  3038. type: 'statechanged'
  3039. });
  3040. }
  3041. return changes;
  3042. }
  3043. };
  3044. /**
  3045. * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
  3046. * object.
  3047. *
  3048. * If the target object is {@link module:evented|evented} and has a
  3049. * `handleStateChanged` method, that method will be automatically bound to the
  3050. * `statechanged` event on itself.
  3051. *
  3052. * @param {Object} target
  3053. * The object to be made stateful.
  3054. *
  3055. * @param {Object} [defaultState]
  3056. * A default set of properties to populate the newly-stateful object's
  3057. * `state` property.
  3058. *
  3059. * @return {Object}
  3060. * Returns the `target`.
  3061. */
  3062. function stateful(target, defaultState) {
  3063. assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state`
  3064. // added in that step.
  3065. target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists.
  3066. if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
  3067. target.on('statechanged', target.handleStateChanged);
  3068. }
  3069. return target;
  3070. }
  3071. /**
  3072. * @file string-cases.js
  3073. * @module to-lower-case
  3074. */
  3075. /**
  3076. * Lowercase the first letter of a string.
  3077. *
  3078. * @param {string} string
  3079. * String to be lowercased
  3080. *
  3081. * @return {string}
  3082. * The string with a lowercased first letter
  3083. */
  3084. var toLowerCase = function toLowerCase(string) {
  3085. if (typeof string !== 'string') {
  3086. return string;
  3087. }
  3088. return string.replace(/./, function (w) {
  3089. return w.toLowerCase();
  3090. });
  3091. };
  3092. /**
  3093. * Uppercase the first letter of a string.
  3094. *
  3095. * @param {string} string
  3096. * String to be uppercased
  3097. *
  3098. * @return {string}
  3099. * The string with an uppercased first letter
  3100. */
  3101. var toTitleCase$1 = function toTitleCase(string) {
  3102. if (typeof string !== 'string') {
  3103. return string;
  3104. }
  3105. return string.replace(/./, function (w) {
  3106. return w.toUpperCase();
  3107. });
  3108. };
  3109. /**
  3110. * Compares the TitleCase versions of the two strings for equality.
  3111. *
  3112. * @param {string} str1
  3113. * The first string to compare
  3114. *
  3115. * @param {string} str2
  3116. * The second string to compare
  3117. *
  3118. * @return {boolean}
  3119. * Whether the TitleCase versions of the strings are equal
  3120. */
  3121. var titleCaseEquals = function titleCaseEquals(str1, str2) {
  3122. return toTitleCase$1(str1) === toTitleCase$1(str2);
  3123. };
  3124. /**
  3125. * @file merge-options.js
  3126. * @module merge-options
  3127. */
  3128. /**
  3129. * Merge two objects recursively.
  3130. *
  3131. * Performs a deep merge like
  3132. * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
  3133. * plain objects (not arrays, elements, or anything else).
  3134. *
  3135. * Non-plain object values will be copied directly from the right-most
  3136. * argument.
  3137. *
  3138. * @static
  3139. * @param {Object[]} sources
  3140. * One or more objects to merge into a new object.
  3141. *
  3142. * @return {Object}
  3143. * A new object that is the merged result of all sources.
  3144. */
  3145. function mergeOptions$3() {
  3146. var result = {};
  3147. for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) {
  3148. sources[_key] = arguments[_key];
  3149. }
  3150. sources.forEach(function (source) {
  3151. if (!source) {
  3152. return;
  3153. }
  3154. each(source, function (value, key) {
  3155. if (!isPlain(value)) {
  3156. result[key] = value;
  3157. return;
  3158. }
  3159. if (!isPlain(result[key])) {
  3160. result[key] = {};
  3161. }
  3162. result[key] = mergeOptions$3(result[key], value);
  3163. });
  3164. });
  3165. return result;
  3166. }
  3167. var MapSham = /*#__PURE__*/function () {
  3168. function MapSham() {
  3169. this.map_ = {};
  3170. }
  3171. var _proto = MapSham.prototype;
  3172. _proto.has = function has(key) {
  3173. return key in this.map_;
  3174. };
  3175. _proto["delete"] = function _delete(key) {
  3176. var has = this.has(key);
  3177. delete this.map_[key];
  3178. return has;
  3179. };
  3180. _proto.set = function set(key, value) {
  3181. this.map_[key] = value;
  3182. return this;
  3183. };
  3184. _proto.forEach = function forEach(callback, thisArg) {
  3185. for (var key in this.map_) {
  3186. callback.call(thisArg, this.map_[key], key, this);
  3187. }
  3188. };
  3189. return MapSham;
  3190. }();
  3191. var Map$1 = window$1.Map ? window$1.Map : MapSham;
  3192. var SetSham = /*#__PURE__*/function () {
  3193. function SetSham() {
  3194. this.set_ = {};
  3195. }
  3196. var _proto = SetSham.prototype;
  3197. _proto.has = function has(key) {
  3198. return key in this.set_;
  3199. };
  3200. _proto["delete"] = function _delete(key) {
  3201. var has = this.has(key);
  3202. delete this.set_[key];
  3203. return has;
  3204. };
  3205. _proto.add = function add(key) {
  3206. this.set_[key] = 1;
  3207. return this;
  3208. };
  3209. _proto.forEach = function forEach(callback, thisArg) {
  3210. for (var key in this.set_) {
  3211. callback.call(thisArg, key, key, this);
  3212. }
  3213. };
  3214. return SetSham;
  3215. }();
  3216. var Set$1 = window$1.Set ? window$1.Set : SetSham;
  3217. /**
  3218. * Player Component - Base class for all UI objects
  3219. *
  3220. * @file component.js
  3221. */
  3222. /**
  3223. * Base class for all UI Components.
  3224. * Components are UI objects which represent both a javascript object and an element
  3225. * in the DOM. They can be children of other components, and can have
  3226. * children themselves.
  3227. *
  3228. * Components can also use methods from {@link EventTarget}
  3229. */
  3230. var Component$1 = /*#__PURE__*/function () {
  3231. /**
  3232. * A callback that is called when a component is ready. Does not have any
  3233. * paramters and any callback value will be ignored.
  3234. *
  3235. * @callback Component~ReadyCallback
  3236. * @this Component
  3237. */
  3238. /**
  3239. * Creates an instance of this class.
  3240. *
  3241. * @param {Player} player
  3242. * The `Player` that this class should be attached to.
  3243. *
  3244. * @param {Object} [options]
  3245. * The key/value store of component options.
  3246. *
  3247. * @param {Object[]} [options.children]
  3248. * An array of children objects to intialize this component with. Children objects have
  3249. * a name property that will be used if more than one component of the same type needs to be
  3250. * added.
  3251. *
  3252. * @param {string} [options.className]
  3253. * A class or space separated list of classes to add the component
  3254. *
  3255. * @param {Component~ReadyCallback} [ready]
  3256. * Function that gets called when the `Component` is ready.
  3257. */
  3258. function Component(player, options, ready) {
  3259. var _this = this;
  3260. // The component might be the player itself and we can't pass `this` to super
  3261. if (!player && this.play) {
  3262. this.player_ = player = this; // eslint-disable-line
  3263. } else {
  3264. this.player_ = player;
  3265. }
  3266. this.isDisposed_ = false; // Hold the reference to the parent component via `addChild` method
  3267. this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults
  3268. this.options_ = mergeOptions$3({}, this.options_); // Updated options with supplied options
  3269. options = this.options_ = mergeOptions$3(this.options_, options); // Get ID from options or options element if one is supplied
  3270. this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one
  3271. if (!this.id_) {
  3272. // Don't require the player ID function in the case of mock players
  3273. var id = player && player.id && player.id() || 'no_player';
  3274. this.id_ = id + "_component_" + newGUID();
  3275. }
  3276. this.name_ = options.name || null; // Create element if one wasn't provided in options
  3277. if (options.el) {
  3278. this.el_ = options.el;
  3279. } else if (options.createEl !== false) {
  3280. this.el_ = this.createEl();
  3281. }
  3282. if (options.className && this.el_) {
  3283. options.className.split(' ').forEach(function (c) {
  3284. return _this.addClass(c);
  3285. });
  3286. } // if evented is anything except false, we want to mixin in evented
  3287. if (options.evented !== false) {
  3288. // Make this an evented object and use `el_`, if available, as its event bus
  3289. evented(this, {
  3290. eventBusKey: this.el_ ? 'el_' : null
  3291. });
  3292. this.handleLanguagechange = this.handleLanguagechange.bind(this);
  3293. this.on(this.player_, 'languagechange', this.handleLanguagechange);
  3294. }
  3295. stateful(this, this.constructor.defaultState);
  3296. this.children_ = [];
  3297. this.childIndex_ = {};
  3298. this.childNameIndex_ = {};
  3299. this.setTimeoutIds_ = new Set$1();
  3300. this.setIntervalIds_ = new Set$1();
  3301. this.rafIds_ = new Set$1();
  3302. this.namedRafs_ = new Map$1();
  3303. this.clearingTimersOnDispose_ = false; // Add any child components in options
  3304. if (options.initChildren !== false) {
  3305. this.initChildren();
  3306. } // Don't want to trigger ready here or it will go before init is actually
  3307. // finished for all children that run this constructor
  3308. this.ready(ready);
  3309. if (options.reportTouchActivity !== false) {
  3310. this.enableTouchActivity();
  3311. }
  3312. }
  3313. /**
  3314. * Dispose of the `Component` and all child components.
  3315. *
  3316. * @fires Component#dispose
  3317. *
  3318. * @param {Object} options
  3319. * @param {Element} options.originalEl element with which to replace player element
  3320. */
  3321. var _proto = Component.prototype;
  3322. _proto.dispose = function dispose(options) {
  3323. if (options === void 0) {
  3324. options = {};
  3325. }
  3326. // Bail out if the component has already been disposed.
  3327. if (this.isDisposed_) {
  3328. return;
  3329. }
  3330. if (this.readyQueue_) {
  3331. this.readyQueue_.length = 0;
  3332. }
  3333. /**
  3334. * Triggered when a `Component` is disposed.
  3335. *
  3336. * @event Component#dispose
  3337. * @type {EventTarget~Event}
  3338. *
  3339. * @property {boolean} [bubbles=false]
  3340. * set to false so that the dispose event does not
  3341. * bubble up
  3342. */
  3343. this.trigger({
  3344. type: 'dispose',
  3345. bubbles: false
  3346. });
  3347. this.isDisposed_ = true; // Dispose all children.
  3348. if (this.children_) {
  3349. for (var i = this.children_.length - 1; i >= 0; i--) {
  3350. if (this.children_[i].dispose) {
  3351. this.children_[i].dispose();
  3352. }
  3353. }
  3354. } // Delete child references
  3355. this.children_ = null;
  3356. this.childIndex_ = null;
  3357. this.childNameIndex_ = null;
  3358. this.parentComponent_ = null;
  3359. if (this.el_) {
  3360. // Remove element from DOM
  3361. if (this.el_.parentNode) {
  3362. if (options.restoreEl) {
  3363. this.el_.parentNode.replaceChild(options.restoreEl, this.el_);
  3364. } else {
  3365. this.el_.parentNode.removeChild(this.el_);
  3366. }
  3367. }
  3368. this.el_ = null;
  3369. } // remove reference to the player after disposing of the element
  3370. this.player_ = null;
  3371. }
  3372. /**
  3373. * Determine whether or not this component has been disposed.
  3374. *
  3375. * @return {boolean}
  3376. * If the component has been disposed, will be `true`. Otherwise, `false`.
  3377. */
  3378. ;
  3379. _proto.isDisposed = function isDisposed() {
  3380. return Boolean(this.isDisposed_);
  3381. }
  3382. /**
  3383. * Return the {@link Player} that the `Component` has attached to.
  3384. *
  3385. * @return {Player}
  3386. * The player that this `Component` has attached to.
  3387. */
  3388. ;
  3389. _proto.player = function player() {
  3390. return this.player_;
  3391. }
  3392. /**
  3393. * Deep merge of options objects with new options.
  3394. * > Note: When both `obj` and `options` contain properties whose values are objects.
  3395. * The two properties get merged using {@link module:mergeOptions}
  3396. *
  3397. * @param {Object} obj
  3398. * The object that contains new options.
  3399. *
  3400. * @return {Object}
  3401. * A new object of `this.options_` and `obj` merged together.
  3402. */
  3403. ;
  3404. _proto.options = function options(obj) {
  3405. if (!obj) {
  3406. return this.options_;
  3407. }
  3408. this.options_ = mergeOptions$3(this.options_, obj);
  3409. return this.options_;
  3410. }
  3411. /**
  3412. * Get the `Component`s DOM element
  3413. *
  3414. * @return {Element}
  3415. * The DOM element for this `Component`.
  3416. */
  3417. ;
  3418. _proto.el = function el() {
  3419. return this.el_;
  3420. }
  3421. /**
  3422. * Create the `Component`s DOM element.
  3423. *
  3424. * @param {string} [tagName]
  3425. * Element's DOM node type. e.g. 'div'
  3426. *
  3427. * @param {Object} [properties]
  3428. * An object of properties that should be set.
  3429. *
  3430. * @param {Object} [attributes]
  3431. * An object of attributes that should be set.
  3432. *
  3433. * @return {Element}
  3434. * The element that gets created.
  3435. */
  3436. ;
  3437. _proto.createEl = function createEl$1(tagName, properties, attributes) {
  3438. return createEl(tagName, properties, attributes);
  3439. }
  3440. /**
  3441. * Localize a string given the string in english.
  3442. *
  3443. * If tokens are provided, it'll try and run a simple token replacement on the provided string.
  3444. * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
  3445. *
  3446. * If a `defaultValue` is provided, it'll use that over `string`,
  3447. * if a value isn't found in provided language files.
  3448. * This is useful if you want to have a descriptive key for token replacement
  3449. * but have a succinct localized string and not require `en.json` to be included.
  3450. *
  3451. * Currently, it is used for the progress bar timing.
  3452. * ```js
  3453. * {
  3454. * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
  3455. * }
  3456. * ```
  3457. * It is then used like so:
  3458. * ```js
  3459. * this.localize('progress bar timing: currentTime={1} duration{2}',
  3460. * [this.player_.currentTime(), this.player_.duration()],
  3461. * '{1} of {2}');
  3462. * ```
  3463. *
  3464. * Which outputs something like: `01:23 of 24:56`.
  3465. *
  3466. *
  3467. * @param {string} string
  3468. * The string to localize and the key to lookup in the language files.
  3469. * @param {string[]} [tokens]
  3470. * If the current item has token replacements, provide the tokens here.
  3471. * @param {string} [defaultValue]
  3472. * Defaults to `string`. Can be a default value to use for token replacement
  3473. * if the lookup key is needed to be separate.
  3474. *
  3475. * @return {string}
  3476. * The localized string or if no localization exists the english string.
  3477. */
  3478. ;
  3479. _proto.localize = function localize(string, tokens, defaultValue) {
  3480. if (defaultValue === void 0) {
  3481. defaultValue = string;
  3482. }
  3483. var code = this.player_.language && this.player_.language();
  3484. var languages = this.player_.languages && this.player_.languages();
  3485. var language = languages && languages[code];
  3486. var primaryCode = code && code.split('-')[0];
  3487. var primaryLang = languages && languages[primaryCode];
  3488. var localizedString = defaultValue;
  3489. if (language && language[string]) {
  3490. localizedString = language[string];
  3491. } else if (primaryLang && primaryLang[string]) {
  3492. localizedString = primaryLang[string];
  3493. }
  3494. if (tokens) {
  3495. localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
  3496. var value = tokens[index - 1];
  3497. var ret = value;
  3498. if (typeof value === 'undefined') {
  3499. ret = match;
  3500. }
  3501. return ret;
  3502. });
  3503. }
  3504. return localizedString;
  3505. }
  3506. /**
  3507. * Handles language change for the player in components. Should be overriden by sub-components.
  3508. *
  3509. * @abstract
  3510. */
  3511. ;
  3512. _proto.handleLanguagechange = function handleLanguagechange() {}
  3513. /**
  3514. * Return the `Component`s DOM element. This is where children get inserted.
  3515. * This will usually be the the same as the element returned in {@link Component#el}.
  3516. *
  3517. * @return {Element}
  3518. * The content element for this `Component`.
  3519. */
  3520. ;
  3521. _proto.contentEl = function contentEl() {
  3522. return this.contentEl_ || this.el_;
  3523. }
  3524. /**
  3525. * Get this `Component`s ID
  3526. *
  3527. * @return {string}
  3528. * The id of this `Component`
  3529. */
  3530. ;
  3531. _proto.id = function id() {
  3532. return this.id_;
  3533. }
  3534. /**
  3535. * Get the `Component`s name. The name gets used to reference the `Component`
  3536. * and is set during registration.
  3537. *
  3538. * @return {string}
  3539. * The name of this `Component`.
  3540. */
  3541. ;
  3542. _proto.name = function name() {
  3543. return this.name_;
  3544. }
  3545. /**
  3546. * Get an array of all child components
  3547. *
  3548. * @return {Array}
  3549. * The children
  3550. */
  3551. ;
  3552. _proto.children = function children() {
  3553. return this.children_;
  3554. }
  3555. /**
  3556. * Returns the child `Component` with the given `id`.
  3557. *
  3558. * @param {string} id
  3559. * The id of the child `Component` to get.
  3560. *
  3561. * @return {Component|undefined}
  3562. * The child `Component` with the given `id` or undefined.
  3563. */
  3564. ;
  3565. _proto.getChildById = function getChildById(id) {
  3566. return this.childIndex_[id];
  3567. }
  3568. /**
  3569. * Returns the child `Component` with the given `name`.
  3570. *
  3571. * @param {string} name
  3572. * The name of the child `Component` to get.
  3573. *
  3574. * @return {Component|undefined}
  3575. * The child `Component` with the given `name` or undefined.
  3576. */
  3577. ;
  3578. _proto.getChild = function getChild(name) {
  3579. if (!name) {
  3580. return;
  3581. }
  3582. return this.childNameIndex_[name];
  3583. }
  3584. /**
  3585. * Returns the descendant `Component` following the givent
  3586. * descendant `names`. For instance ['foo', 'bar', 'baz'] would
  3587. * try to get 'foo' on the current component, 'bar' on the 'foo'
  3588. * component and 'baz' on the 'bar' component and return undefined
  3589. * if any of those don't exist.
  3590. *
  3591. * @param {...string[]|...string} names
  3592. * The name of the child `Component` to get.
  3593. *
  3594. * @return {Component|undefined}
  3595. * The descendant `Component` following the given descendant
  3596. * `names` or undefined.
  3597. */
  3598. ;
  3599. _proto.getDescendant = function getDescendant() {
  3600. for (var _len = arguments.length, names = new Array(_len), _key = 0; _key < _len; _key++) {
  3601. names[_key] = arguments[_key];
  3602. }
  3603. // flatten array argument into the main array
  3604. names = names.reduce(function (acc, n) {
  3605. return acc.concat(n);
  3606. }, []);
  3607. var currentChild = this;
  3608. for (var i = 0; i < names.length; i++) {
  3609. currentChild = currentChild.getChild(names[i]);
  3610. if (!currentChild || !currentChild.getChild) {
  3611. return;
  3612. }
  3613. }
  3614. return currentChild;
  3615. }
  3616. /**
  3617. * Add a child `Component` inside the current `Component`.
  3618. *
  3619. *
  3620. * @param {string|Component} child
  3621. * The name or instance of a child to add.
  3622. *
  3623. * @param {Object} [options={}]
  3624. * The key/value store of options that will get passed to children of
  3625. * the child.
  3626. *
  3627. * @param {number} [index=this.children_.length]
  3628. * The index to attempt to add a child into.
  3629. *
  3630. * @return {Component}
  3631. * The `Component` that gets added as a child. When using a string the
  3632. * `Component` will get created by this process.
  3633. */
  3634. ;
  3635. _proto.addChild = function addChild(child, options, index) {
  3636. if (options === void 0) {
  3637. options = {};
  3638. }
  3639. if (index === void 0) {
  3640. index = this.children_.length;
  3641. }
  3642. var component;
  3643. var componentName; // If child is a string, create component with options
  3644. if (typeof child === 'string') {
  3645. componentName = toTitleCase$1(child);
  3646. var componentClassName = options.componentClass || componentName; // Set name through options
  3647. options.name = componentName; // Create a new object & element for this controls set
  3648. // If there's no .player_, this is a player
  3649. var ComponentClass = Component.getComponent(componentClassName);
  3650. if (!ComponentClass) {
  3651. throw new Error("Component " + componentClassName + " does not exist");
  3652. } // data stored directly on the videojs object may be
  3653. // misidentified as a component to retain
  3654. // backwards-compatibility with 4.x. check to make sure the
  3655. // component class can be instantiated.
  3656. if (typeof ComponentClass !== 'function') {
  3657. return null;
  3658. }
  3659. component = new ComponentClass(this.player_ || this, options); // child is a component instance
  3660. } else {
  3661. component = child;
  3662. }
  3663. if (component.parentComponent_) {
  3664. component.parentComponent_.removeChild(component);
  3665. }
  3666. this.children_.splice(index, 0, component);
  3667. component.parentComponent_ = this;
  3668. if (typeof component.id === 'function') {
  3669. this.childIndex_[component.id()] = component;
  3670. } // If a name wasn't used to create the component, check if we can use the
  3671. // name function of the component
  3672. componentName = componentName || component.name && toTitleCase$1(component.name());
  3673. if (componentName) {
  3674. this.childNameIndex_[componentName] = component;
  3675. this.childNameIndex_[toLowerCase(componentName)] = component;
  3676. } // Add the UI object's element to the container div (box)
  3677. // Having an element is not required
  3678. if (typeof component.el === 'function' && component.el()) {
  3679. // If inserting before a component, insert before that component's element
  3680. var refNode = null;
  3681. if (this.children_[index + 1]) {
  3682. // Most children are components, but the video tech is an HTML element
  3683. if (this.children_[index + 1].el_) {
  3684. refNode = this.children_[index + 1].el_;
  3685. } else if (isEl(this.children_[index + 1])) {
  3686. refNode = this.children_[index + 1];
  3687. }
  3688. }
  3689. this.contentEl().insertBefore(component.el(), refNode);
  3690. } // Return so it can stored on parent object if desired.
  3691. return component;
  3692. }
  3693. /**
  3694. * Remove a child `Component` from this `Component`s list of children. Also removes
  3695. * the child `Component`s element from this `Component`s element.
  3696. *
  3697. * @param {Component} component
  3698. * The child `Component` to remove.
  3699. */
  3700. ;
  3701. _proto.removeChild = function removeChild(component) {
  3702. if (typeof component === 'string') {
  3703. component = this.getChild(component);
  3704. }
  3705. if (!component || !this.children_) {
  3706. return;
  3707. }
  3708. var childFound = false;
  3709. for (var i = this.children_.length - 1; i >= 0; i--) {
  3710. if (this.children_[i] === component) {
  3711. childFound = true;
  3712. this.children_.splice(i, 1);
  3713. break;
  3714. }
  3715. }
  3716. if (!childFound) {
  3717. return;
  3718. }
  3719. component.parentComponent_ = null;
  3720. this.childIndex_[component.id()] = null;
  3721. this.childNameIndex_[toTitleCase$1(component.name())] = null;
  3722. this.childNameIndex_[toLowerCase(component.name())] = null;
  3723. var compEl = component.el();
  3724. if (compEl && compEl.parentNode === this.contentEl()) {
  3725. this.contentEl().removeChild(component.el());
  3726. }
  3727. }
  3728. /**
  3729. * Add and initialize default child `Component`s based upon options.
  3730. */
  3731. ;
  3732. _proto.initChildren = function initChildren() {
  3733. var _this2 = this;
  3734. var children = this.options_.children;
  3735. if (children) {
  3736. // `this` is `parent`
  3737. var parentOptions = this.options_;
  3738. var handleAdd = function handleAdd(child) {
  3739. var name = child.name;
  3740. var opts = child.opts; // Allow options for children to be set at the parent options
  3741. // e.g. videojs(id, { controlBar: false });
  3742. // instead of videojs(id, { children: { controlBar: false });
  3743. if (parentOptions[name] !== undefined) {
  3744. opts = parentOptions[name];
  3745. } // Allow for disabling default components
  3746. // e.g. options['children']['posterImage'] = false
  3747. if (opts === false) {
  3748. return;
  3749. } // Allow options to be passed as a simple boolean if no configuration
  3750. // is necessary.
  3751. if (opts === true) {
  3752. opts = {};
  3753. } // We also want to pass the original player options
  3754. // to each component as well so they don't need to
  3755. // reach back into the player for options later.
  3756. opts.playerOptions = _this2.options_.playerOptions; // Create and add the child component.
  3757. // Add a direct reference to the child by name on the parent instance.
  3758. // If two of the same component are used, different names should be supplied
  3759. // for each
  3760. var newChild = _this2.addChild(name, opts);
  3761. if (newChild) {
  3762. _this2[name] = newChild;
  3763. }
  3764. }; // Allow for an array of children details to passed in the options
  3765. var workingChildren;
  3766. var Tech = Component.getComponent('Tech');
  3767. if (Array.isArray(children)) {
  3768. workingChildren = children;
  3769. } else {
  3770. workingChildren = Object.keys(children);
  3771. }
  3772. workingChildren // children that are in this.options_ but also in workingChildren would
  3773. // give us extra children we do not want. So, we want to filter them out.
  3774. .concat(Object.keys(this.options_).filter(function (child) {
  3775. return !workingChildren.some(function (wchild) {
  3776. if (typeof wchild === 'string') {
  3777. return child === wchild;
  3778. }
  3779. return child === wchild.name;
  3780. });
  3781. })).map(function (child) {
  3782. var name;
  3783. var opts;
  3784. if (typeof child === 'string') {
  3785. name = child;
  3786. opts = children[name] || _this2.options_[name] || {};
  3787. } else {
  3788. name = child.name;
  3789. opts = child;
  3790. }
  3791. return {
  3792. name: name,
  3793. opts: opts
  3794. };
  3795. }).filter(function (child) {
  3796. // we have to make sure that child.name isn't in the techOrder since
  3797. // techs are registerd as Components but can't aren't compatible
  3798. // See https://github.com/videojs/video.js/issues/2772
  3799. var c = Component.getComponent(child.opts.componentClass || toTitleCase$1(child.name));
  3800. return c && !Tech.isTech(c);
  3801. }).forEach(handleAdd);
  3802. }
  3803. }
  3804. /**
  3805. * Builds the default DOM class name. Should be overriden by sub-components.
  3806. *
  3807. * @return {string}
  3808. * The DOM class name for this object.
  3809. *
  3810. * @abstract
  3811. */
  3812. ;
  3813. _proto.buildCSSClass = function buildCSSClass() {
  3814. // Child classes can include a function that does:
  3815. // return 'CLASS NAME' + this._super();
  3816. return '';
  3817. }
  3818. /**
  3819. * Bind a listener to the component's ready state.
  3820. * Different from event listeners in that if the ready event has already happened
  3821. * it will trigger the function immediately.
  3822. *
  3823. * @return {Component}
  3824. * Returns itself; method can be chained.
  3825. */
  3826. ;
  3827. _proto.ready = function ready(fn, sync) {
  3828. if (sync === void 0) {
  3829. sync = false;
  3830. }
  3831. if (!fn) {
  3832. return;
  3833. }
  3834. if (!this.isReady_) {
  3835. this.readyQueue_ = this.readyQueue_ || [];
  3836. this.readyQueue_.push(fn);
  3837. return;
  3838. }
  3839. if (sync) {
  3840. fn.call(this);
  3841. } else {
  3842. // Call the function asynchronously by default for consistency
  3843. this.setTimeout(fn, 1);
  3844. }
  3845. }
  3846. /**
  3847. * Trigger all the ready listeners for this `Component`.
  3848. *
  3849. * @fires Component#ready
  3850. */
  3851. ;
  3852. _proto.triggerReady = function triggerReady() {
  3853. this.isReady_ = true; // Ensure ready is triggered asynchronously
  3854. this.setTimeout(function () {
  3855. var readyQueue = this.readyQueue_; // Reset Ready Queue
  3856. this.readyQueue_ = [];
  3857. if (readyQueue && readyQueue.length > 0) {
  3858. readyQueue.forEach(function (fn) {
  3859. fn.call(this);
  3860. }, this);
  3861. } // Allow for using event listeners also
  3862. /**
  3863. * Triggered when a `Component` is ready.
  3864. *
  3865. * @event Component#ready
  3866. * @type {EventTarget~Event}
  3867. */
  3868. this.trigger('ready');
  3869. }, 1);
  3870. }
  3871. /**
  3872. * Find a single DOM element matching a `selector`. This can be within the `Component`s
  3873. * `contentEl()` or another custom context.
  3874. *
  3875. * @param {string} selector
  3876. * A valid CSS selector, which will be passed to `querySelector`.
  3877. *
  3878. * @param {Element|string} [context=this.contentEl()]
  3879. * A DOM element within which to query. Can also be a selector string in
  3880. * which case the first matching element will get used as context. If
  3881. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3882. * nothing it falls back to `document`.
  3883. *
  3884. * @return {Element|null}
  3885. * the dom element that was found, or null
  3886. *
  3887. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3888. */
  3889. ;
  3890. _proto.$ = function $$1(selector, context) {
  3891. return $(selector, context || this.contentEl());
  3892. }
  3893. /**
  3894. * Finds all DOM element matching a `selector`. This can be within the `Component`s
  3895. * `contentEl()` or another custom context.
  3896. *
  3897. * @param {string} selector
  3898. * A valid CSS selector, which will be passed to `querySelectorAll`.
  3899. *
  3900. * @param {Element|string} [context=this.contentEl()]
  3901. * A DOM element within which to query. Can also be a selector string in
  3902. * which case the first matching element will get used as context. If
  3903. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3904. * nothing it falls back to `document`.
  3905. *
  3906. * @return {NodeList}
  3907. * a list of dom elements that were found
  3908. *
  3909. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3910. */
  3911. ;
  3912. _proto.$$ = function $$$1(selector, context) {
  3913. return $$(selector, context || this.contentEl());
  3914. }
  3915. /**
  3916. * Check if a component's element has a CSS class name.
  3917. *
  3918. * @param {string} classToCheck
  3919. * CSS class name to check.
  3920. *
  3921. * @return {boolean}
  3922. * - True if the `Component` has the class.
  3923. * - False if the `Component` does not have the class`
  3924. */
  3925. ;
  3926. _proto.hasClass = function hasClass$1(classToCheck) {
  3927. return hasClass(this.el_, classToCheck);
  3928. }
  3929. /**
  3930. * Add a CSS class name to the `Component`s element.
  3931. *
  3932. * @param {string} classToAdd
  3933. * CSS class name to add
  3934. */
  3935. ;
  3936. _proto.addClass = function addClass$1(classToAdd) {
  3937. addClass(this.el_, classToAdd);
  3938. }
  3939. /**
  3940. * Remove a CSS class name from the `Component`s element.
  3941. *
  3942. * @param {string} classToRemove
  3943. * CSS class name to remove
  3944. */
  3945. ;
  3946. _proto.removeClass = function removeClass$1(classToRemove) {
  3947. removeClass(this.el_, classToRemove);
  3948. }
  3949. /**
  3950. * Add or remove a CSS class name from the component's element.
  3951. * - `classToToggle` gets added when {@link Component#hasClass} would return false.
  3952. * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
  3953. *
  3954. * @param {string} classToToggle
  3955. * The class to add or remove based on (@link Component#hasClass}
  3956. *
  3957. * @param {boolean|Dom~predicate} [predicate]
  3958. * An {@link Dom~predicate} function or a boolean
  3959. */
  3960. ;
  3961. _proto.toggleClass = function toggleClass$1(classToToggle, predicate) {
  3962. toggleClass(this.el_, classToToggle, predicate);
  3963. }
  3964. /**
  3965. * Show the `Component`s element if it is hidden by removing the
  3966. * 'vjs-hidden' class name from it.
  3967. */
  3968. ;
  3969. _proto.show = function show() {
  3970. this.removeClass('vjs-hidden');
  3971. }
  3972. /**
  3973. * Hide the `Component`s element if it is currently showing by adding the
  3974. * 'vjs-hidden` class name to it.
  3975. */
  3976. ;
  3977. _proto.hide = function hide() {
  3978. this.addClass('vjs-hidden');
  3979. }
  3980. /**
  3981. * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
  3982. * class name to it. Used during fadeIn/fadeOut.
  3983. *
  3984. * @private
  3985. */
  3986. ;
  3987. _proto.lockShowing = function lockShowing() {
  3988. this.addClass('vjs-lock-showing');
  3989. }
  3990. /**
  3991. * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
  3992. * class name from it. Used during fadeIn/fadeOut.
  3993. *
  3994. * @private
  3995. */
  3996. ;
  3997. _proto.unlockShowing = function unlockShowing() {
  3998. this.removeClass('vjs-lock-showing');
  3999. }
  4000. /**
  4001. * Get the value of an attribute on the `Component`s element.
  4002. *
  4003. * @param {string} attribute
  4004. * Name of the attribute to get the value from.
  4005. *
  4006. * @return {string|null}
  4007. * - The value of the attribute that was asked for.
  4008. * - Can be an empty string on some browsers if the attribute does not exist
  4009. * or has no value
  4010. * - Most browsers will return null if the attibute does not exist or has
  4011. * no value.
  4012. *
  4013. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
  4014. */
  4015. ;
  4016. _proto.getAttribute = function getAttribute$1(attribute) {
  4017. return getAttribute(this.el_, attribute);
  4018. }
  4019. /**
  4020. * Set the value of an attribute on the `Component`'s element
  4021. *
  4022. * @param {string} attribute
  4023. * Name of the attribute to set.
  4024. *
  4025. * @param {string} value
  4026. * Value to set the attribute to.
  4027. *
  4028. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
  4029. */
  4030. ;
  4031. _proto.setAttribute = function setAttribute$1(attribute, value) {
  4032. setAttribute(this.el_, attribute, value);
  4033. }
  4034. /**
  4035. * Remove an attribute from the `Component`s element.
  4036. *
  4037. * @param {string} attribute
  4038. * Name of the attribute to remove.
  4039. *
  4040. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
  4041. */
  4042. ;
  4043. _proto.removeAttribute = function removeAttribute$1(attribute) {
  4044. removeAttribute(this.el_, attribute);
  4045. }
  4046. /**
  4047. * Get or set the width of the component based upon the CSS styles.
  4048. * See {@link Component#dimension} for more detailed information.
  4049. *
  4050. * @param {number|string} [num]
  4051. * The width that you want to set postfixed with '%', 'px' or nothing.
  4052. *
  4053. * @param {boolean} [skipListeners]
  4054. * Skip the componentresize event trigger
  4055. *
  4056. * @return {number|string}
  4057. * The width when getting, zero if there is no width. Can be a string
  4058. * postpixed with '%' or 'px'.
  4059. */
  4060. ;
  4061. _proto.width = function width(num, skipListeners) {
  4062. return this.dimension('width', num, skipListeners);
  4063. }
  4064. /**
  4065. * Get or set the height of the component based upon the CSS styles.
  4066. * See {@link Component#dimension} for more detailed information.
  4067. *
  4068. * @param {number|string} [num]
  4069. * The height that you want to set postfixed with '%', 'px' or nothing.
  4070. *
  4071. * @param {boolean} [skipListeners]
  4072. * Skip the componentresize event trigger
  4073. *
  4074. * @return {number|string}
  4075. * The width when getting, zero if there is no width. Can be a string
  4076. * postpixed with '%' or 'px'.
  4077. */
  4078. ;
  4079. _proto.height = function height(num, skipListeners) {
  4080. return this.dimension('height', num, skipListeners);
  4081. }
  4082. /**
  4083. * Set both the width and height of the `Component` element at the same time.
  4084. *
  4085. * @param {number|string} width
  4086. * Width to set the `Component`s element to.
  4087. *
  4088. * @param {number|string} height
  4089. * Height to set the `Component`s element to.
  4090. */
  4091. ;
  4092. _proto.dimensions = function dimensions(width, height) {
  4093. // Skip componentresize listeners on width for optimization
  4094. this.width(width, true);
  4095. this.height(height);
  4096. }
  4097. /**
  4098. * Get or set width or height of the `Component` element. This is the shared code
  4099. * for the {@link Component#width} and {@link Component#height}.
  4100. *
  4101. * Things to know:
  4102. * - If the width or height in an number this will return the number postfixed with 'px'.
  4103. * - If the width/height is a percent this will return the percent postfixed with '%'
  4104. * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
  4105. * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
  4106. * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
  4107. * for more information
  4108. * - If you want the computed style of the component, use {@link Component#currentWidth}
  4109. * and {@link {Component#currentHeight}
  4110. *
  4111. * @fires Component#componentresize
  4112. *
  4113. * @param {string} widthOrHeight
  4114. 8 'width' or 'height'
  4115. *
  4116. * @param {number|string} [num]
  4117. 8 New dimension
  4118. *
  4119. * @param {boolean} [skipListeners]
  4120. * Skip componentresize event trigger
  4121. *
  4122. * @return {number}
  4123. * The dimension when getting or 0 if unset
  4124. */
  4125. ;
  4126. _proto.dimension = function dimension(widthOrHeight, num, skipListeners) {
  4127. if (num !== undefined) {
  4128. // Set to zero if null or literally NaN (NaN !== NaN)
  4129. if (num === null || num !== num) {
  4130. num = 0;
  4131. } // Check if using css width/height (% or px) and adjust
  4132. if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
  4133. this.el_.style[widthOrHeight] = num;
  4134. } else if (num === 'auto') {
  4135. this.el_.style[widthOrHeight] = '';
  4136. } else {
  4137. this.el_.style[widthOrHeight] = num + 'px';
  4138. } // skipListeners allows us to avoid triggering the resize event when setting both width and height
  4139. if (!skipListeners) {
  4140. /**
  4141. * Triggered when a component is resized.
  4142. *
  4143. * @event Component#componentresize
  4144. * @type {EventTarget~Event}
  4145. */
  4146. this.trigger('componentresize');
  4147. }
  4148. return;
  4149. } // Not setting a value, so getting it
  4150. // Make sure element exists
  4151. if (!this.el_) {
  4152. return 0;
  4153. } // Get dimension value from style
  4154. var val = this.el_.style[widthOrHeight];
  4155. var pxIndex = val.indexOf('px');
  4156. if (pxIndex !== -1) {
  4157. // Return the pixel value with no 'px'
  4158. return parseInt(val.slice(0, pxIndex), 10);
  4159. } // No px so using % or no style was set, so falling back to offsetWidth/height
  4160. // If component has display:none, offset will return 0
  4161. // TODO: handle display:none and no dimension style using px
  4162. return parseInt(this.el_['offset' + toTitleCase$1(widthOrHeight)], 10);
  4163. }
  4164. /**
  4165. * Get the computed width or the height of the component's element.
  4166. *
  4167. * Uses `window.getComputedStyle`.
  4168. *
  4169. * @param {string} widthOrHeight
  4170. * A string containing 'width' or 'height'. Whichever one you want to get.
  4171. *
  4172. * @return {number}
  4173. * The dimension that gets asked for or 0 if nothing was set
  4174. * for that dimension.
  4175. */
  4176. ;
  4177. _proto.currentDimension = function currentDimension(widthOrHeight) {
  4178. var computedWidthOrHeight = 0;
  4179. if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
  4180. throw new Error('currentDimension only accepts width or height value');
  4181. }
  4182. computedWidthOrHeight = computedStyle(this.el_, widthOrHeight); // remove 'px' from variable and parse as integer
  4183. computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying
  4184. // and we want to check the offset values.
  4185. // This code also runs wherever getComputedStyle doesn't exist.
  4186. if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) {
  4187. var rule = "offset" + toTitleCase$1(widthOrHeight);
  4188. computedWidthOrHeight = this.el_[rule];
  4189. }
  4190. return computedWidthOrHeight;
  4191. }
  4192. /**
  4193. * An object that contains width and height values of the `Component`s
  4194. * computed style. Uses `window.getComputedStyle`.
  4195. *
  4196. * @typedef {Object} Component~DimensionObject
  4197. *
  4198. * @property {number} width
  4199. * The width of the `Component`s computed style.
  4200. *
  4201. * @property {number} height
  4202. * The height of the `Component`s computed style.
  4203. */
  4204. /**
  4205. * Get an object that contains computed width and height values of the
  4206. * component's element.
  4207. *
  4208. * Uses `window.getComputedStyle`.
  4209. *
  4210. * @return {Component~DimensionObject}
  4211. * The computed dimensions of the component's element.
  4212. */
  4213. ;
  4214. _proto.currentDimensions = function currentDimensions() {
  4215. return {
  4216. width: this.currentDimension('width'),
  4217. height: this.currentDimension('height')
  4218. };
  4219. }
  4220. /**
  4221. * Get the computed width of the component's element.
  4222. *
  4223. * Uses `window.getComputedStyle`.
  4224. *
  4225. * @return {number}
  4226. * The computed width of the component's element.
  4227. */
  4228. ;
  4229. _proto.currentWidth = function currentWidth() {
  4230. return this.currentDimension('width');
  4231. }
  4232. /**
  4233. * Get the computed height of the component's element.
  4234. *
  4235. * Uses `window.getComputedStyle`.
  4236. *
  4237. * @return {number}
  4238. * The computed height of the component's element.
  4239. */
  4240. ;
  4241. _proto.currentHeight = function currentHeight() {
  4242. return this.currentDimension('height');
  4243. }
  4244. /**
  4245. * Set the focus to this component
  4246. */
  4247. ;
  4248. _proto.focus = function focus() {
  4249. this.el_.focus();
  4250. }
  4251. /**
  4252. * Remove the focus from this component
  4253. */
  4254. ;
  4255. _proto.blur = function blur() {
  4256. this.el_.blur();
  4257. }
  4258. /**
  4259. * When this Component receives a `keydown` event which it does not process,
  4260. * it passes the event to the Player for handling.
  4261. *
  4262. * @param {EventTarget~Event} event
  4263. * The `keydown` event that caused this function to be called.
  4264. */
  4265. ;
  4266. _proto.handleKeyDown = function handleKeyDown(event) {
  4267. if (this.player_) {
  4268. // We only stop propagation here because we want unhandled events to fall
  4269. // back to the browser. Exclude Tab for focus trapping.
  4270. if (!keycode.isEventKey(event, 'Tab')) {
  4271. event.stopPropagation();
  4272. }
  4273. this.player_.handleKeyDown(event);
  4274. }
  4275. }
  4276. /**
  4277. * Many components used to have a `handleKeyPress` method, which was poorly
  4278. * named because it listened to a `keydown` event. This method name now
  4279. * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
  4280. * will not see their method calls stop working.
  4281. *
  4282. * @param {EventTarget~Event} event
  4283. * The event that caused this function to be called.
  4284. */
  4285. ;
  4286. _proto.handleKeyPress = function handleKeyPress(event) {
  4287. this.handleKeyDown(event);
  4288. }
  4289. /**
  4290. * Emit a 'tap' events when touch event support gets detected. This gets used to
  4291. * support toggling the controls through a tap on the video. They get enabled
  4292. * because every sub-component would have extra overhead otherwise.
  4293. *
  4294. * @private
  4295. * @fires Component#tap
  4296. * @listens Component#touchstart
  4297. * @listens Component#touchmove
  4298. * @listens Component#touchleave
  4299. * @listens Component#touchcancel
  4300. * @listens Component#touchend
  4301. */
  4302. ;
  4303. _proto.emitTapEvents = function emitTapEvents() {
  4304. // Track the start time so we can determine how long the touch lasted
  4305. var touchStart = 0;
  4306. var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap
  4307. // Other popular libs use anywhere from 2 (hammer.js) to 15,
  4308. // so 10 seems like a nice, round number.
  4309. var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap
  4310. var touchTimeThreshold = 200;
  4311. var couldBeTap;
  4312. this.on('touchstart', function (event) {
  4313. // If more than one finger, don't consider treating this as a click
  4314. if (event.touches.length === 1) {
  4315. // Copy pageX/pageY from the object
  4316. firstTouch = {
  4317. pageX: event.touches[0].pageX,
  4318. pageY: event.touches[0].pageY
  4319. }; // Record start time so we can detect a tap vs. "touch and hold"
  4320. touchStart = window$1.performance.now(); // Reset couldBeTap tracking
  4321. couldBeTap = true;
  4322. }
  4323. });
  4324. this.on('touchmove', function (event) {
  4325. // If more than one finger, don't consider treating this as a click
  4326. if (event.touches.length > 1) {
  4327. couldBeTap = false;
  4328. } else if (firstTouch) {
  4329. // Some devices will throw touchmoves for all but the slightest of taps.
  4330. // So, if we moved only a small distance, this could still be a tap
  4331. var xdiff = event.touches[0].pageX - firstTouch.pageX;
  4332. var ydiff = event.touches[0].pageY - firstTouch.pageY;
  4333. var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
  4334. if (touchDistance > tapMovementThreshold) {
  4335. couldBeTap = false;
  4336. }
  4337. }
  4338. });
  4339. var noTap = function noTap() {
  4340. couldBeTap = false;
  4341. }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
  4342. this.on('touchleave', noTap);
  4343. this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate
  4344. // event
  4345. this.on('touchend', function (event) {
  4346. firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen
  4347. if (couldBeTap === true) {
  4348. // Measure how long the touch lasted
  4349. var touchTime = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap
  4350. if (touchTime < touchTimeThreshold) {
  4351. // Don't let browser turn this into a click
  4352. event.preventDefault();
  4353. /**
  4354. * Triggered when a `Component` is tapped.
  4355. *
  4356. * @event Component#tap
  4357. * @type {EventTarget~Event}
  4358. */
  4359. this.trigger('tap'); // It may be good to copy the touchend event object and change the
  4360. // type to tap, if the other event properties aren't exact after
  4361. // Events.fixEvent runs (e.g. event.target)
  4362. }
  4363. }
  4364. });
  4365. }
  4366. /**
  4367. * This function reports user activity whenever touch events happen. This can get
  4368. * turned off by any sub-components that wants touch events to act another way.
  4369. *
  4370. * Report user touch activity when touch events occur. User activity gets used to
  4371. * determine when controls should show/hide. It is simple when it comes to mouse
  4372. * events, because any mouse event should show the controls. So we capture mouse
  4373. * events that bubble up to the player and report activity when that happens.
  4374. * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
  4375. * controls. So touch events can't help us at the player level either.
  4376. *
  4377. * User activity gets checked asynchronously. So what could happen is a tap event
  4378. * on the video turns the controls off. Then the `touchend` event bubbles up to
  4379. * the player. Which, if it reported user activity, would turn the controls right
  4380. * back on. We also don't want to completely block touch events from bubbling up.
  4381. * Furthermore a `touchmove` event and anything other than a tap, should not turn
  4382. * controls back on.
  4383. *
  4384. * @listens Component#touchstart
  4385. * @listens Component#touchmove
  4386. * @listens Component#touchend
  4387. * @listens Component#touchcancel
  4388. */
  4389. ;
  4390. _proto.enableTouchActivity = function enableTouchActivity() {
  4391. // Don't continue if the root player doesn't support reporting user activity
  4392. if (!this.player() || !this.player().reportUserActivity) {
  4393. return;
  4394. } // listener for reporting that the user is active
  4395. var report = bind(this.player(), this.player().reportUserActivity);
  4396. var touchHolding;
  4397. this.on('touchstart', function () {
  4398. report(); // For as long as the they are touching the device or have their mouse down,
  4399. // we consider them active even if they're not moving their finger or mouse.
  4400. // So we want to continue to update that they are active
  4401. this.clearInterval(touchHolding); // report at the same interval as activityCheck
  4402. touchHolding = this.setInterval(report, 250);
  4403. });
  4404. var touchEnd = function touchEnd(event) {
  4405. report(); // stop the interval that maintains activity if the touch is holding
  4406. this.clearInterval(touchHolding);
  4407. };
  4408. this.on('touchmove', report);
  4409. this.on('touchend', touchEnd);
  4410. this.on('touchcancel', touchEnd);
  4411. }
  4412. /**
  4413. * A callback that has no parameters and is bound into `Component`s context.
  4414. *
  4415. * @callback Component~GenericCallback
  4416. * @this Component
  4417. */
  4418. /**
  4419. * Creates a function that runs after an `x` millisecond timeout. This function is a
  4420. * wrapper around `window.setTimeout`. There are a few reasons to use this one
  4421. * instead though:
  4422. * 1. It gets cleared via {@link Component#clearTimeout} when
  4423. * {@link Component#dispose} gets called.
  4424. * 2. The function callback will gets turned into a {@link Component~GenericCallback}
  4425. *
  4426. * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
  4427. * will cause its dispose listener not to get cleaned up! Please use
  4428. * {@link Component#clearTimeout} or {@link Component#dispose} instead.
  4429. *
  4430. * @param {Component~GenericCallback} fn
  4431. * The function that will be run after `timeout`.
  4432. *
  4433. * @param {number} timeout
  4434. * Timeout in milliseconds to delay before executing the specified function.
  4435. *
  4436. * @return {number}
  4437. * Returns a timeout ID that gets used to identify the timeout. It can also
  4438. * get used in {@link Component#clearTimeout} to clear the timeout that
  4439. * was set.
  4440. *
  4441. * @listens Component#dispose
  4442. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
  4443. */
  4444. ;
  4445. _proto.setTimeout = function setTimeout(fn, timeout) {
  4446. var _this3 = this;
  4447. // declare as variables so they are properly available in timeout function
  4448. // eslint-disable-next-line
  4449. var timeoutId;
  4450. fn = bind(this, fn);
  4451. this.clearTimersOnDispose_();
  4452. timeoutId = window$1.setTimeout(function () {
  4453. if (_this3.setTimeoutIds_.has(timeoutId)) {
  4454. _this3.setTimeoutIds_["delete"](timeoutId);
  4455. }
  4456. fn();
  4457. }, timeout);
  4458. this.setTimeoutIds_.add(timeoutId);
  4459. return timeoutId;
  4460. }
  4461. /**
  4462. * Clears a timeout that gets created via `window.setTimeout` or
  4463. * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
  4464. * use this function instead of `window.clearTimout`. If you don't your dispose
  4465. * listener will not get cleaned up until {@link Component#dispose}!
  4466. *
  4467. * @param {number} timeoutId
  4468. * The id of the timeout to clear. The return value of
  4469. * {@link Component#setTimeout} or `window.setTimeout`.
  4470. *
  4471. * @return {number}
  4472. * Returns the timeout id that was cleared.
  4473. *
  4474. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
  4475. */
  4476. ;
  4477. _proto.clearTimeout = function clearTimeout(timeoutId) {
  4478. if (this.setTimeoutIds_.has(timeoutId)) {
  4479. this.setTimeoutIds_["delete"](timeoutId);
  4480. window$1.clearTimeout(timeoutId);
  4481. }
  4482. return timeoutId;
  4483. }
  4484. /**
  4485. * Creates a function that gets run every `x` milliseconds. This function is a wrapper
  4486. * around `window.setInterval`. There are a few reasons to use this one instead though.
  4487. * 1. It gets cleared via {@link Component#clearInterval} when
  4488. * {@link Component#dispose} gets called.
  4489. * 2. The function callback will be a {@link Component~GenericCallback}
  4490. *
  4491. * @param {Component~GenericCallback} fn
  4492. * The function to run every `x` seconds.
  4493. *
  4494. * @param {number} interval
  4495. * Execute the specified function every `x` milliseconds.
  4496. *
  4497. * @return {number}
  4498. * Returns an id that can be used to identify the interval. It can also be be used in
  4499. * {@link Component#clearInterval} to clear the interval.
  4500. *
  4501. * @listens Component#dispose
  4502. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
  4503. */
  4504. ;
  4505. _proto.setInterval = function setInterval(fn, interval) {
  4506. fn = bind(this, fn);
  4507. this.clearTimersOnDispose_();
  4508. var intervalId = window$1.setInterval(fn, interval);
  4509. this.setIntervalIds_.add(intervalId);
  4510. return intervalId;
  4511. }
  4512. /**
  4513. * Clears an interval that gets created via `window.setInterval` or
  4514. * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
  4515. * use this function instead of `window.clearInterval`. If you don't your dispose
  4516. * listener will not get cleaned up until {@link Component#dispose}!
  4517. *
  4518. * @param {number} intervalId
  4519. * The id of the interval to clear. The return value of
  4520. * {@link Component#setInterval} or `window.setInterval`.
  4521. *
  4522. * @return {number}
  4523. * Returns the interval id that was cleared.
  4524. *
  4525. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
  4526. */
  4527. ;
  4528. _proto.clearInterval = function clearInterval(intervalId) {
  4529. if (this.setIntervalIds_.has(intervalId)) {
  4530. this.setIntervalIds_["delete"](intervalId);
  4531. window$1.clearInterval(intervalId);
  4532. }
  4533. return intervalId;
  4534. }
  4535. /**
  4536. * Queues up a callback to be passed to requestAnimationFrame (rAF), but
  4537. * with a few extra bonuses:
  4538. *
  4539. * - Supports browsers that do not support rAF by falling back to
  4540. * {@link Component#setTimeout}.
  4541. *
  4542. * - The callback is turned into a {@link Component~GenericCallback} (i.e.
  4543. * bound to the component).
  4544. *
  4545. * - Automatic cancellation of the rAF callback is handled if the component
  4546. * is disposed before it is called.
  4547. *
  4548. * @param {Component~GenericCallback} fn
  4549. * A function that will be bound to this component and executed just
  4550. * before the browser's next repaint.
  4551. *
  4552. * @return {number}
  4553. * Returns an rAF ID that gets used to identify the timeout. It can
  4554. * also be used in {@link Component#cancelAnimationFrame} to cancel
  4555. * the animation frame callback.
  4556. *
  4557. * @listens Component#dispose
  4558. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
  4559. */
  4560. ;
  4561. _proto.requestAnimationFrame = function requestAnimationFrame(fn) {
  4562. var _this4 = this;
  4563. // Fall back to using a timer.
  4564. if (!this.supportsRaf_) {
  4565. return this.setTimeout(fn, 1000 / 60);
  4566. }
  4567. this.clearTimersOnDispose_(); // declare as variables so they are properly available in rAF function
  4568. // eslint-disable-next-line
  4569. var id;
  4570. fn = bind(this, fn);
  4571. id = window$1.requestAnimationFrame(function () {
  4572. if (_this4.rafIds_.has(id)) {
  4573. _this4.rafIds_["delete"](id);
  4574. }
  4575. fn();
  4576. });
  4577. this.rafIds_.add(id);
  4578. return id;
  4579. }
  4580. /**
  4581. * Request an animation frame, but only one named animation
  4582. * frame will be queued. Another will never be added until
  4583. * the previous one finishes.
  4584. *
  4585. * @param {string} name
  4586. * The name to give this requestAnimationFrame
  4587. *
  4588. * @param {Component~GenericCallback} fn
  4589. * A function that will be bound to this component and executed just
  4590. * before the browser's next repaint.
  4591. */
  4592. ;
  4593. _proto.requestNamedAnimationFrame = function requestNamedAnimationFrame(name, fn) {
  4594. var _this5 = this;
  4595. if (this.namedRafs_.has(name)) {
  4596. return;
  4597. }
  4598. this.clearTimersOnDispose_();
  4599. fn = bind(this, fn);
  4600. var id = this.requestAnimationFrame(function () {
  4601. fn();
  4602. if (_this5.namedRafs_.has(name)) {
  4603. _this5.namedRafs_["delete"](name);
  4604. }
  4605. });
  4606. this.namedRafs_.set(name, id);
  4607. return name;
  4608. }
  4609. /**
  4610. * Cancels a current named animation frame if it exists.
  4611. *
  4612. * @param {string} name
  4613. * The name of the requestAnimationFrame to cancel.
  4614. */
  4615. ;
  4616. _proto.cancelNamedAnimationFrame = function cancelNamedAnimationFrame(name) {
  4617. if (!this.namedRafs_.has(name)) {
  4618. return;
  4619. }
  4620. this.cancelAnimationFrame(this.namedRafs_.get(name));
  4621. this.namedRafs_["delete"](name);
  4622. }
  4623. /**
  4624. * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
  4625. * (rAF).
  4626. *
  4627. * If you queue an rAF callback via {@link Component#requestAnimationFrame},
  4628. * use this function instead of `window.cancelAnimationFrame`. If you don't,
  4629. * your dispose listener will not get cleaned up until {@link Component#dispose}!
  4630. *
  4631. * @param {number} id
  4632. * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
  4633. *
  4634. * @return {number}
  4635. * Returns the rAF ID that was cleared.
  4636. *
  4637. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
  4638. */
  4639. ;
  4640. _proto.cancelAnimationFrame = function cancelAnimationFrame(id) {
  4641. // Fall back to using a timer.
  4642. if (!this.supportsRaf_) {
  4643. return this.clearTimeout(id);
  4644. }
  4645. if (this.rafIds_.has(id)) {
  4646. this.rafIds_["delete"](id);
  4647. window$1.cancelAnimationFrame(id);
  4648. }
  4649. return id;
  4650. }
  4651. /**
  4652. * A function to setup `requestAnimationFrame`, `setTimeout`,
  4653. * and `setInterval`, clearing on dispose.
  4654. *
  4655. * > Previously each timer added and removed dispose listeners on it's own.
  4656. * For better performance it was decided to batch them all, and use `Set`s
  4657. * to track outstanding timer ids.
  4658. *
  4659. * @private
  4660. */
  4661. ;
  4662. _proto.clearTimersOnDispose_ = function clearTimersOnDispose_() {
  4663. var _this6 = this;
  4664. if (this.clearingTimersOnDispose_) {
  4665. return;
  4666. }
  4667. this.clearingTimersOnDispose_ = true;
  4668. this.one('dispose', function () {
  4669. [['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEach(function (_ref) {
  4670. var idName = _ref[0],
  4671. cancelName = _ref[1];
  4672. // for a `Set` key will actually be the value again
  4673. // so forEach((val, val) =>` but for maps we want to use
  4674. // the key.
  4675. _this6[idName].forEach(function (val, key) {
  4676. return _this6[cancelName](key);
  4677. });
  4678. });
  4679. _this6.clearingTimersOnDispose_ = false;
  4680. });
  4681. }
  4682. /**
  4683. * Register a `Component` with `videojs` given the name and the component.
  4684. *
  4685. * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
  4686. * should be registered using {@link Tech.registerTech} or
  4687. * {@link videojs:videojs.registerTech}.
  4688. *
  4689. * > NOTE: This function can also be seen on videojs as
  4690. * {@link videojs:videojs.registerComponent}.
  4691. *
  4692. * @param {string} name
  4693. * The name of the `Component` to register.
  4694. *
  4695. * @param {Component} ComponentToRegister
  4696. * The `Component` class to register.
  4697. *
  4698. * @return {Component}
  4699. * The `Component` that was registered.
  4700. */
  4701. ;
  4702. Component.registerComponent = function registerComponent(name, ComponentToRegister) {
  4703. if (typeof name !== 'string' || !name) {
  4704. throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string.");
  4705. }
  4706. var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered.
  4707. var isTech = Tech && Tech.isTech(ComponentToRegister);
  4708. var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
  4709. if (isTech || !isComp) {
  4710. var reason;
  4711. if (isTech) {
  4712. reason = 'techs must be registered using Tech.registerTech()';
  4713. } else {
  4714. reason = 'must be a Component subclass';
  4715. }
  4716. throw new Error("Illegal component, \"" + name + "\"; " + reason + ".");
  4717. }
  4718. name = toTitleCase$1(name);
  4719. if (!Component.components_) {
  4720. Component.components_ = {};
  4721. }
  4722. var Player = Component.getComponent('Player');
  4723. if (name === 'Player' && Player && Player.players) {
  4724. var players = Player.players;
  4725. var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be
  4726. // in Players.players. So, we must loop through and verify that the value
  4727. // for each item is not null. This allows registration of the Player component
  4728. // after all players have been disposed or before any were created.
  4729. if (players && playerNames.length > 0 && playerNames.map(function (pname) {
  4730. return players[pname];
  4731. }).every(Boolean)) {
  4732. throw new Error('Can not register Player component after player has been created.');
  4733. }
  4734. }
  4735. Component.components_[name] = ComponentToRegister;
  4736. Component.components_[toLowerCase(name)] = ComponentToRegister;
  4737. return ComponentToRegister;
  4738. }
  4739. /**
  4740. * Get a `Component` based on the name it was registered with.
  4741. *
  4742. * @param {string} name
  4743. * The Name of the component to get.
  4744. *
  4745. * @return {Component}
  4746. * The `Component` that got registered under the given name.
  4747. */
  4748. ;
  4749. Component.getComponent = function getComponent(name) {
  4750. if (!name || !Component.components_) {
  4751. return;
  4752. }
  4753. return Component.components_[name];
  4754. };
  4755. return Component;
  4756. }();
  4757. /**
  4758. * Whether or not this component supports `requestAnimationFrame`.
  4759. *
  4760. * This is exposed primarily for testing purposes.
  4761. *
  4762. * @private
  4763. * @type {Boolean}
  4764. */
  4765. Component$1.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function';
  4766. Component$1.registerComponent('Component', Component$1);
  4767. /**
  4768. * @file time-ranges.js
  4769. * @module time-ranges
  4770. */
  4771. /**
  4772. * Returns the time for the specified index at the start or end
  4773. * of a TimeRange object.
  4774. *
  4775. * @typedef {Function} TimeRangeIndex
  4776. *
  4777. * @param {number} [index=0]
  4778. * The range number to return the time for.
  4779. *
  4780. * @return {number}
  4781. * The time offset at the specified index.
  4782. *
  4783. * @deprecated The index argument must be provided.
  4784. * In the future, leaving it out will throw an error.
  4785. */
  4786. /**
  4787. * An object that contains ranges of time.
  4788. *
  4789. * @typedef {Object} TimeRange
  4790. *
  4791. * @property {number} length
  4792. * The number of time ranges represented by this object.
  4793. *
  4794. * @property {module:time-ranges~TimeRangeIndex} start
  4795. * Returns the time offset at which a specified time range begins.
  4796. *
  4797. * @property {module:time-ranges~TimeRangeIndex} end
  4798. * Returns the time offset at which a specified time range ends.
  4799. *
  4800. * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
  4801. */
  4802. /**
  4803. * Check if any of the time ranges are over the maximum index.
  4804. *
  4805. * @private
  4806. * @param {string} fnName
  4807. * The function name to use for logging
  4808. *
  4809. * @param {number} index
  4810. * The index to check
  4811. *
  4812. * @param {number} maxIndex
  4813. * The maximum possible index
  4814. *
  4815. * @throws {Error} if the timeRanges provided are over the maxIndex
  4816. */
  4817. function rangeCheck(fnName, index, maxIndex) {
  4818. if (typeof index !== 'number' || index < 0 || index > maxIndex) {
  4819. throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ").");
  4820. }
  4821. }
  4822. /**
  4823. * Get the time for the specified index at the start or end
  4824. * of a TimeRange object.
  4825. *
  4826. * @private
  4827. * @param {string} fnName
  4828. * The function name to use for logging
  4829. *
  4830. * @param {string} valueIndex
  4831. * The property that should be used to get the time. should be
  4832. * 'start' or 'end'
  4833. *
  4834. * @param {Array} ranges
  4835. * An array of time ranges
  4836. *
  4837. * @param {Array} [rangeIndex=0]
  4838. * The index to start the search at
  4839. *
  4840. * @return {number}
  4841. * The time that offset at the specified index.
  4842. *
  4843. * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
  4844. * @throws {Error} if rangeIndex is more than the length of ranges
  4845. */
  4846. function getRange(fnName, valueIndex, ranges, rangeIndex) {
  4847. rangeCheck(fnName, rangeIndex, ranges.length - 1);
  4848. return ranges[rangeIndex][valueIndex];
  4849. }
  4850. /**
  4851. * Create a time range object given ranges of time.
  4852. *
  4853. * @private
  4854. * @param {Array} [ranges]
  4855. * An array of time ranges.
  4856. */
  4857. function createTimeRangesObj(ranges) {
  4858. var timeRangesObj;
  4859. if (ranges === undefined || ranges.length === 0) {
  4860. timeRangesObj = {
  4861. length: 0,
  4862. start: function start() {
  4863. throw new Error('This TimeRanges object is empty');
  4864. },
  4865. end: function end() {
  4866. throw new Error('This TimeRanges object is empty');
  4867. }
  4868. };
  4869. } else {
  4870. timeRangesObj = {
  4871. length: ranges.length,
  4872. start: getRange.bind(null, 'start', 0, ranges),
  4873. end: getRange.bind(null, 'end', 1, ranges)
  4874. };
  4875. }
  4876. if (window$1.Symbol && window$1.Symbol.iterator) {
  4877. timeRangesObj[window$1.Symbol.iterator] = function () {
  4878. return (ranges || []).values();
  4879. };
  4880. }
  4881. return timeRangesObj;
  4882. }
  4883. /**
  4884. * Create a `TimeRange` object which mimics an
  4885. * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
  4886. *
  4887. * @param {number|Array[]} start
  4888. * The start of a single range (a number) or an array of ranges (an
  4889. * array of arrays of two numbers each).
  4890. *
  4891. * @param {number} end
  4892. * The end of a single range. Cannot be used with the array form of
  4893. * the `start` argument.
  4894. */
  4895. function createTimeRanges(start, end) {
  4896. if (Array.isArray(start)) {
  4897. return createTimeRangesObj(start);
  4898. } else if (start === undefined || end === undefined) {
  4899. return createTimeRangesObj();
  4900. }
  4901. return createTimeRangesObj([[start, end]]);
  4902. }
  4903. /**
  4904. * @file buffer.js
  4905. * @module buffer
  4906. */
  4907. /**
  4908. * Compute the percentage of the media that has been buffered.
  4909. *
  4910. * @param {TimeRange} buffered
  4911. * The current `TimeRange` object representing buffered time ranges
  4912. *
  4913. * @param {number} duration
  4914. * Total duration of the media
  4915. *
  4916. * @return {number}
  4917. * Percent buffered of the total duration in decimal form.
  4918. */
  4919. function bufferedPercent(buffered, duration) {
  4920. var bufferedDuration = 0;
  4921. var start;
  4922. var end;
  4923. if (!duration) {
  4924. return 0;
  4925. }
  4926. if (!buffered || !buffered.length) {
  4927. buffered = createTimeRanges(0, 0);
  4928. }
  4929. for (var i = 0; i < buffered.length; i++) {
  4930. start = buffered.start(i);
  4931. end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction
  4932. if (end > duration) {
  4933. end = duration;
  4934. }
  4935. bufferedDuration += end - start;
  4936. }
  4937. return bufferedDuration / duration;
  4938. }
  4939. /**
  4940. * @file media-error.js
  4941. */
  4942. /**
  4943. * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
  4944. *
  4945. * @param {number|string|Object|MediaError} value
  4946. * This can be of multiple types:
  4947. * - number: should be a standard error code
  4948. * - string: an error message (the code will be 0)
  4949. * - Object: arbitrary properties
  4950. * - `MediaError` (native): used to populate a video.js `MediaError` object
  4951. * - `MediaError` (video.js): will return itself if it's already a
  4952. * video.js `MediaError` object.
  4953. *
  4954. * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
  4955. * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
  4956. *
  4957. * @class MediaError
  4958. */
  4959. function MediaError(value) {
  4960. // Allow redundant calls to this constructor to avoid having `instanceof`
  4961. // checks peppered around the code.
  4962. if (value instanceof MediaError) {
  4963. return value;
  4964. }
  4965. if (typeof value === 'number') {
  4966. this.code = value;
  4967. } else if (typeof value === 'string') {
  4968. // default code is zero, so this is a custom error
  4969. this.message = value;
  4970. } else if (isObject(value)) {
  4971. // We assign the `code` property manually because native `MediaError` objects
  4972. // do not expose it as an own/enumerable property of the object.
  4973. if (typeof value.code === 'number') {
  4974. this.code = value.code;
  4975. }
  4976. assign(this, value);
  4977. }
  4978. if (!this.message) {
  4979. this.message = MediaError.defaultMessages[this.code] || '';
  4980. }
  4981. }
  4982. /**
  4983. * The error code that refers two one of the defined `MediaError` types
  4984. *
  4985. * @type {Number}
  4986. */
  4987. MediaError.prototype.code = 0;
  4988. /**
  4989. * An optional message that to show with the error. Message is not part of the HTML5
  4990. * video spec but allows for more informative custom errors.
  4991. *
  4992. * @type {String}
  4993. */
  4994. MediaError.prototype.message = '';
  4995. /**
  4996. * An optional status code that can be set by plugins to allow even more detail about
  4997. * the error. For example a plugin might provide a specific HTTP status code and an
  4998. * error message for that code. Then when the plugin gets that error this class will
  4999. * know how to display an error message for it. This allows a custom message to show
  5000. * up on the `Player` error overlay.
  5001. *
  5002. * @type {Array}
  5003. */
  5004. MediaError.prototype.status = null;
  5005. /**
  5006. * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
  5007. * specification listed under {@link MediaError} for more information.
  5008. *
  5009. * @enum {array}
  5010. * @readonly
  5011. * @property {string} 0 - MEDIA_ERR_CUSTOM
  5012. * @property {string} 1 - MEDIA_ERR_ABORTED
  5013. * @property {string} 2 - MEDIA_ERR_NETWORK
  5014. * @property {string} 3 - MEDIA_ERR_DECODE
  5015. * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
  5016. * @property {string} 5 - MEDIA_ERR_ENCRYPTED
  5017. */
  5018. MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
  5019. /**
  5020. * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
  5021. *
  5022. * @type {Array}
  5023. * @constant
  5024. */
  5025. MediaError.defaultMessages = {
  5026. 1: 'You aborted the media playback',
  5027. 2: 'A network error caused the media download to fail part-way.',
  5028. 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
  5029. 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
  5030. 5: 'The media is encrypted and we do not have the keys to decrypt it.'
  5031. }; // Add types as properties on MediaError
  5032. // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
  5033. for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
  5034. MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance
  5035. MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
  5036. } // jsdocs for instance/static members added above
  5037. /**
  5038. * Returns whether an object is `Promise`-like (i.e. has a `then` method).
  5039. *
  5040. * @param {Object} value
  5041. * An object that may or may not be `Promise`-like.
  5042. *
  5043. * @return {boolean}
  5044. * Whether or not the object is `Promise`-like.
  5045. */
  5046. function isPromise(value) {
  5047. return value !== undefined && value !== null && typeof value.then === 'function';
  5048. }
  5049. /**
  5050. * Silence a Promise-like object.
  5051. *
  5052. * This is useful for avoiding non-harmful, but potentially confusing "uncaught
  5053. * play promise" rejection error messages.
  5054. *
  5055. * @param {Object} value
  5056. * An object that may or may not be `Promise`-like.
  5057. */
  5058. function silencePromise(value) {
  5059. if (isPromise(value)) {
  5060. value.then(null, function (e) {});
  5061. }
  5062. }
  5063. /**
  5064. * @file text-track-list-converter.js Utilities for capturing text track state and
  5065. * re-creating tracks based on a capture.
  5066. *
  5067. * @module text-track-list-converter
  5068. */
  5069. /**
  5070. * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
  5071. * represents the {@link TextTrack}'s state.
  5072. *
  5073. * @param {TextTrack} track
  5074. * The text track to query.
  5075. *
  5076. * @return {Object}
  5077. * A serializable javascript representation of the TextTrack.
  5078. * @private
  5079. */
  5080. var trackToJson_ = function trackToJson_(track) {
  5081. var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
  5082. if (track[prop]) {
  5083. acc[prop] = track[prop];
  5084. }
  5085. return acc;
  5086. }, {
  5087. cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
  5088. return {
  5089. startTime: cue.startTime,
  5090. endTime: cue.endTime,
  5091. text: cue.text,
  5092. id: cue.id
  5093. };
  5094. })
  5095. });
  5096. return ret;
  5097. };
  5098. /**
  5099. * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
  5100. * state of all {@link TextTrack}s currently configured. The return array is compatible with
  5101. * {@link text-track-list-converter:jsonToTextTracks}.
  5102. *
  5103. * @param {Tech} tech
  5104. * The tech object to query
  5105. *
  5106. * @return {Array}
  5107. * A serializable javascript representation of the {@link Tech}s
  5108. * {@link TextTrackList}.
  5109. */
  5110. var textTracksToJson = function textTracksToJson(tech) {
  5111. var trackEls = tech.$$('track');
  5112. var trackObjs = Array.prototype.map.call(trackEls, function (t) {
  5113. return t.track;
  5114. });
  5115. var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
  5116. var json = trackToJson_(trackEl.track);
  5117. if (trackEl.src) {
  5118. json.src = trackEl.src;
  5119. }
  5120. return json;
  5121. });
  5122. return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
  5123. return trackObjs.indexOf(track) === -1;
  5124. }).map(trackToJson_));
  5125. };
  5126. /**
  5127. * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
  5128. * object {@link TextTrack} representations.
  5129. *
  5130. * @param {Array} json
  5131. * An array of `TextTrack` representation objects, like those that would be
  5132. * produced by `textTracksToJson`.
  5133. *
  5134. * @param {Tech} tech
  5135. * The `Tech` to create the `TextTrack`s on.
  5136. */
  5137. var jsonToTextTracks = function jsonToTextTracks(json, tech) {
  5138. json.forEach(function (track) {
  5139. var addedTrack = tech.addRemoteTextTrack(track).track;
  5140. if (!track.src && track.cues) {
  5141. track.cues.forEach(function (cue) {
  5142. return addedTrack.addCue(cue);
  5143. });
  5144. }
  5145. });
  5146. return tech.textTracks();
  5147. };
  5148. var textTrackConverter = {
  5149. textTracksToJson: textTracksToJson,
  5150. jsonToTextTracks: jsonToTextTracks,
  5151. trackToJson_: trackToJson_
  5152. };
  5153. var MODAL_CLASS_NAME = 'vjs-modal-dialog';
  5154. /**
  5155. * The `ModalDialog` displays over the video and its controls, which blocks
  5156. * interaction with the player until it is closed.
  5157. *
  5158. * Modal dialogs include a "Close" button and will close when that button
  5159. * is activated - or when ESC is pressed anywhere.
  5160. *
  5161. * @extends Component
  5162. */
  5163. var ModalDialog = /*#__PURE__*/function (_Component) {
  5164. _inheritsLoose(ModalDialog, _Component);
  5165. /**
  5166. * Create an instance of this class.
  5167. *
  5168. * @param {Player} player
  5169. * The `Player` that this class should be attached to.
  5170. *
  5171. * @param {Object} [options]
  5172. * The key/value store of player options.
  5173. *
  5174. * @param {Mixed} [options.content=undefined]
  5175. * Provide customized content for this modal.
  5176. *
  5177. * @param {string} [options.description]
  5178. * A text description for the modal, primarily for accessibility.
  5179. *
  5180. * @param {boolean} [options.fillAlways=false]
  5181. * Normally, modals are automatically filled only the first time
  5182. * they open. This tells the modal to refresh its content
  5183. * every time it opens.
  5184. *
  5185. * @param {string} [options.label]
  5186. * A text label for the modal, primarily for accessibility.
  5187. *
  5188. * @param {boolean} [options.pauseOnOpen=true]
  5189. * If `true`, playback will will be paused if playing when
  5190. * the modal opens, and resumed when it closes.
  5191. *
  5192. * @param {boolean} [options.temporary=true]
  5193. * If `true`, the modal can only be opened once; it will be
  5194. * disposed as soon as it's closed.
  5195. *
  5196. * @param {boolean} [options.uncloseable=false]
  5197. * If `true`, the user will not be able to close the modal
  5198. * through the UI in the normal ways. Programmatic closing is
  5199. * still possible.
  5200. */
  5201. function ModalDialog(player, options) {
  5202. var _this;
  5203. _this = _Component.call(this, player, options) || this;
  5204. _this.handleKeyDown_ = function (e) {
  5205. return _this.handleKeyDown(e);
  5206. };
  5207. _this.close_ = function (e) {
  5208. return _this.close(e);
  5209. };
  5210. _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
  5211. _this.closeable(!_this.options_.uncloseable);
  5212. _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized
  5213. // because we only want the contents of the modal in the contentEl
  5214. // (not the UI elements like the close button).
  5215. _this.contentEl_ = createEl('div', {
  5216. className: MODAL_CLASS_NAME + "-content"
  5217. }, {
  5218. role: 'document'
  5219. });
  5220. _this.descEl_ = createEl('p', {
  5221. className: MODAL_CLASS_NAME + "-description vjs-control-text",
  5222. id: _this.el().getAttribute('aria-describedby')
  5223. });
  5224. textContent(_this.descEl_, _this.description());
  5225. _this.el_.appendChild(_this.descEl_);
  5226. _this.el_.appendChild(_this.contentEl_);
  5227. return _this;
  5228. }
  5229. /**
  5230. * Create the `ModalDialog`'s DOM element
  5231. *
  5232. * @return {Element}
  5233. * The DOM element that gets created.
  5234. */
  5235. var _proto = ModalDialog.prototype;
  5236. _proto.createEl = function createEl() {
  5237. return _Component.prototype.createEl.call(this, 'div', {
  5238. className: this.buildCSSClass(),
  5239. tabIndex: -1
  5240. }, {
  5241. 'aria-describedby': this.id() + "_description",
  5242. 'aria-hidden': 'true',
  5243. 'aria-label': this.label(),
  5244. 'role': 'dialog'
  5245. });
  5246. };
  5247. _proto.dispose = function dispose() {
  5248. this.contentEl_ = null;
  5249. this.descEl_ = null;
  5250. this.previouslyActiveEl_ = null;
  5251. _Component.prototype.dispose.call(this);
  5252. }
  5253. /**
  5254. * Builds the default DOM `className`.
  5255. *
  5256. * @return {string}
  5257. * The DOM `className` for this object.
  5258. */
  5259. ;
  5260. _proto.buildCSSClass = function buildCSSClass() {
  5261. return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this);
  5262. }
  5263. /**
  5264. * Returns the label string for this modal. Primarily used for accessibility.
  5265. *
  5266. * @return {string}
  5267. * the localized or raw label of this modal.
  5268. */
  5269. ;
  5270. _proto.label = function label() {
  5271. return this.localize(this.options_.label || 'Modal Window');
  5272. }
  5273. /**
  5274. * Returns the description string for this modal. Primarily used for
  5275. * accessibility.
  5276. *
  5277. * @return {string}
  5278. * The localized or raw description of this modal.
  5279. */
  5280. ;
  5281. _proto.description = function description() {
  5282. var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable.
  5283. if (this.closeable()) {
  5284. desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
  5285. }
  5286. return desc;
  5287. }
  5288. /**
  5289. * Opens the modal.
  5290. *
  5291. * @fires ModalDialog#beforemodalopen
  5292. * @fires ModalDialog#modalopen
  5293. */
  5294. ;
  5295. _proto.open = function open() {
  5296. if (!this.opened_) {
  5297. var player = this.player();
  5298. /**
  5299. * Fired just before a `ModalDialog` is opened.
  5300. *
  5301. * @event ModalDialog#beforemodalopen
  5302. * @type {EventTarget~Event}
  5303. */
  5304. this.trigger('beforemodalopen');
  5305. this.opened_ = true; // Fill content if the modal has never opened before and
  5306. // never been filled.
  5307. if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
  5308. this.fill();
  5309. } // If the player was playing, pause it and take note of its previously
  5310. // playing state.
  5311. this.wasPlaying_ = !player.paused();
  5312. if (this.options_.pauseOnOpen && this.wasPlaying_) {
  5313. player.pause();
  5314. }
  5315. this.on('keydown', this.handleKeyDown_); // Hide controls and note if they were enabled.
  5316. this.hadControls_ = player.controls();
  5317. player.controls(false);
  5318. this.show();
  5319. this.conditionalFocus_();
  5320. this.el().setAttribute('aria-hidden', 'false');
  5321. /**
  5322. * Fired just after a `ModalDialog` is opened.
  5323. *
  5324. * @event ModalDialog#modalopen
  5325. * @type {EventTarget~Event}
  5326. */
  5327. this.trigger('modalopen');
  5328. this.hasBeenOpened_ = true;
  5329. }
  5330. }
  5331. /**
  5332. * If the `ModalDialog` is currently open or closed.
  5333. *
  5334. * @param {boolean} [value]
  5335. * If given, it will open (`true`) or close (`false`) the modal.
  5336. *
  5337. * @return {boolean}
  5338. * the current open state of the modaldialog
  5339. */
  5340. ;
  5341. _proto.opened = function opened(value) {
  5342. if (typeof value === 'boolean') {
  5343. this[value ? 'open' : 'close']();
  5344. }
  5345. return this.opened_;
  5346. }
  5347. /**
  5348. * Closes the modal, does nothing if the `ModalDialog` is
  5349. * not open.
  5350. *
  5351. * @fires ModalDialog#beforemodalclose
  5352. * @fires ModalDialog#modalclose
  5353. */
  5354. ;
  5355. _proto.close = function close() {
  5356. if (!this.opened_) {
  5357. return;
  5358. }
  5359. var player = this.player();
  5360. /**
  5361. * Fired just before a `ModalDialog` is closed.
  5362. *
  5363. * @event ModalDialog#beforemodalclose
  5364. * @type {EventTarget~Event}
  5365. */
  5366. this.trigger('beforemodalclose');
  5367. this.opened_ = false;
  5368. if (this.wasPlaying_ && this.options_.pauseOnOpen) {
  5369. player.play();
  5370. }
  5371. this.off('keydown', this.handleKeyDown_);
  5372. if (this.hadControls_) {
  5373. player.controls(true);
  5374. }
  5375. this.hide();
  5376. this.el().setAttribute('aria-hidden', 'true');
  5377. /**
  5378. * Fired just after a `ModalDialog` is closed.
  5379. *
  5380. * @event ModalDialog#modalclose
  5381. * @type {EventTarget~Event}
  5382. */
  5383. this.trigger('modalclose');
  5384. this.conditionalBlur_();
  5385. if (this.options_.temporary) {
  5386. this.dispose();
  5387. }
  5388. }
  5389. /**
  5390. * Check to see if the `ModalDialog` is closeable via the UI.
  5391. *
  5392. * @param {boolean} [value]
  5393. * If given as a boolean, it will set the `closeable` option.
  5394. *
  5395. * @return {boolean}
  5396. * Returns the final value of the closable option.
  5397. */
  5398. ;
  5399. _proto.closeable = function closeable(value) {
  5400. if (typeof value === 'boolean') {
  5401. var closeable = this.closeable_ = !!value;
  5402. var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one.
  5403. if (closeable && !close) {
  5404. // The close button should be a child of the modal - not its
  5405. // content element, so temporarily change the content element.
  5406. var temp = this.contentEl_;
  5407. this.contentEl_ = this.el_;
  5408. close = this.addChild('closeButton', {
  5409. controlText: 'Close Modal Dialog'
  5410. });
  5411. this.contentEl_ = temp;
  5412. this.on(close, 'close', this.close_);
  5413. } // If this is being made uncloseable and has a close button, remove it.
  5414. if (!closeable && close) {
  5415. this.off(close, 'close', this.close_);
  5416. this.removeChild(close);
  5417. close.dispose();
  5418. }
  5419. }
  5420. return this.closeable_;
  5421. }
  5422. /**
  5423. * Fill the modal's content element with the modal's "content" option.
  5424. * The content element will be emptied before this change takes place.
  5425. */
  5426. ;
  5427. _proto.fill = function fill() {
  5428. this.fillWith(this.content());
  5429. }
  5430. /**
  5431. * Fill the modal's content element with arbitrary content.
  5432. * The content element will be emptied before this change takes place.
  5433. *
  5434. * @fires ModalDialog#beforemodalfill
  5435. * @fires ModalDialog#modalfill
  5436. *
  5437. * @param {Mixed} [content]
  5438. * The same rules apply to this as apply to the `content` option.
  5439. */
  5440. ;
  5441. _proto.fillWith = function fillWith(content) {
  5442. var contentEl = this.contentEl();
  5443. var parentEl = contentEl.parentNode;
  5444. var nextSiblingEl = contentEl.nextSibling;
  5445. /**
  5446. * Fired just before a `ModalDialog` is filled with content.
  5447. *
  5448. * @event ModalDialog#beforemodalfill
  5449. * @type {EventTarget~Event}
  5450. */
  5451. this.trigger('beforemodalfill');
  5452. this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing
  5453. // manipulation to avoid modifying the live DOM multiple times.
  5454. parentEl.removeChild(contentEl);
  5455. this.empty();
  5456. insertContent(contentEl, content);
  5457. /**
  5458. * Fired just after a `ModalDialog` is filled with content.
  5459. *
  5460. * @event ModalDialog#modalfill
  5461. * @type {EventTarget~Event}
  5462. */
  5463. this.trigger('modalfill'); // Re-inject the re-filled content element.
  5464. if (nextSiblingEl) {
  5465. parentEl.insertBefore(contentEl, nextSiblingEl);
  5466. } else {
  5467. parentEl.appendChild(contentEl);
  5468. } // make sure that the close button is last in the dialog DOM
  5469. var closeButton = this.getChild('closeButton');
  5470. if (closeButton) {
  5471. parentEl.appendChild(closeButton.el_);
  5472. }
  5473. }
  5474. /**
  5475. * Empties the content element. This happens anytime the modal is filled.
  5476. *
  5477. * @fires ModalDialog#beforemodalempty
  5478. * @fires ModalDialog#modalempty
  5479. */
  5480. ;
  5481. _proto.empty = function empty() {
  5482. /**
  5483. * Fired just before a `ModalDialog` is emptied.
  5484. *
  5485. * @event ModalDialog#beforemodalempty
  5486. * @type {EventTarget~Event}
  5487. */
  5488. this.trigger('beforemodalempty');
  5489. emptyEl(this.contentEl());
  5490. /**
  5491. * Fired just after a `ModalDialog` is emptied.
  5492. *
  5493. * @event ModalDialog#modalempty
  5494. * @type {EventTarget~Event}
  5495. */
  5496. this.trigger('modalempty');
  5497. }
  5498. /**
  5499. * Gets or sets the modal content, which gets normalized before being
  5500. * rendered into the DOM.
  5501. *
  5502. * This does not update the DOM or fill the modal, but it is called during
  5503. * that process.
  5504. *
  5505. * @param {Mixed} [value]
  5506. * If defined, sets the internal content value to be used on the
  5507. * next call(s) to `fill`. This value is normalized before being
  5508. * inserted. To "clear" the internal content value, pass `null`.
  5509. *
  5510. * @return {Mixed}
  5511. * The current content of the modal dialog
  5512. */
  5513. ;
  5514. _proto.content = function content(value) {
  5515. if (typeof value !== 'undefined') {
  5516. this.content_ = value;
  5517. }
  5518. return this.content_;
  5519. }
  5520. /**
  5521. * conditionally focus the modal dialog if focus was previously on the player.
  5522. *
  5523. * @private
  5524. */
  5525. ;
  5526. _proto.conditionalFocus_ = function conditionalFocus_() {
  5527. var activeEl = document.activeElement;
  5528. var playerEl = this.player_.el_;
  5529. this.previouslyActiveEl_ = null;
  5530. if (playerEl.contains(activeEl) || playerEl === activeEl) {
  5531. this.previouslyActiveEl_ = activeEl;
  5532. this.focus();
  5533. }
  5534. }
  5535. /**
  5536. * conditionally blur the element and refocus the last focused element
  5537. *
  5538. * @private
  5539. */
  5540. ;
  5541. _proto.conditionalBlur_ = function conditionalBlur_() {
  5542. if (this.previouslyActiveEl_) {
  5543. this.previouslyActiveEl_.focus();
  5544. this.previouslyActiveEl_ = null;
  5545. }
  5546. }
  5547. /**
  5548. * Keydown handler. Attached when modal is focused.
  5549. *
  5550. * @listens keydown
  5551. */
  5552. ;
  5553. _proto.handleKeyDown = function handleKeyDown(event) {
  5554. // Do not allow keydowns to reach out of the modal dialog.
  5555. event.stopPropagation();
  5556. if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
  5557. event.preventDefault();
  5558. this.close();
  5559. return;
  5560. } // exit early if it isn't a tab key
  5561. if (!keycode.isEventKey(event, 'Tab')) {
  5562. return;
  5563. }
  5564. var focusableEls = this.focusableEls_();
  5565. var activeEl = this.el_.querySelector(':focus');
  5566. var focusIndex;
  5567. for (var i = 0; i < focusableEls.length; i++) {
  5568. if (activeEl === focusableEls[i]) {
  5569. focusIndex = i;
  5570. break;
  5571. }
  5572. }
  5573. if (document.activeElement === this.el_) {
  5574. focusIndex = 0;
  5575. }
  5576. if (event.shiftKey && focusIndex === 0) {
  5577. focusableEls[focusableEls.length - 1].focus();
  5578. event.preventDefault();
  5579. } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
  5580. focusableEls[0].focus();
  5581. event.preventDefault();
  5582. }
  5583. }
  5584. /**
  5585. * get all focusable elements
  5586. *
  5587. * @private
  5588. */
  5589. ;
  5590. _proto.focusableEls_ = function focusableEls_() {
  5591. var allChildren = this.el_.querySelectorAll('*');
  5592. return Array.prototype.filter.call(allChildren, function (child) {
  5593. return (child instanceof window$1.HTMLAnchorElement || child instanceof window$1.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window$1.HTMLInputElement || child instanceof window$1.HTMLSelectElement || child instanceof window$1.HTMLTextAreaElement || child instanceof window$1.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window$1.HTMLIFrameElement || child instanceof window$1.HTMLObjectElement || child instanceof window$1.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
  5594. });
  5595. };
  5596. return ModalDialog;
  5597. }(Component$1);
  5598. /**
  5599. * Default options for `ModalDialog` default options.
  5600. *
  5601. * @type {Object}
  5602. * @private
  5603. */
  5604. ModalDialog.prototype.options_ = {
  5605. pauseOnOpen: true,
  5606. temporary: true
  5607. };
  5608. Component$1.registerComponent('ModalDialog', ModalDialog);
  5609. /**
  5610. * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
  5611. * {@link VideoTrackList}
  5612. *
  5613. * @extends EventTarget
  5614. */
  5615. var TrackList = /*#__PURE__*/function (_EventTarget) {
  5616. _inheritsLoose(TrackList, _EventTarget);
  5617. /**
  5618. * Create an instance of this class
  5619. *
  5620. * @param {Track[]} tracks
  5621. * A list of tracks to initialize the list with.
  5622. *
  5623. * @abstract
  5624. */
  5625. function TrackList(tracks) {
  5626. var _this;
  5627. if (tracks === void 0) {
  5628. tracks = [];
  5629. }
  5630. _this = _EventTarget.call(this) || this;
  5631. _this.tracks_ = [];
  5632. /**
  5633. * @memberof TrackList
  5634. * @member {number} length
  5635. * The current number of `Track`s in the this Trackist.
  5636. * @instance
  5637. */
  5638. Object.defineProperty(_assertThisInitialized(_this), 'length', {
  5639. get: function get() {
  5640. return this.tracks_.length;
  5641. }
  5642. });
  5643. for (var i = 0; i < tracks.length; i++) {
  5644. _this.addTrack(tracks[i]);
  5645. }
  5646. return _this;
  5647. }
  5648. /**
  5649. * Add a {@link Track} to the `TrackList`
  5650. *
  5651. * @param {Track} track
  5652. * The audio, video, or text track to add to the list.
  5653. *
  5654. * @fires TrackList#addtrack
  5655. */
  5656. var _proto = TrackList.prototype;
  5657. _proto.addTrack = function addTrack(track) {
  5658. var _this2 = this;
  5659. var index = this.tracks_.length;
  5660. if (!('' + index in this)) {
  5661. Object.defineProperty(this, index, {
  5662. get: function get() {
  5663. return this.tracks_[index];
  5664. }
  5665. });
  5666. } // Do not add duplicate tracks
  5667. if (this.tracks_.indexOf(track) === -1) {
  5668. this.tracks_.push(track);
  5669. /**
  5670. * Triggered when a track is added to a track list.
  5671. *
  5672. * @event TrackList#addtrack
  5673. * @type {EventTarget~Event}
  5674. * @property {Track} track
  5675. * A reference to track that was added.
  5676. */
  5677. this.trigger({
  5678. track: track,
  5679. type: 'addtrack',
  5680. target: this
  5681. });
  5682. }
  5683. /**
  5684. * Triggered when a track label is changed.
  5685. *
  5686. * @event TrackList#addtrack
  5687. * @type {EventTarget~Event}
  5688. * @property {Track} track
  5689. * A reference to track that was added.
  5690. */
  5691. track.labelchange_ = function () {
  5692. _this2.trigger({
  5693. track: track,
  5694. type: 'labelchange',
  5695. target: _this2
  5696. });
  5697. };
  5698. if (isEvented(track)) {
  5699. track.addEventListener('labelchange', track.labelchange_);
  5700. }
  5701. }
  5702. /**
  5703. * Remove a {@link Track} from the `TrackList`
  5704. *
  5705. * @param {Track} rtrack
  5706. * The audio, video, or text track to remove from the list.
  5707. *
  5708. * @fires TrackList#removetrack
  5709. */
  5710. ;
  5711. _proto.removeTrack = function removeTrack(rtrack) {
  5712. var track;
  5713. for (var i = 0, l = this.length; i < l; i++) {
  5714. if (this[i] === rtrack) {
  5715. track = this[i];
  5716. if (track.off) {
  5717. track.off();
  5718. }
  5719. this.tracks_.splice(i, 1);
  5720. break;
  5721. }
  5722. }
  5723. if (!track) {
  5724. return;
  5725. }
  5726. /**
  5727. * Triggered when a track is removed from track list.
  5728. *
  5729. * @event TrackList#removetrack
  5730. * @type {EventTarget~Event}
  5731. * @property {Track} track
  5732. * A reference to track that was removed.
  5733. */
  5734. this.trigger({
  5735. track: track,
  5736. type: 'removetrack',
  5737. target: this
  5738. });
  5739. }
  5740. /**
  5741. * Get a Track from the TrackList by a tracks id
  5742. *
  5743. * @param {string} id - the id of the track to get
  5744. * @method getTrackById
  5745. * @return {Track}
  5746. * @private
  5747. */
  5748. ;
  5749. _proto.getTrackById = function getTrackById(id) {
  5750. var result = null;
  5751. for (var i = 0, l = this.length; i < l; i++) {
  5752. var track = this[i];
  5753. if (track.id === id) {
  5754. result = track;
  5755. break;
  5756. }
  5757. }
  5758. return result;
  5759. };
  5760. return TrackList;
  5761. }(EventTarget$2);
  5762. /**
  5763. * Triggered when a different track is selected/enabled.
  5764. *
  5765. * @event TrackList#change
  5766. * @type {EventTarget~Event}
  5767. */
  5768. /**
  5769. * Events that can be called with on + eventName. See {@link EventHandler}.
  5770. *
  5771. * @property {Object} TrackList#allowedEvents_
  5772. * @private
  5773. */
  5774. TrackList.prototype.allowedEvents_ = {
  5775. change: 'change',
  5776. addtrack: 'addtrack',
  5777. removetrack: 'removetrack',
  5778. labelchange: 'labelchange'
  5779. }; // emulate attribute EventHandler support to allow for feature detection
  5780. for (var event in TrackList.prototype.allowedEvents_) {
  5781. TrackList.prototype['on' + event] = null;
  5782. }
  5783. /**
  5784. * Anywhere we call this function we diverge from the spec
  5785. * as we only support one enabled audiotrack at a time
  5786. *
  5787. * @param {AudioTrackList} list
  5788. * list to work on
  5789. *
  5790. * @param {AudioTrack} track
  5791. * The track to skip
  5792. *
  5793. * @private
  5794. */
  5795. var disableOthers$1 = function disableOthers(list, track) {
  5796. for (var i = 0; i < list.length; i++) {
  5797. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5798. continue;
  5799. } // another audio track is enabled, disable it
  5800. list[i].enabled = false;
  5801. }
  5802. };
  5803. /**
  5804. * The current list of {@link AudioTrack} for a media file.
  5805. *
  5806. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
  5807. * @extends TrackList
  5808. */
  5809. var AudioTrackList = /*#__PURE__*/function (_TrackList) {
  5810. _inheritsLoose(AudioTrackList, _TrackList);
  5811. /**
  5812. * Create an instance of this class.
  5813. *
  5814. * @param {AudioTrack[]} [tracks=[]]
  5815. * A list of `AudioTrack` to instantiate the list with.
  5816. */
  5817. function AudioTrackList(tracks) {
  5818. var _this;
  5819. if (tracks === void 0) {
  5820. tracks = [];
  5821. }
  5822. // make sure only 1 track is enabled
  5823. // sorted from last index to first index
  5824. for (var i = tracks.length - 1; i >= 0; i--) {
  5825. if (tracks[i].enabled) {
  5826. disableOthers$1(tracks, tracks[i]);
  5827. break;
  5828. }
  5829. }
  5830. _this = _TrackList.call(this, tracks) || this;
  5831. _this.changing_ = false;
  5832. return _this;
  5833. }
  5834. /**
  5835. * Add an {@link AudioTrack} to the `AudioTrackList`.
  5836. *
  5837. * @param {AudioTrack} track
  5838. * The AudioTrack to add to the list
  5839. *
  5840. * @fires TrackList#addtrack
  5841. */
  5842. var _proto = AudioTrackList.prototype;
  5843. _proto.addTrack = function addTrack(track) {
  5844. var _this2 = this;
  5845. if (track.enabled) {
  5846. disableOthers$1(this, track);
  5847. }
  5848. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5849. if (!track.addEventListener) {
  5850. return;
  5851. }
  5852. track.enabledChange_ = function () {
  5853. // when we are disabling other tracks (since we don't support
  5854. // more than one track at a time) we will set changing_
  5855. // to true so that we don't trigger additional change events
  5856. if (_this2.changing_) {
  5857. return;
  5858. }
  5859. _this2.changing_ = true;
  5860. disableOthers$1(_this2, track);
  5861. _this2.changing_ = false;
  5862. _this2.trigger('change');
  5863. };
  5864. /**
  5865. * @listens AudioTrack#enabledchange
  5866. * @fires TrackList#change
  5867. */
  5868. track.addEventListener('enabledchange', track.enabledChange_);
  5869. };
  5870. _proto.removeTrack = function removeTrack(rtrack) {
  5871. _TrackList.prototype.removeTrack.call(this, rtrack);
  5872. if (rtrack.removeEventListener && rtrack.enabledChange_) {
  5873. rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
  5874. rtrack.enabledChange_ = null;
  5875. }
  5876. };
  5877. return AudioTrackList;
  5878. }(TrackList);
  5879. /**
  5880. * Un-select all other {@link VideoTrack}s that are selected.
  5881. *
  5882. * @param {VideoTrackList} list
  5883. * list to work on
  5884. *
  5885. * @param {VideoTrack} track
  5886. * The track to skip
  5887. *
  5888. * @private
  5889. */
  5890. var disableOthers = function disableOthers(list, track) {
  5891. for (var i = 0; i < list.length; i++) {
  5892. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5893. continue;
  5894. } // another video track is enabled, disable it
  5895. list[i].selected = false;
  5896. }
  5897. };
  5898. /**
  5899. * The current list of {@link VideoTrack} for a video.
  5900. *
  5901. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
  5902. * @extends TrackList
  5903. */
  5904. var VideoTrackList = /*#__PURE__*/function (_TrackList) {
  5905. _inheritsLoose(VideoTrackList, _TrackList);
  5906. /**
  5907. * Create an instance of this class.
  5908. *
  5909. * @param {VideoTrack[]} [tracks=[]]
  5910. * A list of `VideoTrack` to instantiate the list with.
  5911. */
  5912. function VideoTrackList(tracks) {
  5913. var _this;
  5914. if (tracks === void 0) {
  5915. tracks = [];
  5916. }
  5917. // make sure only 1 track is enabled
  5918. // sorted from last index to first index
  5919. for (var i = tracks.length - 1; i >= 0; i--) {
  5920. if (tracks[i].selected) {
  5921. disableOthers(tracks, tracks[i]);
  5922. break;
  5923. }
  5924. }
  5925. _this = _TrackList.call(this, tracks) || this;
  5926. _this.changing_ = false;
  5927. /**
  5928. * @member {number} VideoTrackList#selectedIndex
  5929. * The current index of the selected {@link VideoTrack`}.
  5930. */
  5931. Object.defineProperty(_assertThisInitialized(_this), 'selectedIndex', {
  5932. get: function get() {
  5933. for (var _i = 0; _i < this.length; _i++) {
  5934. if (this[_i].selected) {
  5935. return _i;
  5936. }
  5937. }
  5938. return -1;
  5939. },
  5940. set: function set() {}
  5941. });
  5942. return _this;
  5943. }
  5944. /**
  5945. * Add a {@link VideoTrack} to the `VideoTrackList`.
  5946. *
  5947. * @param {VideoTrack} track
  5948. * The VideoTrack to add to the list
  5949. *
  5950. * @fires TrackList#addtrack
  5951. */
  5952. var _proto = VideoTrackList.prototype;
  5953. _proto.addTrack = function addTrack(track) {
  5954. var _this2 = this;
  5955. if (track.selected) {
  5956. disableOthers(this, track);
  5957. }
  5958. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5959. if (!track.addEventListener) {
  5960. return;
  5961. }
  5962. track.selectedChange_ = function () {
  5963. if (_this2.changing_) {
  5964. return;
  5965. }
  5966. _this2.changing_ = true;
  5967. disableOthers(_this2, track);
  5968. _this2.changing_ = false;
  5969. _this2.trigger('change');
  5970. };
  5971. /**
  5972. * @listens VideoTrack#selectedchange
  5973. * @fires TrackList#change
  5974. */
  5975. track.addEventListener('selectedchange', track.selectedChange_);
  5976. };
  5977. _proto.removeTrack = function removeTrack(rtrack) {
  5978. _TrackList.prototype.removeTrack.call(this, rtrack);
  5979. if (rtrack.removeEventListener && rtrack.selectedChange_) {
  5980. rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
  5981. rtrack.selectedChange_ = null;
  5982. }
  5983. };
  5984. return VideoTrackList;
  5985. }(TrackList);
  5986. /**
  5987. * The current list of {@link TextTrack} for a media file.
  5988. *
  5989. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
  5990. * @extends TrackList
  5991. */
  5992. var TextTrackList = /*#__PURE__*/function (_TrackList) {
  5993. _inheritsLoose(TextTrackList, _TrackList);
  5994. function TextTrackList() {
  5995. return _TrackList.apply(this, arguments) || this;
  5996. }
  5997. var _proto = TextTrackList.prototype;
  5998. /**
  5999. * Add a {@link TextTrack} to the `TextTrackList`
  6000. *
  6001. * @param {TextTrack} track
  6002. * The text track to add to the list.
  6003. *
  6004. * @fires TrackList#addtrack
  6005. */
  6006. _proto.addTrack = function addTrack(track) {
  6007. var _this = this;
  6008. _TrackList.prototype.addTrack.call(this, track);
  6009. if (!this.queueChange_) {
  6010. this.queueChange_ = function () {
  6011. return _this.queueTrigger('change');
  6012. };
  6013. }
  6014. if (!this.triggerSelectedlanguagechange) {
  6015. this.triggerSelectedlanguagechange_ = function () {
  6016. return _this.trigger('selectedlanguagechange');
  6017. };
  6018. }
  6019. /**
  6020. * @listens TextTrack#modechange
  6021. * @fires TrackList#change
  6022. */
  6023. track.addEventListener('modechange', this.queueChange_);
  6024. var nonLanguageTextTrackKind = ['metadata', 'chapters'];
  6025. if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
  6026. track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
  6027. }
  6028. };
  6029. _proto.removeTrack = function removeTrack(rtrack) {
  6030. _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added
  6031. if (rtrack.removeEventListener) {
  6032. if (this.queueChange_) {
  6033. rtrack.removeEventListener('modechange', this.queueChange_);
  6034. }
  6035. if (this.selectedlanguagechange_) {
  6036. rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
  6037. }
  6038. }
  6039. };
  6040. return TextTrackList;
  6041. }(TrackList);
  6042. /**
  6043. * @file html-track-element-list.js
  6044. */
  6045. /**
  6046. * The current list of {@link HtmlTrackElement}s.
  6047. */
  6048. var HtmlTrackElementList = /*#__PURE__*/function () {
  6049. /**
  6050. * Create an instance of this class.
  6051. *
  6052. * @param {HtmlTrackElement[]} [tracks=[]]
  6053. * A list of `HtmlTrackElement` to instantiate the list with.
  6054. */
  6055. function HtmlTrackElementList(trackElements) {
  6056. if (trackElements === void 0) {
  6057. trackElements = [];
  6058. }
  6059. this.trackElements_ = [];
  6060. /**
  6061. * @memberof HtmlTrackElementList
  6062. * @member {number} length
  6063. * The current number of `Track`s in the this Trackist.
  6064. * @instance
  6065. */
  6066. Object.defineProperty(this, 'length', {
  6067. get: function get() {
  6068. return this.trackElements_.length;
  6069. }
  6070. });
  6071. for (var i = 0, length = trackElements.length; i < length; i++) {
  6072. this.addTrackElement_(trackElements[i]);
  6073. }
  6074. }
  6075. /**
  6076. * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
  6077. *
  6078. * @param {HtmlTrackElement} trackElement
  6079. * The track element to add to the list.
  6080. *
  6081. * @private
  6082. */
  6083. var _proto = HtmlTrackElementList.prototype;
  6084. _proto.addTrackElement_ = function addTrackElement_(trackElement) {
  6085. var index = this.trackElements_.length;
  6086. if (!('' + index in this)) {
  6087. Object.defineProperty(this, index, {
  6088. get: function get() {
  6089. return this.trackElements_[index];
  6090. }
  6091. });
  6092. } // Do not add duplicate elements
  6093. if (this.trackElements_.indexOf(trackElement) === -1) {
  6094. this.trackElements_.push(trackElement);
  6095. }
  6096. }
  6097. /**
  6098. * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
  6099. * {@link TextTrack}.
  6100. *
  6101. * @param {TextTrack} track
  6102. * The track associated with a track element.
  6103. *
  6104. * @return {HtmlTrackElement|undefined}
  6105. * The track element that was found or undefined.
  6106. *
  6107. * @private
  6108. */
  6109. ;
  6110. _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
  6111. var trackElement_;
  6112. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  6113. if (track === this.trackElements_[i].track) {
  6114. trackElement_ = this.trackElements_[i];
  6115. break;
  6116. }
  6117. }
  6118. return trackElement_;
  6119. }
  6120. /**
  6121. * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
  6122. *
  6123. * @param {HtmlTrackElement} trackElement
  6124. * The track element to remove from the list.
  6125. *
  6126. * @private
  6127. */
  6128. ;
  6129. _proto.removeTrackElement_ = function removeTrackElement_(trackElement) {
  6130. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  6131. if (trackElement === this.trackElements_[i]) {
  6132. if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
  6133. this.trackElements_[i].track.off();
  6134. }
  6135. if (typeof this.trackElements_[i].off === 'function') {
  6136. this.trackElements_[i].off();
  6137. }
  6138. this.trackElements_.splice(i, 1);
  6139. break;
  6140. }
  6141. }
  6142. };
  6143. return HtmlTrackElementList;
  6144. }();
  6145. /**
  6146. * @file text-track-cue-list.js
  6147. */
  6148. /**
  6149. * @typedef {Object} TextTrackCueList~TextTrackCue
  6150. *
  6151. * @property {string} id
  6152. * The unique id for this text track cue
  6153. *
  6154. * @property {number} startTime
  6155. * The start time for this text track cue
  6156. *
  6157. * @property {number} endTime
  6158. * The end time for this text track cue
  6159. *
  6160. * @property {boolean} pauseOnExit
  6161. * Pause when the end time is reached if true.
  6162. *
  6163. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
  6164. */
  6165. /**
  6166. * A List of TextTrackCues.
  6167. *
  6168. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
  6169. */
  6170. var TextTrackCueList = /*#__PURE__*/function () {
  6171. /**
  6172. * Create an instance of this class..
  6173. *
  6174. * @param {Array} cues
  6175. * A list of cues to be initialized with
  6176. */
  6177. function TextTrackCueList(cues) {
  6178. TextTrackCueList.prototype.setCues_.call(this, cues);
  6179. /**
  6180. * @memberof TextTrackCueList
  6181. * @member {number} length
  6182. * The current number of `TextTrackCue`s in the TextTrackCueList.
  6183. * @instance
  6184. */
  6185. Object.defineProperty(this, 'length', {
  6186. get: function get() {
  6187. return this.length_;
  6188. }
  6189. });
  6190. }
  6191. /**
  6192. * A setter for cues in this list. Creates getters
  6193. * an an index for the cues.
  6194. *
  6195. * @param {Array} cues
  6196. * An array of cues to set
  6197. *
  6198. * @private
  6199. */
  6200. var _proto = TextTrackCueList.prototype;
  6201. _proto.setCues_ = function setCues_(cues) {
  6202. var oldLength = this.length || 0;
  6203. var i = 0;
  6204. var l = cues.length;
  6205. this.cues_ = cues;
  6206. this.length_ = cues.length;
  6207. var defineProp = function defineProp(index) {
  6208. if (!('' + index in this)) {
  6209. Object.defineProperty(this, '' + index, {
  6210. get: function get() {
  6211. return this.cues_[index];
  6212. }
  6213. });
  6214. }
  6215. };
  6216. if (oldLength < l) {
  6217. i = oldLength;
  6218. for (; i < l; i++) {
  6219. defineProp.call(this, i);
  6220. }
  6221. }
  6222. }
  6223. /**
  6224. * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
  6225. *
  6226. * @param {string} id
  6227. * The id of the cue that should be searched for.
  6228. *
  6229. * @return {TextTrackCueList~TextTrackCue|null}
  6230. * A single cue or null if none was found.
  6231. */
  6232. ;
  6233. _proto.getCueById = function getCueById(id) {
  6234. var result = null;
  6235. for (var i = 0, l = this.length; i < l; i++) {
  6236. var cue = this[i];
  6237. if (cue.id === id) {
  6238. result = cue;
  6239. break;
  6240. }
  6241. }
  6242. return result;
  6243. };
  6244. return TextTrackCueList;
  6245. }();
  6246. /**
  6247. * @file track-kinds.js
  6248. */
  6249. /**
  6250. * All possible `VideoTrackKind`s
  6251. *
  6252. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
  6253. * @typedef VideoTrack~Kind
  6254. * @enum
  6255. */
  6256. var VideoTrackKind = {
  6257. alternative: 'alternative',
  6258. captions: 'captions',
  6259. main: 'main',
  6260. sign: 'sign',
  6261. subtitles: 'subtitles',
  6262. commentary: 'commentary'
  6263. };
  6264. /**
  6265. * All possible `AudioTrackKind`s
  6266. *
  6267. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
  6268. * @typedef AudioTrack~Kind
  6269. * @enum
  6270. */
  6271. var AudioTrackKind = {
  6272. 'alternative': 'alternative',
  6273. 'descriptions': 'descriptions',
  6274. 'main': 'main',
  6275. 'main-desc': 'main-desc',
  6276. 'translation': 'translation',
  6277. 'commentary': 'commentary'
  6278. };
  6279. /**
  6280. * All possible `TextTrackKind`s
  6281. *
  6282. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
  6283. * @typedef TextTrack~Kind
  6284. * @enum
  6285. */
  6286. var TextTrackKind = {
  6287. subtitles: 'subtitles',
  6288. captions: 'captions',
  6289. descriptions: 'descriptions',
  6290. chapters: 'chapters',
  6291. metadata: 'metadata'
  6292. };
  6293. /**
  6294. * All possible `TextTrackMode`s
  6295. *
  6296. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
  6297. * @typedef TextTrack~Mode
  6298. * @enum
  6299. */
  6300. var TextTrackMode = {
  6301. disabled: 'disabled',
  6302. hidden: 'hidden',
  6303. showing: 'showing'
  6304. };
  6305. /**
  6306. * A Track class that contains all of the common functionality for {@link AudioTrack},
  6307. * {@link VideoTrack}, and {@link TextTrack}.
  6308. *
  6309. * > Note: This class should not be used directly
  6310. *
  6311. * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
  6312. * @extends EventTarget
  6313. * @abstract
  6314. */
  6315. var Track = /*#__PURE__*/function (_EventTarget) {
  6316. _inheritsLoose(Track, _EventTarget);
  6317. /**
  6318. * Create an instance of this class.
  6319. *
  6320. * @param {Object} [options={}]
  6321. * Object of option names and values
  6322. *
  6323. * @param {string} [options.kind='']
  6324. * A valid kind for the track type you are creating.
  6325. *
  6326. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6327. * A unique id for this AudioTrack.
  6328. *
  6329. * @param {string} [options.label='']
  6330. * The menu label for this track.
  6331. *
  6332. * @param {string} [options.language='']
  6333. * A valid two character language code.
  6334. *
  6335. * @abstract
  6336. */
  6337. function Track(options) {
  6338. var _this;
  6339. if (options === void 0) {
  6340. options = {};
  6341. }
  6342. _this = _EventTarget.call(this) || this;
  6343. var trackProps = {
  6344. id: options.id || 'vjs_track_' + newGUID(),
  6345. kind: options.kind || '',
  6346. language: options.language || ''
  6347. };
  6348. var label = options.label || '';
  6349. /**
  6350. * @memberof Track
  6351. * @member {string} id
  6352. * The id of this track. Cannot be changed after creation.
  6353. * @instance
  6354. *
  6355. * @readonly
  6356. */
  6357. /**
  6358. * @memberof Track
  6359. * @member {string} kind
  6360. * The kind of track that this is. Cannot be changed after creation.
  6361. * @instance
  6362. *
  6363. * @readonly
  6364. */
  6365. /**
  6366. * @memberof Track
  6367. * @member {string} language
  6368. * The two letter language code for this track. Cannot be changed after
  6369. * creation.
  6370. * @instance
  6371. *
  6372. * @readonly
  6373. */
  6374. var _loop = function _loop(key) {
  6375. Object.defineProperty(_assertThisInitialized(_this), key, {
  6376. get: function get() {
  6377. return trackProps[key];
  6378. },
  6379. set: function set() {}
  6380. });
  6381. };
  6382. for (var key in trackProps) {
  6383. _loop(key);
  6384. }
  6385. /**
  6386. * @memberof Track
  6387. * @member {string} label
  6388. * The label of this track. Cannot be changed after creation.
  6389. * @instance
  6390. *
  6391. * @fires Track#labelchange
  6392. */
  6393. Object.defineProperty(_assertThisInitialized(_this), 'label', {
  6394. get: function get() {
  6395. return label;
  6396. },
  6397. set: function set(newLabel) {
  6398. if (newLabel !== label) {
  6399. label = newLabel;
  6400. /**
  6401. * An event that fires when label changes on this track.
  6402. *
  6403. * > Note: This is not part of the spec!
  6404. *
  6405. * @event Track#labelchange
  6406. * @type {EventTarget~Event}
  6407. */
  6408. this.trigger('labelchange');
  6409. }
  6410. }
  6411. });
  6412. return _this;
  6413. }
  6414. return Track;
  6415. }(EventTarget$2);
  6416. /**
  6417. * @file url.js
  6418. * @module url
  6419. */
  6420. /**
  6421. * @typedef {Object} url:URLObject
  6422. *
  6423. * @property {string} protocol
  6424. * The protocol of the url that was parsed.
  6425. *
  6426. * @property {string} hostname
  6427. * The hostname of the url that was parsed.
  6428. *
  6429. * @property {string} port
  6430. * The port of the url that was parsed.
  6431. *
  6432. * @property {string} pathname
  6433. * The pathname of the url that was parsed.
  6434. *
  6435. * @property {string} search
  6436. * The search query of the url that was parsed.
  6437. *
  6438. * @property {string} hash
  6439. * The hash of the url that was parsed.
  6440. *
  6441. * @property {string} host
  6442. * The host of the url that was parsed.
  6443. */
  6444. /**
  6445. * Resolve and parse the elements of a URL.
  6446. *
  6447. * @function
  6448. * @param {String} url
  6449. * The url to parse
  6450. *
  6451. * @return {url:URLObject}
  6452. * An object of url details
  6453. */
  6454. var parseUrl = function parseUrl(url) {
  6455. // This entire method can be replace with URL once we are able to drop IE11
  6456. var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL
  6457. var a = document.createElement('a');
  6458. a.href = url; // Copy the specific URL properties to a new object
  6459. // This is also needed for IE because the anchor loses its
  6460. // properties when it's removed from the dom
  6461. var details = {};
  6462. for (var i = 0; i < props.length; i++) {
  6463. details[props[i]] = a[props[i]];
  6464. } // IE adds the port to the host property unlike everyone else. If
  6465. // a port identifier is added for standard ports, strip it.
  6466. if (details.protocol === 'http:') {
  6467. details.host = details.host.replace(/:80$/, '');
  6468. }
  6469. if (details.protocol === 'https:') {
  6470. details.host = details.host.replace(/:443$/, '');
  6471. }
  6472. if (!details.protocol) {
  6473. details.protocol = window$1.location.protocol;
  6474. }
  6475. /* istanbul ignore if */
  6476. if (!details.host) {
  6477. details.host = window$1.location.host;
  6478. }
  6479. return details;
  6480. };
  6481. /**
  6482. * Get absolute version of relative URL. Used to tell Flash the correct URL.
  6483. *
  6484. * @function
  6485. * @param {string} url
  6486. * URL to make absolute
  6487. *
  6488. * @return {string}
  6489. * Absolute URL
  6490. *
  6491. * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
  6492. */
  6493. var getAbsoluteURL = function getAbsoluteURL(url) {
  6494. // Check if absolute URL
  6495. if (!url.match(/^https?:\/\//)) {
  6496. // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
  6497. // add the url to an anchor and let the browser parse the URL
  6498. var a = document.createElement('a');
  6499. a.href = url;
  6500. url = a.href;
  6501. }
  6502. return url;
  6503. };
  6504. /**
  6505. * Returns the extension of the passed file name. It will return an empty string
  6506. * if passed an invalid path.
  6507. *
  6508. * @function
  6509. * @param {string} path
  6510. * The fileName path like '/path/to/file.mp4'
  6511. *
  6512. * @return {string}
  6513. * The extension in lower case or an empty string if no
  6514. * extension could be found.
  6515. */
  6516. var getFileExtension = function getFileExtension(path) {
  6517. if (typeof path === 'string') {
  6518. var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/;
  6519. var pathParts = splitPathRe.exec(path);
  6520. if (pathParts) {
  6521. return pathParts.pop().toLowerCase();
  6522. }
  6523. }
  6524. return '';
  6525. };
  6526. /**
  6527. * Returns whether the url passed is a cross domain request or not.
  6528. *
  6529. * @function
  6530. * @param {string} url
  6531. * The url to check.
  6532. *
  6533. * @param {Object} [winLoc]
  6534. * the domain to check the url against, defaults to window.location
  6535. *
  6536. * @param {string} [winLoc.protocol]
  6537. * The window location protocol defaults to window.location.protocol
  6538. *
  6539. * @param {string} [winLoc.host]
  6540. * The window location host defaults to window.location.host
  6541. *
  6542. * @return {boolean}
  6543. * Whether it is a cross domain request or not.
  6544. */
  6545. var isCrossOrigin = function isCrossOrigin(url, winLoc) {
  6546. if (winLoc === void 0) {
  6547. winLoc = window$1.location;
  6548. }
  6549. var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol
  6550. var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin
  6551. // IE8 doesn't know location.origin, so we won't rely on it here
  6552. var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
  6553. return crossOrigin;
  6554. };
  6555. var Url = /*#__PURE__*/Object.freeze({
  6556. __proto__: null,
  6557. parseUrl: parseUrl,
  6558. getAbsoluteURL: getAbsoluteURL,
  6559. getFileExtension: getFileExtension,
  6560. isCrossOrigin: isCrossOrigin
  6561. });
  6562. /**
  6563. * Takes a webvtt file contents and parses it into cues
  6564. *
  6565. * @param {string} srcContent
  6566. * webVTT file contents
  6567. *
  6568. * @param {TextTrack} track
  6569. * TextTrack to add cues to. Cues come from the srcContent.
  6570. *
  6571. * @private
  6572. */
  6573. var parseCues = function parseCues(srcContent, track) {
  6574. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder());
  6575. var errors = [];
  6576. parser.oncue = function (cue) {
  6577. track.addCue(cue);
  6578. };
  6579. parser.onparsingerror = function (error) {
  6580. errors.push(error);
  6581. };
  6582. parser.onflush = function () {
  6583. track.trigger({
  6584. type: 'loadeddata',
  6585. target: track
  6586. });
  6587. };
  6588. parser.parse(srcContent);
  6589. if (errors.length > 0) {
  6590. if (window$1.console && window$1.console.groupCollapsed) {
  6591. window$1.console.groupCollapsed("Text Track parsing errors for " + track.src);
  6592. }
  6593. errors.forEach(function (error) {
  6594. return log$1.error(error);
  6595. });
  6596. if (window$1.console && window$1.console.groupEnd) {
  6597. window$1.console.groupEnd();
  6598. }
  6599. }
  6600. parser.flush();
  6601. };
  6602. /**
  6603. * Load a `TextTrack` from a specified url.
  6604. *
  6605. * @param {string} src
  6606. * Url to load track from.
  6607. *
  6608. * @param {TextTrack} track
  6609. * Track to add cues to. Comes from the content at the end of `url`.
  6610. *
  6611. * @private
  6612. */
  6613. var loadTrack = function loadTrack(src, track) {
  6614. var opts = {
  6615. uri: src
  6616. };
  6617. var crossOrigin = isCrossOrigin(src);
  6618. if (crossOrigin) {
  6619. opts.cors = crossOrigin;
  6620. }
  6621. var withCredentials = track.tech_.crossOrigin() === 'use-credentials';
  6622. if (withCredentials) {
  6623. opts.withCredentials = withCredentials;
  6624. }
  6625. XHR(opts, bind(this, function (err, response, responseBody) {
  6626. if (err) {
  6627. return log$1.error(err, response);
  6628. }
  6629. track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  6630. // NOTE: this is only used for the alt/video.novtt.js build
  6631. if (typeof window$1.WebVTT !== 'function') {
  6632. if (track.tech_) {
  6633. // to prevent use before define eslint error, we define loadHandler
  6634. // as a let here
  6635. track.tech_.any(['vttjsloaded', 'vttjserror'], function (event) {
  6636. if (event.type === 'vttjserror') {
  6637. log$1.error("vttjs failed to load, stopping trying to process " + track.src);
  6638. return;
  6639. }
  6640. return parseCues(responseBody, track);
  6641. });
  6642. }
  6643. } else {
  6644. parseCues(responseBody, track);
  6645. }
  6646. }));
  6647. };
  6648. /**
  6649. * A representation of a single `TextTrack`.
  6650. *
  6651. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
  6652. * @extends Track
  6653. */
  6654. var TextTrack = /*#__PURE__*/function (_Track) {
  6655. _inheritsLoose(TextTrack, _Track);
  6656. /**
  6657. * Create an instance of this class.
  6658. *
  6659. * @param {Object} options={}
  6660. * Object of option names and values
  6661. *
  6662. * @param {Tech} options.tech
  6663. * A reference to the tech that owns this TextTrack.
  6664. *
  6665. * @param {TextTrack~Kind} [options.kind='subtitles']
  6666. * A valid text track kind.
  6667. *
  6668. * @param {TextTrack~Mode} [options.mode='disabled']
  6669. * A valid text track mode.
  6670. *
  6671. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6672. * A unique id for this TextTrack.
  6673. *
  6674. * @param {string} [options.label='']
  6675. * The menu label for this track.
  6676. *
  6677. * @param {string} [options.language='']
  6678. * A valid two character language code.
  6679. *
  6680. * @param {string} [options.srclang='']
  6681. * A valid two character language code. An alternative, but deprioritized
  6682. * version of `options.language`
  6683. *
  6684. * @param {string} [options.src]
  6685. * A url to TextTrack cues.
  6686. *
  6687. * @param {boolean} [options.default]
  6688. * If this track should default to on or off.
  6689. */
  6690. function TextTrack(options) {
  6691. var _this;
  6692. if (options === void 0) {
  6693. options = {};
  6694. }
  6695. if (!options.tech) {
  6696. throw new Error('A tech was not provided.');
  6697. }
  6698. var settings = mergeOptions$3(options, {
  6699. kind: TextTrackKind[options.kind] || 'subtitles',
  6700. language: options.language || options.srclang || ''
  6701. });
  6702. var mode = TextTrackMode[settings.mode] || 'disabled';
  6703. var default_ = settings["default"];
  6704. if (settings.kind === 'metadata' || settings.kind === 'chapters') {
  6705. mode = 'hidden';
  6706. }
  6707. _this = _Track.call(this, settings) || this;
  6708. _this.tech_ = settings.tech;
  6709. _this.cues_ = [];
  6710. _this.activeCues_ = [];
  6711. _this.preload_ = _this.tech_.preloadTextTracks !== false;
  6712. var cues = new TextTrackCueList(_this.cues_);
  6713. var activeCues = new TextTrackCueList(_this.activeCues_);
  6714. var changed = false;
  6715. _this.timeupdateHandler = bind(_assertThisInitialized(_this), function (event) {
  6716. if (event === void 0) {
  6717. event = {};
  6718. }
  6719. if (this.tech_.isDisposed()) {
  6720. return;
  6721. }
  6722. if (!this.tech_.isReady_) {
  6723. if (event.type !== 'timeupdate') {
  6724. this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
  6725. }
  6726. return;
  6727. } // Accessing this.activeCues for the side-effects of updating itself
  6728. // due to its nature as a getter function. Do not remove or cues will
  6729. // stop updating!
  6730. // Use the setter to prevent deletion from uglify (pure_getters rule)
  6731. this.activeCues = this.activeCues;
  6732. if (changed) {
  6733. this.trigger('cuechange');
  6734. changed = false;
  6735. }
  6736. if (event.type !== 'timeupdate') {
  6737. this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
  6738. }
  6739. });
  6740. var disposeHandler = function disposeHandler() {
  6741. _this.stopTracking();
  6742. };
  6743. _this.tech_.one('dispose', disposeHandler);
  6744. if (mode !== 'disabled') {
  6745. _this.startTracking();
  6746. }
  6747. Object.defineProperties(_assertThisInitialized(_this), {
  6748. /**
  6749. * @memberof TextTrack
  6750. * @member {boolean} default
  6751. * If this track was set to be on or off by default. Cannot be changed after
  6752. * creation.
  6753. * @instance
  6754. *
  6755. * @readonly
  6756. */
  6757. "default": {
  6758. get: function get() {
  6759. return default_;
  6760. },
  6761. set: function set() {}
  6762. },
  6763. /**
  6764. * @memberof TextTrack
  6765. * @member {string} mode
  6766. * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
  6767. * not be set if setting to an invalid mode.
  6768. * @instance
  6769. *
  6770. * @fires TextTrack#modechange
  6771. */
  6772. mode: {
  6773. get: function get() {
  6774. return mode;
  6775. },
  6776. set: function set(newMode) {
  6777. if (!TextTrackMode[newMode]) {
  6778. return;
  6779. }
  6780. if (mode === newMode) {
  6781. return;
  6782. }
  6783. mode = newMode;
  6784. if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
  6785. // On-demand load.
  6786. loadTrack(this.src, this);
  6787. }
  6788. this.stopTracking();
  6789. if (mode !== 'disabled') {
  6790. this.startTracking();
  6791. }
  6792. /**
  6793. * An event that fires when mode changes on this track. This allows
  6794. * the TextTrackList that holds this track to act accordingly.
  6795. *
  6796. * > Note: This is not part of the spec!
  6797. *
  6798. * @event TextTrack#modechange
  6799. * @type {EventTarget~Event}
  6800. */
  6801. this.trigger('modechange');
  6802. }
  6803. },
  6804. /**
  6805. * @memberof TextTrack
  6806. * @member {TextTrackCueList} cues
  6807. * The text track cue list for this TextTrack.
  6808. * @instance
  6809. */
  6810. cues: {
  6811. get: function get() {
  6812. if (!this.loaded_) {
  6813. return null;
  6814. }
  6815. return cues;
  6816. },
  6817. set: function set() {}
  6818. },
  6819. /**
  6820. * @memberof TextTrack
  6821. * @member {TextTrackCueList} activeCues
  6822. * The list text track cues that are currently active for this TextTrack.
  6823. * @instance
  6824. */
  6825. activeCues: {
  6826. get: function get() {
  6827. if (!this.loaded_) {
  6828. return null;
  6829. } // nothing to do
  6830. if (this.cues.length === 0) {
  6831. return activeCues;
  6832. }
  6833. var ct = this.tech_.currentTime();
  6834. var active = [];
  6835. for (var i = 0, l = this.cues.length; i < l; i++) {
  6836. var cue = this.cues[i];
  6837. if (cue.startTime <= ct && cue.endTime >= ct) {
  6838. active.push(cue);
  6839. } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
  6840. active.push(cue);
  6841. }
  6842. }
  6843. changed = false;
  6844. if (active.length !== this.activeCues_.length) {
  6845. changed = true;
  6846. } else {
  6847. for (var _i = 0; _i < active.length; _i++) {
  6848. if (this.activeCues_.indexOf(active[_i]) === -1) {
  6849. changed = true;
  6850. }
  6851. }
  6852. }
  6853. this.activeCues_ = active;
  6854. activeCues.setCues_(this.activeCues_);
  6855. return activeCues;
  6856. },
  6857. // /!\ Keep this setter empty (see the timeupdate handler above)
  6858. set: function set() {}
  6859. }
  6860. });
  6861. if (settings.src) {
  6862. _this.src = settings.src;
  6863. if (!_this.preload_) {
  6864. // Tracks will load on-demand.
  6865. // Act like we're loaded for other purposes.
  6866. _this.loaded_ = true;
  6867. }
  6868. if (_this.preload_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') {
  6869. loadTrack(_this.src, _assertThisInitialized(_this));
  6870. }
  6871. } else {
  6872. _this.loaded_ = true;
  6873. }
  6874. return _this;
  6875. }
  6876. var _proto = TextTrack.prototype;
  6877. _proto.startTracking = function startTracking() {
  6878. // More precise cues based on requestVideoFrameCallback with a requestAnimationFram fallback
  6879. this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler); // Also listen to timeupdate in case rVFC/rAF stops (window in background, audio in video el)
  6880. this.tech_.on('timeupdate', this.timeupdateHandler);
  6881. };
  6882. _proto.stopTracking = function stopTracking() {
  6883. if (this.rvf_) {
  6884. this.tech_.cancelVideoFrameCallback(this.rvf_);
  6885. this.rvf_ = undefined;
  6886. }
  6887. this.tech_.off('timeupdate', this.timeupdateHandler);
  6888. }
  6889. /**
  6890. * Add a cue to the internal list of cues.
  6891. *
  6892. * @param {TextTrack~Cue} cue
  6893. * The cue to add to our internal list
  6894. */
  6895. ;
  6896. _proto.addCue = function addCue(originalCue) {
  6897. var cue = originalCue;
  6898. if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) {
  6899. cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
  6900. for (var prop in originalCue) {
  6901. if (!(prop in cue)) {
  6902. cue[prop] = originalCue[prop];
  6903. }
  6904. } // make sure that `id` is copied over
  6905. cue.id = originalCue.id;
  6906. cue.originalCue_ = originalCue;
  6907. }
  6908. var tracks = this.tech_.textTracks();
  6909. for (var i = 0; i < tracks.length; i++) {
  6910. if (tracks[i] !== this) {
  6911. tracks[i].removeCue(cue);
  6912. }
  6913. }
  6914. this.cues_.push(cue);
  6915. this.cues.setCues_(this.cues_);
  6916. }
  6917. /**
  6918. * Remove a cue from our internal list
  6919. *
  6920. * @param {TextTrack~Cue} removeCue
  6921. * The cue to remove from our internal list
  6922. */
  6923. ;
  6924. _proto.removeCue = function removeCue(_removeCue) {
  6925. var i = this.cues_.length;
  6926. while (i--) {
  6927. var cue = this.cues_[i];
  6928. if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
  6929. this.cues_.splice(i, 1);
  6930. this.cues.setCues_(this.cues_);
  6931. break;
  6932. }
  6933. }
  6934. };
  6935. return TextTrack;
  6936. }(Track);
  6937. /**
  6938. * cuechange - One or more cues in the track have become active or stopped being active.
  6939. */
  6940. TextTrack.prototype.allowedEvents_ = {
  6941. cuechange: 'cuechange'
  6942. };
  6943. /**
  6944. * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
  6945. * only one `AudioTrack` in the list will be enabled at a time.
  6946. *
  6947. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
  6948. * @extends Track
  6949. */
  6950. var AudioTrack = /*#__PURE__*/function (_Track) {
  6951. _inheritsLoose(AudioTrack, _Track);
  6952. /**
  6953. * Create an instance of this class.
  6954. *
  6955. * @param {Object} [options={}]
  6956. * Object of option names and values
  6957. *
  6958. * @param {AudioTrack~Kind} [options.kind='']
  6959. * A valid audio track kind
  6960. *
  6961. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6962. * A unique id for this AudioTrack.
  6963. *
  6964. * @param {string} [options.label='']
  6965. * The menu label for this track.
  6966. *
  6967. * @param {string} [options.language='']
  6968. * A valid two character language code.
  6969. *
  6970. * @param {boolean} [options.enabled]
  6971. * If this track is the one that is currently playing. If this track is part of
  6972. * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
  6973. */
  6974. function AudioTrack(options) {
  6975. var _this;
  6976. if (options === void 0) {
  6977. options = {};
  6978. }
  6979. var settings = mergeOptions$3(options, {
  6980. kind: AudioTrackKind[options.kind] || ''
  6981. });
  6982. _this = _Track.call(this, settings) || this;
  6983. var enabled = false;
  6984. /**
  6985. * @memberof AudioTrack
  6986. * @member {boolean} enabled
  6987. * If this `AudioTrack` is enabled or not. When setting this will
  6988. * fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
  6989. * @instance
  6990. *
  6991. * @fires VideoTrack#selectedchange
  6992. */
  6993. Object.defineProperty(_assertThisInitialized(_this), 'enabled', {
  6994. get: function get() {
  6995. return enabled;
  6996. },
  6997. set: function set(newEnabled) {
  6998. // an invalid or unchanged value
  6999. if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
  7000. return;
  7001. }
  7002. enabled = newEnabled;
  7003. /**
  7004. * An event that fires when enabled changes on this track. This allows
  7005. * the AudioTrackList that holds this track to act accordingly.
  7006. *
  7007. * > Note: This is not part of the spec! Native tracks will do
  7008. * this internally without an event.
  7009. *
  7010. * @event AudioTrack#enabledchange
  7011. * @type {EventTarget~Event}
  7012. */
  7013. this.trigger('enabledchange');
  7014. }
  7015. }); // if the user sets this track to selected then
  7016. // set selected to that true value otherwise
  7017. // we keep it false
  7018. if (settings.enabled) {
  7019. _this.enabled = settings.enabled;
  7020. }
  7021. _this.loaded_ = true;
  7022. return _this;
  7023. }
  7024. return AudioTrack;
  7025. }(Track);
  7026. /**
  7027. * A representation of a single `VideoTrack`.
  7028. *
  7029. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
  7030. * @extends Track
  7031. */
  7032. var VideoTrack = /*#__PURE__*/function (_Track) {
  7033. _inheritsLoose(VideoTrack, _Track);
  7034. /**
  7035. * Create an instance of this class.
  7036. *
  7037. * @param {Object} [options={}]
  7038. * Object of option names and values
  7039. *
  7040. * @param {string} [options.kind='']
  7041. * A valid {@link VideoTrack~Kind}
  7042. *
  7043. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  7044. * A unique id for this AudioTrack.
  7045. *
  7046. * @param {string} [options.label='']
  7047. * The menu label for this track.
  7048. *
  7049. * @param {string} [options.language='']
  7050. * A valid two character language code.
  7051. *
  7052. * @param {boolean} [options.selected]
  7053. * If this track is the one that is currently playing.
  7054. */
  7055. function VideoTrack(options) {
  7056. var _this;
  7057. if (options === void 0) {
  7058. options = {};
  7059. }
  7060. var settings = mergeOptions$3(options, {
  7061. kind: VideoTrackKind[options.kind] || ''
  7062. });
  7063. _this = _Track.call(this, settings) || this;
  7064. var selected = false;
  7065. /**
  7066. * @memberof VideoTrack
  7067. * @member {boolean} selected
  7068. * If this `VideoTrack` is selected or not. When setting this will
  7069. * fire {@link VideoTrack#selectedchange} if the state of selected changed.
  7070. * @instance
  7071. *
  7072. * @fires VideoTrack#selectedchange
  7073. */
  7074. Object.defineProperty(_assertThisInitialized(_this), 'selected', {
  7075. get: function get() {
  7076. return selected;
  7077. },
  7078. set: function set(newSelected) {
  7079. // an invalid or unchanged value
  7080. if (typeof newSelected !== 'boolean' || newSelected === selected) {
  7081. return;
  7082. }
  7083. selected = newSelected;
  7084. /**
  7085. * An event that fires when selected changes on this track. This allows
  7086. * the VideoTrackList that holds this track to act accordingly.
  7087. *
  7088. * > Note: This is not part of the spec! Native tracks will do
  7089. * this internally without an event.
  7090. *
  7091. * @event VideoTrack#selectedchange
  7092. * @type {EventTarget~Event}
  7093. */
  7094. this.trigger('selectedchange');
  7095. }
  7096. }); // if the user sets this track to selected then
  7097. // set selected to that true value otherwise
  7098. // we keep it false
  7099. if (settings.selected) {
  7100. _this.selected = settings.selected;
  7101. }
  7102. return _this;
  7103. }
  7104. return VideoTrack;
  7105. }(Track);
  7106. /**
  7107. * @memberof HTMLTrackElement
  7108. * @typedef {HTMLTrackElement~ReadyState}
  7109. * @enum {number}
  7110. */
  7111. var NONE = 0;
  7112. var LOADING = 1;
  7113. var LOADED = 2;
  7114. var ERROR = 3;
  7115. /**
  7116. * A single track represented in the DOM.
  7117. *
  7118. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
  7119. * @extends EventTarget
  7120. */
  7121. var HTMLTrackElement = /*#__PURE__*/function (_EventTarget) {
  7122. _inheritsLoose(HTMLTrackElement, _EventTarget);
  7123. /**
  7124. * Create an instance of this class.
  7125. *
  7126. * @param {Object} options={}
  7127. * Object of option names and values
  7128. *
  7129. * @param {Tech} options.tech
  7130. * A reference to the tech that owns this HTMLTrackElement.
  7131. *
  7132. * @param {TextTrack~Kind} [options.kind='subtitles']
  7133. * A valid text track kind.
  7134. *
  7135. * @param {TextTrack~Mode} [options.mode='disabled']
  7136. * A valid text track mode.
  7137. *
  7138. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  7139. * A unique id for this TextTrack.
  7140. *
  7141. * @param {string} [options.label='']
  7142. * The menu label for this track.
  7143. *
  7144. * @param {string} [options.language='']
  7145. * A valid two character language code.
  7146. *
  7147. * @param {string} [options.srclang='']
  7148. * A valid two character language code. An alternative, but deprioritized
  7149. * version of `options.language`
  7150. *
  7151. * @param {string} [options.src]
  7152. * A url to TextTrack cues.
  7153. *
  7154. * @param {boolean} [options.default]
  7155. * If this track should default to on or off.
  7156. */
  7157. function HTMLTrackElement(options) {
  7158. var _this;
  7159. if (options === void 0) {
  7160. options = {};
  7161. }
  7162. _this = _EventTarget.call(this) || this;
  7163. var readyState;
  7164. var track = new TextTrack(options);
  7165. _this.kind = track.kind;
  7166. _this.src = track.src;
  7167. _this.srclang = track.language;
  7168. _this.label = track.label;
  7169. _this["default"] = track["default"];
  7170. Object.defineProperties(_assertThisInitialized(_this), {
  7171. /**
  7172. * @memberof HTMLTrackElement
  7173. * @member {HTMLTrackElement~ReadyState} readyState
  7174. * The current ready state of the track element.
  7175. * @instance
  7176. */
  7177. readyState: {
  7178. get: function get() {
  7179. return readyState;
  7180. }
  7181. },
  7182. /**
  7183. * @memberof HTMLTrackElement
  7184. * @member {TextTrack} track
  7185. * The underlying TextTrack object.
  7186. * @instance
  7187. *
  7188. */
  7189. track: {
  7190. get: function get() {
  7191. return track;
  7192. }
  7193. }
  7194. });
  7195. readyState = NONE;
  7196. /**
  7197. * @listens TextTrack#loadeddata
  7198. * @fires HTMLTrackElement#load
  7199. */
  7200. track.addEventListener('loadeddata', function () {
  7201. readyState = LOADED;
  7202. _this.trigger({
  7203. type: 'load',
  7204. target: _assertThisInitialized(_this)
  7205. });
  7206. });
  7207. return _this;
  7208. }
  7209. return HTMLTrackElement;
  7210. }(EventTarget$2);
  7211. HTMLTrackElement.prototype.allowedEvents_ = {
  7212. load: 'load'
  7213. };
  7214. HTMLTrackElement.NONE = NONE;
  7215. HTMLTrackElement.LOADING = LOADING;
  7216. HTMLTrackElement.LOADED = LOADED;
  7217. HTMLTrackElement.ERROR = ERROR;
  7218. /*
  7219. * This file contains all track properties that are used in
  7220. * player.js, tech.js, html5.js and possibly other techs in the future.
  7221. */
  7222. var NORMAL = {
  7223. audio: {
  7224. ListClass: AudioTrackList,
  7225. TrackClass: AudioTrack,
  7226. capitalName: 'Audio'
  7227. },
  7228. video: {
  7229. ListClass: VideoTrackList,
  7230. TrackClass: VideoTrack,
  7231. capitalName: 'Video'
  7232. },
  7233. text: {
  7234. ListClass: TextTrackList,
  7235. TrackClass: TextTrack,
  7236. capitalName: 'Text'
  7237. }
  7238. };
  7239. Object.keys(NORMAL).forEach(function (type) {
  7240. NORMAL[type].getterName = type + "Tracks";
  7241. NORMAL[type].privateName = type + "Tracks_";
  7242. });
  7243. var REMOTE = {
  7244. remoteText: {
  7245. ListClass: TextTrackList,
  7246. TrackClass: TextTrack,
  7247. capitalName: 'RemoteText',
  7248. getterName: 'remoteTextTracks',
  7249. privateName: 'remoteTextTracks_'
  7250. },
  7251. remoteTextEl: {
  7252. ListClass: HtmlTrackElementList,
  7253. TrackClass: HTMLTrackElement,
  7254. capitalName: 'RemoteTextTrackEls',
  7255. getterName: 'remoteTextTrackEls',
  7256. privateName: 'remoteTextTrackEls_'
  7257. }
  7258. };
  7259. var ALL = _extends({}, NORMAL, REMOTE);
  7260. REMOTE.names = Object.keys(REMOTE);
  7261. NORMAL.names = Object.keys(NORMAL);
  7262. ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
  7263. /**
  7264. * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
  7265. * that just contains the src url alone.
  7266. * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
  7267. * `var SourceString = 'http://example.com/some-video.mp4';`
  7268. *
  7269. * @typedef {Object|string} Tech~SourceObject
  7270. *
  7271. * @property {string} src
  7272. * The url to the source
  7273. *
  7274. * @property {string} type
  7275. * The mime type of the source
  7276. */
  7277. /**
  7278. * A function used by {@link Tech} to create a new {@link TextTrack}.
  7279. *
  7280. * @private
  7281. *
  7282. * @param {Tech} self
  7283. * An instance of the Tech class.
  7284. *
  7285. * @param {string} kind
  7286. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  7287. *
  7288. * @param {string} [label]
  7289. * Label to identify the text track
  7290. *
  7291. * @param {string} [language]
  7292. * Two letter language abbreviation
  7293. *
  7294. * @param {Object} [options={}]
  7295. * An object with additional text track options
  7296. *
  7297. * @return {TextTrack}
  7298. * The text track that was created.
  7299. */
  7300. function createTrackHelper(self, kind, label, language, options) {
  7301. if (options === void 0) {
  7302. options = {};
  7303. }
  7304. var tracks = self.textTracks();
  7305. options.kind = kind;
  7306. if (label) {
  7307. options.label = label;
  7308. }
  7309. if (language) {
  7310. options.language = language;
  7311. }
  7312. options.tech = self;
  7313. var track = new ALL.text.TrackClass(options);
  7314. tracks.addTrack(track);
  7315. return track;
  7316. }
  7317. /**
  7318. * This is the base class for media playback technology controllers, such as
  7319. * {@link HTML5}
  7320. *
  7321. * @extends Component
  7322. */
  7323. var Tech = /*#__PURE__*/function (_Component) {
  7324. _inheritsLoose(Tech, _Component);
  7325. /**
  7326. * Create an instance of this Tech.
  7327. *
  7328. * @param {Object} [options]
  7329. * The key/value store of player options.
  7330. *
  7331. * @param {Component~ReadyCallback} ready
  7332. * Callback function to call when the `HTML5` Tech is ready.
  7333. */
  7334. function Tech(options, ready) {
  7335. var _this;
  7336. if (options === void 0) {
  7337. options = {};
  7338. }
  7339. if (ready === void 0) {
  7340. ready = function ready() {};
  7341. }
  7342. // we don't want the tech to report user activity automatically.
  7343. // This is done manually in addControlsListeners
  7344. options.reportTouchActivity = false;
  7345. _this = _Component.call(this, null, options, ready) || this;
  7346. _this.onDurationChange_ = function (e) {
  7347. return _this.onDurationChange(e);
  7348. };
  7349. _this.trackProgress_ = function (e) {
  7350. return _this.trackProgress(e);
  7351. };
  7352. _this.trackCurrentTime_ = function (e) {
  7353. return _this.trackCurrentTime(e);
  7354. };
  7355. _this.stopTrackingCurrentTime_ = function (e) {
  7356. return _this.stopTrackingCurrentTime(e);
  7357. };
  7358. _this.disposeSourceHandler_ = function (e) {
  7359. return _this.disposeSourceHandler(e);
  7360. };
  7361. _this.queuedHanders_ = new Set(); // keep track of whether the current source has played at all to
  7362. // implement a very limited played()
  7363. _this.hasStarted_ = false;
  7364. _this.on('playing', function () {
  7365. this.hasStarted_ = true;
  7366. });
  7367. _this.on('loadstart', function () {
  7368. this.hasStarted_ = false;
  7369. });
  7370. ALL.names.forEach(function (name) {
  7371. var props = ALL[name];
  7372. if (options && options[props.getterName]) {
  7373. _this[props.privateName] = options[props.getterName];
  7374. }
  7375. }); // Manually track progress in cases where the browser/tech doesn't report it.
  7376. if (!_this.featuresProgressEvents) {
  7377. _this.manualProgressOn();
  7378. } // Manually track timeupdates in cases where the browser/tech doesn't report it.
  7379. if (!_this.featuresTimeupdateEvents) {
  7380. _this.manualTimeUpdatesOn();
  7381. }
  7382. ['Text', 'Audio', 'Video'].forEach(function (track) {
  7383. if (options["native" + track + "Tracks"] === false) {
  7384. _this["featuresNative" + track + "Tracks"] = false;
  7385. }
  7386. });
  7387. if (options.nativeCaptions === false || options.nativeTextTracks === false) {
  7388. _this.featuresNativeTextTracks = false;
  7389. } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
  7390. _this.featuresNativeTextTracks = true;
  7391. }
  7392. if (!_this.featuresNativeTextTracks) {
  7393. _this.emulateTextTracks();
  7394. }
  7395. _this.preloadTextTracks = options.preloadTextTracks !== false;
  7396. _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
  7397. _this.initTrackListeners(); // Turn on component tap events only if not using native controls
  7398. if (!options.nativeControlsForTouch) {
  7399. _this.emitTapEvents();
  7400. }
  7401. if (_this.constructor) {
  7402. _this.name_ = _this.constructor.name || 'Unknown Tech';
  7403. }
  7404. return _this;
  7405. }
  7406. /**
  7407. * A special function to trigger source set in a way that will allow player
  7408. * to re-trigger if the player or tech are not ready yet.
  7409. *
  7410. * @fires Tech#sourceset
  7411. * @param {string} src The source string at the time of the source changing.
  7412. */
  7413. var _proto = Tech.prototype;
  7414. _proto.triggerSourceset = function triggerSourceset(src) {
  7415. var _this2 = this;
  7416. if (!this.isReady_) {
  7417. // on initial ready we have to trigger source set
  7418. // 1ms after ready so that player can watch for it.
  7419. this.one('ready', function () {
  7420. return _this2.setTimeout(function () {
  7421. return _this2.triggerSourceset(src);
  7422. }, 1);
  7423. });
  7424. }
  7425. /**
  7426. * Fired when the source is set on the tech causing the media element
  7427. * to reload.
  7428. *
  7429. * @see {@link Player#event:sourceset}
  7430. * @event Tech#sourceset
  7431. * @type {EventTarget~Event}
  7432. */
  7433. this.trigger({
  7434. src: src,
  7435. type: 'sourceset'
  7436. });
  7437. }
  7438. /* Fallbacks for unsupported event types
  7439. ================================================================================ */
  7440. /**
  7441. * Polyfill the `progress` event for browsers that don't support it natively.
  7442. *
  7443. * @see {@link Tech#trackProgress}
  7444. */
  7445. ;
  7446. _proto.manualProgressOn = function manualProgressOn() {
  7447. this.on('durationchange', this.onDurationChange_);
  7448. this.manualProgress = true; // Trigger progress watching when a source begins loading
  7449. this.one('ready', this.trackProgress_);
  7450. }
  7451. /**
  7452. * Turn off the polyfill for `progress` events that was created in
  7453. * {@link Tech#manualProgressOn}
  7454. */
  7455. ;
  7456. _proto.manualProgressOff = function manualProgressOff() {
  7457. this.manualProgress = false;
  7458. this.stopTrackingProgress();
  7459. this.off('durationchange', this.onDurationChange_);
  7460. }
  7461. /**
  7462. * This is used to trigger a `progress` event when the buffered percent changes. It
  7463. * sets an interval function that will be called every 500 milliseconds to check if the
  7464. * buffer end percent has changed.
  7465. *
  7466. * > This function is called by {@link Tech#manualProgressOn}
  7467. *
  7468. * @param {EventTarget~Event} event
  7469. * The `ready` event that caused this to run.
  7470. *
  7471. * @listens Tech#ready
  7472. * @fires Tech#progress
  7473. */
  7474. ;
  7475. _proto.trackProgress = function trackProgress(event) {
  7476. this.stopTrackingProgress();
  7477. this.progressInterval = this.setInterval(bind(this, function () {
  7478. // Don't trigger unless buffered amount is greater than last time
  7479. var numBufferedPercent = this.bufferedPercent();
  7480. if (this.bufferedPercent_ !== numBufferedPercent) {
  7481. /**
  7482. * See {@link Player#progress}
  7483. *
  7484. * @event Tech#progress
  7485. * @type {EventTarget~Event}
  7486. */
  7487. this.trigger('progress');
  7488. }
  7489. this.bufferedPercent_ = numBufferedPercent;
  7490. if (numBufferedPercent === 1) {
  7491. this.stopTrackingProgress();
  7492. }
  7493. }), 500);
  7494. }
  7495. /**
  7496. * Update our internal duration on a `durationchange` event by calling
  7497. * {@link Tech#duration}.
  7498. *
  7499. * @param {EventTarget~Event} event
  7500. * The `durationchange` event that caused this to run.
  7501. *
  7502. * @listens Tech#durationchange
  7503. */
  7504. ;
  7505. _proto.onDurationChange = function onDurationChange(event) {
  7506. this.duration_ = this.duration();
  7507. }
  7508. /**
  7509. * Get and create a `TimeRange` object for buffering.
  7510. *
  7511. * @return {TimeRange}
  7512. * The time range object that was created.
  7513. */
  7514. ;
  7515. _proto.buffered = function buffered() {
  7516. return createTimeRanges(0, 0);
  7517. }
  7518. /**
  7519. * Get the percentage of the current video that is currently buffered.
  7520. *
  7521. * @return {number}
  7522. * A number from 0 to 1 that represents the decimal percentage of the
  7523. * video that is buffered.
  7524. *
  7525. */
  7526. ;
  7527. _proto.bufferedPercent = function bufferedPercent$1() {
  7528. return bufferedPercent(this.buffered(), this.duration_);
  7529. }
  7530. /**
  7531. * Turn off the polyfill for `progress` events that was created in
  7532. * {@link Tech#manualProgressOn}
  7533. * Stop manually tracking progress events by clearing the interval that was set in
  7534. * {@link Tech#trackProgress}.
  7535. */
  7536. ;
  7537. _proto.stopTrackingProgress = function stopTrackingProgress() {
  7538. this.clearInterval(this.progressInterval);
  7539. }
  7540. /**
  7541. * Polyfill the `timeupdate` event for browsers that don't support it.
  7542. *
  7543. * @see {@link Tech#trackCurrentTime}
  7544. */
  7545. ;
  7546. _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
  7547. this.manualTimeUpdates = true;
  7548. this.on('play', this.trackCurrentTime_);
  7549. this.on('pause', this.stopTrackingCurrentTime_);
  7550. }
  7551. /**
  7552. * Turn off the polyfill for `timeupdate` events that was created in
  7553. * {@link Tech#manualTimeUpdatesOn}
  7554. */
  7555. ;
  7556. _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
  7557. this.manualTimeUpdates = false;
  7558. this.stopTrackingCurrentTime();
  7559. this.off('play', this.trackCurrentTime_);
  7560. this.off('pause', this.stopTrackingCurrentTime_);
  7561. }
  7562. /**
  7563. * Sets up an interval function to track current time and trigger `timeupdate` every
  7564. * 250 milliseconds.
  7565. *
  7566. * @listens Tech#play
  7567. * @triggers Tech#timeupdate
  7568. */
  7569. ;
  7570. _proto.trackCurrentTime = function trackCurrentTime() {
  7571. if (this.currentTimeInterval) {
  7572. this.stopTrackingCurrentTime();
  7573. }
  7574. this.currentTimeInterval = this.setInterval(function () {
  7575. /**
  7576. * Triggered at an interval of 250ms to indicated that time is passing in the video.
  7577. *
  7578. * @event Tech#timeupdate
  7579. * @type {EventTarget~Event}
  7580. */
  7581. this.trigger({
  7582. type: 'timeupdate',
  7583. target: this,
  7584. manuallyTriggered: true
  7585. }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  7586. }, 250);
  7587. }
  7588. /**
  7589. * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
  7590. * `timeupdate` event is no longer triggered.
  7591. *
  7592. * @listens {Tech#pause}
  7593. */
  7594. ;
  7595. _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
  7596. this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen,
  7597. // the progress bar won't make it all the way to the end
  7598. this.trigger({
  7599. type: 'timeupdate',
  7600. target: this,
  7601. manuallyTriggered: true
  7602. });
  7603. }
  7604. /**
  7605. * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
  7606. * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
  7607. *
  7608. * @fires Component#dispose
  7609. */
  7610. ;
  7611. _proto.dispose = function dispose() {
  7612. // clear out all tracks because we can't reuse them between techs
  7613. this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking
  7614. if (this.manualProgress) {
  7615. this.manualProgressOff();
  7616. }
  7617. if (this.manualTimeUpdates) {
  7618. this.manualTimeUpdatesOff();
  7619. }
  7620. _Component.prototype.dispose.call(this);
  7621. }
  7622. /**
  7623. * Clear out a single `TrackList` or an array of `TrackLists` given their names.
  7624. *
  7625. * > Note: Techs without source handlers should call this between sources for `video`
  7626. * & `audio` tracks. You don't want to use them between tracks!
  7627. *
  7628. * @param {string[]|string} types
  7629. * TrackList names to clear, valid names are `video`, `audio`, and
  7630. * `text`.
  7631. */
  7632. ;
  7633. _proto.clearTracks = function clearTracks(types) {
  7634. var _this3 = this;
  7635. types = [].concat(types); // clear out all tracks because we can't reuse them between techs
  7636. types.forEach(function (type) {
  7637. var list = _this3[type + "Tracks"]() || [];
  7638. var i = list.length;
  7639. while (i--) {
  7640. var track = list[i];
  7641. if (type === 'text') {
  7642. _this3.removeRemoteTextTrack(track);
  7643. }
  7644. list.removeTrack(track);
  7645. }
  7646. });
  7647. }
  7648. /**
  7649. * Remove any TextTracks added via addRemoteTextTrack that are
  7650. * flagged for automatic garbage collection
  7651. */
  7652. ;
  7653. _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
  7654. var list = this.autoRemoteTextTracks_ || [];
  7655. var i = list.length;
  7656. while (i--) {
  7657. var track = list[i];
  7658. this.removeRemoteTextTrack(track);
  7659. }
  7660. }
  7661. /**
  7662. * Reset the tech, which will removes all sources and reset the internal readyState.
  7663. *
  7664. * @abstract
  7665. */
  7666. ;
  7667. _proto.reset = function reset() {}
  7668. /**
  7669. * Get the value of `crossOrigin` from the tech.
  7670. *
  7671. * @abstract
  7672. *
  7673. * @see {Html5#crossOrigin}
  7674. */
  7675. ;
  7676. _proto.crossOrigin = function crossOrigin() {}
  7677. /**
  7678. * Set the value of `crossOrigin` on the tech.
  7679. *
  7680. * @abstract
  7681. *
  7682. * @param {string} crossOrigin the crossOrigin value
  7683. * @see {Html5#setCrossOrigin}
  7684. */
  7685. ;
  7686. _proto.setCrossOrigin = function setCrossOrigin() {}
  7687. /**
  7688. * Get or set an error on the Tech.
  7689. *
  7690. * @param {MediaError} [err]
  7691. * Error to set on the Tech
  7692. *
  7693. * @return {MediaError|null}
  7694. * The current error object on the tech, or null if there isn't one.
  7695. */
  7696. ;
  7697. _proto.error = function error(err) {
  7698. if (err !== undefined) {
  7699. this.error_ = new MediaError(err);
  7700. this.trigger('error');
  7701. }
  7702. return this.error_;
  7703. }
  7704. /**
  7705. * Returns the `TimeRange`s that have been played through for the current source.
  7706. *
  7707. * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
  7708. * It only checks whether the source has played at all or not.
  7709. *
  7710. * @return {TimeRange}
  7711. * - A single time range if this video has played
  7712. * - An empty set of ranges if not.
  7713. */
  7714. ;
  7715. _proto.played = function played() {
  7716. if (this.hasStarted_) {
  7717. return createTimeRanges(0, 0);
  7718. }
  7719. return createTimeRanges();
  7720. }
  7721. /**
  7722. * Start playback
  7723. *
  7724. * @abstract
  7725. *
  7726. * @see {Html5#play}
  7727. */
  7728. ;
  7729. _proto.play = function play() {}
  7730. /**
  7731. * Set whether we are scrubbing or not
  7732. *
  7733. * @abstract
  7734. *
  7735. * @see {Html5#setScrubbing}
  7736. */
  7737. ;
  7738. _proto.setScrubbing = function setScrubbing() {}
  7739. /**
  7740. * Get whether we are scrubbing or not
  7741. *
  7742. * @abstract
  7743. *
  7744. * @see {Html5#scrubbing}
  7745. */
  7746. ;
  7747. _proto.scrubbing = function scrubbing() {}
  7748. /**
  7749. * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
  7750. * previously called.
  7751. *
  7752. * @fires Tech#timeupdate
  7753. */
  7754. ;
  7755. _proto.setCurrentTime = function setCurrentTime() {
  7756. // improve the accuracy of manual timeupdates
  7757. if (this.manualTimeUpdates) {
  7758. /**
  7759. * A manual `timeupdate` event.
  7760. *
  7761. * @event Tech#timeupdate
  7762. * @type {EventTarget~Event}
  7763. */
  7764. this.trigger({
  7765. type: 'timeupdate',
  7766. target: this,
  7767. manuallyTriggered: true
  7768. });
  7769. }
  7770. }
  7771. /**
  7772. * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
  7773. * {@link TextTrackList} events.
  7774. *
  7775. * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`.
  7776. *
  7777. * @fires Tech#audiotrackchange
  7778. * @fires Tech#videotrackchange
  7779. * @fires Tech#texttrackchange
  7780. */
  7781. ;
  7782. _proto.initTrackListeners = function initTrackListeners() {
  7783. var _this4 = this;
  7784. /**
  7785. * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
  7786. *
  7787. * @event Tech#audiotrackchange
  7788. * @type {EventTarget~Event}
  7789. */
  7790. /**
  7791. * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
  7792. *
  7793. * @event Tech#videotrackchange
  7794. * @type {EventTarget~Event}
  7795. */
  7796. /**
  7797. * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
  7798. *
  7799. * @event Tech#texttrackchange
  7800. * @type {EventTarget~Event}
  7801. */
  7802. NORMAL.names.forEach(function (name) {
  7803. var props = NORMAL[name];
  7804. var trackListChanges = function trackListChanges() {
  7805. _this4.trigger(name + "trackchange");
  7806. };
  7807. var tracks = _this4[props.getterName]();
  7808. tracks.addEventListener('removetrack', trackListChanges);
  7809. tracks.addEventListener('addtrack', trackListChanges);
  7810. _this4.on('dispose', function () {
  7811. tracks.removeEventListener('removetrack', trackListChanges);
  7812. tracks.removeEventListener('addtrack', trackListChanges);
  7813. });
  7814. });
  7815. }
  7816. /**
  7817. * Emulate TextTracks using vtt.js if necessary
  7818. *
  7819. * @fires Tech#vttjsloaded
  7820. * @fires Tech#vttjserror
  7821. */
  7822. ;
  7823. _proto.addWebVttScript_ = function addWebVttScript_() {
  7824. var _this5 = this;
  7825. if (window$1.WebVTT) {
  7826. return;
  7827. } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
  7828. // signals that the Tech is ready at which point Tech.el_ is part of the DOM
  7829. // before inserting the WebVTT script
  7830. if (document.body.contains(this.el())) {
  7831. // load via require if available and vtt.js script location was not passed in
  7832. // as an option. novtt builds will turn the above require call into an empty object
  7833. // which will cause this if check to always fail.
  7834. if (!this.options_['vtt.js'] && isPlain(vtt) && Object.keys(vtt).length > 0) {
  7835. this.trigger('vttjsloaded');
  7836. return;
  7837. } // load vtt.js via the script location option or the cdn of no location was
  7838. // passed in
  7839. var script = document.createElement('script');
  7840. script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
  7841. script.onload = function () {
  7842. /**
  7843. * Fired when vtt.js is loaded.
  7844. *
  7845. * @event Tech#vttjsloaded
  7846. * @type {EventTarget~Event}
  7847. */
  7848. _this5.trigger('vttjsloaded');
  7849. };
  7850. script.onerror = function () {
  7851. /**
  7852. * Fired when vtt.js was not loaded due to an error
  7853. *
  7854. * @event Tech#vttjsloaded
  7855. * @type {EventTarget~Event}
  7856. */
  7857. _this5.trigger('vttjserror');
  7858. };
  7859. this.on('dispose', function () {
  7860. script.onload = null;
  7861. script.onerror = null;
  7862. }); // but have not loaded yet and we set it to true before the inject so that
  7863. // we don't overwrite the injected window.WebVTT if it loads right away
  7864. window$1.WebVTT = true;
  7865. this.el().parentNode.appendChild(script);
  7866. } else {
  7867. this.ready(this.addWebVttScript_);
  7868. }
  7869. }
  7870. /**
  7871. * Emulate texttracks
  7872. *
  7873. */
  7874. ;
  7875. _proto.emulateTextTracks = function emulateTextTracks() {
  7876. var _this6 = this;
  7877. var tracks = this.textTracks();
  7878. var remoteTracks = this.remoteTextTracks();
  7879. var handleAddTrack = function handleAddTrack(e) {
  7880. return tracks.addTrack(e.track);
  7881. };
  7882. var handleRemoveTrack = function handleRemoveTrack(e) {
  7883. return tracks.removeTrack(e.track);
  7884. };
  7885. remoteTracks.on('addtrack', handleAddTrack);
  7886. remoteTracks.on('removetrack', handleRemoveTrack);
  7887. this.addWebVttScript_();
  7888. var updateDisplay = function updateDisplay() {
  7889. return _this6.trigger('texttrackchange');
  7890. };
  7891. var textTracksChanges = function textTracksChanges() {
  7892. updateDisplay();
  7893. for (var i = 0; i < tracks.length; i++) {
  7894. var track = tracks[i];
  7895. track.removeEventListener('cuechange', updateDisplay);
  7896. if (track.mode === 'showing') {
  7897. track.addEventListener('cuechange', updateDisplay);
  7898. }
  7899. }
  7900. };
  7901. textTracksChanges();
  7902. tracks.addEventListener('change', textTracksChanges);
  7903. tracks.addEventListener('addtrack', textTracksChanges);
  7904. tracks.addEventListener('removetrack', textTracksChanges);
  7905. this.on('dispose', function () {
  7906. remoteTracks.off('addtrack', handleAddTrack);
  7907. remoteTracks.off('removetrack', handleRemoveTrack);
  7908. tracks.removeEventListener('change', textTracksChanges);
  7909. tracks.removeEventListener('addtrack', textTracksChanges);
  7910. tracks.removeEventListener('removetrack', textTracksChanges);
  7911. for (var i = 0; i < tracks.length; i++) {
  7912. var track = tracks[i];
  7913. track.removeEventListener('cuechange', updateDisplay);
  7914. }
  7915. });
  7916. }
  7917. /**
  7918. * Create and returns a remote {@link TextTrack} object.
  7919. *
  7920. * @param {string} kind
  7921. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  7922. *
  7923. * @param {string} [label]
  7924. * Label to identify the text track
  7925. *
  7926. * @param {string} [language]
  7927. * Two letter language abbreviation
  7928. *
  7929. * @return {TextTrack}
  7930. * The TextTrack that gets created.
  7931. */
  7932. ;
  7933. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  7934. if (!kind) {
  7935. throw new Error('TextTrack kind is required but was not provided');
  7936. }
  7937. return createTrackHelper(this, kind, label, language);
  7938. }
  7939. /**
  7940. * Create an emulated TextTrack for use by addRemoteTextTrack
  7941. *
  7942. * This is intended to be overridden by classes that inherit from
  7943. * Tech in order to create native or custom TextTracks.
  7944. *
  7945. * @param {Object} options
  7946. * The object should contain the options to initialize the TextTrack with.
  7947. *
  7948. * @param {string} [options.kind]
  7949. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  7950. *
  7951. * @param {string} [options.label].
  7952. * Label to identify the text track
  7953. *
  7954. * @param {string} [options.language]
  7955. * Two letter language abbreviation.
  7956. *
  7957. * @return {HTMLTrackElement}
  7958. * The track element that gets created.
  7959. */
  7960. ;
  7961. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  7962. var track = mergeOptions$3(options, {
  7963. tech: this
  7964. });
  7965. return new REMOTE.remoteTextEl.TrackClass(track);
  7966. }
  7967. /**
  7968. * Creates a remote text track object and returns an html track element.
  7969. *
  7970. * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
  7971. *
  7972. * @param {Object} options
  7973. * See {@link Tech#createRemoteTextTrack} for more detailed properties.
  7974. *
  7975. * @param {boolean} [manualCleanup=true]
  7976. * - When false: the TextTrack will be automatically removed from the video
  7977. * element whenever the source changes
  7978. * - When True: The TextTrack will have to be cleaned up manually
  7979. *
  7980. * @return {HTMLTrackElement}
  7981. * An Html Track Element.
  7982. *
  7983. * @deprecated The default functionality for this function will be equivalent
  7984. * to "manualCleanup=false" in the future. The manualCleanup parameter will
  7985. * also be removed.
  7986. */
  7987. ;
  7988. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  7989. var _this7 = this;
  7990. if (options === void 0) {
  7991. options = {};
  7992. }
  7993. var htmlTrackElement = this.createRemoteTextTrack(options);
  7994. if (manualCleanup !== true && manualCleanup !== false) {
  7995. // deprecation warning
  7996. log$1.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
  7997. manualCleanup = true;
  7998. } // store HTMLTrackElement and TextTrack to remote list
  7999. this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
  8000. this.remoteTextTracks().addTrack(htmlTrackElement.track);
  8001. if (manualCleanup !== true) {
  8002. // create the TextTrackList if it doesn't exist
  8003. this.ready(function () {
  8004. return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
  8005. });
  8006. }
  8007. return htmlTrackElement;
  8008. }
  8009. /**
  8010. * Remove a remote text track from the remote `TextTrackList`.
  8011. *
  8012. * @param {TextTrack} track
  8013. * `TextTrack` to remove from the `TextTrackList`
  8014. */
  8015. ;
  8016. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  8017. var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list
  8018. this.remoteTextTrackEls().removeTrackElement_(trackElement);
  8019. this.remoteTextTracks().removeTrack(track);
  8020. this.autoRemoteTextTracks_.removeTrack(track);
  8021. }
  8022. /**
  8023. * Gets available media playback quality metrics as specified by the W3C's Media
  8024. * Playback Quality API.
  8025. *
  8026. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  8027. *
  8028. * @return {Object}
  8029. * An object with supported media playback quality metrics
  8030. *
  8031. * @abstract
  8032. */
  8033. ;
  8034. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  8035. return {};
  8036. }
  8037. /**
  8038. * Attempt to create a floating video window always on top of other windows
  8039. * so that users may continue consuming media while they interact with other
  8040. * content sites, or applications on their device.
  8041. *
  8042. * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
  8043. *
  8044. * @return {Promise|undefined}
  8045. * A promise with a Picture-in-Picture window if the browser supports
  8046. * Promises (or one was passed in as an option). It returns undefined
  8047. * otherwise.
  8048. *
  8049. * @abstract
  8050. */
  8051. ;
  8052. _proto.requestPictureInPicture = function requestPictureInPicture() {
  8053. var PromiseClass = this.options_.Promise || window$1.Promise;
  8054. if (PromiseClass) {
  8055. return PromiseClass.reject();
  8056. }
  8057. }
  8058. /**
  8059. * A method to check for the value of the 'disablePictureInPicture' <video> property.
  8060. * Defaults to true, as it should be considered disabled if the tech does not support pip
  8061. *
  8062. * @abstract
  8063. */
  8064. ;
  8065. _proto.disablePictureInPicture = function disablePictureInPicture() {
  8066. return true;
  8067. }
  8068. /**
  8069. * A method to set or unset the 'disablePictureInPicture' <video> property.
  8070. *
  8071. * @abstract
  8072. */
  8073. ;
  8074. _proto.setDisablePictureInPicture = function setDisablePictureInPicture() {}
  8075. /**
  8076. * A fallback implementation of requestVideoFrameCallback using requestAnimationFrame
  8077. *
  8078. * @param {function} cb
  8079. * @return {number} request id
  8080. */
  8081. ;
  8082. _proto.requestVideoFrameCallback = function requestVideoFrameCallback(cb) {
  8083. var _this8 = this;
  8084. var id = newGUID();
  8085. if (!this.isReady_ || this.paused()) {
  8086. this.queuedHanders_.add(id);
  8087. this.one('playing', function () {
  8088. if (_this8.queuedHanders_.has(id)) {
  8089. _this8.queuedHanders_["delete"](id);
  8090. cb();
  8091. }
  8092. });
  8093. } else {
  8094. this.requestNamedAnimationFrame(id, cb);
  8095. }
  8096. return id;
  8097. }
  8098. /**
  8099. * A fallback implementation of cancelVideoFrameCallback
  8100. *
  8101. * @param {number} id id of callback to be cancelled
  8102. */
  8103. ;
  8104. _proto.cancelVideoFrameCallback = function cancelVideoFrameCallback(id) {
  8105. if (this.queuedHanders_.has(id)) {
  8106. this.queuedHanders_["delete"](id);
  8107. } else {
  8108. this.cancelNamedAnimationFrame(id);
  8109. }
  8110. }
  8111. /**
  8112. * A method to set a poster from a `Tech`.
  8113. *
  8114. * @abstract
  8115. */
  8116. ;
  8117. _proto.setPoster = function setPoster() {}
  8118. /**
  8119. * A method to check for the presence of the 'playsinline' <video> attribute.
  8120. *
  8121. * @abstract
  8122. */
  8123. ;
  8124. _proto.playsinline = function playsinline() {}
  8125. /**
  8126. * A method to set or unset the 'playsinline' <video> attribute.
  8127. *
  8128. * @abstract
  8129. */
  8130. ;
  8131. _proto.setPlaysinline = function setPlaysinline() {}
  8132. /**
  8133. * Attempt to force override of native audio tracks.
  8134. *
  8135. * @param {boolean} override - If set to true native audio will be overridden,
  8136. * otherwise native audio will potentially be used.
  8137. *
  8138. * @abstract
  8139. */
  8140. ;
  8141. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {}
  8142. /**
  8143. * Attempt to force override of native video tracks.
  8144. *
  8145. * @param {boolean} override - If set to true native video will be overridden,
  8146. * otherwise native video will potentially be used.
  8147. *
  8148. * @abstract
  8149. */
  8150. ;
  8151. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {}
  8152. /*
  8153. * Check if the tech can support the given mime-type.
  8154. *
  8155. * The base tech does not support any type, but source handlers might
  8156. * overwrite this.
  8157. *
  8158. * @param {string} type
  8159. * The mimetype to check for support
  8160. *
  8161. * @return {string}
  8162. * 'probably', 'maybe', or empty string
  8163. *
  8164. * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
  8165. *
  8166. * @abstract
  8167. */
  8168. ;
  8169. _proto.canPlayType = function canPlayType() {
  8170. return '';
  8171. }
  8172. /**
  8173. * Check if the type is supported by this tech.
  8174. *
  8175. * The base tech does not support any type, but source handlers might
  8176. * overwrite this.
  8177. *
  8178. * @param {string} type
  8179. * The media type to check
  8180. * @return {string} Returns the native video element's response
  8181. */
  8182. ;
  8183. Tech.canPlayType = function canPlayType() {
  8184. return '';
  8185. }
  8186. /**
  8187. * Check if the tech can support the given source
  8188. *
  8189. * @param {Object} srcObj
  8190. * The source object
  8191. * @param {Object} options
  8192. * The options passed to the tech
  8193. * @return {string} 'probably', 'maybe', or '' (empty string)
  8194. */
  8195. ;
  8196. Tech.canPlaySource = function canPlaySource(srcObj, options) {
  8197. return Tech.canPlayType(srcObj.type);
  8198. }
  8199. /*
  8200. * Return whether the argument is a Tech or not.
  8201. * Can be passed either a Class like `Html5` or a instance like `player.tech_`
  8202. *
  8203. * @param {Object} component
  8204. * The item to check
  8205. *
  8206. * @return {boolean}
  8207. * Whether it is a tech or not
  8208. * - True if it is a tech
  8209. * - False if it is not
  8210. */
  8211. ;
  8212. Tech.isTech = function isTech(component) {
  8213. return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
  8214. }
  8215. /**
  8216. * Registers a `Tech` into a shared list for videojs.
  8217. *
  8218. * @param {string} name
  8219. * Name of the `Tech` to register.
  8220. *
  8221. * @param {Object} tech
  8222. * The `Tech` class to register.
  8223. */
  8224. ;
  8225. Tech.registerTech = function registerTech(name, tech) {
  8226. if (!Tech.techs_) {
  8227. Tech.techs_ = {};
  8228. }
  8229. if (!Tech.isTech(tech)) {
  8230. throw new Error("Tech " + name + " must be a Tech");
  8231. }
  8232. if (!Tech.canPlayType) {
  8233. throw new Error('Techs must have a static canPlayType method on them');
  8234. }
  8235. if (!Tech.canPlaySource) {
  8236. throw new Error('Techs must have a static canPlaySource method on them');
  8237. }
  8238. name = toTitleCase$1(name);
  8239. Tech.techs_[name] = tech;
  8240. Tech.techs_[toLowerCase(name)] = tech;
  8241. if (name !== 'Tech') {
  8242. // camel case the techName for use in techOrder
  8243. Tech.defaultTechOrder_.push(name);
  8244. }
  8245. return tech;
  8246. }
  8247. /**
  8248. * Get a `Tech` from the shared list by name.
  8249. *
  8250. * @param {string} name
  8251. * `camelCase` or `TitleCase` name of the Tech to get
  8252. *
  8253. * @return {Tech|undefined}
  8254. * The `Tech` or undefined if there was no tech with the name requested.
  8255. */
  8256. ;
  8257. Tech.getTech = function getTech(name) {
  8258. if (!name) {
  8259. return;
  8260. }
  8261. if (Tech.techs_ && Tech.techs_[name]) {
  8262. return Tech.techs_[name];
  8263. }
  8264. name = toTitleCase$1(name);
  8265. if (window$1 && window$1.videojs && window$1.videojs[name]) {
  8266. log$1.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)");
  8267. return window$1.videojs[name];
  8268. }
  8269. };
  8270. return Tech;
  8271. }(Component$1);
  8272. /**
  8273. * Get the {@link VideoTrackList}
  8274. *
  8275. * @returns {VideoTrackList}
  8276. * @method Tech.prototype.videoTracks
  8277. */
  8278. /**
  8279. * Get the {@link AudioTrackList}
  8280. *
  8281. * @returns {AudioTrackList}
  8282. * @method Tech.prototype.audioTracks
  8283. */
  8284. /**
  8285. * Get the {@link TextTrackList}
  8286. *
  8287. * @returns {TextTrackList}
  8288. * @method Tech.prototype.textTracks
  8289. */
  8290. /**
  8291. * Get the remote element {@link TextTrackList}
  8292. *
  8293. * @returns {TextTrackList}
  8294. * @method Tech.prototype.remoteTextTracks
  8295. */
  8296. /**
  8297. * Get the remote element {@link HtmlTrackElementList}
  8298. *
  8299. * @returns {HtmlTrackElementList}
  8300. * @method Tech.prototype.remoteTextTrackEls
  8301. */
  8302. ALL.names.forEach(function (name) {
  8303. var props = ALL[name];
  8304. Tech.prototype[props.getterName] = function () {
  8305. this[props.privateName] = this[props.privateName] || new props.ListClass();
  8306. return this[props.privateName];
  8307. };
  8308. });
  8309. /**
  8310. * List of associated text tracks
  8311. *
  8312. * @type {TextTrackList}
  8313. * @private
  8314. * @property Tech#textTracks_
  8315. */
  8316. /**
  8317. * List of associated audio tracks.
  8318. *
  8319. * @type {AudioTrackList}
  8320. * @private
  8321. * @property Tech#audioTracks_
  8322. */
  8323. /**
  8324. * List of associated video tracks.
  8325. *
  8326. * @type {VideoTrackList}
  8327. * @private
  8328. * @property Tech#videoTracks_
  8329. */
  8330. /**
  8331. * Boolean indicating whether the `Tech` supports volume control.
  8332. *
  8333. * @type {boolean}
  8334. * @default
  8335. */
  8336. Tech.prototype.featuresVolumeControl = true;
  8337. /**
  8338. * Boolean indicating whether the `Tech` supports muting volume.
  8339. *
  8340. * @type {bolean}
  8341. * @default
  8342. */
  8343. Tech.prototype.featuresMuteControl = true;
  8344. /**
  8345. * Boolean indicating whether the `Tech` supports fullscreen resize control.
  8346. * Resizing plugins using request fullscreen reloads the plugin
  8347. *
  8348. * @type {boolean}
  8349. * @default
  8350. */
  8351. Tech.prototype.featuresFullscreenResize = false;
  8352. /**
  8353. * Boolean indicating whether the `Tech` supports changing the speed at which the video
  8354. * plays. Examples:
  8355. * - Set player to play 2x (twice) as fast
  8356. * - Set player to play 0.5x (half) as fast
  8357. *
  8358. * @type {boolean}
  8359. * @default
  8360. */
  8361. Tech.prototype.featuresPlaybackRate = false;
  8362. /**
  8363. * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
  8364. * not triggered by video-js-swf. This will be used to determine if
  8365. * {@link Tech#manualProgressOn} should be called.
  8366. *
  8367. * @type {boolean}
  8368. * @default
  8369. */
  8370. Tech.prototype.featuresProgressEvents = false;
  8371. /**
  8372. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  8373. *
  8374. * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
  8375. * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
  8376. * a new source.
  8377. *
  8378. * @type {boolean}
  8379. * @default
  8380. */
  8381. Tech.prototype.featuresSourceset = false;
  8382. /**
  8383. * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
  8384. * not triggered by video-js-swf. This will be used to determine if
  8385. * {@link Tech#manualTimeUpdates} should be called.
  8386. *
  8387. * @type {boolean}
  8388. * @default
  8389. */
  8390. Tech.prototype.featuresTimeupdateEvents = false;
  8391. /**
  8392. * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
  8393. * This will help us integrate with native `TextTrack`s if the browser supports them.
  8394. *
  8395. * @type {boolean}
  8396. * @default
  8397. */
  8398. Tech.prototype.featuresNativeTextTracks = false;
  8399. /**
  8400. * Boolean indicating whether the `Tech` supports `requestVideoFrameCallback`.
  8401. *
  8402. * @type {boolean}
  8403. * @default
  8404. */
  8405. Tech.prototype.featuresVideoFrameCallback = false;
  8406. /**
  8407. * A functional mixin for techs that want to use the Source Handler pattern.
  8408. * Source handlers are scripts for handling specific formats.
  8409. * The source handler pattern is used for adaptive formats (HLS, DASH) that
  8410. * manually load video data and feed it into a Source Buffer (Media Source Extensions)
  8411. * Example: `Tech.withSourceHandlers.call(MyTech);`
  8412. *
  8413. * @param {Tech} _Tech
  8414. * The tech to add source handler functions to.
  8415. *
  8416. * @mixes Tech~SourceHandlerAdditions
  8417. */
  8418. Tech.withSourceHandlers = function (_Tech) {
  8419. /**
  8420. * Register a source handler
  8421. *
  8422. * @param {Function} handler
  8423. * The source handler class
  8424. *
  8425. * @param {number} [index]
  8426. * Register it at the following index
  8427. */
  8428. _Tech.registerSourceHandler = function (handler, index) {
  8429. var handlers = _Tech.sourceHandlers;
  8430. if (!handlers) {
  8431. handlers = _Tech.sourceHandlers = [];
  8432. }
  8433. if (index === undefined) {
  8434. // add to the end of the list
  8435. index = handlers.length;
  8436. }
  8437. handlers.splice(index, 0, handler);
  8438. };
  8439. /**
  8440. * Check if the tech can support the given type. Also checks the
  8441. * Techs sourceHandlers.
  8442. *
  8443. * @param {string} type
  8444. * The mimetype to check.
  8445. *
  8446. * @return {string}
  8447. * 'probably', 'maybe', or '' (empty string)
  8448. */
  8449. _Tech.canPlayType = function (type) {
  8450. var handlers = _Tech.sourceHandlers || [];
  8451. var can;
  8452. for (var i = 0; i < handlers.length; i++) {
  8453. can = handlers[i].canPlayType(type);
  8454. if (can) {
  8455. return can;
  8456. }
  8457. }
  8458. return '';
  8459. };
  8460. /**
  8461. * Returns the first source handler that supports the source.
  8462. *
  8463. * TODO: Answer question: should 'probably' be prioritized over 'maybe'
  8464. *
  8465. * @param {Tech~SourceObject} source
  8466. * The source object
  8467. *
  8468. * @param {Object} options
  8469. * The options passed to the tech
  8470. *
  8471. * @return {SourceHandler|null}
  8472. * The first source handler that supports the source or null if
  8473. * no SourceHandler supports the source
  8474. */
  8475. _Tech.selectSourceHandler = function (source, options) {
  8476. var handlers = _Tech.sourceHandlers || [];
  8477. var can;
  8478. for (var i = 0; i < handlers.length; i++) {
  8479. can = handlers[i].canHandleSource(source, options);
  8480. if (can) {
  8481. return handlers[i];
  8482. }
  8483. }
  8484. return null;
  8485. };
  8486. /**
  8487. * Check if the tech can support the given source.
  8488. *
  8489. * @param {Tech~SourceObject} srcObj
  8490. * The source object
  8491. *
  8492. * @param {Object} options
  8493. * The options passed to the tech
  8494. *
  8495. * @return {string}
  8496. * 'probably', 'maybe', or '' (empty string)
  8497. */
  8498. _Tech.canPlaySource = function (srcObj, options) {
  8499. var sh = _Tech.selectSourceHandler(srcObj, options);
  8500. if (sh) {
  8501. return sh.canHandleSource(srcObj, options);
  8502. }
  8503. return '';
  8504. };
  8505. /**
  8506. * When using a source handler, prefer its implementation of
  8507. * any function normally provided by the tech.
  8508. */
  8509. var deferrable = ['seekable', 'seeking', 'duration'];
  8510. /**
  8511. * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
  8512. * function if it exists, with a fallback to the Techs seekable function.
  8513. *
  8514. * @method _Tech.seekable
  8515. */
  8516. /**
  8517. * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
  8518. * function if it exists, otherwise it will fallback to the techs duration function.
  8519. *
  8520. * @method _Tech.duration
  8521. */
  8522. deferrable.forEach(function (fnName) {
  8523. var originalFn = this[fnName];
  8524. if (typeof originalFn !== 'function') {
  8525. return;
  8526. }
  8527. this[fnName] = function () {
  8528. if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
  8529. return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
  8530. }
  8531. return originalFn.apply(this, arguments);
  8532. };
  8533. }, _Tech.prototype);
  8534. /**
  8535. * Create a function for setting the source using a source object
  8536. * and source handlers.
  8537. * Should never be called unless a source handler was found.
  8538. *
  8539. * @param {Tech~SourceObject} source
  8540. * A source object with src and type keys
  8541. */
  8542. _Tech.prototype.setSource = function (source) {
  8543. var sh = _Tech.selectSourceHandler(source, this.options_);
  8544. if (!sh) {
  8545. // Fall back to a native source hander when unsupported sources are
  8546. // deliberately set
  8547. if (_Tech.nativeSourceHandler) {
  8548. sh = _Tech.nativeSourceHandler;
  8549. } else {
  8550. log$1.error('No source handler found for the current source.');
  8551. }
  8552. } // Dispose any existing source handler
  8553. this.disposeSourceHandler();
  8554. this.off('dispose', this.disposeSourceHandler_);
  8555. if (sh !== _Tech.nativeSourceHandler) {
  8556. this.currentSource_ = source;
  8557. }
  8558. this.sourceHandler_ = sh.handleSource(source, this, this.options_);
  8559. this.one('dispose', this.disposeSourceHandler_);
  8560. };
  8561. /**
  8562. * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
  8563. *
  8564. * @listens Tech#dispose
  8565. */
  8566. _Tech.prototype.disposeSourceHandler = function () {
  8567. // if we have a source and get another one
  8568. // then we are loading something new
  8569. // than clear all of our current tracks
  8570. if (this.currentSource_) {
  8571. this.clearTracks(['audio', 'video']);
  8572. this.currentSource_ = null;
  8573. } // always clean up auto-text tracks
  8574. this.cleanupAutoTextTracks();
  8575. if (this.sourceHandler_) {
  8576. if (this.sourceHandler_.dispose) {
  8577. this.sourceHandler_.dispose();
  8578. }
  8579. this.sourceHandler_ = null;
  8580. }
  8581. };
  8582. }; // The base Tech class needs to be registered as a Component. It is the only
  8583. // Tech that can be registered as a Component.
  8584. Component$1.registerComponent('Tech', Tech);
  8585. Tech.registerTech('Tech', Tech);
  8586. /**
  8587. * A list of techs that should be added to techOrder on Players
  8588. *
  8589. * @private
  8590. */
  8591. Tech.defaultTechOrder_ = [];
  8592. /**
  8593. * @file middleware.js
  8594. * @module middleware
  8595. */
  8596. var middlewares = {};
  8597. var middlewareInstances = {};
  8598. var TERMINATOR = {};
  8599. /**
  8600. * A middleware object is a plain JavaScript object that has methods that
  8601. * match the {@link Tech} methods found in the lists of allowed
  8602. * {@link module:middleware.allowedGetters|getters},
  8603. * {@link module:middleware.allowedSetters|setters}, and
  8604. * {@link module:middleware.allowedMediators|mediators}.
  8605. *
  8606. * @typedef {Object} MiddlewareObject
  8607. */
  8608. /**
  8609. * A middleware factory function that should return a
  8610. * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
  8611. *
  8612. * This factory will be called for each player when needed, with the player
  8613. * passed in as an argument.
  8614. *
  8615. * @callback MiddlewareFactory
  8616. * @param {Player} player
  8617. * A Video.js player.
  8618. */
  8619. /**
  8620. * Define a middleware that the player should use by way of a factory function
  8621. * that returns a middleware object.
  8622. *
  8623. * @param {string} type
  8624. * The MIME type to match or `"*"` for all MIME types.
  8625. *
  8626. * @param {MiddlewareFactory} middleware
  8627. * A middleware factory function that will be executed for
  8628. * matching types.
  8629. */
  8630. function use(type, middleware) {
  8631. middlewares[type] = middlewares[type] || [];
  8632. middlewares[type].push(middleware);
  8633. }
  8634. /**
  8635. * Asynchronously sets a source using middleware by recursing through any
  8636. * matching middlewares and calling `setSource` on each, passing along the
  8637. * previous returned value each time.
  8638. *
  8639. * @param {Player} player
  8640. * A {@link Player} instance.
  8641. *
  8642. * @param {Tech~SourceObject} src
  8643. * A source object.
  8644. *
  8645. * @param {Function}
  8646. * The next middleware to run.
  8647. */
  8648. function setSource(player, src, next) {
  8649. player.setTimeout(function () {
  8650. return setSourceHelper(src, middlewares[src.type], next, player);
  8651. }, 1);
  8652. }
  8653. /**
  8654. * When the tech is set, passes the tech to each middleware's `setTech` method.
  8655. *
  8656. * @param {Object[]} middleware
  8657. * An array of middleware instances.
  8658. *
  8659. * @param {Tech} tech
  8660. * A Video.js tech.
  8661. */
  8662. function setTech(middleware, tech) {
  8663. middleware.forEach(function (mw) {
  8664. return mw.setTech && mw.setTech(tech);
  8665. });
  8666. }
  8667. /**
  8668. * Calls a getter on the tech first, through each middleware
  8669. * from right to left to the player.
  8670. *
  8671. * @param {Object[]} middleware
  8672. * An array of middleware instances.
  8673. *
  8674. * @param {Tech} tech
  8675. * The current tech.
  8676. *
  8677. * @param {string} method
  8678. * A method name.
  8679. *
  8680. * @return {Mixed}
  8681. * The final value from the tech after middleware has intercepted it.
  8682. */
  8683. function get(middleware, tech, method) {
  8684. return middleware.reduceRight(middlewareIterator(method), tech[method]());
  8685. }
  8686. /**
  8687. * Takes the argument given to the player and calls the setter method on each
  8688. * middleware from left to right to the tech.
  8689. *
  8690. * @param {Object[]} middleware
  8691. * An array of middleware instances.
  8692. *
  8693. * @param {Tech} tech
  8694. * The current tech.
  8695. *
  8696. * @param {string} method
  8697. * A method name.
  8698. *
  8699. * @param {Mixed} arg
  8700. * The value to set on the tech.
  8701. *
  8702. * @return {Mixed}
  8703. * The return value of the `method` of the `tech`.
  8704. */
  8705. function set(middleware, tech, method, arg) {
  8706. return tech[method](middleware.reduce(middlewareIterator(method), arg));
  8707. }
  8708. /**
  8709. * Takes the argument given to the player and calls the `call` version of the
  8710. * method on each middleware from left to right.
  8711. *
  8712. * Then, call the passed in method on the tech and return the result unchanged
  8713. * back to the player, through middleware, this time from right to left.
  8714. *
  8715. * @param {Object[]} middleware
  8716. * An array of middleware instances.
  8717. *
  8718. * @param {Tech} tech
  8719. * The current tech.
  8720. *
  8721. * @param {string} method
  8722. * A method name.
  8723. *
  8724. * @param {Mixed} arg
  8725. * The value to set on the tech.
  8726. *
  8727. * @return {Mixed}
  8728. * The return value of the `method` of the `tech`, regardless of the
  8729. * return values of middlewares.
  8730. */
  8731. function mediate(middleware, tech, method, arg) {
  8732. if (arg === void 0) {
  8733. arg = null;
  8734. }
  8735. var callMethod = 'call' + toTitleCase$1(method);
  8736. var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
  8737. var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to
  8738. // prevent confusion if a techs method actually returns null.
  8739. var returnValue = terminated ? null : tech[method](middlewareValue);
  8740. executeRight(middleware, method, returnValue, terminated);
  8741. return returnValue;
  8742. }
  8743. /**
  8744. * Enumeration of allowed getters where the keys are method names.
  8745. *
  8746. * @type {Object}
  8747. */
  8748. var allowedGetters = {
  8749. buffered: 1,
  8750. currentTime: 1,
  8751. duration: 1,
  8752. muted: 1,
  8753. played: 1,
  8754. paused: 1,
  8755. seekable: 1,
  8756. volume: 1,
  8757. ended: 1
  8758. };
  8759. /**
  8760. * Enumeration of allowed setters where the keys are method names.
  8761. *
  8762. * @type {Object}
  8763. */
  8764. var allowedSetters = {
  8765. setCurrentTime: 1,
  8766. setMuted: 1,
  8767. setVolume: 1
  8768. };
  8769. /**
  8770. * Enumeration of allowed mediators where the keys are method names.
  8771. *
  8772. * @type {Object}
  8773. */
  8774. var allowedMediators = {
  8775. play: 1,
  8776. pause: 1
  8777. };
  8778. function middlewareIterator(method) {
  8779. return function (value, mw) {
  8780. // if the previous middleware terminated, pass along the termination
  8781. if (value === TERMINATOR) {
  8782. return TERMINATOR;
  8783. }
  8784. if (mw[method]) {
  8785. return mw[method](value);
  8786. }
  8787. return value;
  8788. };
  8789. }
  8790. function executeRight(mws, method, value, terminated) {
  8791. for (var i = mws.length - 1; i >= 0; i--) {
  8792. var mw = mws[i];
  8793. if (mw[method]) {
  8794. mw[method](terminated, value);
  8795. }
  8796. }
  8797. }
  8798. /**
  8799. * Clear the middleware cache for a player.
  8800. *
  8801. * @param {Player} player
  8802. * A {@link Player} instance.
  8803. */
  8804. function clearCacheForPlayer(player) {
  8805. middlewareInstances[player.id()] = null;
  8806. }
  8807. /**
  8808. * {
  8809. * [playerId]: [[mwFactory, mwInstance], ...]
  8810. * }
  8811. *
  8812. * @private
  8813. */
  8814. function getOrCreateFactory(player, mwFactory) {
  8815. var mws = middlewareInstances[player.id()];
  8816. var mw = null;
  8817. if (mws === undefined || mws === null) {
  8818. mw = mwFactory(player);
  8819. middlewareInstances[player.id()] = [[mwFactory, mw]];
  8820. return mw;
  8821. }
  8822. for (var i = 0; i < mws.length; i++) {
  8823. var _mws$i = mws[i],
  8824. mwf = _mws$i[0],
  8825. mwi = _mws$i[1];
  8826. if (mwf !== mwFactory) {
  8827. continue;
  8828. }
  8829. mw = mwi;
  8830. }
  8831. if (mw === null) {
  8832. mw = mwFactory(player);
  8833. mws.push([mwFactory, mw]);
  8834. }
  8835. return mw;
  8836. }
  8837. function setSourceHelper(src, middleware, next, player, acc, lastRun) {
  8838. if (src === void 0) {
  8839. src = {};
  8840. }
  8841. if (middleware === void 0) {
  8842. middleware = [];
  8843. }
  8844. if (acc === void 0) {
  8845. acc = [];
  8846. }
  8847. if (lastRun === void 0) {
  8848. lastRun = false;
  8849. }
  8850. var _middleware = middleware,
  8851. mwFactory = _middleware[0],
  8852. mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road
  8853. if (typeof mwFactory === 'string') {
  8854. setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw,
  8855. // then call the mw's setSource method
  8856. } else if (mwFactory) {
  8857. var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware
  8858. if (!mw.setSource) {
  8859. acc.push(mw);
  8860. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  8861. }
  8862. mw.setSource(assign({}, src), function (err, _src) {
  8863. // something happened, try the next middleware on the current level
  8864. // make sure to use the old src
  8865. if (err) {
  8866. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  8867. } // we've succeeded, now we need to go deeper
  8868. acc.push(mw); // if it's the same type, continue down the current chain
  8869. // otherwise, we want to go down the new chain
  8870. setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
  8871. });
  8872. } else if (mwrest.length) {
  8873. setSourceHelper(src, mwrest, next, player, acc, lastRun);
  8874. } else if (lastRun) {
  8875. next(src, acc);
  8876. } else {
  8877. setSourceHelper(src, middlewares['*'], next, player, acc, true);
  8878. }
  8879. }
  8880. /**
  8881. * Mimetypes
  8882. *
  8883. * @see https://www.iana.org/assignments/media-types/media-types.xhtml
  8884. * @typedef Mimetypes~Kind
  8885. * @enum
  8886. */
  8887. var MimetypesKind = {
  8888. opus: 'video/ogg',
  8889. ogv: 'video/ogg',
  8890. mp4: 'video/mp4',
  8891. mov: 'video/mp4',
  8892. m4v: 'video/mp4',
  8893. mkv: 'video/x-matroska',
  8894. m4a: 'audio/mp4',
  8895. mp3: 'audio/mpeg',
  8896. aac: 'audio/aac',
  8897. caf: 'audio/x-caf',
  8898. flac: 'audio/flac',
  8899. oga: 'audio/ogg',
  8900. wav: 'audio/wav',
  8901. m3u8: 'application/x-mpegURL',
  8902. mpd: 'application/dash+xml',
  8903. jpg: 'image/jpeg',
  8904. jpeg: 'image/jpeg',
  8905. gif: 'image/gif',
  8906. png: 'image/png',
  8907. svg: 'image/svg+xml',
  8908. webp: 'image/webp'
  8909. };
  8910. /**
  8911. * Get the mimetype of a given src url if possible
  8912. *
  8913. * @param {string} src
  8914. * The url to the src
  8915. *
  8916. * @return {string}
  8917. * return the mimetype if it was known or empty string otherwise
  8918. */
  8919. var getMimetype = function getMimetype(src) {
  8920. if (src === void 0) {
  8921. src = '';
  8922. }
  8923. var ext = getFileExtension(src);
  8924. var mimetype = MimetypesKind[ext.toLowerCase()];
  8925. return mimetype || '';
  8926. };
  8927. /**
  8928. * Find the mime type of a given source string if possible. Uses the player
  8929. * source cache.
  8930. *
  8931. * @param {Player} player
  8932. * The player object
  8933. *
  8934. * @param {string} src
  8935. * The source string
  8936. *
  8937. * @return {string}
  8938. * The type that was found
  8939. */
  8940. var findMimetype = function findMimetype(player, src) {
  8941. if (!src) {
  8942. return '';
  8943. } // 1. check for the type in the `source` cache
  8944. if (player.cache_.source.src === src && player.cache_.source.type) {
  8945. return player.cache_.source.type;
  8946. } // 2. see if we have this source in our `currentSources` cache
  8947. var matchingSources = player.cache_.sources.filter(function (s) {
  8948. return s.src === src;
  8949. });
  8950. if (matchingSources.length) {
  8951. return matchingSources[0].type;
  8952. } // 3. look for the src url in source elements and use the type there
  8953. var sources = player.$$('source');
  8954. for (var i = 0; i < sources.length; i++) {
  8955. var s = sources[i];
  8956. if (s.type && s.src && s.src === src) {
  8957. return s.type;
  8958. }
  8959. } // 4. finally fallback to our list of mime types based on src url extension
  8960. return getMimetype(src);
  8961. };
  8962. /**
  8963. * @module filter-source
  8964. */
  8965. /**
  8966. * Filter out single bad source objects or multiple source objects in an
  8967. * array. Also flattens nested source object arrays into a 1 dimensional
  8968. * array of source objects.
  8969. *
  8970. * @param {Tech~SourceObject|Tech~SourceObject[]} src
  8971. * The src object to filter
  8972. *
  8973. * @return {Tech~SourceObject[]}
  8974. * An array of sourceobjects containing only valid sources
  8975. *
  8976. * @private
  8977. */
  8978. var filterSource = function filterSource(src) {
  8979. // traverse array
  8980. if (Array.isArray(src)) {
  8981. var newsrc = [];
  8982. src.forEach(function (srcobj) {
  8983. srcobj = filterSource(srcobj);
  8984. if (Array.isArray(srcobj)) {
  8985. newsrc = newsrc.concat(srcobj);
  8986. } else if (isObject(srcobj)) {
  8987. newsrc.push(srcobj);
  8988. }
  8989. });
  8990. src = newsrc;
  8991. } else if (typeof src === 'string' && src.trim()) {
  8992. // convert string into object
  8993. src = [fixSource({
  8994. src: src
  8995. })];
  8996. } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
  8997. // src is already valid
  8998. src = [fixSource(src)];
  8999. } else {
  9000. // invalid source, turn it into an empty array
  9001. src = [];
  9002. }
  9003. return src;
  9004. };
  9005. /**
  9006. * Checks src mimetype, adding it when possible
  9007. *
  9008. * @param {Tech~SourceObject} src
  9009. * The src object to check
  9010. * @return {Tech~SourceObject}
  9011. * src Object with known type
  9012. */
  9013. function fixSource(src) {
  9014. if (!src.type) {
  9015. var mimetype = getMimetype(src.src);
  9016. if (mimetype) {
  9017. src.type = mimetype;
  9018. }
  9019. }
  9020. return src;
  9021. }
  9022. /**
  9023. * The `MediaLoader` is the `Component` that decides which playback technology to load
  9024. * when a player is initialized.
  9025. *
  9026. * @extends Component
  9027. */
  9028. var MediaLoader = /*#__PURE__*/function (_Component) {
  9029. _inheritsLoose(MediaLoader, _Component);
  9030. /**
  9031. * Create an instance of this class.
  9032. *
  9033. * @param {Player} player
  9034. * The `Player` that this class should attach to.
  9035. *
  9036. * @param {Object} [options]
  9037. * The key/value store of player options.
  9038. *
  9039. * @param {Component~ReadyCallback} [ready]
  9040. * The function that is run when this component is ready.
  9041. */
  9042. function MediaLoader(player, options, ready) {
  9043. var _this;
  9044. // MediaLoader has no element
  9045. var options_ = mergeOptions$3({
  9046. createEl: false
  9047. }, options);
  9048. _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized,
  9049. // load the first supported playback technology.
  9050. if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
  9051. for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
  9052. var techName = toTitleCase$1(j[i]);
  9053. var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
  9054. // Remove once that deprecated behavior is removed.
  9055. if (!techName) {
  9056. tech = Component$1.getComponent(techName);
  9057. } // Check if the browser supports this technology
  9058. if (tech && tech.isSupported()) {
  9059. player.loadTech_(techName);
  9060. break;
  9061. }
  9062. }
  9063. } else {
  9064. // Loop through playback technologies (e.g. HTML5) and check for support.
  9065. // Then load the best source.
  9066. // A few assumptions here:
  9067. // All playback technologies respect preload false.
  9068. player.src(options.playerOptions.sources);
  9069. }
  9070. return _this;
  9071. }
  9072. return MediaLoader;
  9073. }(Component$1);
  9074. Component$1.registerComponent('MediaLoader', MediaLoader);
  9075. /**
  9076. * Component which is clickable or keyboard actionable, but is not a
  9077. * native HTML button.
  9078. *
  9079. * @extends Component
  9080. */
  9081. var ClickableComponent = /*#__PURE__*/function (_Component) {
  9082. _inheritsLoose(ClickableComponent, _Component);
  9083. /**
  9084. * Creates an instance of this class.
  9085. *
  9086. * @param {Player} player
  9087. * The `Player` that this class should be attached to.
  9088. *
  9089. * @param {Object} [options]
  9090. * The key/value store of component options.
  9091. *
  9092. * @param {function} [options.clickHandler]
  9093. * The function to call when the button is clicked / activated
  9094. *
  9095. * @param {string} [options.controlText]
  9096. * The text to set on the button
  9097. *
  9098. * @param {string} [options.className]
  9099. * A class or space separated list of classes to add the component
  9100. *
  9101. */
  9102. function ClickableComponent(player, options) {
  9103. var _this;
  9104. _this = _Component.call(this, player, options) || this;
  9105. if (_this.options_.controlText) {
  9106. _this.controlText(_this.options_.controlText);
  9107. }
  9108. _this.handleMouseOver_ = function (e) {
  9109. return _this.handleMouseOver(e);
  9110. };
  9111. _this.handleMouseOut_ = function (e) {
  9112. return _this.handleMouseOut(e);
  9113. };
  9114. _this.handleClick_ = function (e) {
  9115. return _this.handleClick(e);
  9116. };
  9117. _this.handleKeyDown_ = function (e) {
  9118. return _this.handleKeyDown(e);
  9119. };
  9120. _this.emitTapEvents();
  9121. _this.enable();
  9122. return _this;
  9123. }
  9124. /**
  9125. * Create the `ClickableComponent`s DOM element.
  9126. *
  9127. * @param {string} [tag=div]
  9128. * The element's node type.
  9129. *
  9130. * @param {Object} [props={}]
  9131. * An object of properties that should be set on the element.
  9132. *
  9133. * @param {Object} [attributes={}]
  9134. * An object of attributes that should be set on the element.
  9135. *
  9136. * @return {Element}
  9137. * The element that gets created.
  9138. */
  9139. var _proto = ClickableComponent.prototype;
  9140. _proto.createEl = function createEl$1(tag, props, attributes) {
  9141. if (tag === void 0) {
  9142. tag = 'div';
  9143. }
  9144. if (props === void 0) {
  9145. props = {};
  9146. }
  9147. if (attributes === void 0) {
  9148. attributes = {};
  9149. }
  9150. props = assign({
  9151. className: this.buildCSSClass(),
  9152. tabIndex: 0
  9153. }, props);
  9154. if (tag === 'button') {
  9155. log$1.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead.");
  9156. } // Add ARIA attributes for clickable element which is not a native HTML button
  9157. attributes = assign({
  9158. role: 'button'
  9159. }, attributes);
  9160. this.tabIndex_ = props.tabIndex;
  9161. var el = createEl(tag, props, attributes);
  9162. el.appendChild(createEl('span', {
  9163. className: 'vjs-icon-placeholder'
  9164. }, {
  9165. 'aria-hidden': true
  9166. }));
  9167. this.createControlTextEl(el);
  9168. return el;
  9169. };
  9170. _proto.dispose = function dispose() {
  9171. // remove controlTextEl_ on dispose
  9172. this.controlTextEl_ = null;
  9173. _Component.prototype.dispose.call(this);
  9174. }
  9175. /**
  9176. * Create a control text element on this `ClickableComponent`
  9177. *
  9178. * @param {Element} [el]
  9179. * Parent element for the control text.
  9180. *
  9181. * @return {Element}
  9182. * The control text element that gets created.
  9183. */
  9184. ;
  9185. _proto.createControlTextEl = function createControlTextEl(el) {
  9186. this.controlTextEl_ = createEl('span', {
  9187. className: 'vjs-control-text'
  9188. }, {
  9189. // let the screen reader user know that the text of the element may change
  9190. 'aria-live': 'polite'
  9191. });
  9192. if (el) {
  9193. el.appendChild(this.controlTextEl_);
  9194. }
  9195. this.controlText(this.controlText_, el);
  9196. return this.controlTextEl_;
  9197. }
  9198. /**
  9199. * Get or set the localize text to use for the controls on the `ClickableComponent`.
  9200. *
  9201. * @param {string} [text]
  9202. * Control text for element.
  9203. *
  9204. * @param {Element} [el=this.el()]
  9205. * Element to set the title on.
  9206. *
  9207. * @return {string}
  9208. * - The control text when getting
  9209. */
  9210. ;
  9211. _proto.controlText = function controlText(text, el) {
  9212. if (el === void 0) {
  9213. el = this.el();
  9214. }
  9215. if (text === undefined) {
  9216. return this.controlText_ || 'Need Text';
  9217. }
  9218. var localizedText = this.localize(text);
  9219. this.controlText_ = text;
  9220. textContent(this.controlTextEl_, localizedText);
  9221. if (!this.nonIconControl && !this.player_.options_.noUITitleAttributes) {
  9222. // Set title attribute if only an icon is shown
  9223. el.setAttribute('title', localizedText);
  9224. }
  9225. }
  9226. /**
  9227. * Builds the default DOM `className`.
  9228. *
  9229. * @return {string}
  9230. * The DOM `className` for this object.
  9231. */
  9232. ;
  9233. _proto.buildCSSClass = function buildCSSClass() {
  9234. return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this);
  9235. }
  9236. /**
  9237. * Enable this `ClickableComponent`
  9238. */
  9239. ;
  9240. _proto.enable = function enable() {
  9241. if (!this.enabled_) {
  9242. this.enabled_ = true;
  9243. this.removeClass('vjs-disabled');
  9244. this.el_.setAttribute('aria-disabled', 'false');
  9245. if (typeof this.tabIndex_ !== 'undefined') {
  9246. this.el_.setAttribute('tabIndex', this.tabIndex_);
  9247. }
  9248. this.on(['tap', 'click'], this.handleClick_);
  9249. this.on('keydown', this.handleKeyDown_);
  9250. }
  9251. }
  9252. /**
  9253. * Disable this `ClickableComponent`
  9254. */
  9255. ;
  9256. _proto.disable = function disable() {
  9257. this.enabled_ = false;
  9258. this.addClass('vjs-disabled');
  9259. this.el_.setAttribute('aria-disabled', 'true');
  9260. if (typeof this.tabIndex_ !== 'undefined') {
  9261. this.el_.removeAttribute('tabIndex');
  9262. }
  9263. this.off('mouseover', this.handleMouseOver_);
  9264. this.off('mouseout', this.handleMouseOut_);
  9265. this.off(['tap', 'click'], this.handleClick_);
  9266. this.off('keydown', this.handleKeyDown_);
  9267. }
  9268. /**
  9269. * Handles language change in ClickableComponent for the player in components
  9270. *
  9271. *
  9272. */
  9273. ;
  9274. _proto.handleLanguagechange = function handleLanguagechange() {
  9275. this.controlText(this.controlText_);
  9276. }
  9277. /**
  9278. * Event handler that is called when a `ClickableComponent` receives a
  9279. * `click` or `tap` event.
  9280. *
  9281. * @param {EventTarget~Event} event
  9282. * The `tap` or `click` event that caused this function to be called.
  9283. *
  9284. * @listens tap
  9285. * @listens click
  9286. * @abstract
  9287. */
  9288. ;
  9289. _proto.handleClick = function handleClick(event) {
  9290. if (this.options_.clickHandler) {
  9291. this.options_.clickHandler.call(this, arguments);
  9292. }
  9293. }
  9294. /**
  9295. * Event handler that is called when a `ClickableComponent` receives a
  9296. * `keydown` event.
  9297. *
  9298. * By default, if the key is Space or Enter, it will trigger a `click` event.
  9299. *
  9300. * @param {EventTarget~Event} event
  9301. * The `keydown` event that caused this function to be called.
  9302. *
  9303. * @listens keydown
  9304. */
  9305. ;
  9306. _proto.handleKeyDown = function handleKeyDown(event) {
  9307. // Support Space or Enter key operation to fire a click event. Also,
  9308. // prevent the event from propagating through the DOM and triggering
  9309. // Player hotkeys.
  9310. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  9311. event.preventDefault();
  9312. event.stopPropagation();
  9313. this.trigger('click');
  9314. } else {
  9315. // Pass keypress handling up for unsupported keys
  9316. _Component.prototype.handleKeyDown.call(this, event);
  9317. }
  9318. };
  9319. return ClickableComponent;
  9320. }(Component$1);
  9321. Component$1.registerComponent('ClickableComponent', ClickableComponent);
  9322. /**
  9323. * A `ClickableComponent` that handles showing the poster image for the player.
  9324. *
  9325. * @extends ClickableComponent
  9326. */
  9327. var PosterImage = /*#__PURE__*/function (_ClickableComponent) {
  9328. _inheritsLoose(PosterImage, _ClickableComponent);
  9329. /**
  9330. * Create an instance of this class.
  9331. *
  9332. * @param {Player} player
  9333. * The `Player` that this class should attach to.
  9334. *
  9335. * @param {Object} [options]
  9336. * The key/value store of player options.
  9337. */
  9338. function PosterImage(player, options) {
  9339. var _this;
  9340. _this = _ClickableComponent.call(this, player, options) || this;
  9341. _this.update();
  9342. _this.update_ = function (e) {
  9343. return _this.update(e);
  9344. };
  9345. player.on('posterchange', _this.update_);
  9346. return _this;
  9347. }
  9348. /**
  9349. * Clean up and dispose of the `PosterImage`.
  9350. */
  9351. var _proto = PosterImage.prototype;
  9352. _proto.dispose = function dispose() {
  9353. this.player().off('posterchange', this.update_);
  9354. _ClickableComponent.prototype.dispose.call(this);
  9355. }
  9356. /**
  9357. * Create the `PosterImage`s DOM element.
  9358. *
  9359. * @return {Element}
  9360. * The element that gets created.
  9361. */
  9362. ;
  9363. _proto.createEl = function createEl$1() {
  9364. var el = createEl('div', {
  9365. className: 'vjs-poster',
  9366. // Don't want poster to be tabbable.
  9367. tabIndex: -1
  9368. });
  9369. return el;
  9370. }
  9371. /**
  9372. * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
  9373. *
  9374. * @listens Player#posterchange
  9375. *
  9376. * @param {EventTarget~Event} [event]
  9377. * The `Player#posterchange` event that triggered this function.
  9378. */
  9379. ;
  9380. _proto.update = function update(event) {
  9381. var url = this.player().poster();
  9382. this.setSrc(url); // If there's no poster source we should display:none on this component
  9383. // so it's not still clickable or right-clickable
  9384. if (url) {
  9385. this.show();
  9386. } else {
  9387. this.hide();
  9388. }
  9389. }
  9390. /**
  9391. * Set the source of the `PosterImage` depending on the display method.
  9392. *
  9393. * @param {string} url
  9394. * The URL to the source for the `PosterImage`.
  9395. */
  9396. ;
  9397. _proto.setSrc = function setSrc(url) {
  9398. var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise
  9399. // this will throw an extra error
  9400. if (url) {
  9401. backgroundImage = "url(\"" + url + "\")";
  9402. }
  9403. this.el_.style.backgroundImage = backgroundImage;
  9404. }
  9405. /**
  9406. * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
  9407. * {@link ClickableComponent#handleClick} for instances where this will be triggered.
  9408. *
  9409. * @listens tap
  9410. * @listens click
  9411. * @listens keydown
  9412. *
  9413. * @param {EventTarget~Event} event
  9414. + The `click`, `tap` or `keydown` event that caused this function to be called.
  9415. */
  9416. ;
  9417. _proto.handleClick = function handleClick(event) {
  9418. // We don't want a click to trigger playback when controls are disabled
  9419. if (!this.player_.controls()) {
  9420. return;
  9421. }
  9422. var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0;
  9423. if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where
  9424. // calling .focus() on the video element causes the video to go black,
  9425. // so we avoid it in that specific case
  9426. !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) {
  9427. this.player_.tech(true).focus();
  9428. }
  9429. if (this.player_.paused()) {
  9430. silencePromise(this.player_.play());
  9431. } else {
  9432. this.player_.pause();
  9433. }
  9434. };
  9435. return PosterImage;
  9436. }(ClickableComponent);
  9437. Component$1.registerComponent('PosterImage', PosterImage);
  9438. var darkGray = '#222';
  9439. var lightGray = '#ccc';
  9440. var fontMap = {
  9441. monospace: 'monospace',
  9442. sansSerif: 'sans-serif',
  9443. serif: 'serif',
  9444. monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
  9445. monospaceSerif: '"Courier New", monospace',
  9446. proportionalSansSerif: 'sans-serif',
  9447. proportionalSerif: 'serif',
  9448. casual: '"Comic Sans MS", Impact, fantasy',
  9449. script: '"Monotype Corsiva", cursive',
  9450. smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
  9451. };
  9452. /**
  9453. * Construct an rgba color from a given hex color code.
  9454. *
  9455. * @param {number} color
  9456. * Hex number for color, like #f0e or #f604e2.
  9457. *
  9458. * @param {number} opacity
  9459. * Value for opacity, 0.0 - 1.0.
  9460. *
  9461. * @return {string}
  9462. * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
  9463. */
  9464. function constructColor(color, opacity) {
  9465. var hex;
  9466. if (color.length === 4) {
  9467. // color looks like "#f0e"
  9468. hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
  9469. } else if (color.length === 7) {
  9470. // color looks like "#f604e2"
  9471. hex = color.slice(1);
  9472. } else {
  9473. throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
  9474. }
  9475. return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
  9476. }
  9477. /**
  9478. * Try to update the style of a DOM element. Some style changes will throw an error,
  9479. * particularly in IE8. Those should be noops.
  9480. *
  9481. * @param {Element} el
  9482. * The DOM element to be styled.
  9483. *
  9484. * @param {string} style
  9485. * The CSS property on the element that should be styled.
  9486. *
  9487. * @param {string} rule
  9488. * The style rule that should be applied to the property.
  9489. *
  9490. * @private
  9491. */
  9492. function tryUpdateStyle(el, style, rule) {
  9493. try {
  9494. el.style[style] = rule;
  9495. } catch (e) {
  9496. // Satisfies linter.
  9497. return;
  9498. }
  9499. }
  9500. /**
  9501. * The component for displaying text track cues.
  9502. *
  9503. * @extends Component
  9504. */
  9505. var TextTrackDisplay = /*#__PURE__*/function (_Component) {
  9506. _inheritsLoose(TextTrackDisplay, _Component);
  9507. /**
  9508. * Creates an instance of this class.
  9509. *
  9510. * @param {Player} player
  9511. * The `Player` that this class should be attached to.
  9512. *
  9513. * @param {Object} [options]
  9514. * The key/value store of player options.
  9515. *
  9516. * @param {Component~ReadyCallback} [ready]
  9517. * The function to call when `TextTrackDisplay` is ready.
  9518. */
  9519. function TextTrackDisplay(player, options, ready) {
  9520. var _this;
  9521. _this = _Component.call(this, player, options, ready) || this;
  9522. var updateDisplayHandler = function updateDisplayHandler(e) {
  9523. return _this.updateDisplay(e);
  9524. };
  9525. player.on('loadstart', function (e) {
  9526. return _this.toggleDisplay(e);
  9527. });
  9528. player.on('texttrackchange', updateDisplayHandler);
  9529. player.on('loadedmetadata', function (e) {
  9530. return _this.preselectTrack(e);
  9531. }); // This used to be called during player init, but was causing an error
  9532. // if a track should show by default and the display hadn't loaded yet.
  9533. // Should probably be moved to an external track loader when we support
  9534. // tracks that don't need a display.
  9535. player.ready(bind(_assertThisInitialized(_this), function () {
  9536. if (player.tech_ && player.tech_.featuresNativeTextTracks) {
  9537. this.hide();
  9538. return;
  9539. }
  9540. player.on('fullscreenchange', updateDisplayHandler);
  9541. player.on('playerresize', updateDisplayHandler);
  9542. window$1.addEventListener('orientationchange', updateDisplayHandler);
  9543. player.on('dispose', function () {
  9544. return window$1.removeEventListener('orientationchange', updateDisplayHandler);
  9545. });
  9546. var tracks = this.options_.playerOptions.tracks || [];
  9547. for (var i = 0; i < tracks.length; i++) {
  9548. this.player_.addRemoteTextTrack(tracks[i], true);
  9549. }
  9550. this.preselectTrack();
  9551. }));
  9552. return _this;
  9553. }
  9554. /**
  9555. * Preselect a track following this precedence:
  9556. * - matches the previously selected {@link TextTrack}'s language and kind
  9557. * - matches the previously selected {@link TextTrack}'s language only
  9558. * - is the first default captions track
  9559. * - is the first default descriptions track
  9560. *
  9561. * @listens Player#loadstart
  9562. */
  9563. var _proto = TextTrackDisplay.prototype;
  9564. _proto.preselectTrack = function preselectTrack() {
  9565. var modes = {
  9566. captions: 1,
  9567. subtitles: 1
  9568. };
  9569. var trackList = this.player_.textTracks();
  9570. var userPref = this.player_.cache_.selectedLanguage;
  9571. var firstDesc;
  9572. var firstCaptions;
  9573. var preferredTrack;
  9574. for (var i = 0; i < trackList.length; i++) {
  9575. var track = trackList[i];
  9576. if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
  9577. // Always choose the track that matches both language and kind
  9578. if (track.kind === userPref.kind) {
  9579. preferredTrack = track; // or choose the first track that matches language
  9580. } else if (!preferredTrack) {
  9581. preferredTrack = track;
  9582. } // clear everything if offTextTrackMenuItem was clicked
  9583. } else if (userPref && !userPref.enabled) {
  9584. preferredTrack = null;
  9585. firstDesc = null;
  9586. firstCaptions = null;
  9587. } else if (track["default"]) {
  9588. if (track.kind === 'descriptions' && !firstDesc) {
  9589. firstDesc = track;
  9590. } else if (track.kind in modes && !firstCaptions) {
  9591. firstCaptions = track;
  9592. }
  9593. }
  9594. } // The preferredTrack matches the user preference and takes
  9595. // precedence over all the other tracks.
  9596. // So, display the preferredTrack before the first default track
  9597. // and the subtitles/captions track before the descriptions track
  9598. if (preferredTrack) {
  9599. preferredTrack.mode = 'showing';
  9600. } else if (firstCaptions) {
  9601. firstCaptions.mode = 'showing';
  9602. } else if (firstDesc) {
  9603. firstDesc.mode = 'showing';
  9604. }
  9605. }
  9606. /**
  9607. * Turn display of {@link TextTrack}'s from the current state into the other state.
  9608. * There are only two states:
  9609. * - 'shown'
  9610. * - 'hidden'
  9611. *
  9612. * @listens Player#loadstart
  9613. */
  9614. ;
  9615. _proto.toggleDisplay = function toggleDisplay() {
  9616. if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
  9617. this.hide();
  9618. } else {
  9619. this.show();
  9620. }
  9621. }
  9622. /**
  9623. * Create the {@link Component}'s DOM element.
  9624. *
  9625. * @return {Element}
  9626. * The element that was created.
  9627. */
  9628. ;
  9629. _proto.createEl = function createEl() {
  9630. return _Component.prototype.createEl.call(this, 'div', {
  9631. className: 'vjs-text-track-display'
  9632. }, {
  9633. 'translate': 'yes',
  9634. 'aria-live': 'off',
  9635. 'aria-atomic': 'true'
  9636. });
  9637. }
  9638. /**
  9639. * Clear all displayed {@link TextTrack}s.
  9640. */
  9641. ;
  9642. _proto.clearDisplay = function clearDisplay() {
  9643. if (typeof window$1.WebVTT === 'function') {
  9644. window$1.WebVTT.processCues(window$1, [], this.el_);
  9645. }
  9646. }
  9647. /**
  9648. * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
  9649. * a {@link Player#fullscreenchange} is fired.
  9650. *
  9651. * @listens Player#texttrackchange
  9652. * @listens Player#fullscreenchange
  9653. */
  9654. ;
  9655. _proto.updateDisplay = function updateDisplay() {
  9656. var tracks = this.player_.textTracks();
  9657. var allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks;
  9658. this.clearDisplay();
  9659. if (allowMultipleShowingTracks) {
  9660. var showingTracks = [];
  9661. for (var _i = 0; _i < tracks.length; ++_i) {
  9662. var track = tracks[_i];
  9663. if (track.mode !== 'showing') {
  9664. continue;
  9665. }
  9666. showingTracks.push(track);
  9667. }
  9668. this.updateForTrack(showingTracks);
  9669. return;
  9670. } // Track display prioritization model: if multiple tracks are 'showing',
  9671. // display the first 'subtitles' or 'captions' track which is 'showing',
  9672. // otherwise display the first 'descriptions' track which is 'showing'
  9673. var descriptionsTrack = null;
  9674. var captionsSubtitlesTrack = null;
  9675. var i = tracks.length;
  9676. while (i--) {
  9677. var _track = tracks[i];
  9678. if (_track.mode === 'showing') {
  9679. if (_track.kind === 'descriptions') {
  9680. descriptionsTrack = _track;
  9681. } else {
  9682. captionsSubtitlesTrack = _track;
  9683. }
  9684. }
  9685. }
  9686. if (captionsSubtitlesTrack) {
  9687. if (this.getAttribute('aria-live') !== 'off') {
  9688. this.setAttribute('aria-live', 'off');
  9689. }
  9690. this.updateForTrack(captionsSubtitlesTrack);
  9691. } else if (descriptionsTrack) {
  9692. if (this.getAttribute('aria-live') !== 'assertive') {
  9693. this.setAttribute('aria-live', 'assertive');
  9694. }
  9695. this.updateForTrack(descriptionsTrack);
  9696. }
  9697. }
  9698. /**
  9699. * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}.
  9700. *
  9701. * @param {TextTrack} track
  9702. * Text track object containing active cues to style.
  9703. */
  9704. ;
  9705. _proto.updateDisplayState = function updateDisplayState(track) {
  9706. var overrides = this.player_.textTrackSettings.getValues();
  9707. var cues = track.activeCues;
  9708. var i = cues.length;
  9709. while (i--) {
  9710. var cue = cues[i];
  9711. if (!cue) {
  9712. continue;
  9713. }
  9714. var cueDiv = cue.displayState;
  9715. if (overrides.color) {
  9716. cueDiv.firstChild.style.color = overrides.color;
  9717. }
  9718. if (overrides.textOpacity) {
  9719. tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
  9720. }
  9721. if (overrides.backgroundColor) {
  9722. cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
  9723. }
  9724. if (overrides.backgroundOpacity) {
  9725. tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
  9726. }
  9727. if (overrides.windowColor) {
  9728. if (overrides.windowOpacity) {
  9729. tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
  9730. } else {
  9731. cueDiv.style.backgroundColor = overrides.windowColor;
  9732. }
  9733. }
  9734. if (overrides.edgeStyle) {
  9735. if (overrides.edgeStyle === 'dropshadow') {
  9736. cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray;
  9737. } else if (overrides.edgeStyle === 'raised') {
  9738. cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray;
  9739. } else if (overrides.edgeStyle === 'depressed') {
  9740. cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray;
  9741. } else if (overrides.edgeStyle === 'uniform') {
  9742. cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray;
  9743. }
  9744. }
  9745. if (overrides.fontPercent && overrides.fontPercent !== 1) {
  9746. var fontSize = window$1.parseFloat(cueDiv.style.fontSize);
  9747. cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
  9748. cueDiv.style.height = 'auto';
  9749. cueDiv.style.top = 'auto';
  9750. }
  9751. if (overrides.fontFamily && overrides.fontFamily !== 'default') {
  9752. if (overrides.fontFamily === 'small-caps') {
  9753. cueDiv.firstChild.style.fontVariant = 'small-caps';
  9754. } else {
  9755. cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
  9756. }
  9757. }
  9758. }
  9759. }
  9760. /**
  9761. * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
  9762. *
  9763. * @param {TextTrack|TextTrack[]} tracks
  9764. * Text track object or text track array to be added to the list.
  9765. */
  9766. ;
  9767. _proto.updateForTrack = function updateForTrack(tracks) {
  9768. if (!Array.isArray(tracks)) {
  9769. tracks = [tracks];
  9770. }
  9771. if (typeof window$1.WebVTT !== 'function' || tracks.every(function (track) {
  9772. return !track.activeCues;
  9773. })) {
  9774. return;
  9775. }
  9776. var cues = []; // push all active track cues
  9777. for (var i = 0; i < tracks.length; ++i) {
  9778. var track = tracks[i];
  9779. for (var j = 0; j < track.activeCues.length; ++j) {
  9780. cues.push(track.activeCues[j]);
  9781. }
  9782. } // removes all cues before it processes new ones
  9783. window$1.WebVTT.processCues(window$1, cues, this.el_); // add unique class to each language text track & add settings styling if necessary
  9784. for (var _i2 = 0; _i2 < tracks.length; ++_i2) {
  9785. var _track2 = tracks[_i2];
  9786. for (var _j = 0; _j < _track2.activeCues.length; ++_j) {
  9787. var cueEl = _track2.activeCues[_j].displayState;
  9788. addClass(cueEl, 'vjs-text-track-cue');
  9789. addClass(cueEl, 'vjs-text-track-cue-' + (_track2.language ? _track2.language : _i2));
  9790. if (_track2.language) {
  9791. setAttribute(cueEl, 'lang', _track2.language);
  9792. }
  9793. }
  9794. if (this.player_.textTrackSettings) {
  9795. this.updateDisplayState(_track2);
  9796. }
  9797. }
  9798. };
  9799. return TextTrackDisplay;
  9800. }(Component$1);
  9801. Component$1.registerComponent('TextTrackDisplay', TextTrackDisplay);
  9802. /**
  9803. * A loading spinner for use during waiting/loading events.
  9804. *
  9805. * @extends Component
  9806. */
  9807. var LoadingSpinner = /*#__PURE__*/function (_Component) {
  9808. _inheritsLoose(LoadingSpinner, _Component);
  9809. function LoadingSpinner() {
  9810. return _Component.apply(this, arguments) || this;
  9811. }
  9812. var _proto = LoadingSpinner.prototype;
  9813. /**
  9814. * Create the `LoadingSpinner`s DOM element.
  9815. *
  9816. * @return {Element}
  9817. * The dom element that gets created.
  9818. */
  9819. _proto.createEl = function createEl$1() {
  9820. var isAudio = this.player_.isAudio();
  9821. var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
  9822. var controlText = createEl('span', {
  9823. className: 'vjs-control-text',
  9824. textContent: this.localize('{1} is loading.', [playerType])
  9825. });
  9826. var el = _Component.prototype.createEl.call(this, 'div', {
  9827. className: 'vjs-loading-spinner',
  9828. dir: 'ltr'
  9829. });
  9830. el.appendChild(controlText);
  9831. return el;
  9832. };
  9833. return LoadingSpinner;
  9834. }(Component$1);
  9835. Component$1.registerComponent('LoadingSpinner', LoadingSpinner);
  9836. /**
  9837. * Base class for all buttons.
  9838. *
  9839. * @extends ClickableComponent
  9840. */
  9841. var Button = /*#__PURE__*/function (_ClickableComponent) {
  9842. _inheritsLoose(Button, _ClickableComponent);
  9843. function Button() {
  9844. return _ClickableComponent.apply(this, arguments) || this;
  9845. }
  9846. var _proto = Button.prototype;
  9847. /**
  9848. * Create the `Button`s DOM element.
  9849. *
  9850. * @param {string} [tag="button"]
  9851. * The element's node type. This argument is IGNORED: no matter what
  9852. * is passed, it will always create a `button` element.
  9853. *
  9854. * @param {Object} [props={}]
  9855. * An object of properties that should be set on the element.
  9856. *
  9857. * @param {Object} [attributes={}]
  9858. * An object of attributes that should be set on the element.
  9859. *
  9860. * @return {Element}
  9861. * The element that gets created.
  9862. */
  9863. _proto.createEl = function createEl$1(tag, props, attributes) {
  9864. if (props === void 0) {
  9865. props = {};
  9866. }
  9867. if (attributes === void 0) {
  9868. attributes = {};
  9869. }
  9870. tag = 'button';
  9871. props = assign({
  9872. className: this.buildCSSClass()
  9873. }, props); // Add attributes for button element
  9874. attributes = assign({
  9875. // Necessary since the default button type is "submit"
  9876. type: 'button'
  9877. }, attributes);
  9878. var el = createEl(tag, props, attributes);
  9879. el.appendChild(createEl('span', {
  9880. className: 'vjs-icon-placeholder'
  9881. }, {
  9882. 'aria-hidden': true
  9883. }));
  9884. this.createControlTextEl(el);
  9885. return el;
  9886. }
  9887. /**
  9888. * Add a child `Component` inside of this `Button`.
  9889. *
  9890. * @param {string|Component} child
  9891. * The name or instance of a child to add.
  9892. *
  9893. * @param {Object} [options={}]
  9894. * The key/value store of options that will get passed to children of
  9895. * the child.
  9896. *
  9897. * @return {Component}
  9898. * The `Component` that gets added as a child. When using a string the
  9899. * `Component` will get created by this process.
  9900. *
  9901. * @deprecated since version 5
  9902. */
  9903. ;
  9904. _proto.addChild = function addChild(child, options) {
  9905. if (options === void 0) {
  9906. options = {};
  9907. }
  9908. var className = this.constructor.name;
  9909. log$1.warn("Adding an actionable (user controllable) child to a Button (" + className + ") is not supported; use a ClickableComponent instead."); // Avoid the error message generated by ClickableComponent's addChild method
  9910. return Component$1.prototype.addChild.call(this, child, options);
  9911. }
  9912. /**
  9913. * Enable the `Button` element so that it can be activated or clicked. Use this with
  9914. * {@link Button#disable}.
  9915. */
  9916. ;
  9917. _proto.enable = function enable() {
  9918. _ClickableComponent.prototype.enable.call(this);
  9919. this.el_.removeAttribute('disabled');
  9920. }
  9921. /**
  9922. * Disable the `Button` element so that it cannot be activated or clicked. Use this with
  9923. * {@link Button#enable}.
  9924. */
  9925. ;
  9926. _proto.disable = function disable() {
  9927. _ClickableComponent.prototype.disable.call(this);
  9928. this.el_.setAttribute('disabled', 'disabled');
  9929. }
  9930. /**
  9931. * This gets called when a `Button` has focus and `keydown` is triggered via a key
  9932. * press.
  9933. *
  9934. * @param {EventTarget~Event} event
  9935. * The event that caused this function to get called.
  9936. *
  9937. * @listens keydown
  9938. */
  9939. ;
  9940. _proto.handleKeyDown = function handleKeyDown(event) {
  9941. // Ignore Space or Enter key operation, which is handled by the browser for
  9942. // a button - though not for its super class, ClickableComponent. Also,
  9943. // prevent the event from propagating through the DOM and triggering Player
  9944. // hotkeys. We do not preventDefault here because we _want_ the browser to
  9945. // handle it.
  9946. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  9947. event.stopPropagation();
  9948. return;
  9949. } // Pass keypress handling up for unsupported keys
  9950. _ClickableComponent.prototype.handleKeyDown.call(this, event);
  9951. };
  9952. return Button;
  9953. }(ClickableComponent);
  9954. Component$1.registerComponent('Button', Button);
  9955. /**
  9956. * The initial play button that shows before the video has played. The hiding of the
  9957. * `BigPlayButton` get done via CSS and `Player` states.
  9958. *
  9959. * @extends Button
  9960. */
  9961. var BigPlayButton = /*#__PURE__*/function (_Button) {
  9962. _inheritsLoose(BigPlayButton, _Button);
  9963. function BigPlayButton(player, options) {
  9964. var _this;
  9965. _this = _Button.call(this, player, options) || this;
  9966. _this.mouseused_ = false;
  9967. _this.on('mousedown', function (e) {
  9968. return _this.handleMouseDown(e);
  9969. });
  9970. return _this;
  9971. }
  9972. /**
  9973. * Builds the default DOM `className`.
  9974. *
  9975. * @return {string}
  9976. * The DOM `className` for this object. Always returns 'vjs-big-play-button'.
  9977. */
  9978. var _proto = BigPlayButton.prototype;
  9979. _proto.buildCSSClass = function buildCSSClass() {
  9980. return 'vjs-big-play-button';
  9981. }
  9982. /**
  9983. * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
  9984. * for more detailed information on what a click can be.
  9985. *
  9986. * @param {EventTarget~Event} event
  9987. * The `keydown`, `tap`, or `click` event that caused this function to be
  9988. * called.
  9989. *
  9990. * @listens tap
  9991. * @listens click
  9992. */
  9993. ;
  9994. _proto.handleClick = function handleClick(event) {
  9995. var playPromise = this.player_.play(); // exit early if clicked via the mouse
  9996. if (this.mouseused_ && event.clientX && event.clientY) {
  9997. var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0;
  9998. silencePromise(playPromise);
  9999. if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where
  10000. // calling .focus() on the video element causes the video to go black,
  10001. // so we avoid it in that specific case
  10002. !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) {
  10003. this.player_.tech(true).focus();
  10004. }
  10005. return;
  10006. }
  10007. var cb = this.player_.getChild('controlBar');
  10008. var playToggle = cb && cb.getChild('playToggle');
  10009. if (!playToggle) {
  10010. this.player_.tech(true).focus();
  10011. return;
  10012. }
  10013. var playFocus = function playFocus() {
  10014. return playToggle.focus();
  10015. };
  10016. if (isPromise(playPromise)) {
  10017. playPromise.then(playFocus, function () {});
  10018. } else {
  10019. this.setTimeout(playFocus, 1);
  10020. }
  10021. };
  10022. _proto.handleKeyDown = function handleKeyDown(event) {
  10023. this.mouseused_ = false;
  10024. _Button.prototype.handleKeyDown.call(this, event);
  10025. };
  10026. _proto.handleMouseDown = function handleMouseDown(event) {
  10027. this.mouseused_ = true;
  10028. };
  10029. return BigPlayButton;
  10030. }(Button);
  10031. /**
  10032. * The text that should display over the `BigPlayButton`s controls. Added to for localization.
  10033. *
  10034. * @type {string}
  10035. * @private
  10036. */
  10037. BigPlayButton.prototype.controlText_ = 'Play Video';
  10038. Component$1.registerComponent('BigPlayButton', BigPlayButton);
  10039. /**
  10040. * The `CloseButton` is a `{@link Button}` that fires a `close` event when
  10041. * it gets clicked.
  10042. *
  10043. * @extends Button
  10044. */
  10045. var CloseButton = /*#__PURE__*/function (_Button) {
  10046. _inheritsLoose(CloseButton, _Button);
  10047. /**
  10048. * Creates an instance of the this class.
  10049. *
  10050. * @param {Player} player
  10051. * The `Player` that this class should be attached to.
  10052. *
  10053. * @param {Object} [options]
  10054. * The key/value store of player options.
  10055. */
  10056. function CloseButton(player, options) {
  10057. var _this;
  10058. _this = _Button.call(this, player, options) || this;
  10059. _this.controlText(options && options.controlText || _this.localize('Close'));
  10060. return _this;
  10061. }
  10062. /**
  10063. * Builds the default DOM `className`.
  10064. *
  10065. * @return {string}
  10066. * The DOM `className` for this object.
  10067. */
  10068. var _proto = CloseButton.prototype;
  10069. _proto.buildCSSClass = function buildCSSClass() {
  10070. return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this);
  10071. }
  10072. /**
  10073. * This gets called when a `CloseButton` gets clicked. See
  10074. * {@link ClickableComponent#handleClick} for more information on when
  10075. * this will be triggered
  10076. *
  10077. * @param {EventTarget~Event} event
  10078. * The `keydown`, `tap`, or `click` event that caused this function to be
  10079. * called.
  10080. *
  10081. * @listens tap
  10082. * @listens click
  10083. * @fires CloseButton#close
  10084. */
  10085. ;
  10086. _proto.handleClick = function handleClick(event) {
  10087. /**
  10088. * Triggered when the a `CloseButton` is clicked.
  10089. *
  10090. * @event CloseButton#close
  10091. * @type {EventTarget~Event}
  10092. *
  10093. * @property {boolean} [bubbles=false]
  10094. * set to false so that the close event does not
  10095. * bubble up to parents if there is no listener
  10096. */
  10097. this.trigger({
  10098. type: 'close',
  10099. bubbles: false
  10100. });
  10101. }
  10102. /**
  10103. * Event handler that is called when a `CloseButton` receives a
  10104. * `keydown` event.
  10105. *
  10106. * By default, if the key is Esc, it will trigger a `click` event.
  10107. *
  10108. * @param {EventTarget~Event} event
  10109. * The `keydown` event that caused this function to be called.
  10110. *
  10111. * @listens keydown
  10112. */
  10113. ;
  10114. _proto.handleKeyDown = function handleKeyDown(event) {
  10115. // Esc button will trigger `click` event
  10116. if (keycode.isEventKey(event, 'Esc')) {
  10117. event.preventDefault();
  10118. event.stopPropagation();
  10119. this.trigger('click');
  10120. } else {
  10121. // Pass keypress handling up for unsupported keys
  10122. _Button.prototype.handleKeyDown.call(this, event);
  10123. }
  10124. };
  10125. return CloseButton;
  10126. }(Button);
  10127. Component$1.registerComponent('CloseButton', CloseButton);
  10128. /**
  10129. * Button to toggle between play and pause.
  10130. *
  10131. * @extends Button
  10132. */
  10133. var PlayToggle = /*#__PURE__*/function (_Button) {
  10134. _inheritsLoose(PlayToggle, _Button);
  10135. /**
  10136. * Creates an instance of this class.
  10137. *
  10138. * @param {Player} player
  10139. * The `Player` that this class should be attached to.
  10140. *
  10141. * @param {Object} [options={}]
  10142. * The key/value store of player options.
  10143. */
  10144. function PlayToggle(player, options) {
  10145. var _this;
  10146. if (options === void 0) {
  10147. options = {};
  10148. }
  10149. _this = _Button.call(this, player, options) || this; // show or hide replay icon
  10150. options.replay = options.replay === undefined || options.replay;
  10151. _this.on(player, 'play', function (e) {
  10152. return _this.handlePlay(e);
  10153. });
  10154. _this.on(player, 'pause', function (e) {
  10155. return _this.handlePause(e);
  10156. });
  10157. if (options.replay) {
  10158. _this.on(player, 'ended', function (e) {
  10159. return _this.handleEnded(e);
  10160. });
  10161. }
  10162. return _this;
  10163. }
  10164. /**
  10165. * Builds the default DOM `className`.
  10166. *
  10167. * @return {string}
  10168. * The DOM `className` for this object.
  10169. */
  10170. var _proto = PlayToggle.prototype;
  10171. _proto.buildCSSClass = function buildCSSClass() {
  10172. return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this);
  10173. }
  10174. /**
  10175. * This gets called when an `PlayToggle` is "clicked". See
  10176. * {@link ClickableComponent} for more detailed information on what a click can be.
  10177. *
  10178. * @param {EventTarget~Event} [event]
  10179. * The `keydown`, `tap`, or `click` event that caused this function to be
  10180. * called.
  10181. *
  10182. * @listens tap
  10183. * @listens click
  10184. */
  10185. ;
  10186. _proto.handleClick = function handleClick(event) {
  10187. if (this.player_.paused()) {
  10188. silencePromise(this.player_.play());
  10189. } else {
  10190. this.player_.pause();
  10191. }
  10192. }
  10193. /**
  10194. * This gets called once after the video has ended and the user seeks so that
  10195. * we can change the replay button back to a play button.
  10196. *
  10197. * @param {EventTarget~Event} [event]
  10198. * The event that caused this function to run.
  10199. *
  10200. * @listens Player#seeked
  10201. */
  10202. ;
  10203. _proto.handleSeeked = function handleSeeked(event) {
  10204. this.removeClass('vjs-ended');
  10205. if (this.player_.paused()) {
  10206. this.handlePause(event);
  10207. } else {
  10208. this.handlePlay(event);
  10209. }
  10210. }
  10211. /**
  10212. * Add the vjs-playing class to the element so it can change appearance.
  10213. *
  10214. * @param {EventTarget~Event} [event]
  10215. * The event that caused this function to run.
  10216. *
  10217. * @listens Player#play
  10218. */
  10219. ;
  10220. _proto.handlePlay = function handlePlay(event) {
  10221. this.removeClass('vjs-ended');
  10222. this.removeClass('vjs-paused');
  10223. this.addClass('vjs-playing'); // change the button text to "Pause"
  10224. this.controlText('Pause');
  10225. }
  10226. /**
  10227. * Add the vjs-paused class to the element so it can change appearance.
  10228. *
  10229. * @param {EventTarget~Event} [event]
  10230. * The event that caused this function to run.
  10231. *
  10232. * @listens Player#pause
  10233. */
  10234. ;
  10235. _proto.handlePause = function handlePause(event) {
  10236. this.removeClass('vjs-playing');
  10237. this.addClass('vjs-paused'); // change the button text to "Play"
  10238. this.controlText('Play');
  10239. }
  10240. /**
  10241. * Add the vjs-ended class to the element so it can change appearance
  10242. *
  10243. * @param {EventTarget~Event} [event]
  10244. * The event that caused this function to run.
  10245. *
  10246. * @listens Player#ended
  10247. */
  10248. ;
  10249. _proto.handleEnded = function handleEnded(event) {
  10250. var _this2 = this;
  10251. this.removeClass('vjs-playing');
  10252. this.addClass('vjs-ended'); // change the button text to "Replay"
  10253. this.controlText('Replay'); // on the next seek remove the replay button
  10254. this.one(this.player_, 'seeked', function (e) {
  10255. return _this2.handleSeeked(e);
  10256. });
  10257. };
  10258. return PlayToggle;
  10259. }(Button);
  10260. /**
  10261. * The text that should display over the `PlayToggle`s controls. Added for localization.
  10262. *
  10263. * @type {string}
  10264. * @private
  10265. */
  10266. PlayToggle.prototype.controlText_ = 'Play';
  10267. Component$1.registerComponent('PlayToggle', PlayToggle);
  10268. /**
  10269. * @file format-time.js
  10270. * @module format-time
  10271. */
  10272. /**
  10273. * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
  10274. * seconds) will force a number of leading zeros to cover the length of the
  10275. * guide.
  10276. *
  10277. * @private
  10278. * @param {number} seconds
  10279. * Number of seconds to be turned into a string
  10280. *
  10281. * @param {number} guide
  10282. * Number (in seconds) to model the string after
  10283. *
  10284. * @return {string}
  10285. * Time formatted as H:MM:SS or M:SS
  10286. */
  10287. var defaultImplementation = function defaultImplementation(seconds, guide) {
  10288. seconds = seconds < 0 ? 0 : seconds;
  10289. var s = Math.floor(seconds % 60);
  10290. var m = Math.floor(seconds / 60 % 60);
  10291. var h = Math.floor(seconds / 3600);
  10292. var gm = Math.floor(guide / 60 % 60);
  10293. var gh = Math.floor(guide / 3600); // handle invalid times
  10294. if (isNaN(seconds) || seconds === Infinity) {
  10295. // '-' is false for all relational operators (e.g. <, >=) so this setting
  10296. // will add the minimum number of fields specified by the guide
  10297. h = m = s = '-';
  10298. } // Check if we need to show hours
  10299. h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero.
  10300. // Always show at least one digit of minutes.
  10301. m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds
  10302. s = s < 10 ? '0' + s : s;
  10303. return h + m + s;
  10304. }; // Internal pointer to the current implementation.
  10305. var implementation = defaultImplementation;
  10306. /**
  10307. * Replaces the default formatTime implementation with a custom implementation.
  10308. *
  10309. * @param {Function} customImplementation
  10310. * A function which will be used in place of the default formatTime
  10311. * implementation. Will receive the current time in seconds and the
  10312. * guide (in seconds) as arguments.
  10313. */
  10314. function setFormatTime(customImplementation) {
  10315. implementation = customImplementation;
  10316. }
  10317. /**
  10318. * Resets formatTime to the default implementation.
  10319. */
  10320. function resetFormatTime() {
  10321. implementation = defaultImplementation;
  10322. }
  10323. /**
  10324. * Delegates to either the default time formatting function or a custom
  10325. * function supplied via `setFormatTime`.
  10326. *
  10327. * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
  10328. * guide (in seconds) will force a number of leading zeros to cover the
  10329. * length of the guide.
  10330. *
  10331. * @static
  10332. * @example formatTime(125, 600) === "02:05"
  10333. * @param {number} seconds
  10334. * Number of seconds to be turned into a string
  10335. *
  10336. * @param {number} guide
  10337. * Number (in seconds) to model the string after
  10338. *
  10339. * @return {string}
  10340. * Time formatted as H:MM:SS or M:SS
  10341. */
  10342. function formatTime(seconds, guide) {
  10343. if (guide === void 0) {
  10344. guide = seconds;
  10345. }
  10346. return implementation(seconds, guide);
  10347. }
  10348. /**
  10349. * Displays time information about the video
  10350. *
  10351. * @extends Component
  10352. */
  10353. var TimeDisplay = /*#__PURE__*/function (_Component) {
  10354. _inheritsLoose(TimeDisplay, _Component);
  10355. /**
  10356. * Creates an instance of this class.
  10357. *
  10358. * @param {Player} player
  10359. * The `Player` that this class should be attached to.
  10360. *
  10361. * @param {Object} [options]
  10362. * The key/value store of player options.
  10363. */
  10364. function TimeDisplay(player, options) {
  10365. var _this;
  10366. _this = _Component.call(this, player, options) || this;
  10367. _this.on(player, ['timeupdate', 'ended'], function (e) {
  10368. return _this.updateContent(e);
  10369. });
  10370. _this.updateTextNode_();
  10371. return _this;
  10372. }
  10373. /**
  10374. * Create the `Component`'s DOM element
  10375. *
  10376. * @return {Element}
  10377. * The element that was created.
  10378. */
  10379. var _proto = TimeDisplay.prototype;
  10380. _proto.createEl = function createEl$1() {
  10381. var className = this.buildCSSClass();
  10382. var el = _Component.prototype.createEl.call(this, 'div', {
  10383. className: className + " vjs-time-control vjs-control"
  10384. });
  10385. var span = createEl('span', {
  10386. className: 'vjs-control-text',
  10387. textContent: this.localize(this.labelText_) + "\xA0"
  10388. }, {
  10389. role: 'presentation'
  10390. });
  10391. el.appendChild(span);
  10392. this.contentEl_ = createEl('span', {
  10393. className: className + "-display"
  10394. }, {
  10395. // tell screen readers not to automatically read the time as it changes
  10396. 'aria-live': 'off',
  10397. // span elements have no implicit role, but some screen readers (notably VoiceOver)
  10398. // treat them as a break between items in the DOM when using arrow keys
  10399. // (or left-to-right swipes on iOS) to read contents of a page. Using
  10400. // role='presentation' causes VoiceOver to NOT treat this span as a break.
  10401. 'role': 'presentation'
  10402. });
  10403. el.appendChild(this.contentEl_);
  10404. return el;
  10405. };
  10406. _proto.dispose = function dispose() {
  10407. this.contentEl_ = null;
  10408. this.textNode_ = null;
  10409. _Component.prototype.dispose.call(this);
  10410. }
  10411. /**
  10412. * Updates the time display text node with a new time
  10413. *
  10414. * @param {number} [time=0] the time to update to
  10415. *
  10416. * @private
  10417. */
  10418. ;
  10419. _proto.updateTextNode_ = function updateTextNode_(time) {
  10420. var _this2 = this;
  10421. if (time === void 0) {
  10422. time = 0;
  10423. }
  10424. time = formatTime(time);
  10425. if (this.formattedTime_ === time) {
  10426. return;
  10427. }
  10428. this.formattedTime_ = time;
  10429. this.requestNamedAnimationFrame('TimeDisplay#updateTextNode_', function () {
  10430. if (!_this2.contentEl_) {
  10431. return;
  10432. }
  10433. var oldNode = _this2.textNode_;
  10434. if (oldNode && _this2.contentEl_.firstChild !== oldNode) {
  10435. oldNode = null;
  10436. log$1.warn('TimeDisplay#updateTextnode_: Prevented replacement of text node element since it was no longer a child of this node. Appending a new node instead.');
  10437. }
  10438. _this2.textNode_ = document.createTextNode(_this2.formattedTime_);
  10439. if (!_this2.textNode_) {
  10440. return;
  10441. }
  10442. if (oldNode) {
  10443. _this2.contentEl_.replaceChild(_this2.textNode_, oldNode);
  10444. } else {
  10445. _this2.contentEl_.appendChild(_this2.textNode_);
  10446. }
  10447. });
  10448. }
  10449. /**
  10450. * To be filled out in the child class, should update the displayed time
  10451. * in accordance with the fact that the current time has changed.
  10452. *
  10453. * @param {EventTarget~Event} [event]
  10454. * The `timeupdate` event that caused this to run.
  10455. *
  10456. * @listens Player#timeupdate
  10457. */
  10458. ;
  10459. _proto.updateContent = function updateContent(event) {};
  10460. return TimeDisplay;
  10461. }(Component$1);
  10462. /**
  10463. * The text that is added to the `TimeDisplay` for screen reader users.
  10464. *
  10465. * @type {string}
  10466. * @private
  10467. */
  10468. TimeDisplay.prototype.labelText_ = 'Time';
  10469. /**
  10470. * The text that should display over the `TimeDisplay`s controls. Added to for localization.
  10471. *
  10472. * @type {string}
  10473. * @private
  10474. *
  10475. * @deprecated in v7; controlText_ is not used in non-active display Components
  10476. */
  10477. TimeDisplay.prototype.controlText_ = 'Time';
  10478. Component$1.registerComponent('TimeDisplay', TimeDisplay);
  10479. /**
  10480. * Displays the current time
  10481. *
  10482. * @extends Component
  10483. */
  10484. var CurrentTimeDisplay = /*#__PURE__*/function (_TimeDisplay) {
  10485. _inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
  10486. function CurrentTimeDisplay() {
  10487. return _TimeDisplay.apply(this, arguments) || this;
  10488. }
  10489. var _proto = CurrentTimeDisplay.prototype;
  10490. /**
  10491. * Builds the default DOM `className`.
  10492. *
  10493. * @return {string}
  10494. * The DOM `className` for this object.
  10495. */
  10496. _proto.buildCSSClass = function buildCSSClass() {
  10497. return 'vjs-current-time';
  10498. }
  10499. /**
  10500. * Update current time display
  10501. *
  10502. * @param {EventTarget~Event} [event]
  10503. * The `timeupdate` event that caused this function to run.
  10504. *
  10505. * @listens Player#timeupdate
  10506. */
  10507. ;
  10508. _proto.updateContent = function updateContent(event) {
  10509. // Allows for smooth scrubbing, when player can't keep up.
  10510. var time;
  10511. if (this.player_.ended()) {
  10512. time = this.player_.duration();
  10513. } else {
  10514. time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  10515. }
  10516. this.updateTextNode_(time);
  10517. };
  10518. return CurrentTimeDisplay;
  10519. }(TimeDisplay);
  10520. /**
  10521. * The text that is added to the `CurrentTimeDisplay` for screen reader users.
  10522. *
  10523. * @type {string}
  10524. * @private
  10525. */
  10526. CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
  10527. /**
  10528. * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
  10529. *
  10530. * @type {string}
  10531. * @private
  10532. *
  10533. * @deprecated in v7; controlText_ is not used in non-active display Components
  10534. */
  10535. CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
  10536. Component$1.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
  10537. /**
  10538. * Displays the duration
  10539. *
  10540. * @extends Component
  10541. */
  10542. var DurationDisplay = /*#__PURE__*/function (_TimeDisplay) {
  10543. _inheritsLoose(DurationDisplay, _TimeDisplay);
  10544. /**
  10545. * Creates an instance of this class.
  10546. *
  10547. * @param {Player} player
  10548. * The `Player` that this class should be attached to.
  10549. *
  10550. * @param {Object} [options]
  10551. * The key/value store of player options.
  10552. */
  10553. function DurationDisplay(player, options) {
  10554. var _this;
  10555. _this = _TimeDisplay.call(this, player, options) || this;
  10556. var updateContent = function updateContent(e) {
  10557. return _this.updateContent(e);
  10558. }; // we do not want to/need to throttle duration changes,
  10559. // as they should always display the changed duration as
  10560. // it has changed
  10561. _this.on(player, 'durationchange', updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded,
  10562. // but the durationchange on the user agent will not fire.
  10563. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  10564. _this.on(player, 'loadstart', updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
  10565. // listeners could have broken dependent applications/libraries. These
  10566. // can likely be removed for 7.0.
  10567. _this.on(player, 'loadedmetadata', updateContent);
  10568. return _this;
  10569. }
  10570. /**
  10571. * Builds the default DOM `className`.
  10572. *
  10573. * @return {string}
  10574. * The DOM `className` for this object.
  10575. */
  10576. var _proto = DurationDisplay.prototype;
  10577. _proto.buildCSSClass = function buildCSSClass() {
  10578. return 'vjs-duration';
  10579. }
  10580. /**
  10581. * Update duration time display.
  10582. *
  10583. * @param {EventTarget~Event} [event]
  10584. * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
  10585. * this function to be called.
  10586. *
  10587. * @listens Player#durationchange
  10588. * @listens Player#timeupdate
  10589. * @listens Player#loadedmetadata
  10590. */
  10591. ;
  10592. _proto.updateContent = function updateContent(event) {
  10593. var duration = this.player_.duration();
  10594. this.updateTextNode_(duration);
  10595. };
  10596. return DurationDisplay;
  10597. }(TimeDisplay);
  10598. /**
  10599. * The text that is added to the `DurationDisplay` for screen reader users.
  10600. *
  10601. * @type {string}
  10602. * @private
  10603. */
  10604. DurationDisplay.prototype.labelText_ = 'Duration';
  10605. /**
  10606. * The text that should display over the `DurationDisplay`s controls. Added to for localization.
  10607. *
  10608. * @type {string}
  10609. * @private
  10610. *
  10611. * @deprecated in v7; controlText_ is not used in non-active display Components
  10612. */
  10613. DurationDisplay.prototype.controlText_ = 'Duration';
  10614. Component$1.registerComponent('DurationDisplay', DurationDisplay);
  10615. /**
  10616. * The separator between the current time and duration.
  10617. * Can be hidden if it's not needed in the design.
  10618. *
  10619. * @extends Component
  10620. */
  10621. var TimeDivider = /*#__PURE__*/function (_Component) {
  10622. _inheritsLoose(TimeDivider, _Component);
  10623. function TimeDivider() {
  10624. return _Component.apply(this, arguments) || this;
  10625. }
  10626. var _proto = TimeDivider.prototype;
  10627. /**
  10628. * Create the component's DOM element
  10629. *
  10630. * @return {Element}
  10631. * The element that was created.
  10632. */
  10633. _proto.createEl = function createEl() {
  10634. var el = _Component.prototype.createEl.call(this, 'div', {
  10635. className: 'vjs-time-control vjs-time-divider'
  10636. }, {
  10637. // this element and its contents can be hidden from assistive techs since
  10638. // it is made extraneous by the announcement of the control text
  10639. // for the current time and duration displays
  10640. 'aria-hidden': true
  10641. });
  10642. var div = _Component.prototype.createEl.call(this, 'div');
  10643. var span = _Component.prototype.createEl.call(this, 'span', {
  10644. textContent: '/'
  10645. });
  10646. div.appendChild(span);
  10647. el.appendChild(div);
  10648. return el;
  10649. };
  10650. return TimeDivider;
  10651. }(Component$1);
  10652. Component$1.registerComponent('TimeDivider', TimeDivider);
  10653. /**
  10654. * Displays the time left in the video
  10655. *
  10656. * @extends Component
  10657. */
  10658. var RemainingTimeDisplay = /*#__PURE__*/function (_TimeDisplay) {
  10659. _inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
  10660. /**
  10661. * Creates an instance of this class.
  10662. *
  10663. * @param {Player} player
  10664. * The `Player` that this class should be attached to.
  10665. *
  10666. * @param {Object} [options]
  10667. * The key/value store of player options.
  10668. */
  10669. function RemainingTimeDisplay(player, options) {
  10670. var _this;
  10671. _this = _TimeDisplay.call(this, player, options) || this;
  10672. _this.on(player, 'durationchange', function (e) {
  10673. return _this.updateContent(e);
  10674. });
  10675. return _this;
  10676. }
  10677. /**
  10678. * Builds the default DOM `className`.
  10679. *
  10680. * @return {string}
  10681. * The DOM `className` for this object.
  10682. */
  10683. var _proto = RemainingTimeDisplay.prototype;
  10684. _proto.buildCSSClass = function buildCSSClass() {
  10685. return 'vjs-remaining-time';
  10686. }
  10687. /**
  10688. * Create the `Component`'s DOM element with the "minus" characted prepend to the time
  10689. *
  10690. * @return {Element}
  10691. * The element that was created.
  10692. */
  10693. ;
  10694. _proto.createEl = function createEl$1() {
  10695. var el = _TimeDisplay.prototype.createEl.call(this);
  10696. if (this.options_.displayNegative !== false) {
  10697. el.insertBefore(createEl('span', {}, {
  10698. 'aria-hidden': true
  10699. }, '-'), this.contentEl_);
  10700. }
  10701. return el;
  10702. }
  10703. /**
  10704. * Update remaining time display.
  10705. *
  10706. * @param {EventTarget~Event} [event]
  10707. * The `timeupdate` or `durationchange` event that caused this to run.
  10708. *
  10709. * @listens Player#timeupdate
  10710. * @listens Player#durationchange
  10711. */
  10712. ;
  10713. _proto.updateContent = function updateContent(event) {
  10714. if (typeof this.player_.duration() !== 'number') {
  10715. return;
  10716. }
  10717. var time; // @deprecated We should only use remainingTimeDisplay
  10718. // as of video.js 7
  10719. if (this.player_.ended()) {
  10720. time = 0;
  10721. } else if (this.player_.remainingTimeDisplay) {
  10722. time = this.player_.remainingTimeDisplay();
  10723. } else {
  10724. time = this.player_.remainingTime();
  10725. }
  10726. this.updateTextNode_(time);
  10727. };
  10728. return RemainingTimeDisplay;
  10729. }(TimeDisplay);
  10730. /**
  10731. * The text that is added to the `RemainingTimeDisplay` for screen reader users.
  10732. *
  10733. * @type {string}
  10734. * @private
  10735. */
  10736. RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
  10737. /**
  10738. * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
  10739. *
  10740. * @type {string}
  10741. * @private
  10742. *
  10743. * @deprecated in v7; controlText_ is not used in non-active display Components
  10744. */
  10745. RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
  10746. Component$1.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
  10747. /**
  10748. * Displays the live indicator when duration is Infinity.
  10749. *
  10750. * @extends Component
  10751. */
  10752. var LiveDisplay = /*#__PURE__*/function (_Component) {
  10753. _inheritsLoose(LiveDisplay, _Component);
  10754. /**
  10755. * Creates an instance of this class.
  10756. *
  10757. * @param {Player} player
  10758. * The `Player` that this class should be attached to.
  10759. *
  10760. * @param {Object} [options]
  10761. * The key/value store of player options.
  10762. */
  10763. function LiveDisplay(player, options) {
  10764. var _this;
  10765. _this = _Component.call(this, player, options) || this;
  10766. _this.updateShowing();
  10767. _this.on(_this.player(), 'durationchange', function (e) {
  10768. return _this.updateShowing(e);
  10769. });
  10770. return _this;
  10771. }
  10772. /**
  10773. * Create the `Component`'s DOM element
  10774. *
  10775. * @return {Element}
  10776. * The element that was created.
  10777. */
  10778. var _proto = LiveDisplay.prototype;
  10779. _proto.createEl = function createEl$1() {
  10780. var el = _Component.prototype.createEl.call(this, 'div', {
  10781. className: 'vjs-live-control vjs-control'
  10782. });
  10783. this.contentEl_ = createEl('div', {
  10784. className: 'vjs-live-display'
  10785. }, {
  10786. 'aria-live': 'off'
  10787. });
  10788. this.contentEl_.appendChild(createEl('span', {
  10789. className: 'vjs-control-text',
  10790. textContent: this.localize('Stream Type') + "\xA0"
  10791. }));
  10792. this.contentEl_.appendChild(document.createTextNode(this.localize('LIVE')));
  10793. el.appendChild(this.contentEl_);
  10794. return el;
  10795. };
  10796. _proto.dispose = function dispose() {
  10797. this.contentEl_ = null;
  10798. _Component.prototype.dispose.call(this);
  10799. }
  10800. /**
  10801. * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
  10802. * it accordingly
  10803. *
  10804. * @param {EventTarget~Event} [event]
  10805. * The {@link Player#durationchange} event that caused this function to run.
  10806. *
  10807. * @listens Player#durationchange
  10808. */
  10809. ;
  10810. _proto.updateShowing = function updateShowing(event) {
  10811. if (this.player().duration() === Infinity) {
  10812. this.show();
  10813. } else {
  10814. this.hide();
  10815. }
  10816. };
  10817. return LiveDisplay;
  10818. }(Component$1);
  10819. Component$1.registerComponent('LiveDisplay', LiveDisplay);
  10820. /**
  10821. * Displays the live indicator when duration is Infinity.
  10822. *
  10823. * @extends Component
  10824. */
  10825. var SeekToLive = /*#__PURE__*/function (_Button) {
  10826. _inheritsLoose(SeekToLive, _Button);
  10827. /**
  10828. * Creates an instance of this class.
  10829. *
  10830. * @param {Player} player
  10831. * The `Player` that this class should be attached to.
  10832. *
  10833. * @param {Object} [options]
  10834. * The key/value store of player options.
  10835. */
  10836. function SeekToLive(player, options) {
  10837. var _this;
  10838. _this = _Button.call(this, player, options) || this;
  10839. _this.updateLiveEdgeStatus();
  10840. if (_this.player_.liveTracker) {
  10841. _this.updateLiveEdgeStatusHandler_ = function (e) {
  10842. return _this.updateLiveEdgeStatus(e);
  10843. };
  10844. _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatusHandler_);
  10845. }
  10846. return _this;
  10847. }
  10848. /**
  10849. * Create the `Component`'s DOM element
  10850. *
  10851. * @return {Element}
  10852. * The element that was created.
  10853. */
  10854. var _proto = SeekToLive.prototype;
  10855. _proto.createEl = function createEl$1() {
  10856. var el = _Button.prototype.createEl.call(this, 'button', {
  10857. className: 'vjs-seek-to-live-control vjs-control'
  10858. });
  10859. this.textEl_ = createEl('span', {
  10860. className: 'vjs-seek-to-live-text',
  10861. textContent: this.localize('LIVE')
  10862. }, {
  10863. 'aria-hidden': 'true'
  10864. });
  10865. el.appendChild(this.textEl_);
  10866. return el;
  10867. }
  10868. /**
  10869. * Update the state of this button if we are at the live edge
  10870. * or not
  10871. */
  10872. ;
  10873. _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus() {
  10874. // default to live edge
  10875. if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
  10876. this.setAttribute('aria-disabled', true);
  10877. this.addClass('vjs-at-live-edge');
  10878. this.controlText('Seek to live, currently playing live');
  10879. } else {
  10880. this.setAttribute('aria-disabled', false);
  10881. this.removeClass('vjs-at-live-edge');
  10882. this.controlText('Seek to live, currently behind live');
  10883. }
  10884. }
  10885. /**
  10886. * On click bring us as near to the live point as possible.
  10887. * This requires that we wait for the next `live-seekable-change`
  10888. * event which will happen every segment length seconds.
  10889. */
  10890. ;
  10891. _proto.handleClick = function handleClick() {
  10892. this.player_.liveTracker.seekToLiveEdge();
  10893. }
  10894. /**
  10895. * Dispose of the element and stop tracking
  10896. */
  10897. ;
  10898. _proto.dispose = function dispose() {
  10899. if (this.player_.liveTracker) {
  10900. this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
  10901. }
  10902. this.textEl_ = null;
  10903. _Button.prototype.dispose.call(this);
  10904. };
  10905. return SeekToLive;
  10906. }(Button);
  10907. SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
  10908. Component$1.registerComponent('SeekToLive', SeekToLive);
  10909. /**
  10910. * Keep a number between a min and a max value
  10911. *
  10912. * @param {number} number
  10913. * The number to clamp
  10914. *
  10915. * @param {number} min
  10916. * The minimum value
  10917. * @param {number} max
  10918. * The maximum value
  10919. *
  10920. * @return {number}
  10921. * the clamped number
  10922. */
  10923. var clamp = function clamp(number, min, max) {
  10924. number = Number(number);
  10925. return Math.min(max, Math.max(min, isNaN(number) ? min : number));
  10926. };
  10927. /**
  10928. * The base functionality for a slider. Can be vertical or horizontal.
  10929. * For instance the volume bar or the seek bar on a video is a slider.
  10930. *
  10931. * @extends Component
  10932. */
  10933. var Slider = /*#__PURE__*/function (_Component) {
  10934. _inheritsLoose(Slider, _Component);
  10935. /**
  10936. * Create an instance of this class
  10937. *
  10938. * @param {Player} player
  10939. * The `Player` that this class should be attached to.
  10940. *
  10941. * @param {Object} [options]
  10942. * The key/value store of player options.
  10943. */
  10944. function Slider(player, options) {
  10945. var _this;
  10946. _this = _Component.call(this, player, options) || this;
  10947. _this.handleMouseDown_ = function (e) {
  10948. return _this.handleMouseDown(e);
  10949. };
  10950. _this.handleMouseUp_ = function (e) {
  10951. return _this.handleMouseUp(e);
  10952. };
  10953. _this.handleKeyDown_ = function (e) {
  10954. return _this.handleKeyDown(e);
  10955. };
  10956. _this.handleClick_ = function (e) {
  10957. return _this.handleClick(e);
  10958. };
  10959. _this.handleMouseMove_ = function (e) {
  10960. return _this.handleMouseMove(e);
  10961. };
  10962. _this.update_ = function (e) {
  10963. return _this.update(e);
  10964. }; // Set property names to bar to match with the child Slider class is looking for
  10965. _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type
  10966. _this.vertical(!!_this.options_.vertical);
  10967. _this.enable();
  10968. return _this;
  10969. }
  10970. /**
  10971. * Are controls are currently enabled for this slider or not.
  10972. *
  10973. * @return {boolean}
  10974. * true if controls are enabled, false otherwise
  10975. */
  10976. var _proto = Slider.prototype;
  10977. _proto.enabled = function enabled() {
  10978. return this.enabled_;
  10979. }
  10980. /**
  10981. * Enable controls for this slider if they are disabled
  10982. */
  10983. ;
  10984. _proto.enable = function enable() {
  10985. if (this.enabled()) {
  10986. return;
  10987. }
  10988. this.on('mousedown', this.handleMouseDown_);
  10989. this.on('touchstart', this.handleMouseDown_);
  10990. this.on('keydown', this.handleKeyDown_);
  10991. this.on('click', this.handleClick_); // TODO: deprecated, controlsvisible does not seem to be fired
  10992. this.on(this.player_, 'controlsvisible', this.update);
  10993. if (this.playerEvent) {
  10994. this.on(this.player_, this.playerEvent, this.update);
  10995. }
  10996. this.removeClass('disabled');
  10997. this.setAttribute('tabindex', 0);
  10998. this.enabled_ = true;
  10999. }
  11000. /**
  11001. * Disable controls for this slider if they are enabled
  11002. */
  11003. ;
  11004. _proto.disable = function disable() {
  11005. if (!this.enabled()) {
  11006. return;
  11007. }
  11008. var doc = this.bar.el_.ownerDocument;
  11009. this.off('mousedown', this.handleMouseDown_);
  11010. this.off('touchstart', this.handleMouseDown_);
  11011. this.off('keydown', this.handleKeyDown_);
  11012. this.off('click', this.handleClick_);
  11013. this.off(this.player_, 'controlsvisible', this.update_);
  11014. this.off(doc, 'mousemove', this.handleMouseMove_);
  11015. this.off(doc, 'mouseup', this.handleMouseUp_);
  11016. this.off(doc, 'touchmove', this.handleMouseMove_);
  11017. this.off(doc, 'touchend', this.handleMouseUp_);
  11018. this.removeAttribute('tabindex');
  11019. this.addClass('disabled');
  11020. if (this.playerEvent) {
  11021. this.off(this.player_, this.playerEvent, this.update);
  11022. }
  11023. this.enabled_ = false;
  11024. }
  11025. /**
  11026. * Create the `Slider`s DOM element.
  11027. *
  11028. * @param {string} type
  11029. * Type of element to create.
  11030. *
  11031. * @param {Object} [props={}]
  11032. * List of properties in Object form.
  11033. *
  11034. * @param {Object} [attributes={}]
  11035. * list of attributes in Object form.
  11036. *
  11037. * @return {Element}
  11038. * The element that gets created.
  11039. */
  11040. ;
  11041. _proto.createEl = function createEl(type, props, attributes) {
  11042. if (props === void 0) {
  11043. props = {};
  11044. }
  11045. if (attributes === void 0) {
  11046. attributes = {};
  11047. }
  11048. // Add the slider element class to all sub classes
  11049. props.className = props.className + ' vjs-slider';
  11050. props = assign({
  11051. tabIndex: 0
  11052. }, props);
  11053. attributes = assign({
  11054. 'role': 'slider',
  11055. 'aria-valuenow': 0,
  11056. 'aria-valuemin': 0,
  11057. 'aria-valuemax': 100,
  11058. 'tabIndex': 0
  11059. }, attributes);
  11060. return _Component.prototype.createEl.call(this, type, props, attributes);
  11061. }
  11062. /**
  11063. * Handle `mousedown` or `touchstart` events on the `Slider`.
  11064. *
  11065. * @param {EventTarget~Event} event
  11066. * `mousedown` or `touchstart` event that triggered this function
  11067. *
  11068. * @listens mousedown
  11069. * @listens touchstart
  11070. * @fires Slider#slideractive
  11071. */
  11072. ;
  11073. _proto.handleMouseDown = function handleMouseDown(event) {
  11074. var doc = this.bar.el_.ownerDocument;
  11075. if (event.type === 'mousedown') {
  11076. event.preventDefault();
  11077. } // Do not call preventDefault() on touchstart in Chrome
  11078. // to avoid console warnings. Use a 'touch-action: none' style
  11079. // instead to prevent unintented scrolling.
  11080. // https://developers.google.com/web/updates/2017/01/scrolling-intervention
  11081. if (event.type === 'touchstart' && !IS_CHROME) {
  11082. event.preventDefault();
  11083. }
  11084. blockTextSelection();
  11085. this.addClass('vjs-sliding');
  11086. /**
  11087. * Triggered when the slider is in an active state
  11088. *
  11089. * @event Slider#slideractive
  11090. * @type {EventTarget~Event}
  11091. */
  11092. this.trigger('slideractive');
  11093. this.on(doc, 'mousemove', this.handleMouseMove_);
  11094. this.on(doc, 'mouseup', this.handleMouseUp_);
  11095. this.on(doc, 'touchmove', this.handleMouseMove_);
  11096. this.on(doc, 'touchend', this.handleMouseUp_);
  11097. this.handleMouseMove(event, true);
  11098. }
  11099. /**
  11100. * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
  11101. * The `mousemove` and `touchmove` events will only only trigger this function during
  11102. * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
  11103. * {@link Slider#handleMouseUp}.
  11104. *
  11105. * @param {EventTarget~Event} event
  11106. * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
  11107. * this function
  11108. * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if coming from mouse down but should happen on regular mouse move handler. Defaults to false.
  11109. *
  11110. * @listens mousemove
  11111. * @listens touchmove
  11112. */
  11113. ;
  11114. _proto.handleMouseMove = function handleMouseMove(event) {}
  11115. /**
  11116. * Handle `mouseup` or `touchend` events on the `Slider`.
  11117. *
  11118. * @param {EventTarget~Event} event
  11119. * `mouseup` or `touchend` event that triggered this function.
  11120. *
  11121. * @listens touchend
  11122. * @listens mouseup
  11123. * @fires Slider#sliderinactive
  11124. */
  11125. ;
  11126. _proto.handleMouseUp = function handleMouseUp() {
  11127. var doc = this.bar.el_.ownerDocument;
  11128. unblockTextSelection();
  11129. this.removeClass('vjs-sliding');
  11130. /**
  11131. * Triggered when the slider is no longer in an active state.
  11132. *
  11133. * @event Slider#sliderinactive
  11134. * @type {EventTarget~Event}
  11135. */
  11136. this.trigger('sliderinactive');
  11137. this.off(doc, 'mousemove', this.handleMouseMove_);
  11138. this.off(doc, 'mouseup', this.handleMouseUp_);
  11139. this.off(doc, 'touchmove', this.handleMouseMove_);
  11140. this.off(doc, 'touchend', this.handleMouseUp_);
  11141. this.update();
  11142. }
  11143. /**
  11144. * Update the progress bar of the `Slider`.
  11145. *
  11146. * @return {number}
  11147. * The percentage of progress the progress bar represents as a
  11148. * number from 0 to 1.
  11149. */
  11150. ;
  11151. _proto.update = function update() {
  11152. var _this2 = this;
  11153. // In VolumeBar init we have a setTimeout for update that pops and update
  11154. // to the end of the execution stack. The player is destroyed before then
  11155. // update will cause an error
  11156. // If there's no bar...
  11157. if (!this.el_ || !this.bar) {
  11158. return;
  11159. } // clamp progress between 0 and 1
  11160. // and only round to four decimal places, as we round to two below
  11161. var progress = this.getProgress();
  11162. if (progress === this.progress_) {
  11163. return progress;
  11164. }
  11165. this.progress_ = progress;
  11166. this.requestNamedAnimationFrame('Slider#update', function () {
  11167. // Set the new bar width or height
  11168. var sizeKey = _this2.vertical() ? 'height' : 'width'; // Convert to a percentage for css value
  11169. _this2.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%';
  11170. });
  11171. return progress;
  11172. }
  11173. /**
  11174. * Get the percentage of the bar that should be filled
  11175. * but clamped and rounded.
  11176. *
  11177. * @return {number}
  11178. * percentage filled that the slider is
  11179. */
  11180. ;
  11181. _proto.getProgress = function getProgress() {
  11182. return Number(clamp(this.getPercent(), 0, 1).toFixed(4));
  11183. }
  11184. /**
  11185. * Calculate distance for slider
  11186. *
  11187. * @param {EventTarget~Event} event
  11188. * The event that caused this function to run.
  11189. *
  11190. * @return {number}
  11191. * The current position of the Slider.
  11192. * - position.x for vertical `Slider`s
  11193. * - position.y for horizontal `Slider`s
  11194. */
  11195. ;
  11196. _proto.calculateDistance = function calculateDistance(event) {
  11197. var position = getPointerPosition(this.el_, event);
  11198. if (this.vertical()) {
  11199. return position.y;
  11200. }
  11201. return position.x;
  11202. }
  11203. /**
  11204. * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
  11205. * arrow keys. This function will only be called when the slider has focus. See
  11206. * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
  11207. *
  11208. * @param {EventTarget~Event} event
  11209. * the `keydown` event that caused this function to run.
  11210. *
  11211. * @listens keydown
  11212. */
  11213. ;
  11214. _proto.handleKeyDown = function handleKeyDown(event) {
  11215. // Left and Down Arrows
  11216. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  11217. event.preventDefault();
  11218. event.stopPropagation();
  11219. this.stepBack(); // Up and Right Arrows
  11220. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  11221. event.preventDefault();
  11222. event.stopPropagation();
  11223. this.stepForward();
  11224. } else {
  11225. // Pass keydown handling up for unsupported keys
  11226. _Component.prototype.handleKeyDown.call(this, event);
  11227. }
  11228. }
  11229. /**
  11230. * Listener for click events on slider, used to prevent clicks
  11231. * from bubbling up to parent elements like button menus.
  11232. *
  11233. * @param {Object} event
  11234. * Event that caused this object to run
  11235. */
  11236. ;
  11237. _proto.handleClick = function handleClick(event) {
  11238. event.stopPropagation();
  11239. event.preventDefault();
  11240. }
  11241. /**
  11242. * Get/set if slider is horizontal for vertical
  11243. *
  11244. * @param {boolean} [bool]
  11245. * - true if slider is vertical,
  11246. * - false is horizontal
  11247. *
  11248. * @return {boolean}
  11249. * - true if slider is vertical, and getting
  11250. * - false if the slider is horizontal, and getting
  11251. */
  11252. ;
  11253. _proto.vertical = function vertical(bool) {
  11254. if (bool === undefined) {
  11255. return this.vertical_ || false;
  11256. }
  11257. this.vertical_ = !!bool;
  11258. if (this.vertical_) {
  11259. this.addClass('vjs-slider-vertical');
  11260. } else {
  11261. this.addClass('vjs-slider-horizontal');
  11262. }
  11263. };
  11264. return Slider;
  11265. }(Component$1);
  11266. Component$1.registerComponent('Slider', Slider);
  11267. var percentify = function percentify(time, end) {
  11268. return clamp(time / end * 100, 0, 100).toFixed(2) + '%';
  11269. };
  11270. /**
  11271. * Shows loading progress
  11272. *
  11273. * @extends Component
  11274. */
  11275. var LoadProgressBar = /*#__PURE__*/function (_Component) {
  11276. _inheritsLoose(LoadProgressBar, _Component);
  11277. /**
  11278. * Creates an instance of this class.
  11279. *
  11280. * @param {Player} player
  11281. * The `Player` that this class should be attached to.
  11282. *
  11283. * @param {Object} [options]
  11284. * The key/value store of player options.
  11285. */
  11286. function LoadProgressBar(player, options) {
  11287. var _this;
  11288. _this = _Component.call(this, player, options) || this;
  11289. _this.partEls_ = [];
  11290. _this.on(player, 'progress', function (e) {
  11291. return _this.update(e);
  11292. });
  11293. return _this;
  11294. }
  11295. /**
  11296. * Create the `Component`'s DOM element
  11297. *
  11298. * @return {Element}
  11299. * The element that was created.
  11300. */
  11301. var _proto = LoadProgressBar.prototype;
  11302. _proto.createEl = function createEl$1() {
  11303. var el = _Component.prototype.createEl.call(this, 'div', {
  11304. className: 'vjs-load-progress'
  11305. });
  11306. var wrapper = createEl('span', {
  11307. className: 'vjs-control-text'
  11308. });
  11309. var loadedText = createEl('span', {
  11310. textContent: this.localize('Loaded')
  11311. });
  11312. var separator = document.createTextNode(': ');
  11313. this.percentageEl_ = createEl('span', {
  11314. className: 'vjs-control-text-loaded-percentage',
  11315. textContent: '0%'
  11316. });
  11317. el.appendChild(wrapper);
  11318. wrapper.appendChild(loadedText);
  11319. wrapper.appendChild(separator);
  11320. wrapper.appendChild(this.percentageEl_);
  11321. return el;
  11322. };
  11323. _proto.dispose = function dispose() {
  11324. this.partEls_ = null;
  11325. this.percentageEl_ = null;
  11326. _Component.prototype.dispose.call(this);
  11327. }
  11328. /**
  11329. * Update progress bar
  11330. *
  11331. * @param {EventTarget~Event} [event]
  11332. * The `progress` event that caused this function to run.
  11333. *
  11334. * @listens Player#progress
  11335. */
  11336. ;
  11337. _proto.update = function update(event) {
  11338. var _this2 = this;
  11339. this.requestNamedAnimationFrame('LoadProgressBar#update', function () {
  11340. var liveTracker = _this2.player_.liveTracker;
  11341. var buffered = _this2.player_.buffered();
  11342. var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : _this2.player_.duration();
  11343. var bufferedEnd = _this2.player_.bufferedEnd();
  11344. var children = _this2.partEls_;
  11345. var percent = percentify(bufferedEnd, duration);
  11346. if (_this2.percent_ !== percent) {
  11347. // update the width of the progress bar
  11348. _this2.el_.style.width = percent; // update the control-text
  11349. textContent(_this2.percentageEl_, percent);
  11350. _this2.percent_ = percent;
  11351. } // add child elements to represent the individual buffered time ranges
  11352. for (var i = 0; i < buffered.length; i++) {
  11353. var start = buffered.start(i);
  11354. var end = buffered.end(i);
  11355. var part = children[i];
  11356. if (!part) {
  11357. part = _this2.el_.appendChild(createEl());
  11358. children[i] = part;
  11359. } // only update if changed
  11360. if (part.dataset.start === start && part.dataset.end === end) {
  11361. continue;
  11362. }
  11363. part.dataset.start = start;
  11364. part.dataset.end = end; // set the percent based on the width of the progress bar (bufferedEnd)
  11365. part.style.left = percentify(start, bufferedEnd);
  11366. part.style.width = percentify(end - start, bufferedEnd);
  11367. } // remove unused buffered range elements
  11368. for (var _i = children.length; _i > buffered.length; _i--) {
  11369. _this2.el_.removeChild(children[_i - 1]);
  11370. }
  11371. children.length = buffered.length;
  11372. });
  11373. };
  11374. return LoadProgressBar;
  11375. }(Component$1);
  11376. Component$1.registerComponent('LoadProgressBar', LoadProgressBar);
  11377. /**
  11378. * Time tooltips display a time above the progress bar.
  11379. *
  11380. * @extends Component
  11381. */
  11382. var TimeTooltip = /*#__PURE__*/function (_Component) {
  11383. _inheritsLoose(TimeTooltip, _Component);
  11384. /**
  11385. * Creates an instance of this class.
  11386. *
  11387. * @param {Player} player
  11388. * The {@link Player} that this class should be attached to.
  11389. *
  11390. * @param {Object} [options]
  11391. * The key/value store of player options.
  11392. */
  11393. function TimeTooltip(player, options) {
  11394. var _this;
  11395. _this = _Component.call(this, player, options) || this;
  11396. _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
  11397. return _this;
  11398. }
  11399. /**
  11400. * Create the time tooltip DOM element
  11401. *
  11402. * @return {Element}
  11403. * The element that was created.
  11404. */
  11405. var _proto = TimeTooltip.prototype;
  11406. _proto.createEl = function createEl() {
  11407. return _Component.prototype.createEl.call(this, 'div', {
  11408. className: 'vjs-time-tooltip'
  11409. }, {
  11410. 'aria-hidden': 'true'
  11411. });
  11412. }
  11413. /**
  11414. * Updates the position of the time tooltip relative to the `SeekBar`.
  11415. *
  11416. * @param {Object} seekBarRect
  11417. * The `ClientRect` for the {@link SeekBar} element.
  11418. *
  11419. * @param {number} seekBarPoint
  11420. * A number from 0 to 1, representing a horizontal reference point
  11421. * from the left edge of the {@link SeekBar}
  11422. */
  11423. ;
  11424. _proto.update = function update(seekBarRect, seekBarPoint, content) {
  11425. var tooltipRect = findPosition(this.el_);
  11426. var playerRect = getBoundingClientRect(this.player_.el());
  11427. var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available
  11428. // for example, if the player isn't in the DOM for testing
  11429. if (!playerRect || !tooltipRect) {
  11430. return;
  11431. } // This is the space left of the `seekBarPoint` available within the bounds
  11432. // of the player. We calculate any gap between the left edge of the player
  11433. // and the left edge of the `SeekBar` and add the number of pixels in the
  11434. // `SeekBar` before hitting the `seekBarPoint`
  11435. var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds
  11436. // of the player. We calculate the number of pixels from the `seekBarPoint`
  11437. // to the right edge of the `SeekBar` and add to that any gap between the
  11438. // right edge of the `SeekBar` and the player.
  11439. var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled
  11440. // further to the right to center it over the `seekBarPoint`.
  11441. var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on
  11442. // the results of the space calculations above.
  11443. if (spaceLeftOfPoint < pullTooltipBy) {
  11444. pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
  11445. } else if (spaceRightOfPoint < pullTooltipBy) {
  11446. pullTooltipBy = spaceRightOfPoint;
  11447. } // Due to the imprecision of decimal/ratio based calculations and varying
  11448. // rounding behaviors, there are cases where the spacing adjustment is off
  11449. // by a pixel or two. This adds insurance to these calculations.
  11450. if (pullTooltipBy < 0) {
  11451. pullTooltipBy = 0;
  11452. } else if (pullTooltipBy > tooltipRect.width) {
  11453. pullTooltipBy = tooltipRect.width;
  11454. } // prevent small width fluctuations within 0.4px from
  11455. // changing the value below.
  11456. // This really helps for live to prevent the play
  11457. // progress time tooltip from jittering
  11458. pullTooltipBy = Math.round(pullTooltipBy);
  11459. this.el_.style.right = "-" + pullTooltipBy + "px";
  11460. this.write(content);
  11461. }
  11462. /**
  11463. * Write the time to the tooltip DOM element.
  11464. *
  11465. * @param {string} content
  11466. * The formatted time for the tooltip.
  11467. */
  11468. ;
  11469. _proto.write = function write(content) {
  11470. textContent(this.el_, content);
  11471. }
  11472. /**
  11473. * Updates the position of the time tooltip relative to the `SeekBar`.
  11474. *
  11475. * @param {Object} seekBarRect
  11476. * The `ClientRect` for the {@link SeekBar} element.
  11477. *
  11478. * @param {number} seekBarPoint
  11479. * A number from 0 to 1, representing a horizontal reference point
  11480. * from the left edge of the {@link SeekBar}
  11481. *
  11482. * @param {number} time
  11483. * The time to update the tooltip to, not used during live playback
  11484. *
  11485. * @param {Function} cb
  11486. * A function that will be called during the request animation frame
  11487. * for tooltips that need to do additional animations from the default
  11488. */
  11489. ;
  11490. _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) {
  11491. var _this2 = this;
  11492. this.requestNamedAnimationFrame('TimeTooltip#updateTime', function () {
  11493. var content;
  11494. var duration = _this2.player_.duration();
  11495. if (_this2.player_.liveTracker && _this2.player_.liveTracker.isLive()) {
  11496. var liveWindow = _this2.player_.liveTracker.liveWindow();
  11497. var secondsBehind = liveWindow - seekBarPoint * liveWindow;
  11498. content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
  11499. } else {
  11500. content = formatTime(time, duration);
  11501. }
  11502. _this2.update(seekBarRect, seekBarPoint, content);
  11503. if (cb) {
  11504. cb();
  11505. }
  11506. });
  11507. };
  11508. return TimeTooltip;
  11509. }(Component$1);
  11510. Component$1.registerComponent('TimeTooltip', TimeTooltip);
  11511. /**
  11512. * Used by {@link SeekBar} to display media playback progress as part of the
  11513. * {@link ProgressControl}.
  11514. *
  11515. * @extends Component
  11516. */
  11517. var PlayProgressBar = /*#__PURE__*/function (_Component) {
  11518. _inheritsLoose(PlayProgressBar, _Component);
  11519. /**
  11520. * Creates an instance of this class.
  11521. *
  11522. * @param {Player} player
  11523. * The {@link Player} that this class should be attached to.
  11524. *
  11525. * @param {Object} [options]
  11526. * The key/value store of player options.
  11527. */
  11528. function PlayProgressBar(player, options) {
  11529. var _this;
  11530. _this = _Component.call(this, player, options) || this;
  11531. _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
  11532. return _this;
  11533. }
  11534. /**
  11535. * Create the the DOM element for this class.
  11536. *
  11537. * @return {Element}
  11538. * The element that was created.
  11539. */
  11540. var _proto = PlayProgressBar.prototype;
  11541. _proto.createEl = function createEl() {
  11542. return _Component.prototype.createEl.call(this, 'div', {
  11543. className: 'vjs-play-progress vjs-slider-bar'
  11544. }, {
  11545. 'aria-hidden': 'true'
  11546. });
  11547. }
  11548. /**
  11549. * Enqueues updates to its own DOM as well as the DOM of its
  11550. * {@link TimeTooltip} child.
  11551. *
  11552. * @param {Object} seekBarRect
  11553. * The `ClientRect` for the {@link SeekBar} element.
  11554. *
  11555. * @param {number} seekBarPoint
  11556. * A number from 0 to 1, representing a horizontal reference point
  11557. * from the left edge of the {@link SeekBar}
  11558. */
  11559. ;
  11560. _proto.update = function update(seekBarRect, seekBarPoint) {
  11561. var timeTooltip = this.getChild('timeTooltip');
  11562. if (!timeTooltip) {
  11563. return;
  11564. }
  11565. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  11566. timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
  11567. };
  11568. return PlayProgressBar;
  11569. }(Component$1);
  11570. /**
  11571. * Default options for {@link PlayProgressBar}.
  11572. *
  11573. * @type {Object}
  11574. * @private
  11575. */
  11576. PlayProgressBar.prototype.options_ = {
  11577. children: []
  11578. }; // Time tooltips should not be added to a player on mobile devices
  11579. if (!IS_IOS && !IS_ANDROID) {
  11580. PlayProgressBar.prototype.options_.children.push('timeTooltip');
  11581. }
  11582. Component$1.registerComponent('PlayProgressBar', PlayProgressBar);
  11583. /**
  11584. * The {@link MouseTimeDisplay} component tracks mouse movement over the
  11585. * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
  11586. * indicating the time which is represented by a given point in the
  11587. * {@link ProgressControl}.
  11588. *
  11589. * @extends Component
  11590. */
  11591. var MouseTimeDisplay = /*#__PURE__*/function (_Component) {
  11592. _inheritsLoose(MouseTimeDisplay, _Component);
  11593. /**
  11594. * Creates an instance of this class.
  11595. *
  11596. * @param {Player} player
  11597. * The {@link Player} that this class should be attached to.
  11598. *
  11599. * @param {Object} [options]
  11600. * The key/value store of player options.
  11601. */
  11602. function MouseTimeDisplay(player, options) {
  11603. var _this;
  11604. _this = _Component.call(this, player, options) || this;
  11605. _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
  11606. return _this;
  11607. }
  11608. /**
  11609. * Create the DOM element for this class.
  11610. *
  11611. * @return {Element}
  11612. * The element that was created.
  11613. */
  11614. var _proto = MouseTimeDisplay.prototype;
  11615. _proto.createEl = function createEl() {
  11616. return _Component.prototype.createEl.call(this, 'div', {
  11617. className: 'vjs-mouse-display'
  11618. });
  11619. }
  11620. /**
  11621. * Enqueues updates to its own DOM as well as the DOM of its
  11622. * {@link TimeTooltip} child.
  11623. *
  11624. * @param {Object} seekBarRect
  11625. * The `ClientRect` for the {@link SeekBar} element.
  11626. *
  11627. * @param {number} seekBarPoint
  11628. * A number from 0 to 1, representing a horizontal reference point
  11629. * from the left edge of the {@link SeekBar}
  11630. */
  11631. ;
  11632. _proto.update = function update(seekBarRect, seekBarPoint) {
  11633. var _this2 = this;
  11634. var time = seekBarPoint * this.player_.duration();
  11635. this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () {
  11636. _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px";
  11637. });
  11638. };
  11639. return MouseTimeDisplay;
  11640. }(Component$1);
  11641. /**
  11642. * Default options for `MouseTimeDisplay`
  11643. *
  11644. * @type {Object}
  11645. * @private
  11646. */
  11647. MouseTimeDisplay.prototype.options_ = {
  11648. children: ['timeTooltip']
  11649. };
  11650. Component$1.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
  11651. var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
  11652. var PAGE_KEY_MULTIPLIER = 12;
  11653. /**
  11654. * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
  11655. * as its `bar`.
  11656. *
  11657. * @extends Slider
  11658. */
  11659. var SeekBar = /*#__PURE__*/function (_Slider) {
  11660. _inheritsLoose(SeekBar, _Slider);
  11661. /**
  11662. * Creates an instance of this class.
  11663. *
  11664. * @param {Player} player
  11665. * The `Player` that this class should be attached to.
  11666. *
  11667. * @param {Object} [options]
  11668. * The key/value store of player options.
  11669. */
  11670. function SeekBar(player, options) {
  11671. var _this;
  11672. _this = _Slider.call(this, player, options) || this;
  11673. _this.setEventHandlers_();
  11674. return _this;
  11675. }
  11676. /**
  11677. * Sets the event handlers
  11678. *
  11679. * @private
  11680. */
  11681. var _proto = SeekBar.prototype;
  11682. _proto.setEventHandlers_ = function setEventHandlers_() {
  11683. var _this2 = this;
  11684. this.update_ = bind(this, this.update);
  11685. this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL);
  11686. this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
  11687. if (this.player_.liveTracker) {
  11688. this.on(this.player_.liveTracker, 'liveedgechange', this.update);
  11689. } // when playing, let's ensure we smoothly update the play progress bar
  11690. // via an interval
  11691. this.updateInterval = null;
  11692. this.enableIntervalHandler_ = function (e) {
  11693. return _this2.enableInterval_(e);
  11694. };
  11695. this.disableIntervalHandler_ = function (e) {
  11696. return _this2.disableInterval_(e);
  11697. };
  11698. this.on(this.player_, ['playing'], this.enableIntervalHandler_);
  11699. this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_); // we don't need to update the play progress if the document is hidden,
  11700. // also, this causes the CPU to spike and eventually crash the page on IE11.
  11701. if ('hidden' in document && 'visibilityState' in document) {
  11702. this.on(document, 'visibilitychange', this.toggleVisibility_);
  11703. }
  11704. };
  11705. _proto.toggleVisibility_ = function toggleVisibility_(e) {
  11706. if (document.visibilityState === 'hidden') {
  11707. this.cancelNamedAnimationFrame('SeekBar#update');
  11708. this.cancelNamedAnimationFrame('Slider#update');
  11709. this.disableInterval_(e);
  11710. } else {
  11711. if (!this.player_.ended() && !this.player_.paused()) {
  11712. this.enableInterval_();
  11713. } // we just switched back to the page and someone may be looking, so, update ASAP
  11714. this.update();
  11715. }
  11716. };
  11717. _proto.enableInterval_ = function enableInterval_() {
  11718. if (this.updateInterval) {
  11719. return;
  11720. }
  11721. this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL);
  11722. };
  11723. _proto.disableInterval_ = function disableInterval_(e) {
  11724. if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') {
  11725. return;
  11726. }
  11727. if (!this.updateInterval) {
  11728. return;
  11729. }
  11730. this.clearInterval(this.updateInterval);
  11731. this.updateInterval = null;
  11732. }
  11733. /**
  11734. * Create the `Component`'s DOM element
  11735. *
  11736. * @return {Element}
  11737. * The element that was created.
  11738. */
  11739. ;
  11740. _proto.createEl = function createEl() {
  11741. return _Slider.prototype.createEl.call(this, 'div', {
  11742. className: 'vjs-progress-holder'
  11743. }, {
  11744. 'aria-label': this.localize('Progress Bar')
  11745. });
  11746. }
  11747. /**
  11748. * This function updates the play progress bar and accessibility
  11749. * attributes to whatever is passed in.
  11750. *
  11751. * @param {EventTarget~Event} [event]
  11752. * The `timeupdate` or `ended` event that caused this to run.
  11753. *
  11754. * @listens Player#timeupdate
  11755. *
  11756. * @return {number}
  11757. * The current percent at a number from 0-1
  11758. */
  11759. ;
  11760. _proto.update = function update(event) {
  11761. var _this3 = this;
  11762. // ignore updates while the tab is hidden
  11763. if (document.visibilityState === 'hidden') {
  11764. return;
  11765. }
  11766. var percent = _Slider.prototype.update.call(this);
  11767. this.requestNamedAnimationFrame('SeekBar#update', function () {
  11768. var currentTime = _this3.player_.ended() ? _this3.player_.duration() : _this3.getCurrentTime_();
  11769. var liveTracker = _this3.player_.liveTracker;
  11770. var duration = _this3.player_.duration();
  11771. if (liveTracker && liveTracker.isLive()) {
  11772. duration = _this3.player_.liveTracker.liveCurrentTime();
  11773. }
  11774. if (_this3.percent_ !== percent) {
  11775. // machine readable value of progress bar (percentage complete)
  11776. _this3.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
  11777. _this3.percent_ = percent;
  11778. }
  11779. if (_this3.currentTime_ !== currentTime || _this3.duration_ !== duration) {
  11780. // human readable value of progress bar (time complete)
  11781. _this3.el_.setAttribute('aria-valuetext', _this3.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
  11782. _this3.currentTime_ = currentTime;
  11783. _this3.duration_ = duration;
  11784. } // update the progress bar time tooltip with the current time
  11785. if (_this3.bar) {
  11786. _this3.bar.update(getBoundingClientRect(_this3.el()), _this3.getProgress());
  11787. }
  11788. });
  11789. return percent;
  11790. }
  11791. /**
  11792. * Prevent liveThreshold from causing seeks to seem like they
  11793. * are not happening from a user perspective.
  11794. *
  11795. * @param {number} ct
  11796. * current time to seek to
  11797. */
  11798. ;
  11799. _proto.userSeek_ = function userSeek_(ct) {
  11800. if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
  11801. this.player_.liveTracker.nextSeekedFromUser();
  11802. }
  11803. this.player_.currentTime(ct);
  11804. }
  11805. /**
  11806. * Get the value of current time but allows for smooth scrubbing,
  11807. * when player can't keep up.
  11808. *
  11809. * @return {number}
  11810. * The current time value to display
  11811. *
  11812. * @private
  11813. */
  11814. ;
  11815. _proto.getCurrentTime_ = function getCurrentTime_() {
  11816. return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  11817. }
  11818. /**
  11819. * Get the percentage of media played so far.
  11820. *
  11821. * @return {number}
  11822. * The percentage of media played so far (0 to 1).
  11823. */
  11824. ;
  11825. _proto.getPercent = function getPercent() {
  11826. var currentTime = this.getCurrentTime_();
  11827. var percent;
  11828. var liveTracker = this.player_.liveTracker;
  11829. if (liveTracker && liveTracker.isLive()) {
  11830. percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge
  11831. if (liveTracker.atLiveEdge()) {
  11832. percent = 1;
  11833. }
  11834. } else {
  11835. percent = currentTime / this.player_.duration();
  11836. }
  11837. return percent;
  11838. }
  11839. /**
  11840. * Handle mouse down on seek bar
  11841. *
  11842. * @param {EventTarget~Event} event
  11843. * The `mousedown` event that caused this to run.
  11844. *
  11845. * @listens mousedown
  11846. */
  11847. ;
  11848. _proto.handleMouseDown = function handleMouseDown(event) {
  11849. if (!isSingleLeftClick(event)) {
  11850. return;
  11851. } // Stop event propagation to prevent double fire in progress-control.js
  11852. event.stopPropagation();
  11853. this.videoWasPlaying = !this.player_.paused();
  11854. this.player_.pause();
  11855. _Slider.prototype.handleMouseDown.call(this, event);
  11856. }
  11857. /**
  11858. * Handle mouse move on seek bar
  11859. *
  11860. * @param {EventTarget~Event} event
  11861. * The `mousemove` event that caused this to run.
  11862. * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if coming from mouse down but should happen on regular mouse move handler. Defaults to false
  11863. *
  11864. * @listens mousemove
  11865. */
  11866. ;
  11867. _proto.handleMouseMove = function handleMouseMove(event, mouseDown) {
  11868. if (mouseDown === void 0) {
  11869. mouseDown = false;
  11870. }
  11871. if (!isSingleLeftClick(event)) {
  11872. return;
  11873. }
  11874. if (!mouseDown && !this.player_.scrubbing()) {
  11875. this.player_.scrubbing(true);
  11876. }
  11877. var newTime;
  11878. var distance = this.calculateDistance(event);
  11879. var liveTracker = this.player_.liveTracker;
  11880. if (!liveTracker || !liveTracker.isLive()) {
  11881. newTime = distance * this.player_.duration(); // Don't let video end while scrubbing.
  11882. if (newTime === this.player_.duration()) {
  11883. newTime = newTime - 0.1;
  11884. }
  11885. } else {
  11886. if (distance >= 0.99) {
  11887. liveTracker.seekToLiveEdge();
  11888. return;
  11889. }
  11890. var seekableStart = liveTracker.seekableStart();
  11891. var seekableEnd = liveTracker.liveCurrentTime();
  11892. newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing.
  11893. if (newTime >= seekableEnd) {
  11894. newTime = seekableEnd;
  11895. } // Compensate for precision differences so that currentTime is not less
  11896. // than seekable start
  11897. if (newTime <= seekableStart) {
  11898. newTime = seekableStart + 0.1;
  11899. } // On android seekableEnd can be Infinity sometimes,
  11900. // this will cause newTime to be Infinity, which is
  11901. // not a valid currentTime.
  11902. if (newTime === Infinity) {
  11903. return;
  11904. }
  11905. } // Set new time (tell player to seek to new time)
  11906. this.userSeek_(newTime);
  11907. };
  11908. _proto.enable = function enable() {
  11909. _Slider.prototype.enable.call(this);
  11910. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  11911. if (!mouseTimeDisplay) {
  11912. return;
  11913. }
  11914. mouseTimeDisplay.show();
  11915. };
  11916. _proto.disable = function disable() {
  11917. _Slider.prototype.disable.call(this);
  11918. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  11919. if (!mouseTimeDisplay) {
  11920. return;
  11921. }
  11922. mouseTimeDisplay.hide();
  11923. }
  11924. /**
  11925. * Handle mouse up on seek bar
  11926. *
  11927. * @param {EventTarget~Event} event
  11928. * The `mouseup` event that caused this to run.
  11929. *
  11930. * @listens mouseup
  11931. */
  11932. ;
  11933. _proto.handleMouseUp = function handleMouseUp(event) {
  11934. _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js
  11935. if (event) {
  11936. event.stopPropagation();
  11937. }
  11938. this.player_.scrubbing(false);
  11939. /**
  11940. * Trigger timeupdate because we're done seeking and the time has changed.
  11941. * This is particularly useful for if the player is paused to time the time displays.
  11942. *
  11943. * @event Tech#timeupdate
  11944. * @type {EventTarget~Event}
  11945. */
  11946. this.player_.trigger({
  11947. type: 'timeupdate',
  11948. target: this,
  11949. manuallyTriggered: true
  11950. });
  11951. if (this.videoWasPlaying) {
  11952. silencePromise(this.player_.play());
  11953. } else {
  11954. // We're done seeking and the time has changed.
  11955. // If the player is paused, make sure we display the correct time on the seek bar.
  11956. this.update_();
  11957. }
  11958. }
  11959. /**
  11960. * Move more quickly fast forward for keyboard-only users
  11961. */
  11962. ;
  11963. _proto.stepForward = function stepForward() {
  11964. this.userSeek_(this.player_.currentTime() + STEP_SECONDS);
  11965. }
  11966. /**
  11967. * Move more quickly rewind for keyboard-only users
  11968. */
  11969. ;
  11970. _proto.stepBack = function stepBack() {
  11971. this.userSeek_(this.player_.currentTime() - STEP_SECONDS);
  11972. }
  11973. /**
  11974. * Toggles the playback state of the player
  11975. * This gets called when enter or space is used on the seekbar
  11976. *
  11977. * @param {EventTarget~Event} event
  11978. * The `keydown` event that caused this function to be called
  11979. *
  11980. */
  11981. ;
  11982. _proto.handleAction = function handleAction(event) {
  11983. if (this.player_.paused()) {
  11984. this.player_.play();
  11985. } else {
  11986. this.player_.pause();
  11987. }
  11988. }
  11989. /**
  11990. * Called when this SeekBar has focus and a key gets pressed down.
  11991. * Supports the following keys:
  11992. *
  11993. * Space or Enter key fire a click event
  11994. * Home key moves to start of the timeline
  11995. * End key moves to end of the timeline
  11996. * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
  11997. * PageDown key moves back a larger step than ArrowDown
  11998. * PageUp key moves forward a large step
  11999. *
  12000. * @param {EventTarget~Event} event
  12001. * The `keydown` event that caused this function to be called.
  12002. *
  12003. * @listens keydown
  12004. */
  12005. ;
  12006. _proto.handleKeyDown = function handleKeyDown(event) {
  12007. var liveTracker = this.player_.liveTracker;
  12008. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  12009. event.preventDefault();
  12010. event.stopPropagation();
  12011. this.handleAction(event);
  12012. } else if (keycode.isEventKey(event, 'Home')) {
  12013. event.preventDefault();
  12014. event.stopPropagation();
  12015. this.userSeek_(0);
  12016. } else if (keycode.isEventKey(event, 'End')) {
  12017. event.preventDefault();
  12018. event.stopPropagation();
  12019. if (liveTracker && liveTracker.isLive()) {
  12020. this.userSeek_(liveTracker.liveCurrentTime());
  12021. } else {
  12022. this.userSeek_(this.player_.duration());
  12023. }
  12024. } else if (/^[0-9]$/.test(keycode(event))) {
  12025. event.preventDefault();
  12026. event.stopPropagation();
  12027. var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
  12028. if (liveTracker && liveTracker.isLive()) {
  12029. this.userSeek_(liveTracker.seekableStart() + liveTracker.liveWindow() * gotoFraction);
  12030. } else {
  12031. this.userSeek_(this.player_.duration() * gotoFraction);
  12032. }
  12033. } else if (keycode.isEventKey(event, 'PgDn')) {
  12034. event.preventDefault();
  12035. event.stopPropagation();
  12036. this.userSeek_(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  12037. } else if (keycode.isEventKey(event, 'PgUp')) {
  12038. event.preventDefault();
  12039. event.stopPropagation();
  12040. this.userSeek_(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  12041. } else {
  12042. // Pass keydown handling up for unsupported keys
  12043. _Slider.prototype.handleKeyDown.call(this, event);
  12044. }
  12045. };
  12046. _proto.dispose = function dispose() {
  12047. this.disableInterval_();
  12048. this.off(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
  12049. if (this.player_.liveTracker) {
  12050. this.off(this.player_.liveTracker, 'liveedgechange', this.update);
  12051. }
  12052. this.off(this.player_, ['playing'], this.enableIntervalHandler_);
  12053. this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_); // we don't need to update the play progress if the document is hidden,
  12054. // also, this causes the CPU to spike and eventually crash the page on IE11.
  12055. if ('hidden' in document && 'visibilityState' in document) {
  12056. this.off(document, 'visibilitychange', this.toggleVisibility_);
  12057. }
  12058. _Slider.prototype.dispose.call(this);
  12059. };
  12060. return SeekBar;
  12061. }(Slider);
  12062. /**
  12063. * Default options for the `SeekBar`
  12064. *
  12065. * @type {Object}
  12066. * @private
  12067. */
  12068. SeekBar.prototype.options_ = {
  12069. children: ['loadProgressBar', 'playProgressBar'],
  12070. barName: 'playProgressBar'
  12071. }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices
  12072. if (!IS_IOS && !IS_ANDROID) {
  12073. SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
  12074. }
  12075. Component$1.registerComponent('SeekBar', SeekBar);
  12076. /**
  12077. * The Progress Control component contains the seek bar, load progress,
  12078. * and play progress.
  12079. *
  12080. * @extends Component
  12081. */
  12082. var ProgressControl = /*#__PURE__*/function (_Component) {
  12083. _inheritsLoose(ProgressControl, _Component);
  12084. /**
  12085. * Creates an instance of this class.
  12086. *
  12087. * @param {Player} player
  12088. * The `Player` that this class should be attached to.
  12089. *
  12090. * @param {Object} [options]
  12091. * The key/value store of player options.
  12092. */
  12093. function ProgressControl(player, options) {
  12094. var _this;
  12095. _this = _Component.call(this, player, options) || this;
  12096. _this.handleMouseMove = throttle(bind(_assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
  12097. _this.throttledHandleMouseSeek = throttle(bind(_assertThisInitialized(_this), _this.handleMouseSeek), UPDATE_REFRESH_INTERVAL);
  12098. _this.handleMouseUpHandler_ = function (e) {
  12099. return _this.handleMouseUp(e);
  12100. };
  12101. _this.handleMouseDownHandler_ = function (e) {
  12102. return _this.handleMouseDown(e);
  12103. };
  12104. _this.enable();
  12105. return _this;
  12106. }
  12107. /**
  12108. * Create the `Component`'s DOM element
  12109. *
  12110. * @return {Element}
  12111. * The element that was created.
  12112. */
  12113. var _proto = ProgressControl.prototype;
  12114. _proto.createEl = function createEl() {
  12115. return _Component.prototype.createEl.call(this, 'div', {
  12116. className: 'vjs-progress-control vjs-control'
  12117. });
  12118. }
  12119. /**
  12120. * When the mouse moves over the `ProgressControl`, the pointer position
  12121. * gets passed down to the `MouseTimeDisplay` component.
  12122. *
  12123. * @param {EventTarget~Event} event
  12124. * The `mousemove` event that caused this function to run.
  12125. *
  12126. * @listen mousemove
  12127. */
  12128. ;
  12129. _proto.handleMouseMove = function handleMouseMove(event) {
  12130. var seekBar = this.getChild('seekBar');
  12131. if (!seekBar) {
  12132. return;
  12133. }
  12134. var playProgressBar = seekBar.getChild('playProgressBar');
  12135. var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
  12136. if (!playProgressBar && !mouseTimeDisplay) {
  12137. return;
  12138. }
  12139. var seekBarEl = seekBar.el();
  12140. var seekBarRect = findPosition(seekBarEl);
  12141. var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
  12142. // that it's possible to trigger this behavior outside the boundaries of
  12143. // the `SeekBar`. This ensures we stay within it at all times.
  12144. seekBarPoint = clamp(seekBarPoint, 0, 1);
  12145. if (mouseTimeDisplay) {
  12146. mouseTimeDisplay.update(seekBarRect, seekBarPoint);
  12147. }
  12148. if (playProgressBar) {
  12149. playProgressBar.update(seekBarRect, seekBar.getProgress());
  12150. }
  12151. }
  12152. /**
  12153. * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
  12154. *
  12155. * @method ProgressControl#throttledHandleMouseSeek
  12156. * @param {EventTarget~Event} event
  12157. * The `mousemove` event that caused this function to run.
  12158. *
  12159. * @listen mousemove
  12160. * @listen touchmove
  12161. */
  12162. /**
  12163. * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
  12164. *
  12165. * @param {EventTarget~Event} event
  12166. * `mousedown` or `touchstart` event that triggered this function
  12167. *
  12168. * @listens mousemove
  12169. * @listens touchmove
  12170. */
  12171. ;
  12172. _proto.handleMouseSeek = function handleMouseSeek(event) {
  12173. var seekBar = this.getChild('seekBar');
  12174. if (seekBar) {
  12175. seekBar.handleMouseMove(event);
  12176. }
  12177. }
  12178. /**
  12179. * Are controls are currently enabled for this progress control.
  12180. *
  12181. * @return {boolean}
  12182. * true if controls are enabled, false otherwise
  12183. */
  12184. ;
  12185. _proto.enabled = function enabled() {
  12186. return this.enabled_;
  12187. }
  12188. /**
  12189. * Disable all controls on the progress control and its children
  12190. */
  12191. ;
  12192. _proto.disable = function disable() {
  12193. this.children().forEach(function (child) {
  12194. return child.disable && child.disable();
  12195. });
  12196. if (!this.enabled()) {
  12197. return;
  12198. }
  12199. this.off(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
  12200. this.off(this.el_, 'mousemove', this.handleMouseMove);
  12201. this.removeListenersAddedOnMousedownAndTouchstart();
  12202. this.addClass('disabled');
  12203. this.enabled_ = false; // Restore normal playback state if controls are disabled while scrubbing
  12204. if (this.player_.scrubbing()) {
  12205. var seekBar = this.getChild('seekBar');
  12206. this.player_.scrubbing(false);
  12207. if (seekBar.videoWasPlaying) {
  12208. silencePromise(this.player_.play());
  12209. }
  12210. }
  12211. }
  12212. /**
  12213. * Enable all controls on the progress control and its children
  12214. */
  12215. ;
  12216. _proto.enable = function enable() {
  12217. this.children().forEach(function (child) {
  12218. return child.enable && child.enable();
  12219. });
  12220. if (this.enabled()) {
  12221. return;
  12222. }
  12223. this.on(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
  12224. this.on(this.el_, 'mousemove', this.handleMouseMove);
  12225. this.removeClass('disabled');
  12226. this.enabled_ = true;
  12227. }
  12228. /**
  12229. * Cleanup listeners after the user finishes interacting with the progress controls
  12230. */
  12231. ;
  12232. _proto.removeListenersAddedOnMousedownAndTouchstart = function removeListenersAddedOnMousedownAndTouchstart() {
  12233. var doc = this.el_.ownerDocument;
  12234. this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
  12235. this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
  12236. this.off(doc, 'mouseup', this.handleMouseUpHandler_);
  12237. this.off(doc, 'touchend', this.handleMouseUpHandler_);
  12238. }
  12239. /**
  12240. * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
  12241. *
  12242. * @param {EventTarget~Event} event
  12243. * `mousedown` or `touchstart` event that triggered this function
  12244. *
  12245. * @listens mousedown
  12246. * @listens touchstart
  12247. */
  12248. ;
  12249. _proto.handleMouseDown = function handleMouseDown(event) {
  12250. var doc = this.el_.ownerDocument;
  12251. var seekBar = this.getChild('seekBar');
  12252. if (seekBar) {
  12253. seekBar.handleMouseDown(event);
  12254. }
  12255. this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
  12256. this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
  12257. this.on(doc, 'mouseup', this.handleMouseUpHandler_);
  12258. this.on(doc, 'touchend', this.handleMouseUpHandler_);
  12259. }
  12260. /**
  12261. * Handle `mouseup` or `touchend` events on the `ProgressControl`.
  12262. *
  12263. * @param {EventTarget~Event} event
  12264. * `mouseup` or `touchend` event that triggered this function.
  12265. *
  12266. * @listens touchend
  12267. * @listens mouseup
  12268. */
  12269. ;
  12270. _proto.handleMouseUp = function handleMouseUp(event) {
  12271. var seekBar = this.getChild('seekBar');
  12272. if (seekBar) {
  12273. seekBar.handleMouseUp(event);
  12274. }
  12275. this.removeListenersAddedOnMousedownAndTouchstart();
  12276. };
  12277. return ProgressControl;
  12278. }(Component$1);
  12279. /**
  12280. * Default options for `ProgressControl`
  12281. *
  12282. * @type {Object}
  12283. * @private
  12284. */
  12285. ProgressControl.prototype.options_ = {
  12286. children: ['seekBar']
  12287. };
  12288. Component$1.registerComponent('ProgressControl', ProgressControl);
  12289. /**
  12290. * Toggle Picture-in-Picture mode
  12291. *
  12292. * @extends Button
  12293. */
  12294. var PictureInPictureToggle = /*#__PURE__*/function (_Button) {
  12295. _inheritsLoose(PictureInPictureToggle, _Button);
  12296. /**
  12297. * Creates an instance of this class.
  12298. *
  12299. * @param {Player} player
  12300. * The `Player` that this class should be attached to.
  12301. *
  12302. * @param {Object} [options]
  12303. * The key/value store of player options.
  12304. *
  12305. * @listens Player#enterpictureinpicture
  12306. * @listens Player#leavepictureinpicture
  12307. */
  12308. function PictureInPictureToggle(player, options) {
  12309. var _this;
  12310. _this = _Button.call(this, player, options) || this;
  12311. _this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], function (e) {
  12312. return _this.handlePictureInPictureChange(e);
  12313. });
  12314. _this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], function (e) {
  12315. return _this.handlePictureInPictureEnabledChange(e);
  12316. });
  12317. _this.on(player, ['loadedmetadata', 'audioonlymodechange', 'audiopostermodechange'], function () {
  12318. // This audio detection will not detect HLS or DASH audio-only streams because there was no reliable way to detect them at the time
  12319. var isSourceAudio = player.currentType().substring(0, 5) === 'audio';
  12320. if (isSourceAudio || player.audioPosterMode() || player.audioOnlyMode()) {
  12321. if (player.isInPictureInPicture()) {
  12322. player.exitPictureInPicture();
  12323. }
  12324. _this.hide();
  12325. } else {
  12326. _this.show();
  12327. }
  12328. }); // TODO: Deactivate button on player emptied event.
  12329. _this.disable();
  12330. return _this;
  12331. }
  12332. /**
  12333. * Builds the default DOM `className`.
  12334. *
  12335. * @return {string}
  12336. * The DOM `className` for this object.
  12337. */
  12338. var _proto = PictureInPictureToggle.prototype;
  12339. _proto.buildCSSClass = function buildCSSClass() {
  12340. return "vjs-picture-in-picture-control " + _Button.prototype.buildCSSClass.call(this);
  12341. }
  12342. /**
  12343. * Enables or disables button based on document.pictureInPictureEnabled property value
  12344. * or on value returned by player.disablePictureInPicture() method.
  12345. */
  12346. ;
  12347. _proto.handlePictureInPictureEnabledChange = function handlePictureInPictureEnabledChange() {
  12348. if (document.pictureInPictureEnabled && this.player_.disablePictureInPicture() === false) {
  12349. this.enable();
  12350. } else {
  12351. this.disable();
  12352. }
  12353. }
  12354. /**
  12355. * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
  12356. *
  12357. * @param {EventTarget~Event} [event]
  12358. * The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be
  12359. * called.
  12360. *
  12361. * @listens Player#enterpictureinpicture
  12362. * @listens Player#leavepictureinpicture
  12363. */
  12364. ;
  12365. _proto.handlePictureInPictureChange = function handlePictureInPictureChange(event) {
  12366. if (this.player_.isInPictureInPicture()) {
  12367. this.controlText('Exit Picture-in-Picture');
  12368. } else {
  12369. this.controlText('Picture-in-Picture');
  12370. }
  12371. this.handlePictureInPictureEnabledChange();
  12372. }
  12373. /**
  12374. * This gets called when an `PictureInPictureToggle` is "clicked". See
  12375. * {@link ClickableComponent} for more detailed information on what a click can be.
  12376. *
  12377. * @param {EventTarget~Event} [event]
  12378. * The `keydown`, `tap`, or `click` event that caused this function to be
  12379. * called.
  12380. *
  12381. * @listens tap
  12382. * @listens click
  12383. */
  12384. ;
  12385. _proto.handleClick = function handleClick(event) {
  12386. if (!this.player_.isInPictureInPicture()) {
  12387. this.player_.requestPictureInPicture();
  12388. } else {
  12389. this.player_.exitPictureInPicture();
  12390. }
  12391. };
  12392. return PictureInPictureToggle;
  12393. }(Button);
  12394. /**
  12395. * The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
  12396. *
  12397. * @type {string}
  12398. * @private
  12399. */
  12400. PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';
  12401. Component$1.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
  12402. /**
  12403. * Toggle fullscreen video
  12404. *
  12405. * @extends Button
  12406. */
  12407. var FullscreenToggle = /*#__PURE__*/function (_Button) {
  12408. _inheritsLoose(FullscreenToggle, _Button);
  12409. /**
  12410. * Creates an instance of this class.
  12411. *
  12412. * @param {Player} player
  12413. * The `Player` that this class should be attached to.
  12414. *
  12415. * @param {Object} [options]
  12416. * The key/value store of player options.
  12417. */
  12418. function FullscreenToggle(player, options) {
  12419. var _this;
  12420. _this = _Button.call(this, player, options) || this;
  12421. _this.on(player, 'fullscreenchange', function (e) {
  12422. return _this.handleFullscreenChange(e);
  12423. });
  12424. if (document[player.fsApi_.fullscreenEnabled] === false) {
  12425. _this.disable();
  12426. }
  12427. return _this;
  12428. }
  12429. /**
  12430. * Builds the default DOM `className`.
  12431. *
  12432. * @return {string}
  12433. * The DOM `className` for this object.
  12434. */
  12435. var _proto = FullscreenToggle.prototype;
  12436. _proto.buildCSSClass = function buildCSSClass() {
  12437. return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this);
  12438. }
  12439. /**
  12440. * Handles fullscreenchange on the player and change control text accordingly.
  12441. *
  12442. * @param {EventTarget~Event} [event]
  12443. * The {@link Player#fullscreenchange} event that caused this function to be
  12444. * called.
  12445. *
  12446. * @listens Player#fullscreenchange
  12447. */
  12448. ;
  12449. _proto.handleFullscreenChange = function handleFullscreenChange(event) {
  12450. if (this.player_.isFullscreen()) {
  12451. this.controlText('Non-Fullscreen');
  12452. } else {
  12453. this.controlText('Fullscreen');
  12454. }
  12455. }
  12456. /**
  12457. * This gets called when an `FullscreenToggle` is "clicked". See
  12458. * {@link ClickableComponent} for more detailed information on what a click can be.
  12459. *
  12460. * @param {EventTarget~Event} [event]
  12461. * The `keydown`, `tap`, or `click` event that caused this function to be
  12462. * called.
  12463. *
  12464. * @listens tap
  12465. * @listens click
  12466. */
  12467. ;
  12468. _proto.handleClick = function handleClick(event) {
  12469. if (!this.player_.isFullscreen()) {
  12470. this.player_.requestFullscreen();
  12471. } else {
  12472. this.player_.exitFullscreen();
  12473. }
  12474. };
  12475. return FullscreenToggle;
  12476. }(Button);
  12477. /**
  12478. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  12479. *
  12480. * @type {string}
  12481. * @private
  12482. */
  12483. FullscreenToggle.prototype.controlText_ = 'Fullscreen';
  12484. Component$1.registerComponent('FullscreenToggle', FullscreenToggle);
  12485. /**
  12486. * Check if volume control is supported and if it isn't hide the
  12487. * `Component` that was passed using the `vjs-hidden` class.
  12488. *
  12489. * @param {Component} self
  12490. * The component that should be hidden if volume is unsupported
  12491. *
  12492. * @param {Player} player
  12493. * A reference to the player
  12494. *
  12495. * @private
  12496. */
  12497. var checkVolumeSupport = function checkVolumeSupport(self, player) {
  12498. // hide volume controls when they're not supported by the current tech
  12499. if (player.tech_ && !player.tech_.featuresVolumeControl) {
  12500. self.addClass('vjs-hidden');
  12501. }
  12502. self.on(player, 'loadstart', function () {
  12503. if (!player.tech_.featuresVolumeControl) {
  12504. self.addClass('vjs-hidden');
  12505. } else {
  12506. self.removeClass('vjs-hidden');
  12507. }
  12508. });
  12509. };
  12510. /**
  12511. * Shows volume level
  12512. *
  12513. * @extends Component
  12514. */
  12515. var VolumeLevel = /*#__PURE__*/function (_Component) {
  12516. _inheritsLoose(VolumeLevel, _Component);
  12517. function VolumeLevel() {
  12518. return _Component.apply(this, arguments) || this;
  12519. }
  12520. var _proto = VolumeLevel.prototype;
  12521. /**
  12522. * Create the `Component`'s DOM element
  12523. *
  12524. * @return {Element}
  12525. * The element that was created.
  12526. */
  12527. _proto.createEl = function createEl() {
  12528. var el = _Component.prototype.createEl.call(this, 'div', {
  12529. className: 'vjs-volume-level'
  12530. });
  12531. el.appendChild(_Component.prototype.createEl.call(this, 'span', {
  12532. className: 'vjs-control-text'
  12533. }));
  12534. return el;
  12535. };
  12536. return VolumeLevel;
  12537. }(Component$1);
  12538. Component$1.registerComponent('VolumeLevel', VolumeLevel);
  12539. /**
  12540. * Volume level tooltips display a volume above or side by side the volume bar.
  12541. *
  12542. * @extends Component
  12543. */
  12544. var VolumeLevelTooltip = /*#__PURE__*/function (_Component) {
  12545. _inheritsLoose(VolumeLevelTooltip, _Component);
  12546. /**
  12547. * Creates an instance of this class.
  12548. *
  12549. * @param {Player} player
  12550. * The {@link Player} that this class should be attached to.
  12551. *
  12552. * @param {Object} [options]
  12553. * The key/value store of player options.
  12554. */
  12555. function VolumeLevelTooltip(player, options) {
  12556. var _this;
  12557. _this = _Component.call(this, player, options) || this;
  12558. _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
  12559. return _this;
  12560. }
  12561. /**
  12562. * Create the volume tooltip DOM element
  12563. *
  12564. * @return {Element}
  12565. * The element that was created.
  12566. */
  12567. var _proto = VolumeLevelTooltip.prototype;
  12568. _proto.createEl = function createEl() {
  12569. return _Component.prototype.createEl.call(this, 'div', {
  12570. className: 'vjs-volume-tooltip'
  12571. }, {
  12572. 'aria-hidden': 'true'
  12573. });
  12574. }
  12575. /**
  12576. * Updates the position of the tooltip relative to the `VolumeBar` and
  12577. * its content text.
  12578. *
  12579. * @param {Object} rangeBarRect
  12580. * The `ClientRect` for the {@link VolumeBar} element.
  12581. *
  12582. * @param {number} rangeBarPoint
  12583. * A number from 0 to 1, representing a horizontal/vertical reference point
  12584. * from the left edge of the {@link VolumeBar}
  12585. *
  12586. * @param {boolean} vertical
  12587. * Referees to the Volume control position
  12588. * in the control bar{@link VolumeControl}
  12589. *
  12590. */
  12591. ;
  12592. _proto.update = function update(rangeBarRect, rangeBarPoint, vertical, content) {
  12593. if (!vertical) {
  12594. var tooltipRect = getBoundingClientRect(this.el_);
  12595. var playerRect = getBoundingClientRect(this.player_.el());
  12596. var volumeBarPointPx = rangeBarRect.width * rangeBarPoint;
  12597. if (!playerRect || !tooltipRect) {
  12598. return;
  12599. }
  12600. var spaceLeftOfPoint = rangeBarRect.left - playerRect.left + volumeBarPointPx;
  12601. var spaceRightOfPoint = rangeBarRect.width - volumeBarPointPx + (playerRect.right - rangeBarRect.right);
  12602. var pullTooltipBy = tooltipRect.width / 2;
  12603. if (spaceLeftOfPoint < pullTooltipBy) {
  12604. pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
  12605. } else if (spaceRightOfPoint < pullTooltipBy) {
  12606. pullTooltipBy = spaceRightOfPoint;
  12607. }
  12608. if (pullTooltipBy < 0) {
  12609. pullTooltipBy = 0;
  12610. } else if (pullTooltipBy > tooltipRect.width) {
  12611. pullTooltipBy = tooltipRect.width;
  12612. }
  12613. this.el_.style.right = "-" + pullTooltipBy + "px";
  12614. }
  12615. this.write(content + "%");
  12616. }
  12617. /**
  12618. * Write the volume to the tooltip DOM element.
  12619. *
  12620. * @param {string} content
  12621. * The formatted volume for the tooltip.
  12622. */
  12623. ;
  12624. _proto.write = function write(content) {
  12625. textContent(this.el_, content);
  12626. }
  12627. /**
  12628. * Updates the position of the volume tooltip relative to the `VolumeBar`.
  12629. *
  12630. * @param {Object} rangeBarRect
  12631. * The `ClientRect` for the {@link VolumeBar} element.
  12632. *
  12633. * @param {number} rangeBarPoint
  12634. * A number from 0 to 1, representing a horizontal/vertical reference point
  12635. * from the left edge of the {@link VolumeBar}
  12636. *
  12637. * @param {boolean} vertical
  12638. * Referees to the Volume control position
  12639. * in the control bar{@link VolumeControl}
  12640. *
  12641. * @param {number} volume
  12642. * The volume level to update the tooltip to
  12643. *
  12644. * @param {Function} cb
  12645. * A function that will be called during the request animation frame
  12646. * for tooltips that need to do additional animations from the default
  12647. */
  12648. ;
  12649. _proto.updateVolume = function updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, cb) {
  12650. var _this2 = this;
  12651. this.requestNamedAnimationFrame('VolumeLevelTooltip#updateVolume', function () {
  12652. _this2.update(rangeBarRect, rangeBarPoint, vertical, volume.toFixed(0));
  12653. if (cb) {
  12654. cb();
  12655. }
  12656. });
  12657. };
  12658. return VolumeLevelTooltip;
  12659. }(Component$1);
  12660. Component$1.registerComponent('VolumeLevelTooltip', VolumeLevelTooltip);
  12661. /**
  12662. * The {@link MouseVolumeLevelDisplay} component tracks mouse movement over the
  12663. * {@link VolumeControl}. It displays an indicator and a {@link VolumeLevelTooltip}
  12664. * indicating the volume level which is represented by a given point in the
  12665. * {@link VolumeBar}.
  12666. *
  12667. * @extends Component
  12668. */
  12669. var MouseVolumeLevelDisplay = /*#__PURE__*/function (_Component) {
  12670. _inheritsLoose(MouseVolumeLevelDisplay, _Component);
  12671. /**
  12672. * Creates an instance of this class.
  12673. *
  12674. * @param {Player} player
  12675. * The {@link Player} that this class should be attached to.
  12676. *
  12677. * @param {Object} [options]
  12678. * The key/value store of player options.
  12679. */
  12680. function MouseVolumeLevelDisplay(player, options) {
  12681. var _this;
  12682. _this = _Component.call(this, player, options) || this;
  12683. _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
  12684. return _this;
  12685. }
  12686. /**
  12687. * Create the DOM element for this class.
  12688. *
  12689. * @return {Element}
  12690. * The element that was created.
  12691. */
  12692. var _proto = MouseVolumeLevelDisplay.prototype;
  12693. _proto.createEl = function createEl() {
  12694. return _Component.prototype.createEl.call(this, 'div', {
  12695. className: 'vjs-mouse-display'
  12696. });
  12697. }
  12698. /**
  12699. * Enquires updates to its own DOM as well as the DOM of its
  12700. * {@link VolumeLevelTooltip} child.
  12701. *
  12702. * @param {Object} rangeBarRect
  12703. * The `ClientRect` for the {@link VolumeBar} element.
  12704. *
  12705. * @param {number} rangeBarPoint
  12706. * A number from 0 to 1, representing a horizontal/vertical reference point
  12707. * from the left edge of the {@link VolumeBar}
  12708. *
  12709. * @param {boolean} vertical
  12710. * Referees to the Volume control position
  12711. * in the control bar{@link VolumeControl}
  12712. *
  12713. */
  12714. ;
  12715. _proto.update = function update(rangeBarRect, rangeBarPoint, vertical) {
  12716. var _this2 = this;
  12717. var volume = 100 * rangeBarPoint;
  12718. this.getChild('volumeLevelTooltip').updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, function () {
  12719. if (vertical) {
  12720. _this2.el_.style.bottom = rangeBarRect.height * rangeBarPoint + "px";
  12721. } else {
  12722. _this2.el_.style.left = rangeBarRect.width * rangeBarPoint + "px";
  12723. }
  12724. });
  12725. };
  12726. return MouseVolumeLevelDisplay;
  12727. }(Component$1);
  12728. /**
  12729. * Default options for `MouseVolumeLevelDisplay`
  12730. *
  12731. * @type {Object}
  12732. * @private
  12733. */
  12734. MouseVolumeLevelDisplay.prototype.options_ = {
  12735. children: ['volumeLevelTooltip']
  12736. };
  12737. Component$1.registerComponent('MouseVolumeLevelDisplay', MouseVolumeLevelDisplay);
  12738. /**
  12739. * The bar that contains the volume level and can be clicked on to adjust the level
  12740. *
  12741. * @extends Slider
  12742. */
  12743. var VolumeBar = /*#__PURE__*/function (_Slider) {
  12744. _inheritsLoose(VolumeBar, _Slider);
  12745. /**
  12746. * Creates an instance of this class.
  12747. *
  12748. * @param {Player} player
  12749. * The `Player` that this class should be attached to.
  12750. *
  12751. * @param {Object} [options]
  12752. * The key/value store of player options.
  12753. */
  12754. function VolumeBar(player, options) {
  12755. var _this;
  12756. _this = _Slider.call(this, player, options) || this;
  12757. _this.on('slideractive', function (e) {
  12758. return _this.updateLastVolume_(e);
  12759. });
  12760. _this.on(player, 'volumechange', function (e) {
  12761. return _this.updateARIAAttributes(e);
  12762. });
  12763. player.ready(function () {
  12764. return _this.updateARIAAttributes();
  12765. });
  12766. return _this;
  12767. }
  12768. /**
  12769. * Create the `Component`'s DOM element
  12770. *
  12771. * @return {Element}
  12772. * The element that was created.
  12773. */
  12774. var _proto = VolumeBar.prototype;
  12775. _proto.createEl = function createEl() {
  12776. return _Slider.prototype.createEl.call(this, 'div', {
  12777. className: 'vjs-volume-bar vjs-slider-bar'
  12778. }, {
  12779. 'aria-label': this.localize('Volume Level'),
  12780. 'aria-live': 'polite'
  12781. });
  12782. }
  12783. /**
  12784. * Handle mouse down on volume bar
  12785. *
  12786. * @param {EventTarget~Event} event
  12787. * The `mousedown` event that caused this to run.
  12788. *
  12789. * @listens mousedown
  12790. */
  12791. ;
  12792. _proto.handleMouseDown = function handleMouseDown(event) {
  12793. if (!isSingleLeftClick(event)) {
  12794. return;
  12795. }
  12796. _Slider.prototype.handleMouseDown.call(this, event);
  12797. }
  12798. /**
  12799. * Handle movement events on the {@link VolumeMenuButton}.
  12800. *
  12801. * @param {EventTarget~Event} event
  12802. * The event that caused this function to run.
  12803. *
  12804. * @listens mousemove
  12805. */
  12806. ;
  12807. _proto.handleMouseMove = function handleMouseMove(event) {
  12808. var mouseVolumeLevelDisplay = this.getChild('mouseVolumeLevelDisplay');
  12809. if (mouseVolumeLevelDisplay) {
  12810. var volumeBarEl = this.el();
  12811. var volumeBarRect = getBoundingClientRect(volumeBarEl);
  12812. var vertical = this.vertical();
  12813. var volumeBarPoint = getPointerPosition(volumeBarEl, event);
  12814. volumeBarPoint = vertical ? volumeBarPoint.y : volumeBarPoint.x; // The default skin has a gap on either side of the `VolumeBar`. This means
  12815. // that it's possible to trigger this behavior outside the boundaries of
  12816. // the `VolumeBar`. This ensures we stay within it at all times.
  12817. volumeBarPoint = clamp(volumeBarPoint, 0, 1);
  12818. mouseVolumeLevelDisplay.update(volumeBarRect, volumeBarPoint, vertical);
  12819. }
  12820. if (!isSingleLeftClick(event)) {
  12821. return;
  12822. }
  12823. this.checkMuted();
  12824. this.player_.volume(this.calculateDistance(event));
  12825. }
  12826. /**
  12827. * If the player is muted unmute it.
  12828. */
  12829. ;
  12830. _proto.checkMuted = function checkMuted() {
  12831. if (this.player_.muted()) {
  12832. this.player_.muted(false);
  12833. }
  12834. }
  12835. /**
  12836. * Get percent of volume level
  12837. *
  12838. * @return {number}
  12839. * Volume level percent as a decimal number.
  12840. */
  12841. ;
  12842. _proto.getPercent = function getPercent() {
  12843. if (this.player_.muted()) {
  12844. return 0;
  12845. }
  12846. return this.player_.volume();
  12847. }
  12848. /**
  12849. * Increase volume level for keyboard users
  12850. */
  12851. ;
  12852. _proto.stepForward = function stepForward() {
  12853. this.checkMuted();
  12854. this.player_.volume(this.player_.volume() + 0.1);
  12855. }
  12856. /**
  12857. * Decrease volume level for keyboard users
  12858. */
  12859. ;
  12860. _proto.stepBack = function stepBack() {
  12861. this.checkMuted();
  12862. this.player_.volume(this.player_.volume() - 0.1);
  12863. }
  12864. /**
  12865. * Update ARIA accessibility attributes
  12866. *
  12867. * @param {EventTarget~Event} [event]
  12868. * The `volumechange` event that caused this function to run.
  12869. *
  12870. * @listens Player#volumechange
  12871. */
  12872. ;
  12873. _proto.updateARIAAttributes = function updateARIAAttributes(event) {
  12874. var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
  12875. this.el_.setAttribute('aria-valuenow', ariaValue);
  12876. this.el_.setAttribute('aria-valuetext', ariaValue + '%');
  12877. }
  12878. /**
  12879. * Returns the current value of the player volume as a percentage
  12880. *
  12881. * @private
  12882. */
  12883. ;
  12884. _proto.volumeAsPercentage_ = function volumeAsPercentage_() {
  12885. return Math.round(this.player_.volume() * 100);
  12886. }
  12887. /**
  12888. * When user starts dragging the VolumeBar, store the volume and listen for
  12889. * the end of the drag. When the drag ends, if the volume was set to zero,
  12890. * set lastVolume to the stored volume.
  12891. *
  12892. * @listens slideractive
  12893. * @private
  12894. */
  12895. ;
  12896. _proto.updateLastVolume_ = function updateLastVolume_() {
  12897. var _this2 = this;
  12898. var volumeBeforeDrag = this.player_.volume();
  12899. this.one('sliderinactive', function () {
  12900. if (_this2.player_.volume() === 0) {
  12901. _this2.player_.lastVolume_(volumeBeforeDrag);
  12902. }
  12903. });
  12904. };
  12905. return VolumeBar;
  12906. }(Slider);
  12907. /**
  12908. * Default options for the `VolumeBar`
  12909. *
  12910. * @type {Object}
  12911. * @private
  12912. */
  12913. VolumeBar.prototype.options_ = {
  12914. children: ['volumeLevel'],
  12915. barName: 'volumeLevel'
  12916. }; // MouseVolumeLevelDisplay tooltip should not be added to a player on mobile devices
  12917. if (!IS_IOS && !IS_ANDROID) {
  12918. VolumeBar.prototype.options_.children.splice(0, 0, 'mouseVolumeLevelDisplay');
  12919. }
  12920. /**
  12921. * Call the update event for this Slider when this event happens on the player.
  12922. *
  12923. * @type {string}
  12924. */
  12925. VolumeBar.prototype.playerEvent = 'volumechange';
  12926. Component$1.registerComponent('VolumeBar', VolumeBar);
  12927. /**
  12928. * The component for controlling the volume level
  12929. *
  12930. * @extends Component
  12931. */
  12932. var VolumeControl = /*#__PURE__*/function (_Component) {
  12933. _inheritsLoose(VolumeControl, _Component);
  12934. /**
  12935. * Creates an instance of this class.
  12936. *
  12937. * @param {Player} player
  12938. * The `Player` that this class should be attached to.
  12939. *
  12940. * @param {Object} [options={}]
  12941. * The key/value store of player options.
  12942. */
  12943. function VolumeControl(player, options) {
  12944. var _this;
  12945. if (options === void 0) {
  12946. options = {};
  12947. }
  12948. options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if
  12949. // the VolumeBar is turned on.
  12950. if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
  12951. options.volumeBar = options.volumeBar || {};
  12952. options.volumeBar.vertical = options.vertical;
  12953. }
  12954. _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing
  12955. checkVolumeSupport(_assertThisInitialized(_this), player);
  12956. _this.throttledHandleMouseMove = throttle(bind(_assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
  12957. _this.handleMouseUpHandler_ = function (e) {
  12958. return _this.handleMouseUp(e);
  12959. };
  12960. _this.on('mousedown', function (e) {
  12961. return _this.handleMouseDown(e);
  12962. });
  12963. _this.on('touchstart', function (e) {
  12964. return _this.handleMouseDown(e);
  12965. });
  12966. _this.on('mousemove', function (e) {
  12967. return _this.handleMouseMove(e);
  12968. }); // while the slider is active (the mouse has been pressed down and
  12969. // is dragging) or in focus we do not want to hide the VolumeBar
  12970. _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
  12971. _this.volumeBar.addClass('vjs-slider-active');
  12972. _this.addClass('vjs-slider-active');
  12973. _this.trigger('slideractive');
  12974. });
  12975. _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
  12976. _this.volumeBar.removeClass('vjs-slider-active');
  12977. _this.removeClass('vjs-slider-active');
  12978. _this.trigger('sliderinactive');
  12979. });
  12980. return _this;
  12981. }
  12982. /**
  12983. * Create the `Component`'s DOM element
  12984. *
  12985. * @return {Element}
  12986. * The element that was created.
  12987. */
  12988. var _proto = VolumeControl.prototype;
  12989. _proto.createEl = function createEl() {
  12990. var orientationClass = 'vjs-volume-horizontal';
  12991. if (this.options_.vertical) {
  12992. orientationClass = 'vjs-volume-vertical';
  12993. }
  12994. return _Component.prototype.createEl.call(this, 'div', {
  12995. className: "vjs-volume-control vjs-control " + orientationClass
  12996. });
  12997. }
  12998. /**
  12999. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  13000. *
  13001. * @param {EventTarget~Event} event
  13002. * `mousedown` or `touchstart` event that triggered this function
  13003. *
  13004. * @listens mousedown
  13005. * @listens touchstart
  13006. */
  13007. ;
  13008. _proto.handleMouseDown = function handleMouseDown(event) {
  13009. var doc = this.el_.ownerDocument;
  13010. this.on(doc, 'mousemove', this.throttledHandleMouseMove);
  13011. this.on(doc, 'touchmove', this.throttledHandleMouseMove);
  13012. this.on(doc, 'mouseup', this.handleMouseUpHandler_);
  13013. this.on(doc, 'touchend', this.handleMouseUpHandler_);
  13014. }
  13015. /**
  13016. * Handle `mouseup` or `touchend` events on the `VolumeControl`.
  13017. *
  13018. * @param {EventTarget~Event} event
  13019. * `mouseup` or `touchend` event that triggered this function.
  13020. *
  13021. * @listens touchend
  13022. * @listens mouseup
  13023. */
  13024. ;
  13025. _proto.handleMouseUp = function handleMouseUp(event) {
  13026. var doc = this.el_.ownerDocument;
  13027. this.off(doc, 'mousemove', this.throttledHandleMouseMove);
  13028. this.off(doc, 'touchmove', this.throttledHandleMouseMove);
  13029. this.off(doc, 'mouseup', this.handleMouseUpHandler_);
  13030. this.off(doc, 'touchend', this.handleMouseUpHandler_);
  13031. }
  13032. /**
  13033. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  13034. *
  13035. * @param {EventTarget~Event} event
  13036. * `mousedown` or `touchstart` event that triggered this function
  13037. *
  13038. * @listens mousedown
  13039. * @listens touchstart
  13040. */
  13041. ;
  13042. _proto.handleMouseMove = function handleMouseMove(event) {
  13043. this.volumeBar.handleMouseMove(event);
  13044. };
  13045. return VolumeControl;
  13046. }(Component$1);
  13047. /**
  13048. * Default options for the `VolumeControl`
  13049. *
  13050. * @type {Object}
  13051. * @private
  13052. */
  13053. VolumeControl.prototype.options_ = {
  13054. children: ['volumeBar']
  13055. };
  13056. Component$1.registerComponent('VolumeControl', VolumeControl);
  13057. /**
  13058. * Check if muting volume is supported and if it isn't hide the mute toggle
  13059. * button.
  13060. *
  13061. * @param {Component} self
  13062. * A reference to the mute toggle button
  13063. *
  13064. * @param {Player} player
  13065. * A reference to the player
  13066. *
  13067. * @private
  13068. */
  13069. var checkMuteSupport = function checkMuteSupport(self, player) {
  13070. // hide mute toggle button if it's not supported by the current tech
  13071. if (player.tech_ && !player.tech_.featuresMuteControl) {
  13072. self.addClass('vjs-hidden');
  13073. }
  13074. self.on(player, 'loadstart', function () {
  13075. if (!player.tech_.featuresMuteControl) {
  13076. self.addClass('vjs-hidden');
  13077. } else {
  13078. self.removeClass('vjs-hidden');
  13079. }
  13080. });
  13081. };
  13082. /**
  13083. * A button component for muting the audio.
  13084. *
  13085. * @extends Button
  13086. */
  13087. var MuteToggle = /*#__PURE__*/function (_Button) {
  13088. _inheritsLoose(MuteToggle, _Button);
  13089. /**
  13090. * Creates an instance of this class.
  13091. *
  13092. * @param {Player} player
  13093. * The `Player` that this class should be attached to.
  13094. *
  13095. * @param {Object} [options]
  13096. * The key/value store of player options.
  13097. */
  13098. function MuteToggle(player, options) {
  13099. var _this;
  13100. _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing
  13101. checkMuteSupport(_assertThisInitialized(_this), player);
  13102. _this.on(player, ['loadstart', 'volumechange'], function (e) {
  13103. return _this.update(e);
  13104. });
  13105. return _this;
  13106. }
  13107. /**
  13108. * Builds the default DOM `className`.
  13109. *
  13110. * @return {string}
  13111. * The DOM `className` for this object.
  13112. */
  13113. var _proto = MuteToggle.prototype;
  13114. _proto.buildCSSClass = function buildCSSClass() {
  13115. return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this);
  13116. }
  13117. /**
  13118. * This gets called when an `MuteToggle` is "clicked". See
  13119. * {@link ClickableComponent} for more detailed information on what a click can be.
  13120. *
  13121. * @param {EventTarget~Event} [event]
  13122. * The `keydown`, `tap`, or `click` event that caused this function to be
  13123. * called.
  13124. *
  13125. * @listens tap
  13126. * @listens click
  13127. */
  13128. ;
  13129. _proto.handleClick = function handleClick(event) {
  13130. var vol = this.player_.volume();
  13131. var lastVolume = this.player_.lastVolume_();
  13132. if (vol === 0) {
  13133. var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
  13134. this.player_.volume(volumeToSet);
  13135. this.player_.muted(false);
  13136. } else {
  13137. this.player_.muted(this.player_.muted() ? false : true);
  13138. }
  13139. }
  13140. /**
  13141. * Update the `MuteToggle` button based on the state of `volume` and `muted`
  13142. * on the player.
  13143. *
  13144. * @param {EventTarget~Event} [event]
  13145. * The {@link Player#loadstart} event if this function was called
  13146. * through an event.
  13147. *
  13148. * @listens Player#loadstart
  13149. * @listens Player#volumechange
  13150. */
  13151. ;
  13152. _proto.update = function update(event) {
  13153. this.updateIcon_();
  13154. this.updateControlText_();
  13155. }
  13156. /**
  13157. * Update the appearance of the `MuteToggle` icon.
  13158. *
  13159. * Possible states (given `level` variable below):
  13160. * - 0: crossed out
  13161. * - 1: zero bars of volume
  13162. * - 2: one bar of volume
  13163. * - 3: two bars of volume
  13164. *
  13165. * @private
  13166. */
  13167. ;
  13168. _proto.updateIcon_ = function updateIcon_() {
  13169. var vol = this.player_.volume();
  13170. var level = 3; // in iOS when a player is loaded with muted attribute
  13171. // and volume is changed with a native mute button
  13172. // we want to make sure muted state is updated
  13173. if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
  13174. this.player_.muted(this.player_.tech_.el_.muted);
  13175. }
  13176. if (vol === 0 || this.player_.muted()) {
  13177. level = 0;
  13178. } else if (vol < 0.33) {
  13179. level = 1;
  13180. } else if (vol < 0.67) {
  13181. level = 2;
  13182. } // TODO improve muted icon classes
  13183. for (var i = 0; i < 4; i++) {
  13184. removeClass(this.el_, "vjs-vol-" + i);
  13185. }
  13186. addClass(this.el_, "vjs-vol-" + level);
  13187. }
  13188. /**
  13189. * If `muted` has changed on the player, update the control text
  13190. * (`title` attribute on `vjs-mute-control` element and content of
  13191. * `vjs-control-text` element).
  13192. *
  13193. * @private
  13194. */
  13195. ;
  13196. _proto.updateControlText_ = function updateControlText_() {
  13197. var soundOff = this.player_.muted() || this.player_.volume() === 0;
  13198. var text = soundOff ? 'Unmute' : 'Mute';
  13199. if (this.controlText() !== text) {
  13200. this.controlText(text);
  13201. }
  13202. };
  13203. return MuteToggle;
  13204. }(Button);
  13205. /**
  13206. * The text that should display over the `MuteToggle`s controls. Added for localization.
  13207. *
  13208. * @type {string}
  13209. * @private
  13210. */
  13211. MuteToggle.prototype.controlText_ = 'Mute';
  13212. Component$1.registerComponent('MuteToggle', MuteToggle);
  13213. /**
  13214. * A Component to contain the MuteToggle and VolumeControl so that
  13215. * they can work together.
  13216. *
  13217. * @extends Component
  13218. */
  13219. var VolumePanel = /*#__PURE__*/function (_Component) {
  13220. _inheritsLoose(VolumePanel, _Component);
  13221. /**
  13222. * Creates an instance of this class.
  13223. *
  13224. * @param {Player} player
  13225. * The `Player` that this class should be attached to.
  13226. *
  13227. * @param {Object} [options={}]
  13228. * The key/value store of player options.
  13229. */
  13230. function VolumePanel(player, options) {
  13231. var _this;
  13232. if (options === void 0) {
  13233. options = {};
  13234. }
  13235. if (typeof options.inline !== 'undefined') {
  13236. options.inline = options.inline;
  13237. } else {
  13238. options.inline = true;
  13239. } // pass the inline option down to the VolumeControl as vertical if
  13240. // the VolumeControl is on.
  13241. if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
  13242. options.volumeControl = options.volumeControl || {};
  13243. options.volumeControl.vertical = !options.inline;
  13244. }
  13245. _this = _Component.call(this, player, options) || this; // this handler is used by mouse handler methods below
  13246. _this.handleKeyPressHandler_ = function (e) {
  13247. return _this.handleKeyPress(e);
  13248. };
  13249. _this.on(player, ['loadstart'], function (e) {
  13250. return _this.volumePanelState_(e);
  13251. });
  13252. _this.on(_this.muteToggle, 'keyup', function (e) {
  13253. return _this.handleKeyPress(e);
  13254. });
  13255. _this.on(_this.volumeControl, 'keyup', function (e) {
  13256. return _this.handleVolumeControlKeyUp(e);
  13257. });
  13258. _this.on('keydown', function (e) {
  13259. return _this.handleKeyPress(e);
  13260. });
  13261. _this.on('mouseover', function (e) {
  13262. return _this.handleMouseOver(e);
  13263. });
  13264. _this.on('mouseout', function (e) {
  13265. return _this.handleMouseOut(e);
  13266. }); // while the slider is active (the mouse has been pressed down and
  13267. // is dragging) we do not want to hide the VolumeBar
  13268. _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
  13269. _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
  13270. return _this;
  13271. }
  13272. /**
  13273. * Add vjs-slider-active class to the VolumePanel
  13274. *
  13275. * @listens VolumeControl#slideractive
  13276. * @private
  13277. */
  13278. var _proto = VolumePanel.prototype;
  13279. _proto.sliderActive_ = function sliderActive_() {
  13280. this.addClass('vjs-slider-active');
  13281. }
  13282. /**
  13283. * Removes vjs-slider-active class to the VolumePanel
  13284. *
  13285. * @listens VolumeControl#sliderinactive
  13286. * @private
  13287. */
  13288. ;
  13289. _proto.sliderInactive_ = function sliderInactive_() {
  13290. this.removeClass('vjs-slider-active');
  13291. }
  13292. /**
  13293. * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
  13294. * depending on MuteToggle and VolumeControl state
  13295. *
  13296. * @listens Player#loadstart
  13297. * @private
  13298. */
  13299. ;
  13300. _proto.volumePanelState_ = function volumePanelState_() {
  13301. // hide volume panel if neither volume control or mute toggle
  13302. // are displayed
  13303. if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
  13304. this.addClass('vjs-hidden');
  13305. } // if only mute toggle is visible we don't want
  13306. // volume panel expanding when hovered or active
  13307. if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
  13308. this.addClass('vjs-mute-toggle-only');
  13309. }
  13310. }
  13311. /**
  13312. * Create the `Component`'s DOM element
  13313. *
  13314. * @return {Element}
  13315. * The element that was created.
  13316. */
  13317. ;
  13318. _proto.createEl = function createEl() {
  13319. var orientationClass = 'vjs-volume-panel-horizontal';
  13320. if (!this.options_.inline) {
  13321. orientationClass = 'vjs-volume-panel-vertical';
  13322. }
  13323. return _Component.prototype.createEl.call(this, 'div', {
  13324. className: "vjs-volume-panel vjs-control " + orientationClass
  13325. });
  13326. }
  13327. /**
  13328. * Dispose of the `volume-panel` and all child components.
  13329. */
  13330. ;
  13331. _proto.dispose = function dispose() {
  13332. this.handleMouseOut();
  13333. _Component.prototype.dispose.call(this);
  13334. }
  13335. /**
  13336. * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes
  13337. * the volume panel and sets focus on `MuteToggle`.
  13338. *
  13339. * @param {EventTarget~Event} event
  13340. * The `keyup` event that caused this function to be called.
  13341. *
  13342. * @listens keyup
  13343. */
  13344. ;
  13345. _proto.handleVolumeControlKeyUp = function handleVolumeControlKeyUp(event) {
  13346. if (keycode.isEventKey(event, 'Esc')) {
  13347. this.muteToggle.focus();
  13348. }
  13349. }
  13350. /**
  13351. * This gets called when a `VolumePanel` gains hover via a `mouseover` event.
  13352. * Turns on listening for `mouseover` event. When they happen it
  13353. * calls `this.handleMouseOver`.
  13354. *
  13355. * @param {EventTarget~Event} event
  13356. * The `mouseover` event that caused this function to be called.
  13357. *
  13358. * @listens mouseover
  13359. */
  13360. ;
  13361. _proto.handleMouseOver = function handleMouseOver(event) {
  13362. this.addClass('vjs-hover');
  13363. on(document, 'keyup', this.handleKeyPressHandler_);
  13364. }
  13365. /**
  13366. * This gets called when a `VolumePanel` gains hover via a `mouseout` event.
  13367. * Turns on listening for `mouseout` event. When they happen it
  13368. * calls `this.handleMouseOut`.
  13369. *
  13370. * @param {EventTarget~Event} event
  13371. * The `mouseout` event that caused this function to be called.
  13372. *
  13373. * @listens mouseout
  13374. */
  13375. ;
  13376. _proto.handleMouseOut = function handleMouseOut(event) {
  13377. this.removeClass('vjs-hover');
  13378. off(document, 'keyup', this.handleKeyPressHandler_);
  13379. }
  13380. /**
  13381. * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`,
  13382. * looking for ESC, which hides the `VolumeControl`.
  13383. *
  13384. * @param {EventTarget~Event} event
  13385. * The keypress that triggered this event.
  13386. *
  13387. * @listens keydown | keyup
  13388. */
  13389. ;
  13390. _proto.handleKeyPress = function handleKeyPress(event) {
  13391. if (keycode.isEventKey(event, 'Esc')) {
  13392. this.handleMouseOut();
  13393. }
  13394. };
  13395. return VolumePanel;
  13396. }(Component$1);
  13397. /**
  13398. * Default options for the `VolumeControl`
  13399. *
  13400. * @type {Object}
  13401. * @private
  13402. */
  13403. VolumePanel.prototype.options_ = {
  13404. children: ['muteToggle', 'volumeControl']
  13405. };
  13406. Component$1.registerComponent('VolumePanel', VolumePanel);
  13407. /**
  13408. * The Menu component is used to build popup menus, including subtitle and
  13409. * captions selection menus.
  13410. *
  13411. * @extends Component
  13412. */
  13413. var Menu = /*#__PURE__*/function (_Component) {
  13414. _inheritsLoose(Menu, _Component);
  13415. /**
  13416. * Create an instance of this class.
  13417. *
  13418. * @param {Player} player
  13419. * the player that this component should attach to
  13420. *
  13421. * @param {Object} [options]
  13422. * Object of option names and values
  13423. *
  13424. */
  13425. function Menu(player, options) {
  13426. var _this;
  13427. _this = _Component.call(this, player, options) || this;
  13428. if (options) {
  13429. _this.menuButton_ = options.menuButton;
  13430. }
  13431. _this.focusedChild_ = -1;
  13432. _this.on('keydown', function (e) {
  13433. return _this.handleKeyDown(e);
  13434. }); // All the menu item instances share the same blur handler provided by the menu container.
  13435. _this.boundHandleBlur_ = function (e) {
  13436. return _this.handleBlur(e);
  13437. };
  13438. _this.boundHandleTapClick_ = function (e) {
  13439. return _this.handleTapClick(e);
  13440. };
  13441. return _this;
  13442. }
  13443. /**
  13444. * Add event listeners to the {@link MenuItem}.
  13445. *
  13446. * @param {Object} component
  13447. * The instance of the `MenuItem` to add listeners to.
  13448. *
  13449. */
  13450. var _proto = Menu.prototype;
  13451. _proto.addEventListenerForItem = function addEventListenerForItem(component) {
  13452. if (!(component instanceof Component$1)) {
  13453. return;
  13454. }
  13455. this.on(component, 'blur', this.boundHandleBlur_);
  13456. this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
  13457. }
  13458. /**
  13459. * Remove event listeners from the {@link MenuItem}.
  13460. *
  13461. * @param {Object} component
  13462. * The instance of the `MenuItem` to remove listeners.
  13463. *
  13464. */
  13465. ;
  13466. _proto.removeEventListenerForItem = function removeEventListenerForItem(component) {
  13467. if (!(component instanceof Component$1)) {
  13468. return;
  13469. }
  13470. this.off(component, 'blur', this.boundHandleBlur_);
  13471. this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
  13472. }
  13473. /**
  13474. * This method will be called indirectly when the component has been added
  13475. * before the component adds to the new menu instance by `addItem`.
  13476. * In this case, the original menu instance will remove the component
  13477. * by calling `removeChild`.
  13478. *
  13479. * @param {Object} component
  13480. * The instance of the `MenuItem`
  13481. */
  13482. ;
  13483. _proto.removeChild = function removeChild(component) {
  13484. if (typeof component === 'string') {
  13485. component = this.getChild(component);
  13486. }
  13487. this.removeEventListenerForItem(component);
  13488. _Component.prototype.removeChild.call(this, component);
  13489. }
  13490. /**
  13491. * Add a {@link MenuItem} to the menu.
  13492. *
  13493. * @param {Object|string} component
  13494. * The name or instance of the `MenuItem` to add.
  13495. *
  13496. */
  13497. ;
  13498. _proto.addItem = function addItem(component) {
  13499. var childComponent = this.addChild(component);
  13500. if (childComponent) {
  13501. this.addEventListenerForItem(childComponent);
  13502. }
  13503. }
  13504. /**
  13505. * Create the `Menu`s DOM element.
  13506. *
  13507. * @return {Element}
  13508. * the element that was created
  13509. */
  13510. ;
  13511. _proto.createEl = function createEl$1() {
  13512. var contentElType = this.options_.contentElType || 'ul';
  13513. this.contentEl_ = createEl(contentElType, {
  13514. className: 'vjs-menu-content'
  13515. });
  13516. this.contentEl_.setAttribute('role', 'menu');
  13517. var el = _Component.prototype.createEl.call(this, 'div', {
  13518. append: this.contentEl_,
  13519. className: 'vjs-menu'
  13520. });
  13521. el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons,
  13522. // where a click on the parent is significant
  13523. on(el, 'click', function (event) {
  13524. event.preventDefault();
  13525. event.stopImmediatePropagation();
  13526. });
  13527. return el;
  13528. };
  13529. _proto.dispose = function dispose() {
  13530. this.contentEl_ = null;
  13531. this.boundHandleBlur_ = null;
  13532. this.boundHandleTapClick_ = null;
  13533. _Component.prototype.dispose.call(this);
  13534. }
  13535. /**
  13536. * Called when a `MenuItem` loses focus.
  13537. *
  13538. * @param {EventTarget~Event} event
  13539. * The `blur` event that caused this function to be called.
  13540. *
  13541. * @listens blur
  13542. */
  13543. ;
  13544. _proto.handleBlur = function handleBlur(event) {
  13545. var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu
  13546. if (!this.children().some(function (element) {
  13547. return element.el() === relatedTarget;
  13548. })) {
  13549. var btn = this.menuButton_;
  13550. if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
  13551. btn.unpressButton();
  13552. }
  13553. }
  13554. }
  13555. /**
  13556. * Called when a `MenuItem` gets clicked or tapped.
  13557. *
  13558. * @param {EventTarget~Event} event
  13559. * The `click` or `tap` event that caused this function to be called.
  13560. *
  13561. * @listens click,tap
  13562. */
  13563. ;
  13564. _proto.handleTapClick = function handleTapClick(event) {
  13565. // Unpress the associated MenuButton, and move focus back to it
  13566. if (this.menuButton_) {
  13567. this.menuButton_.unpressButton();
  13568. var childComponents = this.children();
  13569. if (!Array.isArray(childComponents)) {
  13570. return;
  13571. }
  13572. var foundComponent = childComponents.filter(function (component) {
  13573. return component.el() === event.target;
  13574. })[0];
  13575. if (!foundComponent) {
  13576. return;
  13577. } // don't focus menu button if item is a caption settings item
  13578. // because focus will move elsewhere
  13579. if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
  13580. this.menuButton_.focus();
  13581. }
  13582. }
  13583. }
  13584. /**
  13585. * Handle a `keydown` event on this menu. This listener is added in the constructor.
  13586. *
  13587. * @param {EventTarget~Event} event
  13588. * A `keydown` event that happened on the menu.
  13589. *
  13590. * @listens keydown
  13591. */
  13592. ;
  13593. _proto.handleKeyDown = function handleKeyDown(event) {
  13594. // Left and Down Arrows
  13595. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  13596. event.preventDefault();
  13597. event.stopPropagation();
  13598. this.stepForward(); // Up and Right Arrows
  13599. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  13600. event.preventDefault();
  13601. event.stopPropagation();
  13602. this.stepBack();
  13603. }
  13604. }
  13605. /**
  13606. * Move to next (lower) menu item for keyboard users.
  13607. */
  13608. ;
  13609. _proto.stepForward = function stepForward() {
  13610. var stepChild = 0;
  13611. if (this.focusedChild_ !== undefined) {
  13612. stepChild = this.focusedChild_ + 1;
  13613. }
  13614. this.focus(stepChild);
  13615. }
  13616. /**
  13617. * Move to previous (higher) menu item for keyboard users.
  13618. */
  13619. ;
  13620. _proto.stepBack = function stepBack() {
  13621. var stepChild = 0;
  13622. if (this.focusedChild_ !== undefined) {
  13623. stepChild = this.focusedChild_ - 1;
  13624. }
  13625. this.focus(stepChild);
  13626. }
  13627. /**
  13628. * Set focus on a {@link MenuItem} in the `Menu`.
  13629. *
  13630. * @param {Object|string} [item=0]
  13631. * Index of child item set focus on.
  13632. */
  13633. ;
  13634. _proto.focus = function focus(item) {
  13635. if (item === void 0) {
  13636. item = 0;
  13637. }
  13638. var children = this.children().slice();
  13639. var haveTitle = children.length && children[0].hasClass('vjs-menu-title');
  13640. if (haveTitle) {
  13641. children.shift();
  13642. }
  13643. if (children.length > 0) {
  13644. if (item < 0) {
  13645. item = 0;
  13646. } else if (item >= children.length) {
  13647. item = children.length - 1;
  13648. }
  13649. this.focusedChild_ = item;
  13650. children[item].el_.focus();
  13651. }
  13652. };
  13653. return Menu;
  13654. }(Component$1);
  13655. Component$1.registerComponent('Menu', Menu);
  13656. /**
  13657. * A `MenuButton` class for any popup {@link Menu}.
  13658. *
  13659. * @extends Component
  13660. */
  13661. var MenuButton = /*#__PURE__*/function (_Component) {
  13662. _inheritsLoose(MenuButton, _Component);
  13663. /**
  13664. * Creates an instance of this class.
  13665. *
  13666. * @param {Player} player
  13667. * The `Player` that this class should be attached to.
  13668. *
  13669. * @param {Object} [options={}]
  13670. * The key/value store of player options.
  13671. */
  13672. function MenuButton(player, options) {
  13673. var _this;
  13674. if (options === void 0) {
  13675. options = {};
  13676. }
  13677. _this = _Component.call(this, player, options) || this;
  13678. _this.menuButton_ = new Button(player, options);
  13679. _this.menuButton_.controlText(_this.controlText_);
  13680. _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper
  13681. var buttonClass = Button.prototype.buildCSSClass();
  13682. _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
  13683. _this.menuButton_.removeClass('vjs-control');
  13684. _this.addChild(_this.menuButton_);
  13685. _this.update();
  13686. _this.enabled_ = true;
  13687. var handleClick = function handleClick(e) {
  13688. return _this.handleClick(e);
  13689. };
  13690. _this.handleMenuKeyUp_ = function (e) {
  13691. return _this.handleMenuKeyUp(e);
  13692. };
  13693. _this.on(_this.menuButton_, 'tap', handleClick);
  13694. _this.on(_this.menuButton_, 'click', handleClick);
  13695. _this.on(_this.menuButton_, 'keydown', function (e) {
  13696. return _this.handleKeyDown(e);
  13697. });
  13698. _this.on(_this.menuButton_, 'mouseenter', function () {
  13699. _this.addClass('vjs-hover');
  13700. _this.menu.show();
  13701. on(document, 'keyup', _this.handleMenuKeyUp_);
  13702. });
  13703. _this.on('mouseleave', function (e) {
  13704. return _this.handleMouseLeave(e);
  13705. });
  13706. _this.on('keydown', function (e) {
  13707. return _this.handleSubmenuKeyDown(e);
  13708. });
  13709. return _this;
  13710. }
  13711. /**
  13712. * Update the menu based on the current state of its items.
  13713. */
  13714. var _proto = MenuButton.prototype;
  13715. _proto.update = function update() {
  13716. var menu = this.createMenu();
  13717. if (this.menu) {
  13718. this.menu.dispose();
  13719. this.removeChild(this.menu);
  13720. }
  13721. this.menu = menu;
  13722. this.addChild(menu);
  13723. /**
  13724. * Track the state of the menu button
  13725. *
  13726. * @type {Boolean}
  13727. * @private
  13728. */
  13729. this.buttonPressed_ = false;
  13730. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  13731. if (this.items && this.items.length <= this.hideThreshold_) {
  13732. this.hide();
  13733. this.menu.contentEl_.removeAttribute('role');
  13734. } else {
  13735. this.show();
  13736. this.menu.contentEl_.setAttribute('role', 'menu');
  13737. }
  13738. }
  13739. /**
  13740. * Create the menu and add all items to it.
  13741. *
  13742. * @return {Menu}
  13743. * The constructed menu
  13744. */
  13745. ;
  13746. _proto.createMenu = function createMenu() {
  13747. var menu = new Menu(this.player_, {
  13748. menuButton: this
  13749. });
  13750. /**
  13751. * Hide the menu if the number of items is less than or equal to this threshold. This defaults
  13752. * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
  13753. * it here because every time we run `createMenu` we need to reset the value.
  13754. *
  13755. * @protected
  13756. * @type {Number}
  13757. */
  13758. this.hideThreshold_ = 0; // Add a title list item to the top
  13759. if (this.options_.title) {
  13760. var titleEl = createEl('li', {
  13761. className: 'vjs-menu-title',
  13762. textContent: toTitleCase$1(this.options_.title),
  13763. tabIndex: -1
  13764. });
  13765. var titleComponent = new Component$1(this.player_, {
  13766. el: titleEl
  13767. });
  13768. menu.addItem(titleComponent);
  13769. }
  13770. this.items = this.createItems();
  13771. if (this.items) {
  13772. // Add menu items to the menu
  13773. for (var i = 0; i < this.items.length; i++) {
  13774. menu.addItem(this.items[i]);
  13775. }
  13776. }
  13777. return menu;
  13778. }
  13779. /**
  13780. * Create the list of menu items. Specific to each subclass.
  13781. *
  13782. * @abstract
  13783. */
  13784. ;
  13785. _proto.createItems = function createItems() {}
  13786. /**
  13787. * Create the `MenuButtons`s DOM element.
  13788. *
  13789. * @return {Element}
  13790. * The element that gets created.
  13791. */
  13792. ;
  13793. _proto.createEl = function createEl() {
  13794. return _Component.prototype.createEl.call(this, 'div', {
  13795. className: this.buildWrapperCSSClass()
  13796. }, {});
  13797. }
  13798. /**
  13799. * Allow sub components to stack CSS class names for the wrapper element
  13800. *
  13801. * @return {string}
  13802. * The constructed wrapper DOM `className`
  13803. */
  13804. ;
  13805. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  13806. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  13807. if (this.options_.inline === true) {
  13808. menuButtonClass += '-inline';
  13809. } else {
  13810. menuButtonClass += '-popup';
  13811. } // TODO: Fix the CSS so that this isn't necessary
  13812. var buttonClass = Button.prototype.buildCSSClass();
  13813. return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this);
  13814. }
  13815. /**
  13816. * Builds the default DOM `className`.
  13817. *
  13818. * @return {string}
  13819. * The DOM `className` for this object.
  13820. */
  13821. ;
  13822. _proto.buildCSSClass = function buildCSSClass() {
  13823. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  13824. if (this.options_.inline === true) {
  13825. menuButtonClass += '-inline';
  13826. } else {
  13827. menuButtonClass += '-popup';
  13828. }
  13829. return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this);
  13830. }
  13831. /**
  13832. * Get or set the localized control text that will be used for accessibility.
  13833. *
  13834. * > NOTE: This will come from the internal `menuButton_` element.
  13835. *
  13836. * @param {string} [text]
  13837. * Control text for element.
  13838. *
  13839. * @param {Element} [el=this.menuButton_.el()]
  13840. * Element to set the title on.
  13841. *
  13842. * @return {string}
  13843. * - The control text when getting
  13844. */
  13845. ;
  13846. _proto.controlText = function controlText(text, el) {
  13847. if (el === void 0) {
  13848. el = this.menuButton_.el();
  13849. }
  13850. return this.menuButton_.controlText(text, el);
  13851. }
  13852. /**
  13853. * Dispose of the `menu-button` and all child components.
  13854. */
  13855. ;
  13856. _proto.dispose = function dispose() {
  13857. this.handleMouseLeave();
  13858. _Component.prototype.dispose.call(this);
  13859. }
  13860. /**
  13861. * Handle a click on a `MenuButton`.
  13862. * See {@link ClickableComponent#handleClick} for instances where this is called.
  13863. *
  13864. * @param {EventTarget~Event} event
  13865. * The `keydown`, `tap`, or `click` event that caused this function to be
  13866. * called.
  13867. *
  13868. * @listens tap
  13869. * @listens click
  13870. */
  13871. ;
  13872. _proto.handleClick = function handleClick(event) {
  13873. if (this.buttonPressed_) {
  13874. this.unpressButton();
  13875. } else {
  13876. this.pressButton();
  13877. }
  13878. }
  13879. /**
  13880. * Handle `mouseleave` for `MenuButton`.
  13881. *
  13882. * @param {EventTarget~Event} event
  13883. * The `mouseleave` event that caused this function to be called.
  13884. *
  13885. * @listens mouseleave
  13886. */
  13887. ;
  13888. _proto.handleMouseLeave = function handleMouseLeave(event) {
  13889. this.removeClass('vjs-hover');
  13890. off(document, 'keyup', this.handleMenuKeyUp_);
  13891. }
  13892. /**
  13893. * Set the focus to the actual button, not to this element
  13894. */
  13895. ;
  13896. _proto.focus = function focus() {
  13897. this.menuButton_.focus();
  13898. }
  13899. /**
  13900. * Remove the focus from the actual button, not this element
  13901. */
  13902. ;
  13903. _proto.blur = function blur() {
  13904. this.menuButton_.blur();
  13905. }
  13906. /**
  13907. * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
  13908. * {@link ClickableComponent#handleKeyDown} for instances where this is called.
  13909. *
  13910. * @param {EventTarget~Event} event
  13911. * The `keydown` event that caused this function to be called.
  13912. *
  13913. * @listens keydown
  13914. */
  13915. ;
  13916. _proto.handleKeyDown = function handleKeyDown(event) {
  13917. // Escape or Tab unpress the 'button'
  13918. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  13919. if (this.buttonPressed_) {
  13920. this.unpressButton();
  13921. } // Don't preventDefault for Tab key - we still want to lose focus
  13922. if (!keycode.isEventKey(event, 'Tab')) {
  13923. event.preventDefault(); // Set focus back to the menu button's button
  13924. this.menuButton_.focus();
  13925. } // Up Arrow or Down Arrow also 'press' the button to open the menu
  13926. } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
  13927. if (!this.buttonPressed_) {
  13928. event.preventDefault();
  13929. this.pressButton();
  13930. }
  13931. }
  13932. }
  13933. /**
  13934. * Handle a `keyup` event on a `MenuButton`. The listener for this is added in
  13935. * the constructor.
  13936. *
  13937. * @param {EventTarget~Event} event
  13938. * Key press event
  13939. *
  13940. * @listens keyup
  13941. */
  13942. ;
  13943. _proto.handleMenuKeyUp = function handleMenuKeyUp(event) {
  13944. // Escape hides popup menu
  13945. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  13946. this.removeClass('vjs-hover');
  13947. }
  13948. }
  13949. /**
  13950. * This method name now delegates to `handleSubmenuKeyDown`. This means
  13951. * anyone calling `handleSubmenuKeyPress` will not see their method calls
  13952. * stop working.
  13953. *
  13954. * @param {EventTarget~Event} event
  13955. * The event that caused this function to be called.
  13956. */
  13957. ;
  13958. _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
  13959. this.handleSubmenuKeyDown(event);
  13960. }
  13961. /**
  13962. * Handle a `keydown` event on a sub-menu. The listener for this is added in
  13963. * the constructor.
  13964. *
  13965. * @param {EventTarget~Event} event
  13966. * Key press event
  13967. *
  13968. * @listens keydown
  13969. */
  13970. ;
  13971. _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) {
  13972. // Escape or Tab unpress the 'button'
  13973. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  13974. if (this.buttonPressed_) {
  13975. this.unpressButton();
  13976. } // Don't preventDefault for Tab key - we still want to lose focus
  13977. if (!keycode.isEventKey(event, 'Tab')) {
  13978. event.preventDefault(); // Set focus back to the menu button's button
  13979. this.menuButton_.focus();
  13980. }
  13981. }
  13982. }
  13983. /**
  13984. * Put the current `MenuButton` into a pressed state.
  13985. */
  13986. ;
  13987. _proto.pressButton = function pressButton() {
  13988. if (this.enabled_) {
  13989. this.buttonPressed_ = true;
  13990. this.menu.show();
  13991. this.menu.lockShowing();
  13992. this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in
  13993. // undesired scrolling behavior when the player is in an iframe
  13994. if (IS_IOS && isInFrame()) {
  13995. // Return early so that the menu isn't focused
  13996. return;
  13997. }
  13998. this.menu.focus();
  13999. }
  14000. }
  14001. /**
  14002. * Take the current `MenuButton` out of a pressed state.
  14003. */
  14004. ;
  14005. _proto.unpressButton = function unpressButton() {
  14006. if (this.enabled_) {
  14007. this.buttonPressed_ = false;
  14008. this.menu.unlockShowing();
  14009. this.menu.hide();
  14010. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  14011. }
  14012. }
  14013. /**
  14014. * Disable the `MenuButton`. Don't allow it to be clicked.
  14015. */
  14016. ;
  14017. _proto.disable = function disable() {
  14018. this.unpressButton();
  14019. this.enabled_ = false;
  14020. this.addClass('vjs-disabled');
  14021. this.menuButton_.disable();
  14022. }
  14023. /**
  14024. * Enable the `MenuButton`. Allow it to be clicked.
  14025. */
  14026. ;
  14027. _proto.enable = function enable() {
  14028. this.enabled_ = true;
  14029. this.removeClass('vjs-disabled');
  14030. this.menuButton_.enable();
  14031. };
  14032. return MenuButton;
  14033. }(Component$1);
  14034. Component$1.registerComponent('MenuButton', MenuButton);
  14035. /**
  14036. * The base class for buttons that toggle specific track types (e.g. subtitles).
  14037. *
  14038. * @extends MenuButton
  14039. */
  14040. var TrackButton = /*#__PURE__*/function (_MenuButton) {
  14041. _inheritsLoose(TrackButton, _MenuButton);
  14042. /**
  14043. * Creates an instance of this class.
  14044. *
  14045. * @param {Player} player
  14046. * The `Player` that this class should be attached to.
  14047. *
  14048. * @param {Object} [options]
  14049. * The key/value store of player options.
  14050. */
  14051. function TrackButton(player, options) {
  14052. var _this;
  14053. var tracks = options.tracks;
  14054. _this = _MenuButton.call(this, player, options) || this;
  14055. if (_this.items.length <= 1) {
  14056. _this.hide();
  14057. }
  14058. if (!tracks) {
  14059. return _assertThisInitialized(_this);
  14060. }
  14061. var updateHandler = bind(_assertThisInitialized(_this), _this.update);
  14062. tracks.addEventListener('removetrack', updateHandler);
  14063. tracks.addEventListener('addtrack', updateHandler);
  14064. tracks.addEventListener('labelchange', updateHandler);
  14065. _this.player_.on('ready', updateHandler);
  14066. _this.player_.on('dispose', function () {
  14067. tracks.removeEventListener('removetrack', updateHandler);
  14068. tracks.removeEventListener('addtrack', updateHandler);
  14069. tracks.removeEventListener('labelchange', updateHandler);
  14070. });
  14071. return _this;
  14072. }
  14073. return TrackButton;
  14074. }(MenuButton);
  14075. Component$1.registerComponent('TrackButton', TrackButton);
  14076. /**
  14077. * @file menu-keys.js
  14078. */
  14079. /**
  14080. * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
  14081. * Note that 'Enter' and 'Space' are not included here (otherwise they would
  14082. * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
  14083. * @typedef MenuKeys
  14084. * @array
  14085. */
  14086. var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
  14087. /**
  14088. * The component for a menu item. `<li>`
  14089. *
  14090. * @extends ClickableComponent
  14091. */
  14092. var MenuItem = /*#__PURE__*/function (_ClickableComponent) {
  14093. _inheritsLoose(MenuItem, _ClickableComponent);
  14094. /**
  14095. * Creates an instance of the this class.
  14096. *
  14097. * @param {Player} player
  14098. * The `Player` that this class should be attached to.
  14099. *
  14100. * @param {Object} [options={}]
  14101. * The key/value store of player options.
  14102. *
  14103. */
  14104. function MenuItem(player, options) {
  14105. var _this;
  14106. _this = _ClickableComponent.call(this, player, options) || this;
  14107. _this.selectable = options.selectable;
  14108. _this.isSelected_ = options.selected || false;
  14109. _this.multiSelectable = options.multiSelectable;
  14110. _this.selected(_this.isSelected_);
  14111. if (_this.selectable) {
  14112. if (_this.multiSelectable) {
  14113. _this.el_.setAttribute('role', 'menuitemcheckbox');
  14114. } else {
  14115. _this.el_.setAttribute('role', 'menuitemradio');
  14116. }
  14117. } else {
  14118. _this.el_.setAttribute('role', 'menuitem');
  14119. }
  14120. return _this;
  14121. }
  14122. /**
  14123. * Create the `MenuItem's DOM element
  14124. *
  14125. * @param {string} [type=li]
  14126. * Element's node type, not actually used, always set to `li`.
  14127. *
  14128. * @param {Object} [props={}]
  14129. * An object of properties that should be set on the element
  14130. *
  14131. * @param {Object} [attrs={}]
  14132. * An object of attributes that should be set on the element
  14133. *
  14134. * @return {Element}
  14135. * The element that gets created.
  14136. */
  14137. var _proto = MenuItem.prototype;
  14138. _proto.createEl = function createEl$1(type, props, attrs) {
  14139. // The control is textual, not just an icon
  14140. this.nonIconControl = true;
  14141. var el = _ClickableComponent.prototype.createEl.call(this, 'li', assign({
  14142. className: 'vjs-menu-item',
  14143. tabIndex: -1
  14144. }, props), attrs); // swap icon with menu item text.
  14145. el.replaceChild(createEl('span', {
  14146. className: 'vjs-menu-item-text',
  14147. textContent: this.localize(this.options_.label)
  14148. }), el.querySelector('.vjs-icon-placeholder'));
  14149. return el;
  14150. }
  14151. /**
  14152. * Ignore keys which are used by the menu, but pass any other ones up. See
  14153. * {@link ClickableComponent#handleKeyDown} for instances where this is called.
  14154. *
  14155. * @param {EventTarget~Event} event
  14156. * The `keydown` event that caused this function to be called.
  14157. *
  14158. * @listens keydown
  14159. */
  14160. ;
  14161. _proto.handleKeyDown = function handleKeyDown(event) {
  14162. if (!MenuKeys.some(function (key) {
  14163. return keycode.isEventKey(event, key);
  14164. })) {
  14165. // Pass keydown handling up for unused keys
  14166. _ClickableComponent.prototype.handleKeyDown.call(this, event);
  14167. }
  14168. }
  14169. /**
  14170. * Any click on a `MenuItem` puts it into the selected state.
  14171. * See {@link ClickableComponent#handleClick} for instances where this is called.
  14172. *
  14173. * @param {EventTarget~Event} event
  14174. * The `keydown`, `tap`, or `click` event that caused this function to be
  14175. * called.
  14176. *
  14177. * @listens tap
  14178. * @listens click
  14179. */
  14180. ;
  14181. _proto.handleClick = function handleClick(event) {
  14182. this.selected(true);
  14183. }
  14184. /**
  14185. * Set the state for this menu item as selected or not.
  14186. *
  14187. * @param {boolean} selected
  14188. * if the menu item is selected or not
  14189. */
  14190. ;
  14191. _proto.selected = function selected(_selected) {
  14192. if (this.selectable) {
  14193. if (_selected) {
  14194. this.addClass('vjs-selected');
  14195. this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers,
  14196. // so indicate selected state to screen reader in the control text.
  14197. this.controlText(', selected');
  14198. this.isSelected_ = true;
  14199. } else {
  14200. this.removeClass('vjs-selected');
  14201. this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader
  14202. this.controlText('');
  14203. this.isSelected_ = false;
  14204. }
  14205. }
  14206. };
  14207. return MenuItem;
  14208. }(ClickableComponent);
  14209. Component$1.registerComponent('MenuItem', MenuItem);
  14210. /**
  14211. * The specific menu item type for selecting a language within a text track kind
  14212. *
  14213. * @extends MenuItem
  14214. */
  14215. var TextTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
  14216. _inheritsLoose(TextTrackMenuItem, _MenuItem);
  14217. /**
  14218. * Creates an instance of this class.
  14219. *
  14220. * @param {Player} player
  14221. * The `Player` that this class should be attached to.
  14222. *
  14223. * @param {Object} [options]
  14224. * The key/value store of player options.
  14225. */
  14226. function TextTrackMenuItem(player, options) {
  14227. var _this;
  14228. var track = options.track;
  14229. var tracks = player.textTracks(); // Modify options for parent MenuItem class's init.
  14230. options.label = track.label || track.language || 'Unknown';
  14231. options.selected = track.mode === 'showing';
  14232. _this = _MenuItem.call(this, player, options) || this;
  14233. _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter
  14234. // out empty kinds.
  14235. _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean);
  14236. var changeHandler = function changeHandler() {
  14237. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  14238. args[_key] = arguments[_key];
  14239. }
  14240. _this.handleTracksChange.apply(_assertThisInitialized(_this), args);
  14241. };
  14242. var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
  14243. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  14244. args[_key2] = arguments[_key2];
  14245. }
  14246. _this.handleSelectedLanguageChange.apply(_assertThisInitialized(_this), args);
  14247. };
  14248. player.on(['loadstart', 'texttrackchange'], changeHandler);
  14249. tracks.addEventListener('change', changeHandler);
  14250. tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  14251. _this.on('dispose', function () {
  14252. player.off(['loadstart', 'texttrackchange'], changeHandler);
  14253. tracks.removeEventListener('change', changeHandler);
  14254. tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  14255. }); // iOS7 doesn't dispatch change events to TextTrackLists when an
  14256. // associated track's mode changes. Without something like
  14257. // Object.observe() (also not present on iOS7), it's not
  14258. // possible to detect changes to the mode attribute and polyfill
  14259. // the change event. As a poor substitute, we manually dispatch
  14260. // change events whenever the controls modify the mode.
  14261. if (tracks.onchange === undefined) {
  14262. var event;
  14263. _this.on(['tap', 'click'], function () {
  14264. if (typeof window$1.Event !== 'object') {
  14265. // Android 2.3 throws an Illegal Constructor error for window.Event
  14266. try {
  14267. event = new window$1.Event('change');
  14268. } catch (err) {// continue regardless of error
  14269. }
  14270. }
  14271. if (!event) {
  14272. event = document.createEvent('Event');
  14273. event.initEvent('change', true, true);
  14274. }
  14275. tracks.dispatchEvent(event);
  14276. });
  14277. } // set the default state based on current tracks
  14278. _this.handleTracksChange();
  14279. return _this;
  14280. }
  14281. /**
  14282. * This gets called when an `TextTrackMenuItem` is "clicked". See
  14283. * {@link ClickableComponent} for more detailed information on what a click can be.
  14284. *
  14285. * @param {EventTarget~Event} event
  14286. * The `keydown`, `tap`, or `click` event that caused this function to be
  14287. * called.
  14288. *
  14289. * @listens tap
  14290. * @listens click
  14291. */
  14292. var _proto = TextTrackMenuItem.prototype;
  14293. _proto.handleClick = function handleClick(event) {
  14294. var referenceTrack = this.track;
  14295. var tracks = this.player_.textTracks();
  14296. _MenuItem.prototype.handleClick.call(this, event);
  14297. if (!tracks) {
  14298. return;
  14299. }
  14300. for (var i = 0; i < tracks.length; i++) {
  14301. var track = tracks[i]; // If the track from the text tracks list is not of the right kind,
  14302. // skip it. We do not want to affect tracks of incompatible kind(s).
  14303. if (this.kinds.indexOf(track.kind) === -1) {
  14304. continue;
  14305. } // If this text track is the component's track and it is not showing,
  14306. // set it to showing.
  14307. if (track === referenceTrack) {
  14308. if (track.mode !== 'showing') {
  14309. track.mode = 'showing';
  14310. } // If this text track is not the component's track and it is not
  14311. // disabled, set it to disabled.
  14312. } else if (track.mode !== 'disabled') {
  14313. track.mode = 'disabled';
  14314. }
  14315. }
  14316. }
  14317. /**
  14318. * Handle text track list change
  14319. *
  14320. * @param {EventTarget~Event} event
  14321. * The `change` event that caused this function to be called.
  14322. *
  14323. * @listens TextTrackList#change
  14324. */
  14325. ;
  14326. _proto.handleTracksChange = function handleTracksChange(event) {
  14327. var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause
  14328. // screen readers to read the appended control text unnecessarily
  14329. if (shouldBeSelected !== this.isSelected_) {
  14330. this.selected(shouldBeSelected);
  14331. }
  14332. };
  14333. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  14334. if (this.track.mode === 'showing') {
  14335. var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language
  14336. if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
  14337. return;
  14338. }
  14339. this.player_.cache_.selectedLanguage = {
  14340. enabled: true,
  14341. language: this.track.language,
  14342. kind: this.track.kind
  14343. };
  14344. }
  14345. };
  14346. _proto.dispose = function dispose() {
  14347. // remove reference to track object on dispose
  14348. this.track = null;
  14349. _MenuItem.prototype.dispose.call(this);
  14350. };
  14351. return TextTrackMenuItem;
  14352. }(MenuItem);
  14353. Component$1.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
  14354. /**
  14355. * A special menu item for turning of a specific type of text track
  14356. *
  14357. * @extends TextTrackMenuItem
  14358. */
  14359. var OffTextTrackMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
  14360. _inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
  14361. /**
  14362. * Creates an instance of this class.
  14363. *
  14364. * @param {Player} player
  14365. * The `Player` that this class should be attached to.
  14366. *
  14367. * @param {Object} [options]
  14368. * The key/value store of player options.
  14369. */
  14370. function OffTextTrackMenuItem(player, options) {
  14371. // Create pseudo track info
  14372. // Requires options['kind']
  14373. options.track = {
  14374. player: player,
  14375. // it is no longer necessary to store `kind` or `kinds` on the track itself
  14376. // since they are now stored in the `kinds` property of all instances of
  14377. // TextTrackMenuItem, but this will remain for backwards compatibility
  14378. kind: options.kind,
  14379. kinds: options.kinds,
  14380. "default": false,
  14381. mode: 'disabled'
  14382. };
  14383. if (!options.kinds) {
  14384. options.kinds = [options.kind];
  14385. }
  14386. if (options.label) {
  14387. options.track.label = options.label;
  14388. } else {
  14389. options.track.label = options.kinds.join(' and ') + ' off';
  14390. } // MenuItem is selectable
  14391. options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  14392. options.multiSelectable = false;
  14393. return _TextTrackMenuItem.call(this, player, options) || this;
  14394. }
  14395. /**
  14396. * Handle text track change
  14397. *
  14398. * @param {EventTarget~Event} event
  14399. * The event that caused this function to run
  14400. */
  14401. var _proto = OffTextTrackMenuItem.prototype;
  14402. _proto.handleTracksChange = function handleTracksChange(event) {
  14403. var tracks = this.player().textTracks();
  14404. var shouldBeSelected = true;
  14405. for (var i = 0, l = tracks.length; i < l; i++) {
  14406. var track = tracks[i];
  14407. if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
  14408. shouldBeSelected = false;
  14409. break;
  14410. }
  14411. } // Prevent redundant selected() calls because they may cause
  14412. // screen readers to read the appended control text unnecessarily
  14413. if (shouldBeSelected !== this.isSelected_) {
  14414. this.selected(shouldBeSelected);
  14415. }
  14416. };
  14417. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  14418. var tracks = this.player().textTracks();
  14419. var allHidden = true;
  14420. for (var i = 0, l = tracks.length; i < l; i++) {
  14421. var track = tracks[i];
  14422. if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
  14423. allHidden = false;
  14424. break;
  14425. }
  14426. }
  14427. if (allHidden) {
  14428. this.player_.cache_.selectedLanguage = {
  14429. enabled: false
  14430. };
  14431. }
  14432. };
  14433. return OffTextTrackMenuItem;
  14434. }(TextTrackMenuItem);
  14435. Component$1.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
  14436. /**
  14437. * The base class for buttons that toggle specific text track types (e.g. subtitles)
  14438. *
  14439. * @extends MenuButton
  14440. */
  14441. var TextTrackButton = /*#__PURE__*/function (_TrackButton) {
  14442. _inheritsLoose(TextTrackButton, _TrackButton);
  14443. /**
  14444. * Creates an instance of this class.
  14445. *
  14446. * @param {Player} player
  14447. * The `Player` that this class should be attached to.
  14448. *
  14449. * @param {Object} [options={}]
  14450. * The key/value store of player options.
  14451. */
  14452. function TextTrackButton(player, options) {
  14453. if (options === void 0) {
  14454. options = {};
  14455. }
  14456. options.tracks = player.textTracks();
  14457. return _TrackButton.call(this, player, options) || this;
  14458. }
  14459. /**
  14460. * Create a menu item for each text track
  14461. *
  14462. * @param {TextTrackMenuItem[]} [items=[]]
  14463. * Existing array of items to use during creation
  14464. *
  14465. * @return {TextTrackMenuItem[]}
  14466. * Array of menu items that were created
  14467. */
  14468. var _proto = TextTrackButton.prototype;
  14469. _proto.createItems = function createItems(items, TrackMenuItem) {
  14470. if (items === void 0) {
  14471. items = [];
  14472. }
  14473. if (TrackMenuItem === void 0) {
  14474. TrackMenuItem = TextTrackMenuItem;
  14475. }
  14476. // Label is an override for the [track] off label
  14477. // USed to localise captions/subtitles
  14478. var label;
  14479. if (this.label_) {
  14480. label = this.label_ + " off";
  14481. } // Add an OFF menu item to turn all tracks off
  14482. items.push(new OffTextTrackMenuItem(this.player_, {
  14483. kinds: this.kinds_,
  14484. kind: this.kind_,
  14485. label: label
  14486. }));
  14487. this.hideThreshold_ += 1;
  14488. var tracks = this.player_.textTracks();
  14489. if (!Array.isArray(this.kinds_)) {
  14490. this.kinds_ = [this.kind_];
  14491. }
  14492. for (var i = 0; i < tracks.length; i++) {
  14493. var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label
  14494. if (this.kinds_.indexOf(track.kind) > -1) {
  14495. var item = new TrackMenuItem(this.player_, {
  14496. track: track,
  14497. kinds: this.kinds_,
  14498. kind: this.kind_,
  14499. // MenuItem is selectable
  14500. selectable: true,
  14501. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  14502. multiSelectable: false
  14503. });
  14504. item.addClass("vjs-" + track.kind + "-menu-item");
  14505. items.push(item);
  14506. }
  14507. }
  14508. return items;
  14509. };
  14510. return TextTrackButton;
  14511. }(TrackButton);
  14512. Component$1.registerComponent('TextTrackButton', TextTrackButton);
  14513. /**
  14514. * The chapter track menu item
  14515. *
  14516. * @extends MenuItem
  14517. */
  14518. var ChaptersTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
  14519. _inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
  14520. /**
  14521. * Creates an instance of this class.
  14522. *
  14523. * @param {Player} player
  14524. * The `Player` that this class should be attached to.
  14525. *
  14526. * @param {Object} [options]
  14527. * The key/value store of player options.
  14528. */
  14529. function ChaptersTrackMenuItem(player, options) {
  14530. var _this;
  14531. var track = options.track;
  14532. var cue = options.cue;
  14533. var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init.
  14534. options.selectable = true;
  14535. options.multiSelectable = false;
  14536. options.label = cue.text;
  14537. options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
  14538. _this = _MenuItem.call(this, player, options) || this;
  14539. _this.track = track;
  14540. _this.cue = cue;
  14541. return _this;
  14542. }
  14543. /**
  14544. * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
  14545. * {@link ClickableComponent} for more detailed information on what a click can be.
  14546. *
  14547. * @param {EventTarget~Event} [event]
  14548. * The `keydown`, `tap`, or `click` event that caused this function to be
  14549. * called.
  14550. *
  14551. * @listens tap
  14552. * @listens click
  14553. */
  14554. var _proto = ChaptersTrackMenuItem.prototype;
  14555. _proto.handleClick = function handleClick(event) {
  14556. _MenuItem.prototype.handleClick.call(this);
  14557. this.player_.currentTime(this.cue.startTime);
  14558. };
  14559. return ChaptersTrackMenuItem;
  14560. }(MenuItem);
  14561. Component$1.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
  14562. /**
  14563. * The button component for toggling and selecting chapters
  14564. * Chapters act much differently than other text tracks
  14565. * Cues are navigation vs. other tracks of alternative languages
  14566. *
  14567. * @extends TextTrackButton
  14568. */
  14569. var ChaptersButton = /*#__PURE__*/function (_TextTrackButton) {
  14570. _inheritsLoose(ChaptersButton, _TextTrackButton);
  14571. /**
  14572. * Creates an instance of this class.
  14573. *
  14574. * @param {Player} player
  14575. * The `Player` that this class should be attached to.
  14576. *
  14577. * @param {Object} [options]
  14578. * The key/value store of player options.
  14579. *
  14580. * @param {Component~ReadyCallback} [ready]
  14581. * The function to call when this function is ready.
  14582. */
  14583. function ChaptersButton(player, options, ready) {
  14584. var _this;
  14585. _this = _TextTrackButton.call(this, player, options, ready) || this;
  14586. _this.selectCurrentItem_ = function () {
  14587. _this.items.forEach(function (item) {
  14588. item.selected(_this.track_.activeCues[0] === item.cue);
  14589. });
  14590. };
  14591. return _this;
  14592. }
  14593. /**
  14594. * Builds the default DOM `className`.
  14595. *
  14596. * @return {string}
  14597. * The DOM `className` for this object.
  14598. */
  14599. var _proto = ChaptersButton.prototype;
  14600. _proto.buildCSSClass = function buildCSSClass() {
  14601. return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  14602. };
  14603. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  14604. return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  14605. }
  14606. /**
  14607. * Update the menu based on the current state of its items.
  14608. *
  14609. * @param {EventTarget~Event} [event]
  14610. * An event that triggered this function to run.
  14611. *
  14612. * @listens TextTrackList#addtrack
  14613. * @listens TextTrackList#removetrack
  14614. * @listens TextTrackList#change
  14615. */
  14616. ;
  14617. _proto.update = function update(event) {
  14618. if (event && event.track && event.track.kind !== 'chapters') {
  14619. return;
  14620. }
  14621. var track = this.findChaptersTrack();
  14622. if (track !== this.track_) {
  14623. this.setTrack(track);
  14624. _TextTrackButton.prototype.update.call(this);
  14625. } else if (!this.items || track && track.cues && track.cues.length !== this.items.length) {
  14626. // Update the menu initially or if the number of cues has changed since set
  14627. _TextTrackButton.prototype.update.call(this);
  14628. }
  14629. }
  14630. /**
  14631. * Set the currently selected track for the chapters button.
  14632. *
  14633. * @param {TextTrack} track
  14634. * The new track to select. Nothing will change if this is the currently selected
  14635. * track.
  14636. */
  14637. ;
  14638. _proto.setTrack = function setTrack(track) {
  14639. if (this.track_ === track) {
  14640. return;
  14641. }
  14642. if (!this.updateHandler_) {
  14643. this.updateHandler_ = this.update.bind(this);
  14644. } // here this.track_ refers to the old track instance
  14645. if (this.track_) {
  14646. var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  14647. if (remoteTextTrackEl) {
  14648. remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
  14649. }
  14650. this.track_.removeEventListener('cuechange', this.selectCurrentItem_);
  14651. this.track_ = null;
  14652. }
  14653. this.track_ = track; // here this.track_ refers to the new track instance
  14654. if (this.track_) {
  14655. this.track_.mode = 'hidden';
  14656. var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  14657. if (_remoteTextTrackEl) {
  14658. _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
  14659. }
  14660. this.track_.addEventListener('cuechange', this.selectCurrentItem_);
  14661. }
  14662. }
  14663. /**
  14664. * Find the track object that is currently in use by this ChaptersButton
  14665. *
  14666. * @return {TextTrack|undefined}
  14667. * The current track or undefined if none was found.
  14668. */
  14669. ;
  14670. _proto.findChaptersTrack = function findChaptersTrack() {
  14671. var tracks = this.player_.textTracks() || [];
  14672. for (var i = tracks.length - 1; i >= 0; i--) {
  14673. // We will always choose the last track as our chaptersTrack
  14674. var track = tracks[i];
  14675. if (track.kind === this.kind_) {
  14676. return track;
  14677. }
  14678. }
  14679. }
  14680. /**
  14681. * Get the caption for the ChaptersButton based on the track label. This will also
  14682. * use the current tracks localized kind as a fallback if a label does not exist.
  14683. *
  14684. * @return {string}
  14685. * The tracks current label or the localized track kind.
  14686. */
  14687. ;
  14688. _proto.getMenuCaption = function getMenuCaption() {
  14689. if (this.track_ && this.track_.label) {
  14690. return this.track_.label;
  14691. }
  14692. return this.localize(toTitleCase$1(this.kind_));
  14693. }
  14694. /**
  14695. * Create menu from chapter track
  14696. *
  14697. * @return {Menu}
  14698. * New menu for the chapter buttons
  14699. */
  14700. ;
  14701. _proto.createMenu = function createMenu() {
  14702. this.options_.title = this.getMenuCaption();
  14703. return _TextTrackButton.prototype.createMenu.call(this);
  14704. }
  14705. /**
  14706. * Create a menu item for each text track
  14707. *
  14708. * @return {TextTrackMenuItem[]}
  14709. * Array of menu items
  14710. */
  14711. ;
  14712. _proto.createItems = function createItems() {
  14713. var items = [];
  14714. if (!this.track_) {
  14715. return items;
  14716. }
  14717. var cues = this.track_.cues;
  14718. if (!cues) {
  14719. return items;
  14720. }
  14721. for (var i = 0, l = cues.length; i < l; i++) {
  14722. var cue = cues[i];
  14723. var mi = new ChaptersTrackMenuItem(this.player_, {
  14724. track: this.track_,
  14725. cue: cue
  14726. });
  14727. items.push(mi);
  14728. }
  14729. return items;
  14730. };
  14731. return ChaptersButton;
  14732. }(TextTrackButton);
  14733. /**
  14734. * `kind` of TextTrack to look for to associate it with this menu.
  14735. *
  14736. * @type {string}
  14737. * @private
  14738. */
  14739. ChaptersButton.prototype.kind_ = 'chapters';
  14740. /**
  14741. * The text that should display over the `ChaptersButton`s controls. Added for localization.
  14742. *
  14743. * @type {string}
  14744. * @private
  14745. */
  14746. ChaptersButton.prototype.controlText_ = 'Chapters';
  14747. Component$1.registerComponent('ChaptersButton', ChaptersButton);
  14748. /**
  14749. * The button component for toggling and selecting descriptions
  14750. *
  14751. * @extends TextTrackButton
  14752. */
  14753. var DescriptionsButton = /*#__PURE__*/function (_TextTrackButton) {
  14754. _inheritsLoose(DescriptionsButton, _TextTrackButton);
  14755. /**
  14756. * Creates an instance of this class.
  14757. *
  14758. * @param {Player} player
  14759. * The `Player` that this class should be attached to.
  14760. *
  14761. * @param {Object} [options]
  14762. * The key/value store of player options.
  14763. *
  14764. * @param {Component~ReadyCallback} [ready]
  14765. * The function to call when this component is ready.
  14766. */
  14767. function DescriptionsButton(player, options, ready) {
  14768. var _this;
  14769. _this = _TextTrackButton.call(this, player, options, ready) || this;
  14770. var tracks = player.textTracks();
  14771. var changeHandler = bind(_assertThisInitialized(_this), _this.handleTracksChange);
  14772. tracks.addEventListener('change', changeHandler);
  14773. _this.on('dispose', function () {
  14774. tracks.removeEventListener('change', changeHandler);
  14775. });
  14776. return _this;
  14777. }
  14778. /**
  14779. * Handle text track change
  14780. *
  14781. * @param {EventTarget~Event} event
  14782. * The event that caused this function to run
  14783. *
  14784. * @listens TextTrackList#change
  14785. */
  14786. var _proto = DescriptionsButton.prototype;
  14787. _proto.handleTracksChange = function handleTracksChange(event) {
  14788. var tracks = this.player().textTracks();
  14789. var disabled = false; // Check whether a track of a different kind is showing
  14790. for (var i = 0, l = tracks.length; i < l; i++) {
  14791. var track = tracks[i];
  14792. if (track.kind !== this.kind_ && track.mode === 'showing') {
  14793. disabled = true;
  14794. break;
  14795. }
  14796. } // If another track is showing, disable this menu button
  14797. if (disabled) {
  14798. this.disable();
  14799. } else {
  14800. this.enable();
  14801. }
  14802. }
  14803. /**
  14804. * Builds the default DOM `className`.
  14805. *
  14806. * @return {string}
  14807. * The DOM `className` for this object.
  14808. */
  14809. ;
  14810. _proto.buildCSSClass = function buildCSSClass() {
  14811. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  14812. };
  14813. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  14814. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  14815. };
  14816. return DescriptionsButton;
  14817. }(TextTrackButton);
  14818. /**
  14819. * `kind` of TextTrack to look for to associate it with this menu.
  14820. *
  14821. * @type {string}
  14822. * @private
  14823. */
  14824. DescriptionsButton.prototype.kind_ = 'descriptions';
  14825. /**
  14826. * The text that should display over the `DescriptionsButton`s controls. Added for localization.
  14827. *
  14828. * @type {string}
  14829. * @private
  14830. */
  14831. DescriptionsButton.prototype.controlText_ = 'Descriptions';
  14832. Component$1.registerComponent('DescriptionsButton', DescriptionsButton);
  14833. /**
  14834. * The button component for toggling and selecting subtitles
  14835. *
  14836. * @extends TextTrackButton
  14837. */
  14838. var SubtitlesButton = /*#__PURE__*/function (_TextTrackButton) {
  14839. _inheritsLoose(SubtitlesButton, _TextTrackButton);
  14840. /**
  14841. * Creates an instance of this class.
  14842. *
  14843. * @param {Player} player
  14844. * The `Player` that this class should be attached to.
  14845. *
  14846. * @param {Object} [options]
  14847. * The key/value store of player options.
  14848. *
  14849. * @param {Component~ReadyCallback} [ready]
  14850. * The function to call when this component is ready.
  14851. */
  14852. function SubtitlesButton(player, options, ready) {
  14853. return _TextTrackButton.call(this, player, options, ready) || this;
  14854. }
  14855. /**
  14856. * Builds the default DOM `className`.
  14857. *
  14858. * @return {string}
  14859. * The DOM `className` for this object.
  14860. */
  14861. var _proto = SubtitlesButton.prototype;
  14862. _proto.buildCSSClass = function buildCSSClass() {
  14863. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  14864. };
  14865. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  14866. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  14867. };
  14868. return SubtitlesButton;
  14869. }(TextTrackButton);
  14870. /**
  14871. * `kind` of TextTrack to look for to associate it with this menu.
  14872. *
  14873. * @type {string}
  14874. * @private
  14875. */
  14876. SubtitlesButton.prototype.kind_ = 'subtitles';
  14877. /**
  14878. * The text that should display over the `SubtitlesButton`s controls. Added for localization.
  14879. *
  14880. * @type {string}
  14881. * @private
  14882. */
  14883. SubtitlesButton.prototype.controlText_ = 'Subtitles';
  14884. Component$1.registerComponent('SubtitlesButton', SubtitlesButton);
  14885. /**
  14886. * The menu item for caption track settings menu
  14887. *
  14888. * @extends TextTrackMenuItem
  14889. */
  14890. var CaptionSettingsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
  14891. _inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
  14892. /**
  14893. * Creates an instance of this class.
  14894. *
  14895. * @param {Player} player
  14896. * The `Player` that this class should be attached to.
  14897. *
  14898. * @param {Object} [options]
  14899. * The key/value store of player options.
  14900. */
  14901. function CaptionSettingsMenuItem(player, options) {
  14902. var _this;
  14903. options.track = {
  14904. player: player,
  14905. kind: options.kind,
  14906. label: options.kind + ' settings',
  14907. selectable: false,
  14908. "default": false,
  14909. mode: 'disabled'
  14910. }; // CaptionSettingsMenuItem has no concept of 'selected'
  14911. options.selectable = false;
  14912. options.name = 'CaptionSettingsMenuItem';
  14913. _this = _TextTrackMenuItem.call(this, player, options) || this;
  14914. _this.addClass('vjs-texttrack-settings');
  14915. _this.controlText(', opens ' + options.kind + ' settings dialog');
  14916. return _this;
  14917. }
  14918. /**
  14919. * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
  14920. * {@link ClickableComponent} for more detailed information on what a click can be.
  14921. *
  14922. * @param {EventTarget~Event} [event]
  14923. * The `keydown`, `tap`, or `click` event that caused this function to be
  14924. * called.
  14925. *
  14926. * @listens tap
  14927. * @listens click
  14928. */
  14929. var _proto = CaptionSettingsMenuItem.prototype;
  14930. _proto.handleClick = function handleClick(event) {
  14931. this.player().getChild('textTrackSettings').open();
  14932. };
  14933. return CaptionSettingsMenuItem;
  14934. }(TextTrackMenuItem);
  14935. Component$1.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
  14936. /**
  14937. * The button component for toggling and selecting captions
  14938. *
  14939. * @extends TextTrackButton
  14940. */
  14941. var CaptionsButton = /*#__PURE__*/function (_TextTrackButton) {
  14942. _inheritsLoose(CaptionsButton, _TextTrackButton);
  14943. /**
  14944. * Creates an instance of this class.
  14945. *
  14946. * @param {Player} player
  14947. * The `Player` that this class should be attached to.
  14948. *
  14949. * @param {Object} [options]
  14950. * The key/value store of player options.
  14951. *
  14952. * @param {Component~ReadyCallback} [ready]
  14953. * The function to call when this component is ready.
  14954. */
  14955. function CaptionsButton(player, options, ready) {
  14956. return _TextTrackButton.call(this, player, options, ready) || this;
  14957. }
  14958. /**
  14959. * Builds the default DOM `className`.
  14960. *
  14961. * @return {string}
  14962. * The DOM `className` for this object.
  14963. */
  14964. var _proto = CaptionsButton.prototype;
  14965. _proto.buildCSSClass = function buildCSSClass() {
  14966. return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  14967. };
  14968. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  14969. return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  14970. }
  14971. /**
  14972. * Create caption menu items
  14973. *
  14974. * @return {CaptionSettingsMenuItem[]}
  14975. * The array of current menu items.
  14976. */
  14977. ;
  14978. _proto.createItems = function createItems() {
  14979. var items = [];
  14980. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  14981. items.push(new CaptionSettingsMenuItem(this.player_, {
  14982. kind: this.kind_
  14983. }));
  14984. this.hideThreshold_ += 1;
  14985. }
  14986. return _TextTrackButton.prototype.createItems.call(this, items);
  14987. };
  14988. return CaptionsButton;
  14989. }(TextTrackButton);
  14990. /**
  14991. * `kind` of TextTrack to look for to associate it with this menu.
  14992. *
  14993. * @type {string}
  14994. * @private
  14995. */
  14996. CaptionsButton.prototype.kind_ = 'captions';
  14997. /**
  14998. * The text that should display over the `CaptionsButton`s controls. Added for localization.
  14999. *
  15000. * @type {string}
  15001. * @private
  15002. */
  15003. CaptionsButton.prototype.controlText_ = 'Captions';
  15004. Component$1.registerComponent('CaptionsButton', CaptionsButton);
  15005. /**
  15006. * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
  15007. * in the SubsCapsMenu.
  15008. *
  15009. * @extends TextTrackMenuItem
  15010. */
  15011. var SubsCapsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
  15012. _inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
  15013. function SubsCapsMenuItem() {
  15014. return _TextTrackMenuItem.apply(this, arguments) || this;
  15015. }
  15016. var _proto = SubsCapsMenuItem.prototype;
  15017. _proto.createEl = function createEl$1(type, props, attrs) {
  15018. var el = _TextTrackMenuItem.prototype.createEl.call(this, type, props, attrs);
  15019. var parentSpan = el.querySelector('.vjs-menu-item-text');
  15020. if (this.options_.track.kind === 'captions') {
  15021. parentSpan.appendChild(createEl('span', {
  15022. className: 'vjs-icon-placeholder'
  15023. }, {
  15024. 'aria-hidden': true
  15025. }));
  15026. parentSpan.appendChild(createEl('span', {
  15027. className: 'vjs-control-text',
  15028. // space added as the text will visually flow with the
  15029. // label
  15030. textContent: " " + this.localize('Captions')
  15031. }));
  15032. }
  15033. return el;
  15034. };
  15035. return SubsCapsMenuItem;
  15036. }(TextTrackMenuItem);
  15037. Component$1.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
  15038. /**
  15039. * The button component for toggling and selecting captions and/or subtitles
  15040. *
  15041. * @extends TextTrackButton
  15042. */
  15043. var SubsCapsButton = /*#__PURE__*/function (_TextTrackButton) {
  15044. _inheritsLoose(SubsCapsButton, _TextTrackButton);
  15045. function SubsCapsButton(player, options) {
  15046. var _this;
  15047. if (options === void 0) {
  15048. options = {};
  15049. }
  15050. _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for
  15051. // "captions and subtitles" other locales use "subtitles"
  15052. _this.label_ = 'subtitles';
  15053. if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
  15054. _this.label_ = 'captions';
  15055. }
  15056. _this.menuButton_.controlText(toTitleCase$1(_this.label_));
  15057. return _this;
  15058. }
  15059. /**
  15060. * Builds the default DOM `className`.
  15061. *
  15062. * @return {string}
  15063. * The DOM `className` for this object.
  15064. */
  15065. var _proto = SubsCapsButton.prototype;
  15066. _proto.buildCSSClass = function buildCSSClass() {
  15067. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  15068. };
  15069. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15070. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  15071. }
  15072. /**
  15073. * Create caption/subtitles menu items
  15074. *
  15075. * @return {CaptionSettingsMenuItem[]}
  15076. * The array of current menu items.
  15077. */
  15078. ;
  15079. _proto.createItems = function createItems() {
  15080. var items = [];
  15081. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  15082. items.push(new CaptionSettingsMenuItem(this.player_, {
  15083. kind: this.label_
  15084. }));
  15085. this.hideThreshold_ += 1;
  15086. }
  15087. items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
  15088. return items;
  15089. };
  15090. return SubsCapsButton;
  15091. }(TextTrackButton);
  15092. /**
  15093. * `kind`s of TextTrack to look for to associate it with this menu.
  15094. *
  15095. * @type {array}
  15096. * @private
  15097. */
  15098. SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
  15099. /**
  15100. * The text that should display over the `SubsCapsButton`s controls.
  15101. *
  15102. *
  15103. * @type {string}
  15104. * @private
  15105. */
  15106. SubsCapsButton.prototype.controlText_ = 'Subtitles';
  15107. Component$1.registerComponent('SubsCapsButton', SubsCapsButton);
  15108. /**
  15109. * An {@link AudioTrack} {@link MenuItem}
  15110. *
  15111. * @extends MenuItem
  15112. */
  15113. var AudioTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
  15114. _inheritsLoose(AudioTrackMenuItem, _MenuItem);
  15115. /**
  15116. * Creates an instance of this class.
  15117. *
  15118. * @param {Player} player
  15119. * The `Player` that this class should be attached to.
  15120. *
  15121. * @param {Object} [options]
  15122. * The key/value store of player options.
  15123. */
  15124. function AudioTrackMenuItem(player, options) {
  15125. var _this;
  15126. var track = options.track;
  15127. var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init.
  15128. options.label = track.label || track.language || 'Unknown';
  15129. options.selected = track.enabled;
  15130. _this = _MenuItem.call(this, player, options) || this;
  15131. _this.track = track;
  15132. _this.addClass("vjs-" + track.kind + "-menu-item");
  15133. var changeHandler = function changeHandler() {
  15134. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  15135. args[_key] = arguments[_key];
  15136. }
  15137. _this.handleTracksChange.apply(_assertThisInitialized(_this), args);
  15138. };
  15139. tracks.addEventListener('change', changeHandler);
  15140. _this.on('dispose', function () {
  15141. tracks.removeEventListener('change', changeHandler);
  15142. });
  15143. return _this;
  15144. }
  15145. var _proto = AudioTrackMenuItem.prototype;
  15146. _proto.createEl = function createEl$1(type, props, attrs) {
  15147. var el = _MenuItem.prototype.createEl.call(this, type, props, attrs);
  15148. var parentSpan = el.querySelector('.vjs-menu-item-text');
  15149. if (this.options_.track.kind === 'main-desc') {
  15150. parentSpan.appendChild(createEl('span', {
  15151. className: 'vjs-icon-placeholder'
  15152. }, {
  15153. 'aria-hidden': true
  15154. }));
  15155. parentSpan.appendChild(createEl('span', {
  15156. className: 'vjs-control-text',
  15157. textContent: ' ' + this.localize('Descriptions')
  15158. }));
  15159. }
  15160. return el;
  15161. }
  15162. /**
  15163. * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
  15164. * for more detailed information on what a click can be.
  15165. *
  15166. * @param {EventTarget~Event} [event]
  15167. * The `keydown`, `tap`, or `click` event that caused this function to be
  15168. * called.
  15169. *
  15170. * @listens tap
  15171. * @listens click
  15172. */
  15173. ;
  15174. _proto.handleClick = function handleClick(event) {
  15175. _MenuItem.prototype.handleClick.call(this, event); // the audio track list will automatically toggle other tracks
  15176. // off for us.
  15177. this.track.enabled = true; // when native audio tracks are used, we want to make sure that other tracks are turned off
  15178. if (this.player_.tech_.featuresNativeAudioTracks) {
  15179. var tracks = this.player_.audioTracks();
  15180. for (var i = 0; i < tracks.length; i++) {
  15181. var track = tracks[i]; // skip the current track since we enabled it above
  15182. if (track === this.track) {
  15183. continue;
  15184. }
  15185. track.enabled = track === this.track;
  15186. }
  15187. }
  15188. }
  15189. /**
  15190. * Handle any {@link AudioTrack} change.
  15191. *
  15192. * @param {EventTarget~Event} [event]
  15193. * The {@link AudioTrackList#change} event that caused this to run.
  15194. *
  15195. * @listens AudioTrackList#change
  15196. */
  15197. ;
  15198. _proto.handleTracksChange = function handleTracksChange(event) {
  15199. this.selected(this.track.enabled);
  15200. };
  15201. return AudioTrackMenuItem;
  15202. }(MenuItem);
  15203. Component$1.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
  15204. /**
  15205. * The base class for buttons that toggle specific {@link AudioTrack} types.
  15206. *
  15207. * @extends TrackButton
  15208. */
  15209. var AudioTrackButton = /*#__PURE__*/function (_TrackButton) {
  15210. _inheritsLoose(AudioTrackButton, _TrackButton);
  15211. /**
  15212. * Creates an instance of this class.
  15213. *
  15214. * @param {Player} player
  15215. * The `Player` that this class should be attached to.
  15216. *
  15217. * @param {Object} [options={}]
  15218. * The key/value store of player options.
  15219. */
  15220. function AudioTrackButton(player, options) {
  15221. if (options === void 0) {
  15222. options = {};
  15223. }
  15224. options.tracks = player.audioTracks();
  15225. return _TrackButton.call(this, player, options) || this;
  15226. }
  15227. /**
  15228. * Builds the default DOM `className`.
  15229. *
  15230. * @return {string}
  15231. * The DOM `className` for this object.
  15232. */
  15233. var _proto = AudioTrackButton.prototype;
  15234. _proto.buildCSSClass = function buildCSSClass() {
  15235. return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this);
  15236. };
  15237. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15238. return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this);
  15239. }
  15240. /**
  15241. * Create a menu item for each audio track
  15242. *
  15243. * @param {AudioTrackMenuItem[]} [items=[]]
  15244. * An array of existing menu items to use.
  15245. *
  15246. * @return {AudioTrackMenuItem[]}
  15247. * An array of menu items
  15248. */
  15249. ;
  15250. _proto.createItems = function createItems(items) {
  15251. if (items === void 0) {
  15252. items = [];
  15253. }
  15254. // if there's only one audio track, there no point in showing it
  15255. this.hideThreshold_ = 1;
  15256. var tracks = this.player_.audioTracks();
  15257. for (var i = 0; i < tracks.length; i++) {
  15258. var track = tracks[i];
  15259. items.push(new AudioTrackMenuItem(this.player_, {
  15260. track: track,
  15261. // MenuItem is selectable
  15262. selectable: true,
  15263. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  15264. multiSelectable: false
  15265. }));
  15266. }
  15267. return items;
  15268. };
  15269. return AudioTrackButton;
  15270. }(TrackButton);
  15271. /**
  15272. * The text that should display over the `AudioTrackButton`s controls. Added for localization.
  15273. *
  15274. * @type {string}
  15275. * @private
  15276. */
  15277. AudioTrackButton.prototype.controlText_ = 'Audio Track';
  15278. Component$1.registerComponent('AudioTrackButton', AudioTrackButton);
  15279. /**
  15280. * The specific menu item type for selecting a playback rate.
  15281. *
  15282. * @extends MenuItem
  15283. */
  15284. var PlaybackRateMenuItem = /*#__PURE__*/function (_MenuItem) {
  15285. _inheritsLoose(PlaybackRateMenuItem, _MenuItem);
  15286. /**
  15287. * Creates an instance of this class.
  15288. *
  15289. * @param {Player} player
  15290. * The `Player` that this class should be attached to.
  15291. *
  15292. * @param {Object} [options]
  15293. * The key/value store of player options.
  15294. */
  15295. function PlaybackRateMenuItem(player, options) {
  15296. var _this;
  15297. var label = options.rate;
  15298. var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init.
  15299. options.label = label;
  15300. options.selected = rate === player.playbackRate();
  15301. options.selectable = true;
  15302. options.multiSelectable = false;
  15303. _this = _MenuItem.call(this, player, options) || this;
  15304. _this.label = label;
  15305. _this.rate = rate;
  15306. _this.on(player, 'ratechange', function (e) {
  15307. return _this.update(e);
  15308. });
  15309. return _this;
  15310. }
  15311. /**
  15312. * This gets called when an `PlaybackRateMenuItem` is "clicked". See
  15313. * {@link ClickableComponent} for more detailed information on what a click can be.
  15314. *
  15315. * @param {EventTarget~Event} [event]
  15316. * The `keydown`, `tap`, or `click` event that caused this function to be
  15317. * called.
  15318. *
  15319. * @listens tap
  15320. * @listens click
  15321. */
  15322. var _proto = PlaybackRateMenuItem.prototype;
  15323. _proto.handleClick = function handleClick(event) {
  15324. _MenuItem.prototype.handleClick.call(this);
  15325. this.player().playbackRate(this.rate);
  15326. }
  15327. /**
  15328. * Update the PlaybackRateMenuItem when the playbackrate changes.
  15329. *
  15330. * @param {EventTarget~Event} [event]
  15331. * The `ratechange` event that caused this function to run.
  15332. *
  15333. * @listens Player#ratechange
  15334. */
  15335. ;
  15336. _proto.update = function update(event) {
  15337. this.selected(this.player().playbackRate() === this.rate);
  15338. };
  15339. return PlaybackRateMenuItem;
  15340. }(MenuItem);
  15341. /**
  15342. * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
  15343. *
  15344. * @type {string}
  15345. * @private
  15346. */
  15347. PlaybackRateMenuItem.prototype.contentElType = 'button';
  15348. Component$1.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
  15349. /**
  15350. * The component for controlling the playback rate.
  15351. *
  15352. * @extends MenuButton
  15353. */
  15354. var PlaybackRateMenuButton = /*#__PURE__*/function (_MenuButton) {
  15355. _inheritsLoose(PlaybackRateMenuButton, _MenuButton);
  15356. /**
  15357. * Creates an instance of this class.
  15358. *
  15359. * @param {Player} player
  15360. * The `Player` that this class should be attached to.
  15361. *
  15362. * @param {Object} [options]
  15363. * The key/value store of player options.
  15364. */
  15365. function PlaybackRateMenuButton(player, options) {
  15366. var _this;
  15367. _this = _MenuButton.call(this, player, options) || this;
  15368. _this.menuButton_.el_.setAttribute('aria-describedby', _this.labelElId_);
  15369. _this.updateVisibility();
  15370. _this.updateLabel();
  15371. _this.on(player, 'loadstart', function (e) {
  15372. return _this.updateVisibility(e);
  15373. });
  15374. _this.on(player, 'ratechange', function (e) {
  15375. return _this.updateLabel(e);
  15376. });
  15377. _this.on(player, 'playbackrateschange', function (e) {
  15378. return _this.handlePlaybackRateschange(e);
  15379. });
  15380. return _this;
  15381. }
  15382. /**
  15383. * Create the `Component`'s DOM element
  15384. *
  15385. * @return {Element}
  15386. * The element that was created.
  15387. */
  15388. var _proto = PlaybackRateMenuButton.prototype;
  15389. _proto.createEl = function createEl$1() {
  15390. var el = _MenuButton.prototype.createEl.call(this);
  15391. this.labelElId_ = 'vjs-playback-rate-value-label-' + this.id_;
  15392. this.labelEl_ = createEl('div', {
  15393. className: 'vjs-playback-rate-value',
  15394. id: this.labelElId_,
  15395. textContent: '1x'
  15396. });
  15397. el.appendChild(this.labelEl_);
  15398. return el;
  15399. };
  15400. _proto.dispose = function dispose() {
  15401. this.labelEl_ = null;
  15402. _MenuButton.prototype.dispose.call(this);
  15403. }
  15404. /**
  15405. * Builds the default DOM `className`.
  15406. *
  15407. * @return {string}
  15408. * The DOM `className` for this object.
  15409. */
  15410. ;
  15411. _proto.buildCSSClass = function buildCSSClass() {
  15412. return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this);
  15413. };
  15414. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15415. return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this);
  15416. }
  15417. /**
  15418. * Create the list of menu items. Specific to each subclass.
  15419. *
  15420. */
  15421. ;
  15422. _proto.createItems = function createItems() {
  15423. var rates = this.playbackRates();
  15424. var items = [];
  15425. for (var i = rates.length - 1; i >= 0; i--) {
  15426. items.push(new PlaybackRateMenuItem(this.player(), {
  15427. rate: rates[i] + 'x'
  15428. }));
  15429. }
  15430. return items;
  15431. }
  15432. /**
  15433. * Updates ARIA accessibility attributes
  15434. */
  15435. ;
  15436. _proto.updateARIAAttributes = function updateARIAAttributes() {
  15437. // Current playback rate
  15438. this.el().setAttribute('aria-valuenow', this.player().playbackRate());
  15439. }
  15440. /**
  15441. * This gets called when an `PlaybackRateMenuButton` is "clicked". See
  15442. * {@link ClickableComponent} for more detailed information on what a click can be.
  15443. *
  15444. * @param {EventTarget~Event} [event]
  15445. * The `keydown`, `tap`, or `click` event that caused this function to be
  15446. * called.
  15447. *
  15448. * @listens tap
  15449. * @listens click
  15450. */
  15451. ;
  15452. _proto.handleClick = function handleClick(event) {
  15453. // select next rate option
  15454. var currentRate = this.player().playbackRate();
  15455. var rates = this.playbackRates();
  15456. var currentIndex = rates.indexOf(currentRate); // this get the next rate and it will select first one if the last one currently selected
  15457. var newIndex = (currentIndex + 1) % rates.length;
  15458. this.player().playbackRate(rates[newIndex]);
  15459. }
  15460. /**
  15461. * On playbackrateschange, update the menu to account for the new items.
  15462. *
  15463. * @listens Player#playbackrateschange
  15464. */
  15465. ;
  15466. _proto.handlePlaybackRateschange = function handlePlaybackRateschange(event) {
  15467. this.update();
  15468. }
  15469. /**
  15470. * Get possible playback rates
  15471. *
  15472. * @return {Array}
  15473. * All possible playback rates
  15474. */
  15475. ;
  15476. _proto.playbackRates = function playbackRates() {
  15477. var player = this.player();
  15478. return player.playbackRates && player.playbackRates() || [];
  15479. }
  15480. /**
  15481. * Get whether playback rates is supported by the tech
  15482. * and an array of playback rates exists
  15483. *
  15484. * @return {boolean}
  15485. * Whether changing playback rate is supported
  15486. */
  15487. ;
  15488. _proto.playbackRateSupported = function playbackRateSupported() {
  15489. return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
  15490. }
  15491. /**
  15492. * Hide playback rate controls when they're no playback rate options to select
  15493. *
  15494. * @param {EventTarget~Event} [event]
  15495. * The event that caused this function to run.
  15496. *
  15497. * @listens Player#loadstart
  15498. */
  15499. ;
  15500. _proto.updateVisibility = function updateVisibility(event) {
  15501. if (this.playbackRateSupported()) {
  15502. this.removeClass('vjs-hidden');
  15503. } else {
  15504. this.addClass('vjs-hidden');
  15505. }
  15506. }
  15507. /**
  15508. * Update button label when rate changed
  15509. *
  15510. * @param {EventTarget~Event} [event]
  15511. * The event that caused this function to run.
  15512. *
  15513. * @listens Player#ratechange
  15514. */
  15515. ;
  15516. _proto.updateLabel = function updateLabel(event) {
  15517. if (this.playbackRateSupported()) {
  15518. this.labelEl_.textContent = this.player().playbackRate() + 'x';
  15519. }
  15520. };
  15521. return PlaybackRateMenuButton;
  15522. }(MenuButton);
  15523. /**
  15524. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  15525. *
  15526. * @type {string}
  15527. * @private
  15528. */
  15529. PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
  15530. Component$1.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
  15531. /**
  15532. * Just an empty spacer element that can be used as an append point for plugins, etc.
  15533. * Also can be used to create space between elements when necessary.
  15534. *
  15535. * @extends Component
  15536. */
  15537. var Spacer = /*#__PURE__*/function (_Component) {
  15538. _inheritsLoose(Spacer, _Component);
  15539. function Spacer() {
  15540. return _Component.apply(this, arguments) || this;
  15541. }
  15542. var _proto = Spacer.prototype;
  15543. /**
  15544. * Builds the default DOM `className`.
  15545. *
  15546. * @return {string}
  15547. * The DOM `className` for this object.
  15548. */
  15549. _proto.buildCSSClass = function buildCSSClass() {
  15550. return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this);
  15551. }
  15552. /**
  15553. * Create the `Component`'s DOM element
  15554. *
  15555. * @return {Element}
  15556. * The element that was created.
  15557. */
  15558. ;
  15559. _proto.createEl = function createEl(tag, props, attributes) {
  15560. if (tag === void 0) {
  15561. tag = 'div';
  15562. }
  15563. if (props === void 0) {
  15564. props = {};
  15565. }
  15566. if (attributes === void 0) {
  15567. attributes = {};
  15568. }
  15569. if (!props.className) {
  15570. props.className = this.buildCSSClass();
  15571. }
  15572. return _Component.prototype.createEl.call(this, tag, props, attributes);
  15573. };
  15574. return Spacer;
  15575. }(Component$1);
  15576. Component$1.registerComponent('Spacer', Spacer);
  15577. /**
  15578. * Spacer specifically meant to be used as an insertion point for new plugins, etc.
  15579. *
  15580. * @extends Spacer
  15581. */
  15582. var CustomControlSpacer = /*#__PURE__*/function (_Spacer) {
  15583. _inheritsLoose(CustomControlSpacer, _Spacer);
  15584. function CustomControlSpacer() {
  15585. return _Spacer.apply(this, arguments) || this;
  15586. }
  15587. var _proto = CustomControlSpacer.prototype;
  15588. /**
  15589. * Builds the default DOM `className`.
  15590. *
  15591. * @return {string}
  15592. * The DOM `className` for this object.
  15593. */
  15594. _proto.buildCSSClass = function buildCSSClass() {
  15595. return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this);
  15596. }
  15597. /**
  15598. * Create the `Component`'s DOM element
  15599. *
  15600. * @return {Element}
  15601. * The element that was created.
  15602. */
  15603. ;
  15604. _proto.createEl = function createEl() {
  15605. return _Spacer.prototype.createEl.call(this, 'div', {
  15606. className: this.buildCSSClass(),
  15607. // No-flex/table-cell mode requires there be some content
  15608. // in the cell to fill the remaining space of the table.
  15609. textContent: "\xA0"
  15610. });
  15611. };
  15612. return CustomControlSpacer;
  15613. }(Spacer);
  15614. Component$1.registerComponent('CustomControlSpacer', CustomControlSpacer);
  15615. /**
  15616. * Container of main controls.
  15617. *
  15618. * @extends Component
  15619. */
  15620. var ControlBar = /*#__PURE__*/function (_Component) {
  15621. _inheritsLoose(ControlBar, _Component);
  15622. function ControlBar() {
  15623. return _Component.apply(this, arguments) || this;
  15624. }
  15625. var _proto = ControlBar.prototype;
  15626. /**
  15627. * Create the `Component`'s DOM element
  15628. *
  15629. * @return {Element}
  15630. * The element that was created.
  15631. */
  15632. _proto.createEl = function createEl() {
  15633. return _Component.prototype.createEl.call(this, 'div', {
  15634. className: 'vjs-control-bar',
  15635. dir: 'ltr'
  15636. });
  15637. };
  15638. return ControlBar;
  15639. }(Component$1);
  15640. /**
  15641. * Default options for `ControlBar`
  15642. *
  15643. * @type {Object}
  15644. * @private
  15645. */
  15646. ControlBar.prototype.options_ = {
  15647. children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
  15648. };
  15649. if ('exitPictureInPicture' in document) {
  15650. ControlBar.prototype.options_.children.splice(ControlBar.prototype.options_.children.length - 1, 0, 'pictureInPictureToggle');
  15651. }
  15652. Component$1.registerComponent('ControlBar', ControlBar);
  15653. /**
  15654. * A display that indicates an error has occurred. This means that the video
  15655. * is unplayable.
  15656. *
  15657. * @extends ModalDialog
  15658. */
  15659. var ErrorDisplay = /*#__PURE__*/function (_ModalDialog) {
  15660. _inheritsLoose(ErrorDisplay, _ModalDialog);
  15661. /**
  15662. * Creates an instance of this class.
  15663. *
  15664. * @param {Player} player
  15665. * The `Player` that this class should be attached to.
  15666. *
  15667. * @param {Object} [options]
  15668. * The key/value store of player options.
  15669. */
  15670. function ErrorDisplay(player, options) {
  15671. var _this;
  15672. _this = _ModalDialog.call(this, player, options) || this;
  15673. _this.on(player, 'error', function (e) {
  15674. return _this.open(e);
  15675. });
  15676. return _this;
  15677. }
  15678. /**
  15679. * Builds the default DOM `className`.
  15680. *
  15681. * @return {string}
  15682. * The DOM `className` for this object.
  15683. *
  15684. * @deprecated Since version 5.
  15685. */
  15686. var _proto = ErrorDisplay.prototype;
  15687. _proto.buildCSSClass = function buildCSSClass() {
  15688. return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this);
  15689. }
  15690. /**
  15691. * Gets the localized error message based on the `Player`s error.
  15692. *
  15693. * @return {string}
  15694. * The `Player`s error message localized or an empty string.
  15695. */
  15696. ;
  15697. _proto.content = function content() {
  15698. var error = this.player().error();
  15699. return error ? this.localize(error.message) : '';
  15700. };
  15701. return ErrorDisplay;
  15702. }(ModalDialog);
  15703. /**
  15704. * The default options for an `ErrorDisplay`.
  15705. *
  15706. * @private
  15707. */
  15708. ErrorDisplay.prototype.options_ = _extends({}, ModalDialog.prototype.options_, {
  15709. pauseOnOpen: false,
  15710. fillAlways: true,
  15711. temporary: false,
  15712. uncloseable: true
  15713. });
  15714. Component$1.registerComponent('ErrorDisplay', ErrorDisplay);
  15715. var LOCAL_STORAGE_KEY$1 = 'vjs-text-track-settings';
  15716. var COLOR_BLACK = ['#000', 'Black'];
  15717. var COLOR_BLUE = ['#00F', 'Blue'];
  15718. var COLOR_CYAN = ['#0FF', 'Cyan'];
  15719. var COLOR_GREEN = ['#0F0', 'Green'];
  15720. var COLOR_MAGENTA = ['#F0F', 'Magenta'];
  15721. var COLOR_RED = ['#F00', 'Red'];
  15722. var COLOR_WHITE = ['#FFF', 'White'];
  15723. var COLOR_YELLOW = ['#FF0', 'Yellow'];
  15724. var OPACITY_OPAQUE = ['1', 'Opaque'];
  15725. var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
  15726. var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component.
  15727. //
  15728. // Possible keys include:
  15729. //
  15730. // `default`:
  15731. // The default option index. Only needs to be provided if not zero.
  15732. // `parser`:
  15733. // A function which is used to parse the value from the selected option in
  15734. // a customized way.
  15735. // `selector`:
  15736. // The selector used to find the associated <select> element.
  15737. var selectConfigs = {
  15738. backgroundColor: {
  15739. selector: '.vjs-bg-color > select',
  15740. id: 'captions-background-color-%s',
  15741. label: 'Color',
  15742. options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  15743. },
  15744. backgroundOpacity: {
  15745. selector: '.vjs-bg-opacity > select',
  15746. id: 'captions-background-opacity-%s',
  15747. label: 'Transparency',
  15748. options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
  15749. },
  15750. color: {
  15751. selector: '.vjs-fg-color > select',
  15752. id: 'captions-foreground-color-%s',
  15753. label: 'Color',
  15754. options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  15755. },
  15756. edgeStyle: {
  15757. selector: '.vjs-edge-style > select',
  15758. id: '%s',
  15759. label: 'Text Edge Style',
  15760. options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
  15761. },
  15762. fontFamily: {
  15763. selector: '.vjs-font-family > select',
  15764. id: 'captions-font-family-%s',
  15765. label: 'Font Family',
  15766. options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
  15767. },
  15768. fontPercent: {
  15769. selector: '.vjs-font-percent > select',
  15770. id: 'captions-font-size-%s',
  15771. label: 'Font Size',
  15772. options: [['0.50', '50%'], ['0.75', '75%'], ['1.00', '100%'], ['1.25', '125%'], ['1.50', '150%'], ['1.75', '175%'], ['2.00', '200%'], ['3.00', '300%'], ['4.00', '400%']],
  15773. "default": 2,
  15774. parser: function parser(v) {
  15775. return v === '1.00' ? null : Number(v);
  15776. }
  15777. },
  15778. textOpacity: {
  15779. selector: '.vjs-text-opacity > select',
  15780. id: 'captions-foreground-opacity-%s',
  15781. label: 'Transparency',
  15782. options: [OPACITY_OPAQUE, OPACITY_SEMI]
  15783. },
  15784. // Options for this object are defined below.
  15785. windowColor: {
  15786. selector: '.vjs-window-color > select',
  15787. id: 'captions-window-color-%s',
  15788. label: 'Color'
  15789. },
  15790. // Options for this object are defined below.
  15791. windowOpacity: {
  15792. selector: '.vjs-window-opacity > select',
  15793. id: 'captions-window-opacity-%s',
  15794. label: 'Transparency',
  15795. options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
  15796. }
  15797. };
  15798. selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
  15799. /**
  15800. * Get the actual value of an option.
  15801. *
  15802. * @param {string} value
  15803. * The value to get
  15804. *
  15805. * @param {Function} [parser]
  15806. * Optional function to adjust the value.
  15807. *
  15808. * @return {Mixed}
  15809. * - Will be `undefined` if no value exists
  15810. * - Will be `undefined` if the given value is "none".
  15811. * - Will be the actual value otherwise.
  15812. *
  15813. * @private
  15814. */
  15815. function parseOptionValue(value, parser) {
  15816. if (parser) {
  15817. value = parser(value);
  15818. }
  15819. if (value && value !== 'none') {
  15820. return value;
  15821. }
  15822. }
  15823. /**
  15824. * Gets the value of the selected <option> element within a <select> element.
  15825. *
  15826. * @param {Element} el
  15827. * the element to look in
  15828. *
  15829. * @param {Function} [parser]
  15830. * Optional function to adjust the value.
  15831. *
  15832. * @return {Mixed}
  15833. * - Will be `undefined` if no value exists
  15834. * - Will be `undefined` if the given value is "none".
  15835. * - Will be the actual value otherwise.
  15836. *
  15837. * @private
  15838. */
  15839. function getSelectedOptionValue(el, parser) {
  15840. var value = el.options[el.options.selectedIndex].value;
  15841. return parseOptionValue(value, parser);
  15842. }
  15843. /**
  15844. * Sets the selected <option> element within a <select> element based on a
  15845. * given value.
  15846. *
  15847. * @param {Element} el
  15848. * The element to look in.
  15849. *
  15850. * @param {string} value
  15851. * the property to look on.
  15852. *
  15853. * @param {Function} [parser]
  15854. * Optional function to adjust the value before comparing.
  15855. *
  15856. * @private
  15857. */
  15858. function setSelectedOption(el, value, parser) {
  15859. if (!value) {
  15860. return;
  15861. }
  15862. for (var i = 0; i < el.options.length; i++) {
  15863. if (parseOptionValue(el.options[i].value, parser) === value) {
  15864. el.selectedIndex = i;
  15865. break;
  15866. }
  15867. }
  15868. }
  15869. /**
  15870. * Manipulate Text Tracks settings.
  15871. *
  15872. * @extends ModalDialog
  15873. */
  15874. var TextTrackSettings = /*#__PURE__*/function (_ModalDialog) {
  15875. _inheritsLoose(TextTrackSettings, _ModalDialog);
  15876. /**
  15877. * Creates an instance of this class.
  15878. *
  15879. * @param {Player} player
  15880. * The `Player` that this class should be attached to.
  15881. *
  15882. * @param {Object} [options]
  15883. * The key/value store of player options.
  15884. */
  15885. function TextTrackSettings(player, options) {
  15886. var _this;
  15887. options.temporary = false;
  15888. _this = _ModalDialog.call(this, player, options) || this;
  15889. _this.updateDisplay = _this.updateDisplay.bind(_assertThisInitialized(_this)); // fill the modal and pretend we have opened it
  15890. _this.fill();
  15891. _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
  15892. _this.endDialog = createEl('p', {
  15893. className: 'vjs-control-text',
  15894. textContent: _this.localize('End of dialog window.')
  15895. });
  15896. _this.el().appendChild(_this.endDialog);
  15897. _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options
  15898. if (options.persistTextTrackSettings === undefined) {
  15899. _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
  15900. }
  15901. _this.on(_this.$('.vjs-done-button'), 'click', function () {
  15902. _this.saveSettings();
  15903. _this.close();
  15904. });
  15905. _this.on(_this.$('.vjs-default-button'), 'click', function () {
  15906. _this.setDefaults();
  15907. _this.updateDisplay();
  15908. });
  15909. each(selectConfigs, function (config) {
  15910. _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
  15911. });
  15912. if (_this.options_.persistTextTrackSettings) {
  15913. _this.restoreSettings();
  15914. }
  15915. return _this;
  15916. }
  15917. var _proto = TextTrackSettings.prototype;
  15918. _proto.dispose = function dispose() {
  15919. this.endDialog = null;
  15920. _ModalDialog.prototype.dispose.call(this);
  15921. }
  15922. /**
  15923. * Create a <select> element with configured options.
  15924. *
  15925. * @param {string} key
  15926. * Configuration key to use during creation.
  15927. *
  15928. * @return {string}
  15929. * An HTML string.
  15930. *
  15931. * @private
  15932. */
  15933. ;
  15934. _proto.createElSelect_ = function createElSelect_(key, legendId, type) {
  15935. var _this2 = this;
  15936. if (legendId === void 0) {
  15937. legendId = '';
  15938. }
  15939. if (type === void 0) {
  15940. type = 'label';
  15941. }
  15942. var config = selectConfigs[key];
  15943. var id = config.id.replace('%s', this.id_);
  15944. var selectLabelledbyIds = [legendId, id].join(' ').trim();
  15945. return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) {
  15946. var optionId = id + '-' + o[1].replace(/\W+/g, '');
  15947. return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join('');
  15948. })).concat('</select>').join('');
  15949. }
  15950. /**
  15951. * Create foreground color element for the component
  15952. *
  15953. * @return {string}
  15954. * An HTML string.
  15955. *
  15956. * @private
  15957. */
  15958. ;
  15959. _proto.createElFgColor_ = function createElFgColor_() {
  15960. var legendId = "captions-text-legend-" + this.id_;
  15961. return ['<fieldset class="vjs-fg-color vjs-track-setting">', "<legend id=\"" + legendId + "\">", this.localize('Text'), '</legend>', this.createElSelect_('color', legendId), '<span class="vjs-text-opacity vjs-opacity">', this.createElSelect_('textOpacity', legendId), '</span>', '</fieldset>'].join('');
  15962. }
  15963. /**
  15964. * Create background color element for the component
  15965. *
  15966. * @return {string}
  15967. * An HTML string.
  15968. *
  15969. * @private
  15970. */
  15971. ;
  15972. _proto.createElBgColor_ = function createElBgColor_() {
  15973. var legendId = "captions-background-" + this.id_;
  15974. return ['<fieldset class="vjs-bg-color vjs-track-setting">', "<legend id=\"" + legendId + "\">", this.localize('Background'), '</legend>', this.createElSelect_('backgroundColor', legendId), '<span class="vjs-bg-opacity vjs-opacity">', this.createElSelect_('backgroundOpacity', legendId), '</span>', '</fieldset>'].join('');
  15975. }
  15976. /**
  15977. * Create window color element for the component
  15978. *
  15979. * @return {string}
  15980. * An HTML string.
  15981. *
  15982. * @private
  15983. */
  15984. ;
  15985. _proto.createElWinColor_ = function createElWinColor_() {
  15986. var legendId = "captions-window-" + this.id_;
  15987. return ['<fieldset class="vjs-window-color vjs-track-setting">', "<legend id=\"" + legendId + "\">", this.localize('Window'), '</legend>', this.createElSelect_('windowColor', legendId), '<span class="vjs-window-opacity vjs-opacity">', this.createElSelect_('windowOpacity', legendId), '</span>', '</fieldset>'].join('');
  15988. }
  15989. /**
  15990. * Create color elements for the component
  15991. *
  15992. * @return {Element}
  15993. * The element that was created
  15994. *
  15995. * @private
  15996. */
  15997. ;
  15998. _proto.createElColors_ = function createElColors_() {
  15999. return createEl('div', {
  16000. className: 'vjs-track-settings-colors',
  16001. innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
  16002. });
  16003. }
  16004. /**
  16005. * Create font elements for the component
  16006. *
  16007. * @return {Element}
  16008. * The element that was created.
  16009. *
  16010. * @private
  16011. */
  16012. ;
  16013. _proto.createElFont_ = function createElFont_() {
  16014. return createEl('div', {
  16015. className: 'vjs-track-settings-font',
  16016. innerHTML: ['<fieldset class="vjs-font-percent vjs-track-setting">', this.createElSelect_('fontPercent', '', 'legend'), '</fieldset>', '<fieldset class="vjs-edge-style vjs-track-setting">', this.createElSelect_('edgeStyle', '', 'legend'), '</fieldset>', '<fieldset class="vjs-font-family vjs-track-setting">', this.createElSelect_('fontFamily', '', 'legend'), '</fieldset>'].join('')
  16017. });
  16018. }
  16019. /**
  16020. * Create controls for the component
  16021. *
  16022. * @return {Element}
  16023. * The element that was created.
  16024. *
  16025. * @private
  16026. */
  16027. ;
  16028. _proto.createElControls_ = function createElControls_() {
  16029. var defaultsDescription = this.localize('restore all settings to the default values');
  16030. return createEl('div', {
  16031. className: 'vjs-track-settings-controls',
  16032. innerHTML: ["<button type=\"button\" class=\"vjs-default-button\" title=\"" + defaultsDescription + "\">", this.localize('Reset'), "<span class=\"vjs-control-text\"> " + defaultsDescription + "</span>", '</button>', "<button type=\"button\" class=\"vjs-done-button\">" + this.localize('Done') + "</button>"].join('')
  16033. });
  16034. };
  16035. _proto.content = function content() {
  16036. return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
  16037. };
  16038. _proto.label = function label() {
  16039. return this.localize('Caption Settings Dialog');
  16040. };
  16041. _proto.description = function description() {
  16042. return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
  16043. };
  16044. _proto.buildCSSClass = function buildCSSClass() {
  16045. return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
  16046. }
  16047. /**
  16048. * Gets an object of text track settings (or null).
  16049. *
  16050. * @return {Object}
  16051. * An object with config values parsed from the DOM or localStorage.
  16052. */
  16053. ;
  16054. _proto.getValues = function getValues() {
  16055. var _this3 = this;
  16056. return reduce(selectConfigs, function (accum, config, key) {
  16057. var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
  16058. if (value !== undefined) {
  16059. accum[key] = value;
  16060. }
  16061. return accum;
  16062. }, {});
  16063. }
  16064. /**
  16065. * Sets text track settings from an object of values.
  16066. *
  16067. * @param {Object} values
  16068. * An object with config values parsed from the DOM or localStorage.
  16069. */
  16070. ;
  16071. _proto.setValues = function setValues(values) {
  16072. var _this4 = this;
  16073. each(selectConfigs, function (config, key) {
  16074. setSelectedOption(_this4.$(config.selector), values[key], config.parser);
  16075. });
  16076. }
  16077. /**
  16078. * Sets all `<select>` elements to their default values.
  16079. */
  16080. ;
  16081. _proto.setDefaults = function setDefaults() {
  16082. var _this5 = this;
  16083. each(selectConfigs, function (config) {
  16084. var index = config.hasOwnProperty('default') ? config["default"] : 0;
  16085. _this5.$(config.selector).selectedIndex = index;
  16086. });
  16087. }
  16088. /**
  16089. * Restore texttrack settings from localStorage
  16090. */
  16091. ;
  16092. _proto.restoreSettings = function restoreSettings() {
  16093. var values;
  16094. try {
  16095. values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY$1));
  16096. } catch (err) {
  16097. log$1.warn(err);
  16098. }
  16099. if (values) {
  16100. this.setValues(values);
  16101. }
  16102. }
  16103. /**
  16104. * Save text track settings to localStorage
  16105. */
  16106. ;
  16107. _proto.saveSettings = function saveSettings() {
  16108. if (!this.options_.persistTextTrackSettings) {
  16109. return;
  16110. }
  16111. var values = this.getValues();
  16112. try {
  16113. if (Object.keys(values).length) {
  16114. window$1.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(values));
  16115. } else {
  16116. window$1.localStorage.removeItem(LOCAL_STORAGE_KEY$1);
  16117. }
  16118. } catch (err) {
  16119. log$1.warn(err);
  16120. }
  16121. }
  16122. /**
  16123. * Update display of text track settings
  16124. */
  16125. ;
  16126. _proto.updateDisplay = function updateDisplay() {
  16127. var ttDisplay = this.player_.getChild('textTrackDisplay');
  16128. if (ttDisplay) {
  16129. ttDisplay.updateDisplay();
  16130. }
  16131. }
  16132. /**
  16133. * conditionally blur the element and refocus the captions button
  16134. *
  16135. * @private
  16136. */
  16137. ;
  16138. _proto.conditionalBlur_ = function conditionalBlur_() {
  16139. this.previouslyActiveEl_ = null;
  16140. var cb = this.player_.controlBar;
  16141. var subsCapsBtn = cb && cb.subsCapsButton;
  16142. var ccBtn = cb && cb.captionsButton;
  16143. if (subsCapsBtn) {
  16144. subsCapsBtn.focus();
  16145. } else if (ccBtn) {
  16146. ccBtn.focus();
  16147. }
  16148. };
  16149. return TextTrackSettings;
  16150. }(ModalDialog);
  16151. Component$1.registerComponent('TextTrackSettings', TextTrackSettings);
  16152. /**
  16153. * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
  16154. *
  16155. * It'll either create an iframe and use a debounced resize handler on it or use the new {@link https://wicg.github.io/ResizeObserver/|ResizeObserver}.
  16156. *
  16157. * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
  16158. * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
  16159. * @example <caption>How to disable the resize manager</caption>
  16160. * const player = videojs('#vid', {
  16161. * resizeManager: false
  16162. * });
  16163. *
  16164. * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
  16165. *
  16166. * @extends Component
  16167. */
  16168. var ResizeManager = /*#__PURE__*/function (_Component) {
  16169. _inheritsLoose(ResizeManager, _Component);
  16170. /**
  16171. * Create the ResizeManager.
  16172. *
  16173. * @param {Object} player
  16174. * The `Player` that this class should be attached to.
  16175. *
  16176. * @param {Object} [options]
  16177. * The key/value store of ResizeManager options.
  16178. *
  16179. * @param {Object} [options.ResizeObserver]
  16180. * A polyfill for ResizeObserver can be passed in here.
  16181. * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
  16182. */
  16183. function ResizeManager(player, options) {
  16184. var _this;
  16185. var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
  16186. if (options.ResizeObserver === null) {
  16187. RESIZE_OBSERVER_AVAILABLE = false;
  16188. } // Only create an element when ResizeObserver isn't available
  16189. var options_ = mergeOptions$3({
  16190. createEl: !RESIZE_OBSERVER_AVAILABLE,
  16191. reportTouchActivity: false
  16192. }, options);
  16193. _this = _Component.call(this, player, options_) || this;
  16194. _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver;
  16195. _this.loadListener_ = null;
  16196. _this.resizeObserver_ = null;
  16197. _this.debouncedHandler_ = debounce(function () {
  16198. _this.resizeHandler();
  16199. }, 100, false, _assertThisInitialized(_this));
  16200. if (RESIZE_OBSERVER_AVAILABLE) {
  16201. _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
  16202. _this.resizeObserver_.observe(player.el());
  16203. } else {
  16204. _this.loadListener_ = function () {
  16205. if (!_this.el_ || !_this.el_.contentWindow) {
  16206. return;
  16207. }
  16208. var debouncedHandler_ = _this.debouncedHandler_;
  16209. var unloadListener_ = _this.unloadListener_ = function () {
  16210. off(this, 'resize', debouncedHandler_);
  16211. off(this, 'unload', unloadListener_);
  16212. unloadListener_ = null;
  16213. }; // safari and edge can unload the iframe before resizemanager dispose
  16214. // we have to dispose of event handlers correctly before that happens
  16215. on(_this.el_.contentWindow, 'unload', unloadListener_);
  16216. on(_this.el_.contentWindow, 'resize', debouncedHandler_);
  16217. };
  16218. _this.one('load', _this.loadListener_);
  16219. }
  16220. return _this;
  16221. }
  16222. var _proto = ResizeManager.prototype;
  16223. _proto.createEl = function createEl() {
  16224. return _Component.prototype.createEl.call(this, 'iframe', {
  16225. className: 'vjs-resize-manager',
  16226. tabIndex: -1,
  16227. title: this.localize('No content')
  16228. }, {
  16229. 'aria-hidden': 'true'
  16230. });
  16231. }
  16232. /**
  16233. * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
  16234. *
  16235. * @fires Player#playerresize
  16236. */
  16237. ;
  16238. _proto.resizeHandler = function resizeHandler() {
  16239. /**
  16240. * Called when the player size has changed
  16241. *
  16242. * @event Player#playerresize
  16243. * @type {EventTarget~Event}
  16244. */
  16245. // make sure player is still around to trigger
  16246. // prevents this from causing an error after dispose
  16247. if (!this.player_ || !this.player_.trigger) {
  16248. return;
  16249. }
  16250. this.player_.trigger('playerresize');
  16251. };
  16252. _proto.dispose = function dispose() {
  16253. if (this.debouncedHandler_) {
  16254. this.debouncedHandler_.cancel();
  16255. }
  16256. if (this.resizeObserver_) {
  16257. if (this.player_.el()) {
  16258. this.resizeObserver_.unobserve(this.player_.el());
  16259. }
  16260. this.resizeObserver_.disconnect();
  16261. }
  16262. if (this.loadListener_) {
  16263. this.off('load', this.loadListener_);
  16264. }
  16265. if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
  16266. this.unloadListener_.call(this.el_.contentWindow);
  16267. }
  16268. this.ResizeObserver = null;
  16269. this.resizeObserver = null;
  16270. this.debouncedHandler_ = null;
  16271. this.loadListener_ = null;
  16272. _Component.prototype.dispose.call(this);
  16273. };
  16274. return ResizeManager;
  16275. }(Component$1);
  16276. Component$1.registerComponent('ResizeManager', ResizeManager);
  16277. var defaults = {
  16278. trackingThreshold: 20,
  16279. liveTolerance: 15
  16280. };
  16281. /*
  16282. track when we are at the live edge, and other helpers for live playback */
  16283. /**
  16284. * A class for checking live current time and determining when the player
  16285. * is at or behind the live edge.
  16286. */
  16287. var LiveTracker = /*#__PURE__*/function (_Component) {
  16288. _inheritsLoose(LiveTracker, _Component);
  16289. /**
  16290. * Creates an instance of this class.
  16291. *
  16292. * @param {Player} player
  16293. * The `Player` that this class should be attached to.
  16294. *
  16295. * @param {Object} [options]
  16296. * The key/value store of player options.
  16297. *
  16298. * @param {number} [options.trackingThreshold=20]
  16299. * Number of seconds of live window (seekableEnd - seekableStart) that
  16300. * media needs to have before the liveui will be shown.
  16301. *
  16302. * @param {number} [options.liveTolerance=15]
  16303. * Number of seconds behind live that we have to be
  16304. * before we will be considered non-live. Note that this will only
  16305. * be used when playing at the live edge. This allows large seekable end
  16306. * changes to not effect wether we are live or not.
  16307. */
  16308. function LiveTracker(player, options) {
  16309. var _this;
  16310. // LiveTracker does not need an element
  16311. var options_ = mergeOptions$3(defaults, options, {
  16312. createEl: false
  16313. });
  16314. _this = _Component.call(this, player, options_) || this;
  16315. _this.handleVisibilityChange_ = function (e) {
  16316. return _this.handleVisibilityChange(e);
  16317. };
  16318. _this.trackLiveHandler_ = function () {
  16319. return _this.trackLive_();
  16320. };
  16321. _this.handlePlay_ = function (e) {
  16322. return _this.handlePlay(e);
  16323. };
  16324. _this.handleFirstTimeupdate_ = function (e) {
  16325. return _this.handleFirstTimeupdate(e);
  16326. };
  16327. _this.handleSeeked_ = function (e) {
  16328. return _this.handleSeeked(e);
  16329. };
  16330. _this.seekToLiveEdge_ = function (e) {
  16331. return _this.seekToLiveEdge(e);
  16332. };
  16333. _this.reset_();
  16334. _this.on(_this.player_, 'durationchange', function (e) {
  16335. return _this.handleDurationchange(e);
  16336. }); // we should try to toggle tracking on canplay as native playback engines, like Safari
  16337. // may not have the proper values for things like seekableEnd until then
  16338. _this.on(_this.player_, 'canplay', function () {
  16339. return _this.toggleTracking();
  16340. }); // we don't need to track live playback if the document is hidden,
  16341. // also, tracking when the document is hidden can
  16342. // cause the CPU to spike and eventually crash the page on IE11.
  16343. if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
  16344. _this.on(document, 'visibilitychange', _this.handleVisibilityChange_);
  16345. }
  16346. return _this;
  16347. }
  16348. /**
  16349. * toggle tracking based on document visiblility
  16350. */
  16351. var _proto = LiveTracker.prototype;
  16352. _proto.handleVisibilityChange = function handleVisibilityChange() {
  16353. if (this.player_.duration() !== Infinity) {
  16354. return;
  16355. }
  16356. if (document.hidden) {
  16357. this.stopTracking();
  16358. } else {
  16359. this.startTracking();
  16360. }
  16361. }
  16362. /**
  16363. * all the functionality for tracking when seek end changes
  16364. * and for tracking how far past seek end we should be
  16365. */
  16366. ;
  16367. _proto.trackLive_ = function trackLive_() {
  16368. var seekable = this.player_.seekable(); // skip undefined seekable
  16369. if (!seekable || !seekable.length) {
  16370. return;
  16371. }
  16372. var newTime = Number(window$1.performance.now().toFixed(4));
  16373. var deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000;
  16374. this.lastTime_ = newTime;
  16375. this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime;
  16376. var liveCurrentTime = this.liveCurrentTime();
  16377. var currentTime = this.player_.currentTime(); // we are behind live if any are true
  16378. // 1. the player is paused
  16379. // 2. the user seeked to a location 2 seconds away from live
  16380. // 3. the difference between live and current time is greater
  16381. // liveTolerance which defaults to 15s
  16382. var isBehind = this.player_.paused() || this.seekedBehindLive_ || Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance; // we cannot be behind if
  16383. // 1. until we have not seen a timeupdate yet
  16384. // 2. liveCurrentTime is Infinity, which happens on Android and Native Safari
  16385. if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) {
  16386. isBehind = false;
  16387. }
  16388. if (isBehind !== this.behindLiveEdge_) {
  16389. this.behindLiveEdge_ = isBehind;
  16390. this.trigger('liveedgechange');
  16391. }
  16392. }
  16393. /**
  16394. * handle a durationchange event on the player
  16395. * and start/stop tracking accordingly.
  16396. */
  16397. ;
  16398. _proto.handleDurationchange = function handleDurationchange() {
  16399. this.toggleTracking();
  16400. }
  16401. /**
  16402. * start/stop tracking
  16403. */
  16404. ;
  16405. _proto.toggleTracking = function toggleTracking() {
  16406. if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) {
  16407. if (this.player_.options_.liveui) {
  16408. this.player_.addClass('vjs-liveui');
  16409. }
  16410. this.startTracking();
  16411. } else {
  16412. this.player_.removeClass('vjs-liveui');
  16413. this.stopTracking();
  16414. }
  16415. }
  16416. /**
  16417. * start tracking live playback
  16418. */
  16419. ;
  16420. _proto.startTracking = function startTracking() {
  16421. if (this.isTracking()) {
  16422. return;
  16423. } // If we haven't seen a timeupdate, we need to check whether playback
  16424. // began before this component started tracking. This can happen commonly
  16425. // when using autoplay.
  16426. if (!this.timeupdateSeen_) {
  16427. this.timeupdateSeen_ = this.player_.hasStarted();
  16428. }
  16429. this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, UPDATE_REFRESH_INTERVAL);
  16430. this.trackLive_();
  16431. this.on(this.player_, ['play', 'pause'], this.trackLiveHandler_);
  16432. if (!this.timeupdateSeen_) {
  16433. this.one(this.player_, 'play', this.handlePlay_);
  16434. this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
  16435. } else {
  16436. this.on(this.player_, 'seeked', this.handleSeeked_);
  16437. }
  16438. }
  16439. /**
  16440. * handle the first timeupdate on the player if it wasn't already playing
  16441. * when live tracker started tracking.
  16442. */
  16443. ;
  16444. _proto.handleFirstTimeupdate = function handleFirstTimeupdate() {
  16445. this.timeupdateSeen_ = true;
  16446. this.on(this.player_, 'seeked', this.handleSeeked_);
  16447. }
  16448. /**
  16449. * Keep track of what time a seek starts, and listen for seeked
  16450. * to find where a seek ends.
  16451. */
  16452. ;
  16453. _proto.handleSeeked = function handleSeeked() {
  16454. var timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime());
  16455. this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2;
  16456. this.nextSeekedFromUser_ = false;
  16457. this.trackLive_();
  16458. }
  16459. /**
  16460. * handle the first play on the player, and make sure that we seek
  16461. * right to the live edge.
  16462. */
  16463. ;
  16464. _proto.handlePlay = function handlePlay() {
  16465. this.one(this.player_, 'timeupdate', this.seekToLiveEdge_);
  16466. }
  16467. /**
  16468. * Stop tracking, and set all internal variables to
  16469. * their initial value.
  16470. */
  16471. ;
  16472. _proto.reset_ = function reset_() {
  16473. this.lastTime_ = -1;
  16474. this.pastSeekEnd_ = 0;
  16475. this.lastSeekEnd_ = -1;
  16476. this.behindLiveEdge_ = true;
  16477. this.timeupdateSeen_ = false;
  16478. this.seekedBehindLive_ = false;
  16479. this.nextSeekedFromUser_ = false;
  16480. this.clearInterval(this.trackingInterval_);
  16481. this.trackingInterval_ = null;
  16482. this.off(this.player_, ['play', 'pause'], this.trackLiveHandler_);
  16483. this.off(this.player_, 'seeked', this.handleSeeked_);
  16484. this.off(this.player_, 'play', this.handlePlay_);
  16485. this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
  16486. this.off(this.player_, 'timeupdate', this.seekToLiveEdge_);
  16487. }
  16488. /**
  16489. * The next seeked event is from the user. Meaning that any seek
  16490. * > 2s behind live will be considered behind live for real and
  16491. * liveTolerance will be ignored.
  16492. */
  16493. ;
  16494. _proto.nextSeekedFromUser = function nextSeekedFromUser() {
  16495. this.nextSeekedFromUser_ = true;
  16496. }
  16497. /**
  16498. * stop tracking live playback
  16499. */
  16500. ;
  16501. _proto.stopTracking = function stopTracking() {
  16502. if (!this.isTracking()) {
  16503. return;
  16504. }
  16505. this.reset_();
  16506. this.trigger('liveedgechange');
  16507. }
  16508. /**
  16509. * A helper to get the player seekable end
  16510. * so that we don't have to null check everywhere
  16511. *
  16512. * @return {number}
  16513. * The furthest seekable end or Infinity.
  16514. */
  16515. ;
  16516. _proto.seekableEnd = function seekableEnd() {
  16517. var seekable = this.player_.seekable();
  16518. var seekableEnds = [];
  16519. var i = seekable ? seekable.length : 0;
  16520. while (i--) {
  16521. seekableEnds.push(seekable.end(i));
  16522. } // grab the furthest seekable end after sorting, or if there are none
  16523. // default to Infinity
  16524. return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
  16525. }
  16526. /**
  16527. * A helper to get the player seekable start
  16528. * so that we don't have to null check everywhere
  16529. *
  16530. * @return {number}
  16531. * The earliest seekable start or 0.
  16532. */
  16533. ;
  16534. _proto.seekableStart = function seekableStart() {
  16535. var seekable = this.player_.seekable();
  16536. var seekableStarts = [];
  16537. var i = seekable ? seekable.length : 0;
  16538. while (i--) {
  16539. seekableStarts.push(seekable.start(i));
  16540. } // grab the first seekable start after sorting, or if there are none
  16541. // default to 0
  16542. return seekableStarts.length ? seekableStarts.sort()[0] : 0;
  16543. }
  16544. /**
  16545. * Get the live time window aka
  16546. * the amount of time between seekable start and
  16547. * live current time.
  16548. *
  16549. * @return {number}
  16550. * The amount of seconds that are seekable in
  16551. * the live video.
  16552. */
  16553. ;
  16554. _proto.liveWindow = function liveWindow() {
  16555. var liveCurrentTime = this.liveCurrentTime(); // if liveCurrenTime is Infinity then we don't have a liveWindow at all
  16556. if (liveCurrentTime === Infinity) {
  16557. return 0;
  16558. }
  16559. return liveCurrentTime - this.seekableStart();
  16560. }
  16561. /**
  16562. * Determines if the player is live, only checks if this component
  16563. * is tracking live playback or not
  16564. *
  16565. * @return {boolean}
  16566. * Wether liveTracker is tracking
  16567. */
  16568. ;
  16569. _proto.isLive = function isLive() {
  16570. return this.isTracking();
  16571. }
  16572. /**
  16573. * Determines if currentTime is at the live edge and won't fall behind
  16574. * on each seekableendchange
  16575. *
  16576. * @return {boolean}
  16577. * Wether playback is at the live edge
  16578. */
  16579. ;
  16580. _proto.atLiveEdge = function atLiveEdge() {
  16581. return !this.behindLiveEdge();
  16582. }
  16583. /**
  16584. * get what we expect the live current time to be
  16585. *
  16586. * @return {number}
  16587. * The expected live current time
  16588. */
  16589. ;
  16590. _proto.liveCurrentTime = function liveCurrentTime() {
  16591. return this.pastSeekEnd() + this.seekableEnd();
  16592. }
  16593. /**
  16594. * The number of seconds that have occured after seekable end
  16595. * changed. This will be reset to 0 once seekable end changes.
  16596. *
  16597. * @return {number}
  16598. * Seconds past the current seekable end
  16599. */
  16600. ;
  16601. _proto.pastSeekEnd = function pastSeekEnd() {
  16602. var seekableEnd = this.seekableEnd();
  16603. if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
  16604. this.pastSeekEnd_ = 0;
  16605. }
  16606. this.lastSeekEnd_ = seekableEnd;
  16607. return this.pastSeekEnd_;
  16608. }
  16609. /**
  16610. * If we are currently behind the live edge, aka currentTime will be
  16611. * behind on a seekableendchange
  16612. *
  16613. * @return {boolean}
  16614. * If we are behind the live edge
  16615. */
  16616. ;
  16617. _proto.behindLiveEdge = function behindLiveEdge() {
  16618. return this.behindLiveEdge_;
  16619. }
  16620. /**
  16621. * Wether live tracker is currently tracking or not.
  16622. */
  16623. ;
  16624. _proto.isTracking = function isTracking() {
  16625. return typeof this.trackingInterval_ === 'number';
  16626. }
  16627. /**
  16628. * Seek to the live edge if we are behind the live edge
  16629. */
  16630. ;
  16631. _proto.seekToLiveEdge = function seekToLiveEdge() {
  16632. this.seekedBehindLive_ = false;
  16633. if (this.atLiveEdge()) {
  16634. return;
  16635. }
  16636. this.nextSeekedFromUser_ = false;
  16637. this.player_.currentTime(this.liveCurrentTime());
  16638. }
  16639. /**
  16640. * Dispose of liveTracker
  16641. */
  16642. ;
  16643. _proto.dispose = function dispose() {
  16644. this.off(document, 'visibilitychange', this.handleVisibilityChange_);
  16645. this.stopTracking();
  16646. _Component.prototype.dispose.call(this);
  16647. };
  16648. return LiveTracker;
  16649. }(Component$1);
  16650. Component$1.registerComponent('LiveTracker', LiveTracker);
  16651. /**
  16652. * This function is used to fire a sourceset when there is something
  16653. * similar to `mediaEl.load()` being called. It will try to find the source via
  16654. * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
  16655. * with the source that was found or empty string if we cannot know. If it cannot
  16656. * find a source then `sourceset` will not be fired.
  16657. *
  16658. * @param {Html5} tech
  16659. * The tech object that sourceset was setup on
  16660. *
  16661. * @return {boolean}
  16662. * returns false if the sourceset was not fired and true otherwise.
  16663. */
  16664. var sourcesetLoad = function sourcesetLoad(tech) {
  16665. var el = tech.el(); // if `el.src` is set, that source will be loaded.
  16666. if (el.hasAttribute('src')) {
  16667. tech.triggerSourceset(el.src);
  16668. return true;
  16669. }
  16670. /**
  16671. * Since there isn't a src property on the media element, source elements will be used for
  16672. * implementing the source selection algorithm. This happens asynchronously and
  16673. * for most cases were there is more than one source we cannot tell what source will
  16674. * be loaded, without re-implementing the source selection algorithm. At this time we are not
  16675. * going to do that. There are three special cases that we do handle here though:
  16676. *
  16677. * 1. If there are no sources, do not fire `sourceset`.
  16678. * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
  16679. * 3. If there is more than one `<source>` but all of them have the same `src` url.
  16680. * That will be our src.
  16681. */
  16682. var sources = tech.$$('source');
  16683. var srcUrls = [];
  16684. var src = ''; // if there are no sources, do not fire sourceset
  16685. if (!sources.length) {
  16686. return false;
  16687. } // only count valid/non-duplicate source elements
  16688. for (var i = 0; i < sources.length; i++) {
  16689. var url = sources[i].src;
  16690. if (url && srcUrls.indexOf(url) === -1) {
  16691. srcUrls.push(url);
  16692. }
  16693. } // there were no valid sources
  16694. if (!srcUrls.length) {
  16695. return false;
  16696. } // there is only one valid source element url
  16697. // use that
  16698. if (srcUrls.length === 1) {
  16699. src = srcUrls[0];
  16700. }
  16701. tech.triggerSourceset(src);
  16702. return true;
  16703. };
  16704. /**
  16705. * our implementation of an `innerHTML` descriptor for browsers
  16706. * that do not have one.
  16707. */
  16708. var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
  16709. get: function get() {
  16710. return this.cloneNode(true).innerHTML;
  16711. },
  16712. set: function set(v) {
  16713. // make a dummy node to use innerHTML on
  16714. var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided
  16715. dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy
  16716. var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy
  16717. // to the document fragment
  16718. while (dummy.childNodes.length) {
  16719. docFrag.appendChild(dummy.childNodes[0]);
  16720. } // remove content
  16721. this.innerText = ''; // now we add all of that html in one by appending the
  16722. // document fragment. This is how innerHTML does it.
  16723. window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would
  16724. return this.innerHTML;
  16725. }
  16726. });
  16727. /**
  16728. * Get a property descriptor given a list of priorities and the
  16729. * property to get.
  16730. */
  16731. var getDescriptor = function getDescriptor(priority, prop) {
  16732. var descriptor = {};
  16733. for (var i = 0; i < priority.length; i++) {
  16734. descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
  16735. if (descriptor && descriptor.set && descriptor.get) {
  16736. break;
  16737. }
  16738. }
  16739. descriptor.enumerable = true;
  16740. descriptor.configurable = true;
  16741. return descriptor;
  16742. };
  16743. var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
  16744. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
  16745. };
  16746. /**
  16747. * Patches browser internal functions so that we can tell synchronously
  16748. * if a `<source>` was appended to the media element. For some reason this
  16749. * causes a `sourceset` if the the media element is ready and has no source.
  16750. * This happens when:
  16751. * - The page has just loaded and the media element does not have a source.
  16752. * - The media element was emptied of all sources, then `load()` was called.
  16753. *
  16754. * It does this by patching the following functions/properties when they are supported:
  16755. *
  16756. * - `append()` - can be used to add a `<source>` element to the media element
  16757. * - `appendChild()` - can be used to add a `<source>` element to the media element
  16758. * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element
  16759. * - `innerHTML` - can be used to add a `<source>` element to the media element
  16760. *
  16761. * @param {Html5} tech
  16762. * The tech object that sourceset is being setup on.
  16763. */
  16764. var firstSourceWatch = function firstSourceWatch(tech) {
  16765. var el = tech.el(); // make sure firstSourceWatch isn't setup twice.
  16766. if (el.resetSourceWatch_) {
  16767. return;
  16768. }
  16769. var old = {};
  16770. var innerDescriptor = getInnerHTMLDescriptor(tech);
  16771. var appendWrapper = function appendWrapper(appendFn) {
  16772. return function () {
  16773. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  16774. args[_key] = arguments[_key];
  16775. }
  16776. var retval = appendFn.apply(el, args);
  16777. sourcesetLoad(tech);
  16778. return retval;
  16779. };
  16780. };
  16781. ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
  16782. if (!el[k]) {
  16783. return;
  16784. } // store the old function
  16785. old[k] = el[k]; // call the old function with a sourceset if a source
  16786. // was loaded
  16787. el[k] = appendWrapper(old[k]);
  16788. });
  16789. Object.defineProperty(el, 'innerHTML', mergeOptions$3(innerDescriptor, {
  16790. set: appendWrapper(innerDescriptor.set)
  16791. }));
  16792. el.resetSourceWatch_ = function () {
  16793. el.resetSourceWatch_ = null;
  16794. Object.keys(old).forEach(function (k) {
  16795. el[k] = old[k];
  16796. });
  16797. Object.defineProperty(el, 'innerHTML', innerDescriptor);
  16798. }; // on the first sourceset, we need to revert our changes
  16799. tech.one('sourceset', el.resetSourceWatch_);
  16800. };
  16801. /**
  16802. * our implementation of a `src` descriptor for browsers
  16803. * that do not have one.
  16804. */
  16805. var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
  16806. get: function get() {
  16807. if (this.hasAttribute('src')) {
  16808. return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src'));
  16809. }
  16810. return '';
  16811. },
  16812. set: function set(v) {
  16813. window$1.Element.prototype.setAttribute.call(this, 'src', v);
  16814. return v;
  16815. }
  16816. });
  16817. var getSrcDescriptor = function getSrcDescriptor(tech) {
  16818. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
  16819. };
  16820. /**
  16821. * setup `sourceset` handling on the `Html5` tech. This function
  16822. * patches the following element properties/functions:
  16823. *
  16824. * - `src` - to determine when `src` is set
  16825. * - `setAttribute()` - to determine when `src` is set
  16826. * - `load()` - this re-triggers the source selection algorithm, and can
  16827. * cause a sourceset.
  16828. *
  16829. * If there is no source when we are adding `sourceset` support or during a `load()`
  16830. * we also patch the functions listed in `firstSourceWatch`.
  16831. *
  16832. * @param {Html5} tech
  16833. * The tech to patch
  16834. */
  16835. var setupSourceset = function setupSourceset(tech) {
  16836. if (!tech.featuresSourceset) {
  16837. return;
  16838. }
  16839. var el = tech.el(); // make sure sourceset isn't setup twice.
  16840. if (el.resetSourceset_) {
  16841. return;
  16842. }
  16843. var srcDescriptor = getSrcDescriptor(tech);
  16844. var oldSetAttribute = el.setAttribute;
  16845. var oldLoad = el.load;
  16846. Object.defineProperty(el, 'src', mergeOptions$3(srcDescriptor, {
  16847. set: function set(v) {
  16848. var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src
  16849. tech.triggerSourceset(el.src);
  16850. return retval;
  16851. }
  16852. }));
  16853. el.setAttribute = function (n, v) {
  16854. var retval = oldSetAttribute.call(el, n, v);
  16855. if (/src/i.test(n)) {
  16856. tech.triggerSourceset(el.src);
  16857. }
  16858. return retval;
  16859. };
  16860. el.load = function () {
  16861. var retval = oldLoad.call(el); // if load was called, but there was no source to fire
  16862. // sourceset on. We have to watch for a source append
  16863. // as that can trigger a `sourceset` when the media element
  16864. // has no source
  16865. if (!sourcesetLoad(tech)) {
  16866. tech.triggerSourceset('');
  16867. firstSourceWatch(tech);
  16868. }
  16869. return retval;
  16870. };
  16871. if (el.currentSrc) {
  16872. tech.triggerSourceset(el.currentSrc);
  16873. } else if (!sourcesetLoad(tech)) {
  16874. firstSourceWatch(tech);
  16875. }
  16876. el.resetSourceset_ = function () {
  16877. el.resetSourceset_ = null;
  16878. el.load = oldLoad;
  16879. el.setAttribute = oldSetAttribute;
  16880. Object.defineProperty(el, 'src', srcDescriptor);
  16881. if (el.resetSourceWatch_) {
  16882. el.resetSourceWatch_();
  16883. }
  16884. };
  16885. };
  16886. /**
  16887. * Object.defineProperty but "lazy", which means that the value is only set after
  16888. * it retrieved the first time, rather than being set right away.
  16889. *
  16890. * @param {Object} obj the object to set the property on
  16891. * @param {string} key the key for the property to set
  16892. * @param {Function} getValue the function used to get the value when it is needed.
  16893. * @param {boolean} setter wether a setter shoould be allowed or not
  16894. */
  16895. var defineLazyProperty = function defineLazyProperty(obj, key, getValue, setter) {
  16896. if (setter === void 0) {
  16897. setter = true;
  16898. }
  16899. var set = function set(value) {
  16900. return Object.defineProperty(obj, key, {
  16901. value: value,
  16902. enumerable: true,
  16903. writable: true
  16904. });
  16905. };
  16906. var options = {
  16907. configurable: true,
  16908. enumerable: true,
  16909. get: function get() {
  16910. var value = getValue();
  16911. set(value);
  16912. return value;
  16913. }
  16914. };
  16915. if (setter) {
  16916. options.set = set;
  16917. }
  16918. return Object.defineProperty(obj, key, options);
  16919. };
  16920. /**
  16921. * HTML5 Media Controller - Wrapper for HTML5 Media API
  16922. *
  16923. * @mixes Tech~SourceHandlerAdditions
  16924. * @extends Tech
  16925. */
  16926. var Html5 = /*#__PURE__*/function (_Tech) {
  16927. _inheritsLoose(Html5, _Tech);
  16928. /**
  16929. * Create an instance of this Tech.
  16930. *
  16931. * @param {Object} [options]
  16932. * The key/value store of player options.
  16933. *
  16934. * @param {Component~ReadyCallback} ready
  16935. * Callback function to call when the `HTML5` Tech is ready.
  16936. */
  16937. function Html5(options, ready) {
  16938. var _this;
  16939. _this = _Tech.call(this, options, ready) || this;
  16940. var source = options.source;
  16941. var crossoriginTracks = false;
  16942. _this.featuresVideoFrameCallback = _this.featuresVideoFrameCallback && _this.el_.tagName === 'VIDEO'; // Set the source if one is provided
  16943. // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
  16944. // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
  16945. // anyway so the error gets fired.
  16946. if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
  16947. _this.setSource(source);
  16948. } else {
  16949. _this.handleLateInit_(_this.el_);
  16950. } // setup sourceset after late sourceset/init
  16951. if (options.enableSourceset) {
  16952. _this.setupSourcesetHandling_();
  16953. }
  16954. _this.isScrubbing_ = false;
  16955. if (_this.el_.hasChildNodes()) {
  16956. var nodes = _this.el_.childNodes;
  16957. var nodesLength = nodes.length;
  16958. var removeNodes = [];
  16959. while (nodesLength--) {
  16960. var node = nodes[nodesLength];
  16961. var nodeName = node.nodeName.toLowerCase();
  16962. if (nodeName === 'track') {
  16963. if (!_this.featuresNativeTextTracks) {
  16964. // Empty video tag tracks so the built-in player doesn't use them also.
  16965. // This may not be fast enough to stop HTML5 browsers from reading the tags
  16966. // so we'll need to turn off any default tracks if we're manually doing
  16967. // captions and subtitles. videoElement.textTracks
  16968. removeNodes.push(node);
  16969. } else {
  16970. // store HTMLTrackElement and TextTrack to remote list
  16971. _this.remoteTextTrackEls().addTrackElement_(node);
  16972. _this.remoteTextTracks().addTrack(node.track);
  16973. _this.textTracks().addTrack(node.track);
  16974. if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
  16975. crossoriginTracks = true;
  16976. }
  16977. }
  16978. }
  16979. }
  16980. for (var i = 0; i < removeNodes.length; i++) {
  16981. _this.el_.removeChild(removeNodes[i]);
  16982. }
  16983. }
  16984. _this.proxyNativeTracks_();
  16985. if (_this.featuresNativeTextTracks && crossoriginTracks) {
  16986. log$1.warn('Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n' + 'This may prevent text tracks from loading.');
  16987. } // prevent iOS Safari from disabling metadata text tracks during native playback
  16988. _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used
  16989. // Our goal should be to get the custom controls on mobile solid everywhere
  16990. // so we can remove this all together. Right now this will block custom
  16991. // controls on touch enabled laptops like the Chrome Pixel
  16992. if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
  16993. _this.setControls(true);
  16994. } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
  16995. // into a `fullscreenchange` event
  16996. _this.proxyWebkitFullscreen_();
  16997. _this.triggerReady();
  16998. return _this;
  16999. }
  17000. /**
  17001. * Dispose of `HTML5` media element and remove all tracks.
  17002. */
  17003. var _proto = Html5.prototype;
  17004. _proto.dispose = function dispose() {
  17005. if (this.el_ && this.el_.resetSourceset_) {
  17006. this.el_.resetSourceset_();
  17007. }
  17008. Html5.disposeMediaElement(this.el_);
  17009. this.options_ = null; // tech will handle clearing of the emulated track list
  17010. _Tech.prototype.dispose.call(this);
  17011. }
  17012. /**
  17013. * Modify the media element so that we can detect when
  17014. * the source is changed. Fires `sourceset` just after the source has changed
  17015. */
  17016. ;
  17017. _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() {
  17018. setupSourceset(this);
  17019. }
  17020. /**
  17021. * When a captions track is enabled in the iOS Safari native player, all other
  17022. * tracks are disabled (including metadata tracks), which nulls all of their
  17023. * associated cue points. This will restore metadata tracks to their pre-fullscreen
  17024. * state in those cases so that cue points are not needlessly lost.
  17025. *
  17026. * @private
  17027. */
  17028. ;
  17029. _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
  17030. var textTracks = this.textTracks();
  17031. var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state
  17032. var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
  17033. metadataTracksPreFullscreenState = [];
  17034. for (var i = 0; i < textTracks.length; i++) {
  17035. var track = textTracks[i];
  17036. if (track.kind === 'metadata') {
  17037. metadataTracksPreFullscreenState.push({
  17038. track: track,
  17039. storedMode: track.mode
  17040. });
  17041. }
  17042. }
  17043. }; // snapshot each metadata track's initial state, and update the snapshot
  17044. // each time there is a track 'change' event
  17045. takeMetadataTrackSnapshot();
  17046. textTracks.addEventListener('change', takeMetadataTrackSnapshot);
  17047. this.on('dispose', function () {
  17048. return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  17049. });
  17050. var restoreTrackMode = function restoreTrackMode() {
  17051. for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
  17052. var storedTrack = metadataTracksPreFullscreenState[i];
  17053. if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
  17054. storedTrack.track.mode = storedTrack.storedMode;
  17055. }
  17056. } // we only want this handler to be executed on the first 'change' event
  17057. textTracks.removeEventListener('change', restoreTrackMode);
  17058. }; // when we enter fullscreen playback, stop updating the snapshot and
  17059. // restore all track modes to their pre-fullscreen state
  17060. this.on('webkitbeginfullscreen', function () {
  17061. textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed
  17062. textTracks.removeEventListener('change', restoreTrackMode);
  17063. textTracks.addEventListener('change', restoreTrackMode);
  17064. }); // start updating the snapshot again after leaving fullscreen
  17065. this.on('webkitendfullscreen', function () {
  17066. // remove the listener before adding it just in case it wasn't previously removed
  17067. textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  17068. textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
  17069. textTracks.removeEventListener('change', restoreTrackMode);
  17070. });
  17071. }
  17072. /**
  17073. * Attempt to force override of tracks for the given type
  17074. *
  17075. * @param {string} type - Track type to override, possible values include 'Audio',
  17076. * 'Video', and 'Text'.
  17077. * @param {boolean} override - If set to true native audio/video will be overridden,
  17078. * otherwise native audio/video will potentially be used.
  17079. * @private
  17080. */
  17081. ;
  17082. _proto.overrideNative_ = function overrideNative_(type, override) {
  17083. var _this2 = this;
  17084. // If there is no behavioral change don't add/remove listeners
  17085. if (override !== this["featuresNative" + type + "Tracks"]) {
  17086. return;
  17087. }
  17088. var lowerCaseType = type.toLowerCase();
  17089. if (this[lowerCaseType + "TracksListeners_"]) {
  17090. Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) {
  17091. var elTracks = _this2.el()[lowerCaseType + "Tracks"];
  17092. elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]);
  17093. });
  17094. }
  17095. this["featuresNative" + type + "Tracks"] = !override;
  17096. this[lowerCaseType + "TracksListeners_"] = null;
  17097. this.proxyNativeTracksForType_(lowerCaseType);
  17098. }
  17099. /**
  17100. * Attempt to force override of native audio tracks.
  17101. *
  17102. * @param {boolean} override - If set to true native audio will be overridden,
  17103. * otherwise native audio will potentially be used.
  17104. */
  17105. ;
  17106. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
  17107. this.overrideNative_('Audio', override);
  17108. }
  17109. /**
  17110. * Attempt to force override of native video tracks.
  17111. *
  17112. * @param {boolean} override - If set to true native video will be overridden,
  17113. * otherwise native video will potentially be used.
  17114. */
  17115. ;
  17116. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
  17117. this.overrideNative_('Video', override);
  17118. }
  17119. /**
  17120. * Proxy native track list events for the given type to our track
  17121. * lists if the browser we are playing in supports that type of track list.
  17122. *
  17123. * @param {string} name - Track type; values include 'audio', 'video', and 'text'
  17124. * @private
  17125. */
  17126. ;
  17127. _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
  17128. var _this3 = this;
  17129. var props = NORMAL[name];
  17130. var elTracks = this.el()[props.getterName];
  17131. var techTracks = this[props.getterName]();
  17132. if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) {
  17133. return;
  17134. }
  17135. var listeners = {
  17136. change: function change(e) {
  17137. var event = {
  17138. type: 'change',
  17139. target: techTracks,
  17140. currentTarget: techTracks,
  17141. srcElement: techTracks
  17142. };
  17143. techTracks.trigger(event); // if we are a text track change event, we should also notify the
  17144. // remote text track list. This can potentially cause a false positive
  17145. // if we were to get a change event on a non-remote track and
  17146. // we triggered the event on the remote text track list which doesn't
  17147. // contain that track. However, best practices mean looping through the
  17148. // list of tracks and searching for the appropriate mode value, so,
  17149. // this shouldn't pose an issue
  17150. if (name === 'text') {
  17151. _this3[REMOTE.remoteText.getterName]().trigger(event);
  17152. }
  17153. },
  17154. addtrack: function addtrack(e) {
  17155. techTracks.addTrack(e.track);
  17156. },
  17157. removetrack: function removetrack(e) {
  17158. techTracks.removeTrack(e.track);
  17159. }
  17160. };
  17161. var removeOldTracks = function removeOldTracks() {
  17162. var removeTracks = [];
  17163. for (var i = 0; i < techTracks.length; i++) {
  17164. var found = false;
  17165. for (var j = 0; j < elTracks.length; j++) {
  17166. if (elTracks[j] === techTracks[i]) {
  17167. found = true;
  17168. break;
  17169. }
  17170. }
  17171. if (!found) {
  17172. removeTracks.push(techTracks[i]);
  17173. }
  17174. }
  17175. while (removeTracks.length) {
  17176. techTracks.removeTrack(removeTracks.shift());
  17177. }
  17178. };
  17179. this[props.getterName + 'Listeners_'] = listeners;
  17180. Object.keys(listeners).forEach(function (eventName) {
  17181. var listener = listeners[eventName];
  17182. elTracks.addEventListener(eventName, listener);
  17183. _this3.on('dispose', function (e) {
  17184. return elTracks.removeEventListener(eventName, listener);
  17185. });
  17186. }); // Remove (native) tracks that are not used anymore
  17187. this.on('loadstart', removeOldTracks);
  17188. this.on('dispose', function (e) {
  17189. return _this3.off('loadstart', removeOldTracks);
  17190. });
  17191. }
  17192. /**
  17193. * Proxy all native track list events to our track lists if the browser we are playing
  17194. * in supports that type of track list.
  17195. *
  17196. * @private
  17197. */
  17198. ;
  17199. _proto.proxyNativeTracks_ = function proxyNativeTracks_() {
  17200. var _this4 = this;
  17201. NORMAL.names.forEach(function (name) {
  17202. _this4.proxyNativeTracksForType_(name);
  17203. });
  17204. }
  17205. /**
  17206. * Create the `Html5` Tech's DOM element.
  17207. *
  17208. * @return {Element}
  17209. * The element that gets created.
  17210. */
  17211. ;
  17212. _proto.createEl = function createEl() {
  17213. var el = this.options_.tag; // Check if this browser supports moving the element into the box.
  17214. // On the iPhone video will break if you move the element,
  17215. // So we have to create a brand new element.
  17216. // If we ingested the player div, we do not need to move the media element.
  17217. if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
  17218. // If the original tag is still there, clone and remove it.
  17219. if (el) {
  17220. var clone = el.cloneNode(true);
  17221. if (el.parentNode) {
  17222. el.parentNode.insertBefore(clone, el);
  17223. }
  17224. Html5.disposeMediaElement(el);
  17225. el = clone;
  17226. } else {
  17227. el = document.createElement('video'); // determine if native controls should be used
  17228. var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
  17229. var attributes = mergeOptions$3({}, tagAttributes);
  17230. if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
  17231. delete attributes.controls;
  17232. }
  17233. setAttributes(el, assign(attributes, {
  17234. id: this.options_.techId,
  17235. "class": 'vjs-tech'
  17236. }));
  17237. }
  17238. el.playerId = this.options_.playerId;
  17239. }
  17240. if (typeof this.options_.preload !== 'undefined') {
  17241. setAttribute(el, 'preload', this.options_.preload);
  17242. }
  17243. if (this.options_.disablePictureInPicture !== undefined) {
  17244. el.disablePictureInPicture = this.options_.disablePictureInPicture;
  17245. } // Update specific tag settings, in case they were overridden
  17246. // `autoplay` has to be *last* so that `muted` and `playsinline` are present
  17247. // when iOS/Safari or other browsers attempt to autoplay.
  17248. var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
  17249. for (var i = 0; i < settingsAttrs.length; i++) {
  17250. var attr = settingsAttrs[i];
  17251. var value = this.options_[attr];
  17252. if (typeof value !== 'undefined') {
  17253. if (value) {
  17254. setAttribute(el, attr, attr);
  17255. } else {
  17256. removeAttribute(el, attr);
  17257. }
  17258. el[attr] = value;
  17259. }
  17260. }
  17261. return el;
  17262. }
  17263. /**
  17264. * This will be triggered if the loadstart event has already fired, before videojs was
  17265. * ready. Two known examples of when this can happen are:
  17266. * 1. If we're loading the playback object after it has started loading
  17267. * 2. The media is already playing the (often with autoplay on) then
  17268. *
  17269. * This function will fire another loadstart so that videojs can catchup.
  17270. *
  17271. * @fires Tech#loadstart
  17272. *
  17273. * @return {undefined}
  17274. * returns nothing.
  17275. */
  17276. ;
  17277. _proto.handleLateInit_ = function handleLateInit_(el) {
  17278. if (el.networkState === 0 || el.networkState === 3) {
  17279. // The video element hasn't started loading the source yet
  17280. // or didn't find a source
  17281. return;
  17282. }
  17283. if (el.readyState === 0) {
  17284. // NetworkState is set synchronously BUT loadstart is fired at the
  17285. // end of the current stack, usually before setInterval(fn, 0).
  17286. // So at this point we know loadstart may have already fired or is
  17287. // about to fire, and either way the player hasn't seen it yet.
  17288. // We don't want to fire loadstart prematurely here and cause a
  17289. // double loadstart so we'll wait and see if it happens between now
  17290. // and the next loop, and fire it if not.
  17291. // HOWEVER, we also want to make sure it fires before loadedmetadata
  17292. // which could also happen between now and the next loop, so we'll
  17293. // watch for that also.
  17294. var loadstartFired = false;
  17295. var setLoadstartFired = function setLoadstartFired() {
  17296. loadstartFired = true;
  17297. };
  17298. this.on('loadstart', setLoadstartFired);
  17299. var triggerLoadstart = function triggerLoadstart() {
  17300. // We did miss the original loadstart. Make sure the player
  17301. // sees loadstart before loadedmetadata
  17302. if (!loadstartFired) {
  17303. this.trigger('loadstart');
  17304. }
  17305. };
  17306. this.on('loadedmetadata', triggerLoadstart);
  17307. this.ready(function () {
  17308. this.off('loadstart', setLoadstartFired);
  17309. this.off('loadedmetadata', triggerLoadstart);
  17310. if (!loadstartFired) {
  17311. // We did miss the original native loadstart. Fire it now.
  17312. this.trigger('loadstart');
  17313. }
  17314. });
  17315. return;
  17316. } // From here on we know that loadstart already fired and we missed it.
  17317. // The other readyState events aren't as much of a problem if we double
  17318. // them, so not going to go to as much trouble as loadstart to prevent
  17319. // that unless we find reason to.
  17320. var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
  17321. eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
  17322. if (el.readyState >= 2) {
  17323. eventsToTrigger.push('loadeddata');
  17324. } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
  17325. if (el.readyState >= 3) {
  17326. eventsToTrigger.push('canplay');
  17327. } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
  17328. if (el.readyState >= 4) {
  17329. eventsToTrigger.push('canplaythrough');
  17330. } // We still need to give the player time to add event listeners
  17331. this.ready(function () {
  17332. eventsToTrigger.forEach(function (type) {
  17333. this.trigger(type);
  17334. }, this);
  17335. });
  17336. }
  17337. /**
  17338. * Set whether we are scrubbing or not.
  17339. * This is used to decide whether we should use `fastSeek` or not.
  17340. * `fastSeek` is used to provide trick play on Safari browsers.
  17341. *
  17342. * @param {boolean} isScrubbing
  17343. * - true for we are currently scrubbing
  17344. * - false for we are no longer scrubbing
  17345. */
  17346. ;
  17347. _proto.setScrubbing = function setScrubbing(isScrubbing) {
  17348. this.isScrubbing_ = isScrubbing;
  17349. }
  17350. /**
  17351. * Get whether we are scrubbing or not.
  17352. *
  17353. * @return {boolean} isScrubbing
  17354. * - true for we are currently scrubbing
  17355. * - false for we are no longer scrubbing
  17356. */
  17357. ;
  17358. _proto.scrubbing = function scrubbing() {
  17359. return this.isScrubbing_;
  17360. }
  17361. /**
  17362. * Set current time for the `HTML5` tech.
  17363. *
  17364. * @param {number} seconds
  17365. * Set the current time of the media to this.
  17366. */
  17367. ;
  17368. _proto.setCurrentTime = function setCurrentTime(seconds) {
  17369. try {
  17370. if (this.isScrubbing_ && this.el_.fastSeek && IS_ANY_SAFARI) {
  17371. this.el_.fastSeek(seconds);
  17372. } else {
  17373. this.el_.currentTime = seconds;
  17374. }
  17375. } catch (e) {
  17376. log$1(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady);
  17377. }
  17378. }
  17379. /**
  17380. * Get the current duration of the HTML5 media element.
  17381. *
  17382. * @return {number}
  17383. * The duration of the media or 0 if there is no duration.
  17384. */
  17385. ;
  17386. _proto.duration = function duration() {
  17387. var _this5 = this;
  17388. // Android Chrome will report duration as Infinity for VOD HLS until after
  17389. // playback has started, which triggers the live display erroneously.
  17390. // Return NaN if playback has not started and trigger a durationupdate once
  17391. // the duration can be reliably known.
  17392. if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
  17393. // Wait for the first `timeupdate` with currentTime > 0 - there may be
  17394. // several with 0
  17395. var checkProgress = function checkProgress() {
  17396. if (_this5.el_.currentTime > 0) {
  17397. // Trigger durationchange for genuinely live video
  17398. if (_this5.el_.duration === Infinity) {
  17399. _this5.trigger('durationchange');
  17400. }
  17401. _this5.off('timeupdate', checkProgress);
  17402. }
  17403. };
  17404. this.on('timeupdate', checkProgress);
  17405. return NaN;
  17406. }
  17407. return this.el_.duration || NaN;
  17408. }
  17409. /**
  17410. * Get the current width of the HTML5 media element.
  17411. *
  17412. * @return {number}
  17413. * The width of the HTML5 media element.
  17414. */
  17415. ;
  17416. _proto.width = function width() {
  17417. return this.el_.offsetWidth;
  17418. }
  17419. /**
  17420. * Get the current height of the HTML5 media element.
  17421. *
  17422. * @return {number}
  17423. * The height of the HTML5 media element.
  17424. */
  17425. ;
  17426. _proto.height = function height() {
  17427. return this.el_.offsetHeight;
  17428. }
  17429. /**
  17430. * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
  17431. * `fullscreenchange` event.
  17432. *
  17433. * @private
  17434. * @fires fullscreenchange
  17435. * @listens webkitendfullscreen
  17436. * @listens webkitbeginfullscreen
  17437. * @listens webkitbeginfullscreen
  17438. */
  17439. ;
  17440. _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
  17441. var _this6 = this;
  17442. if (!('webkitDisplayingFullscreen' in this.el_)) {
  17443. return;
  17444. }
  17445. var endFn = function endFn() {
  17446. this.trigger('fullscreenchange', {
  17447. isFullscreen: false
  17448. }); // Safari will sometimes set contols on the videoelement when existing fullscreen.
  17449. if (this.el_.controls && !this.options_.nativeControlsForTouch && this.controls()) {
  17450. this.el_.controls = false;
  17451. }
  17452. };
  17453. var beginFn = function beginFn() {
  17454. if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
  17455. this.one('webkitendfullscreen', endFn);
  17456. this.trigger('fullscreenchange', {
  17457. isFullscreen: true,
  17458. // set a flag in case another tech triggers fullscreenchange
  17459. nativeIOSFullscreen: true
  17460. });
  17461. }
  17462. };
  17463. this.on('webkitbeginfullscreen', beginFn);
  17464. this.on('dispose', function () {
  17465. _this6.off('webkitbeginfullscreen', beginFn);
  17466. _this6.off('webkitendfullscreen', endFn);
  17467. });
  17468. }
  17469. /**
  17470. * Check if fullscreen is supported on the current playback device.
  17471. *
  17472. * @return {boolean}
  17473. * - True if fullscreen is supported.
  17474. * - False if fullscreen is not supported.
  17475. */
  17476. ;
  17477. _proto.supportsFullScreen = function supportsFullScreen() {
  17478. if (typeof this.el_.webkitEnterFullScreen === 'function') {
  17479. var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard
  17480. if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
  17481. return true;
  17482. }
  17483. }
  17484. return false;
  17485. }
  17486. /**
  17487. * Request that the `HTML5` Tech enter fullscreen.
  17488. */
  17489. ;
  17490. _proto.enterFullScreen = function enterFullScreen() {
  17491. var video = this.el_;
  17492. if (video.paused && video.networkState <= video.HAVE_METADATA) {
  17493. // attempt to prime the video element for programmatic access
  17494. // this isn't necessary on the desktop but shouldn't hurt
  17495. silencePromise(this.el_.play()); // playing and pausing synchronously during the transition to fullscreen
  17496. // can get iOS ~6.1 devices into a play/pause loop
  17497. this.setTimeout(function () {
  17498. video.pause();
  17499. try {
  17500. video.webkitEnterFullScreen();
  17501. } catch (e) {
  17502. this.trigger('fullscreenerror', e);
  17503. }
  17504. }, 0);
  17505. } else {
  17506. try {
  17507. video.webkitEnterFullScreen();
  17508. } catch (e) {
  17509. this.trigger('fullscreenerror', e);
  17510. }
  17511. }
  17512. }
  17513. /**
  17514. * Request that the `HTML5` Tech exit fullscreen.
  17515. */
  17516. ;
  17517. _proto.exitFullScreen = function exitFullScreen() {
  17518. if (!this.el_.webkitDisplayingFullscreen) {
  17519. this.trigger('fullscreenerror', new Error('The video is not fullscreen'));
  17520. return;
  17521. }
  17522. this.el_.webkitExitFullScreen();
  17523. }
  17524. /**
  17525. * Create a floating video window always on top of other windows so that users may
  17526. * continue consuming media while they interact with other content sites, or
  17527. * applications on their device.
  17528. *
  17529. * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
  17530. *
  17531. * @return {Promise}
  17532. * A promise with a Picture-in-Picture window.
  17533. */
  17534. ;
  17535. _proto.requestPictureInPicture = function requestPictureInPicture() {
  17536. return this.el_.requestPictureInPicture();
  17537. }
  17538. /**
  17539. * Native requestVideoFrameCallback if supported by browser/tech, or fallback
  17540. * Don't use rVCF on Safari when DRM is playing, as it doesn't fire
  17541. * Needs to be checked later than the constructor
  17542. * This will be a false positive for clear sources loaded after a Fairplay source
  17543. *
  17544. * @param {function} cb function to call
  17545. * @return {number} id of request
  17546. */
  17547. ;
  17548. _proto.requestVideoFrameCallback = function requestVideoFrameCallback(cb) {
  17549. if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
  17550. return this.el_.requestVideoFrameCallback(cb);
  17551. }
  17552. return _Tech.prototype.requestVideoFrameCallback.call(this, cb);
  17553. }
  17554. /**
  17555. * Native or fallback requestVideoFrameCallback
  17556. *
  17557. * @param {number} id request id to cancel
  17558. */
  17559. ;
  17560. _proto.cancelVideoFrameCallback = function cancelVideoFrameCallback(id) {
  17561. if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
  17562. this.el_.cancelVideoFrameCallback(id);
  17563. } else {
  17564. _Tech.prototype.cancelVideoFrameCallback.call(this, id);
  17565. }
  17566. }
  17567. /**
  17568. * A getter/setter for the `Html5` Tech's source object.
  17569. * > Note: Please use {@link Html5#setSource}
  17570. *
  17571. * @param {Tech~SourceObject} [src]
  17572. * The source object you want to set on the `HTML5` techs element.
  17573. *
  17574. * @return {Tech~SourceObject|undefined}
  17575. * - The current source object when a source is not passed in.
  17576. * - undefined when setting
  17577. *
  17578. * @deprecated Since version 5.
  17579. */
  17580. ;
  17581. _proto.src = function src(_src) {
  17582. if (_src === undefined) {
  17583. return this.el_.src;
  17584. } // Setting src through `src` instead of `setSrc` will be deprecated
  17585. this.setSrc(_src);
  17586. }
  17587. /**
  17588. * Reset the tech by removing all sources and then calling
  17589. * {@link Html5.resetMediaElement}.
  17590. */
  17591. ;
  17592. _proto.reset = function reset() {
  17593. Html5.resetMediaElement(this.el_);
  17594. }
  17595. /**
  17596. * Get the current source on the `HTML5` Tech. Falls back to returning the source from
  17597. * the HTML5 media element.
  17598. *
  17599. * @return {Tech~SourceObject}
  17600. * The current source object from the HTML5 tech. With a fallback to the
  17601. * elements source.
  17602. */
  17603. ;
  17604. _proto.currentSrc = function currentSrc() {
  17605. if (this.currentSource_) {
  17606. return this.currentSource_.src;
  17607. }
  17608. return this.el_.currentSrc;
  17609. }
  17610. /**
  17611. * Set controls attribute for the HTML5 media Element.
  17612. *
  17613. * @param {string} val
  17614. * Value to set the controls attribute to
  17615. */
  17616. ;
  17617. _proto.setControls = function setControls(val) {
  17618. this.el_.controls = !!val;
  17619. }
  17620. /**
  17621. * Create and returns a remote {@link TextTrack} object.
  17622. *
  17623. * @param {string} kind
  17624. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  17625. *
  17626. * @param {string} [label]
  17627. * Label to identify the text track
  17628. *
  17629. * @param {string} [language]
  17630. * Two letter language abbreviation
  17631. *
  17632. * @return {TextTrack}
  17633. * The TextTrack that gets created.
  17634. */
  17635. ;
  17636. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  17637. if (!this.featuresNativeTextTracks) {
  17638. return _Tech.prototype.addTextTrack.call(this, kind, label, language);
  17639. }
  17640. return this.el_.addTextTrack(kind, label, language);
  17641. }
  17642. /**
  17643. * Creates either native TextTrack or an emulated TextTrack depending
  17644. * on the value of `featuresNativeTextTracks`
  17645. *
  17646. * @param {Object} options
  17647. * The object should contain the options to initialize the TextTrack with.
  17648. *
  17649. * @param {string} [options.kind]
  17650. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  17651. *
  17652. * @param {string} [options.label]
  17653. * Label to identify the text track
  17654. *
  17655. * @param {string} [options.language]
  17656. * Two letter language abbreviation.
  17657. *
  17658. * @param {boolean} [options.default]
  17659. * Default this track to on.
  17660. *
  17661. * @param {string} [options.id]
  17662. * The internal id to assign this track.
  17663. *
  17664. * @param {string} [options.src]
  17665. * A source url for the track.
  17666. *
  17667. * @return {HTMLTrackElement}
  17668. * The track element that gets created.
  17669. */
  17670. ;
  17671. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  17672. if (!this.featuresNativeTextTracks) {
  17673. return _Tech.prototype.createRemoteTextTrack.call(this, options);
  17674. }
  17675. var htmlTrackElement = document.createElement('track');
  17676. if (options.kind) {
  17677. htmlTrackElement.kind = options.kind;
  17678. }
  17679. if (options.label) {
  17680. htmlTrackElement.label = options.label;
  17681. }
  17682. if (options.language || options.srclang) {
  17683. htmlTrackElement.srclang = options.language || options.srclang;
  17684. }
  17685. if (options["default"]) {
  17686. htmlTrackElement["default"] = options["default"];
  17687. }
  17688. if (options.id) {
  17689. htmlTrackElement.id = options.id;
  17690. }
  17691. if (options.src) {
  17692. htmlTrackElement.src = options.src;
  17693. }
  17694. return htmlTrackElement;
  17695. }
  17696. /**
  17697. * Creates a remote text track object and returns an html track element.
  17698. *
  17699. * @param {Object} options The object should contain values for
  17700. * kind, language, label, and src (location of the WebVTT file)
  17701. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  17702. * automatically removed from the video element whenever the source changes
  17703. * @return {HTMLTrackElement} An Html Track Element.
  17704. * This can be an emulated {@link HTMLTrackElement} or a native one.
  17705. * @deprecated The default value of the "manualCleanup" parameter will default
  17706. * to "false" in upcoming versions of Video.js
  17707. */
  17708. ;
  17709. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  17710. var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
  17711. if (this.featuresNativeTextTracks) {
  17712. this.el().appendChild(htmlTrackElement);
  17713. }
  17714. return htmlTrackElement;
  17715. }
  17716. /**
  17717. * Remove remote `TextTrack` from `TextTrackList` object
  17718. *
  17719. * @param {TextTrack} track
  17720. * `TextTrack` object to remove
  17721. */
  17722. ;
  17723. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  17724. _Tech.prototype.removeRemoteTextTrack.call(this, track);
  17725. if (this.featuresNativeTextTracks) {
  17726. var tracks = this.$$('track');
  17727. var i = tracks.length;
  17728. while (i--) {
  17729. if (track === tracks[i] || track === tracks[i].track) {
  17730. this.el().removeChild(tracks[i]);
  17731. }
  17732. }
  17733. }
  17734. }
  17735. /**
  17736. * Gets available media playback quality metrics as specified by the W3C's Media
  17737. * Playback Quality API.
  17738. *
  17739. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  17740. *
  17741. * @return {Object}
  17742. * An object with supported media playback quality metrics
  17743. */
  17744. ;
  17745. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  17746. if (typeof this.el().getVideoPlaybackQuality === 'function') {
  17747. return this.el().getVideoPlaybackQuality();
  17748. }
  17749. var videoPlaybackQuality = {};
  17750. if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
  17751. videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
  17752. videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
  17753. }
  17754. if (window$1.performance && typeof window$1.performance.now === 'function') {
  17755. videoPlaybackQuality.creationTime = window$1.performance.now();
  17756. } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') {
  17757. videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart;
  17758. }
  17759. return videoPlaybackQuality;
  17760. };
  17761. return Html5;
  17762. }(Tech);
  17763. /* HTML5 Support Testing ---------------------------------------------------- */
  17764. /**
  17765. * Element for testing browser HTML5 media capabilities
  17766. *
  17767. * @type {Element}
  17768. * @constant
  17769. * @private
  17770. */
  17771. defineLazyProperty(Html5, 'TEST_VID', function () {
  17772. if (!isReal()) {
  17773. return;
  17774. }
  17775. var video = document.createElement('video');
  17776. var track = document.createElement('track');
  17777. track.kind = 'captions';
  17778. track.srclang = 'en';
  17779. track.label = 'English';
  17780. video.appendChild(track);
  17781. return video;
  17782. });
  17783. /**
  17784. * Check if HTML5 media is supported by this browser/device.
  17785. *
  17786. * @return {boolean}
  17787. * - True if HTML5 media is supported.
  17788. * - False if HTML5 media is not supported.
  17789. */
  17790. Html5.isSupported = function () {
  17791. // IE with no Media Player is a LIAR! (#984)
  17792. try {
  17793. Html5.TEST_VID.volume = 0.5;
  17794. } catch (e) {
  17795. return false;
  17796. }
  17797. return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
  17798. };
  17799. /**
  17800. * Check if the tech can support the given type
  17801. *
  17802. * @param {string} type
  17803. * The mimetype to check
  17804. * @return {string} 'probably', 'maybe', or '' (empty string)
  17805. */
  17806. Html5.canPlayType = function (type) {
  17807. return Html5.TEST_VID.canPlayType(type);
  17808. };
  17809. /**
  17810. * Check if the tech can support the given source
  17811. *
  17812. * @param {Object} srcObj
  17813. * The source object
  17814. * @param {Object} options
  17815. * The options passed to the tech
  17816. * @return {string} 'probably', 'maybe', or '' (empty string)
  17817. */
  17818. Html5.canPlaySource = function (srcObj, options) {
  17819. return Html5.canPlayType(srcObj.type);
  17820. };
  17821. /**
  17822. * Check if the volume can be changed in this browser/device.
  17823. * Volume cannot be changed in a lot of mobile devices.
  17824. * Specifically, it can't be changed from 1 on iOS.
  17825. *
  17826. * @return {boolean}
  17827. * - True if volume can be controlled
  17828. * - False otherwise
  17829. */
  17830. Html5.canControlVolume = function () {
  17831. // IE will error if Windows Media Player not installed #3315
  17832. try {
  17833. var volume = Html5.TEST_VID.volume;
  17834. Html5.TEST_VID.volume = volume / 2 + 0.1;
  17835. var canControl = volume !== Html5.TEST_VID.volume; // With the introduction of iOS 15, there are cases where the volume is read as
  17836. // changed but reverts back to its original state at the start of the next tick.
  17837. // To determine whether volume can be controlled on iOS,
  17838. // a timeout is set and the volume is checked asynchronously.
  17839. // Since `features` doesn't currently work asynchronously, the value is manually set.
  17840. if (canControl && IS_IOS) {
  17841. window$1.setTimeout(function () {
  17842. if (Html5 && Html5.prototype) {
  17843. Html5.prototype.featuresVolumeControl = volume !== Html5.TEST_VID.volume;
  17844. }
  17845. }); // default iOS to false, which will be updated in the timeout above.
  17846. return false;
  17847. }
  17848. return canControl;
  17849. } catch (e) {
  17850. return false;
  17851. }
  17852. };
  17853. /**
  17854. * Check if the volume can be muted in this browser/device.
  17855. * Some devices, e.g. iOS, don't allow changing volume
  17856. * but permits muting/unmuting.
  17857. *
  17858. * @return {bolean}
  17859. * - True if volume can be muted
  17860. * - False otherwise
  17861. */
  17862. Html5.canMuteVolume = function () {
  17863. try {
  17864. var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always
  17865. // work, so we want to set both property and attribute
  17866. Html5.TEST_VID.muted = !muted;
  17867. if (Html5.TEST_VID.muted) {
  17868. setAttribute(Html5.TEST_VID, 'muted', 'muted');
  17869. } else {
  17870. removeAttribute(Html5.TEST_VID, 'muted', 'muted');
  17871. }
  17872. return muted !== Html5.TEST_VID.muted;
  17873. } catch (e) {
  17874. return false;
  17875. }
  17876. };
  17877. /**
  17878. * Check if the playback rate can be changed in this browser/device.
  17879. *
  17880. * @return {boolean}
  17881. * - True if playback rate can be controlled
  17882. * - False otherwise
  17883. */
  17884. Html5.canControlPlaybackRate = function () {
  17885. // Playback rate API is implemented in Android Chrome, but doesn't do anything
  17886. // https://github.com/videojs/video.js/issues/3180
  17887. if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
  17888. return false;
  17889. } // IE will error if Windows Media Player not installed #3315
  17890. try {
  17891. var playbackRate = Html5.TEST_VID.playbackRate;
  17892. Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
  17893. return playbackRate !== Html5.TEST_VID.playbackRate;
  17894. } catch (e) {
  17895. return false;
  17896. }
  17897. };
  17898. /**
  17899. * Check if we can override a video/audio elements attributes, with
  17900. * Object.defineProperty.
  17901. *
  17902. * @return {boolean}
  17903. * - True if builtin attributes can be overridden
  17904. * - False otherwise
  17905. */
  17906. Html5.canOverrideAttributes = function () {
  17907. // if we cannot overwrite the src/innerHTML property, there is no support
  17908. // iOS 7 safari for instance cannot do this.
  17909. try {
  17910. var noop = function noop() {};
  17911. Object.defineProperty(document.createElement('video'), 'src', {
  17912. get: noop,
  17913. set: noop
  17914. });
  17915. Object.defineProperty(document.createElement('audio'), 'src', {
  17916. get: noop,
  17917. set: noop
  17918. });
  17919. Object.defineProperty(document.createElement('video'), 'innerHTML', {
  17920. get: noop,
  17921. set: noop
  17922. });
  17923. Object.defineProperty(document.createElement('audio'), 'innerHTML', {
  17924. get: noop,
  17925. set: noop
  17926. });
  17927. } catch (e) {
  17928. return false;
  17929. }
  17930. return true;
  17931. };
  17932. /**
  17933. * Check to see if native `TextTrack`s are supported by this browser/device.
  17934. *
  17935. * @return {boolean}
  17936. * - True if native `TextTrack`s are supported.
  17937. * - False otherwise
  17938. */
  17939. Html5.supportsNativeTextTracks = function () {
  17940. return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
  17941. };
  17942. /**
  17943. * Check to see if native `VideoTrack`s are supported by this browser/device
  17944. *
  17945. * @return {boolean}
  17946. * - True if native `VideoTrack`s are supported.
  17947. * - False otherwise
  17948. */
  17949. Html5.supportsNativeVideoTracks = function () {
  17950. return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
  17951. };
  17952. /**
  17953. * Check to see if native `AudioTrack`s are supported by this browser/device
  17954. *
  17955. * @return {boolean}
  17956. * - True if native `AudioTrack`s are supported.
  17957. * - False otherwise
  17958. */
  17959. Html5.supportsNativeAudioTracks = function () {
  17960. return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
  17961. };
  17962. /**
  17963. * An array of events available on the Html5 tech.
  17964. *
  17965. * @private
  17966. * @type {Array}
  17967. */
  17968. Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'resize', 'volumechange'];
  17969. /**
  17970. * Boolean indicating whether the `Tech` supports volume control.
  17971. *
  17972. * @type {boolean}
  17973. * @default {@link Html5.canControlVolume}
  17974. */
  17975. /**
  17976. * Boolean indicating whether the `Tech` supports muting volume.
  17977. *
  17978. * @type {bolean}
  17979. * @default {@link Html5.canMuteVolume}
  17980. */
  17981. /**
  17982. * Boolean indicating whether the `Tech` supports changing the speed at which the media
  17983. * plays. Examples:
  17984. * - Set player to play 2x (twice) as fast
  17985. * - Set player to play 0.5x (half) as fast
  17986. *
  17987. * @type {boolean}
  17988. * @default {@link Html5.canControlPlaybackRate}
  17989. */
  17990. /**
  17991. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  17992. *
  17993. * @type {boolean}
  17994. * @default
  17995. */
  17996. /**
  17997. * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
  17998. *
  17999. * @type {boolean}
  18000. * @default {@link Html5.supportsNativeTextTracks}
  18001. */
  18002. /**
  18003. * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
  18004. *
  18005. * @type {boolean}
  18006. * @default {@link Html5.supportsNativeVideoTracks}
  18007. */
  18008. /**
  18009. * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
  18010. *
  18011. * @type {boolean}
  18012. * @default {@link Html5.supportsNativeAudioTracks}
  18013. */
  18014. [['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function (_ref) {
  18015. var key = _ref[0],
  18016. fn = _ref[1];
  18017. defineLazyProperty(Html5.prototype, key, function () {
  18018. return Html5[fn]();
  18019. }, true);
  18020. });
  18021. Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
  18022. /**
  18023. * Boolean indicating whether the `HTML5` tech currently supports the media element
  18024. * moving in the DOM. iOS breaks if you move the media element, so this is set this to
  18025. * false there. Everywhere else this should be true.
  18026. *
  18027. * @type {boolean}
  18028. * @default
  18029. */
  18030. Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed.
  18031. // Is this true?
  18032. /**
  18033. * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
  18034. * when going into fullscreen.
  18035. *
  18036. * @type {boolean}
  18037. * @default
  18038. */
  18039. Html5.prototype.featuresFullscreenResize = true;
  18040. /**
  18041. * Boolean indicating whether the `HTML5` tech currently supports the progress event.
  18042. * If this is false, manual `progress` events will be triggered instead.
  18043. *
  18044. * @type {boolean}
  18045. * @default
  18046. */
  18047. Html5.prototype.featuresProgressEvents = true;
  18048. /**
  18049. * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
  18050. * If this is false, manual `timeupdate` events will be triggered instead.
  18051. *
  18052. * @default
  18053. */
  18054. Html5.prototype.featuresTimeupdateEvents = true;
  18055. /**
  18056. * Whether the HTML5 el supports `requestVideoFrameCallback`
  18057. *
  18058. * @type {boolean}
  18059. */
  18060. Html5.prototype.featuresVideoFrameCallback = !!(Html5.TEST_VID && Html5.TEST_VID.requestVideoFrameCallback); // HTML5 Feature detection and Device Fixes --------------------------------- //
  18061. var canPlayType;
  18062. Html5.patchCanPlayType = function () {
  18063. // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
  18064. // Firefox and Chrome report correctly
  18065. if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
  18066. canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
  18067. Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
  18068. var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
  18069. if (type && mpegurlRE.test(type)) {
  18070. return 'maybe';
  18071. }
  18072. return canPlayType.call(this, type);
  18073. };
  18074. }
  18075. };
  18076. Html5.unpatchCanPlayType = function () {
  18077. var r = Html5.TEST_VID.constructor.prototype.canPlayType;
  18078. if (canPlayType) {
  18079. Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
  18080. }
  18081. return r;
  18082. }; // by default, patch the media element
  18083. Html5.patchCanPlayType();
  18084. Html5.disposeMediaElement = function (el) {
  18085. if (!el) {
  18086. return;
  18087. }
  18088. if (el.parentNode) {
  18089. el.parentNode.removeChild(el);
  18090. } // remove any child track or source nodes to prevent their loading
  18091. while (el.hasChildNodes()) {
  18092. el.removeChild(el.firstChild);
  18093. } // remove any src reference. not setting `src=''` because that causes a warning
  18094. // in firefox
  18095. el.removeAttribute('src'); // force the media element to update its loading state by calling load()
  18096. // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
  18097. if (typeof el.load === 'function') {
  18098. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  18099. (function () {
  18100. try {
  18101. el.load();
  18102. } catch (e) {// not supported
  18103. }
  18104. })();
  18105. }
  18106. };
  18107. Html5.resetMediaElement = function (el) {
  18108. if (!el) {
  18109. return;
  18110. }
  18111. var sources = el.querySelectorAll('source');
  18112. var i = sources.length;
  18113. while (i--) {
  18114. el.removeChild(sources[i]);
  18115. } // remove any src reference.
  18116. // not setting `src=''` because that throws an error
  18117. el.removeAttribute('src');
  18118. if (typeof el.load === 'function') {
  18119. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  18120. (function () {
  18121. try {
  18122. el.load();
  18123. } catch (e) {// satisfy linter
  18124. }
  18125. })();
  18126. }
  18127. };
  18128. /* Native HTML5 element property wrapping ----------------------------------- */
  18129. // Wrap native boolean attributes with getters that check both property and attribute
  18130. // The list is as followed:
  18131. // muted, defaultMuted, autoplay, controls, loop, playsinline
  18132. [
  18133. /**
  18134. * Get the value of `muted` from the media element. `muted` indicates
  18135. * that the volume for the media should be set to silent. This does not actually change
  18136. * the `volume` attribute.
  18137. *
  18138. * @method Html5#muted
  18139. * @return {boolean}
  18140. * - True if the value of `volume` should be ignored and the audio set to silent.
  18141. * - False if the value of `volume` should be used.
  18142. *
  18143. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  18144. */
  18145. 'muted',
  18146. /**
  18147. * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
  18148. * whether the media should start muted or not. Only changes the default state of the
  18149. * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
  18150. * current state.
  18151. *
  18152. * @method Html5#defaultMuted
  18153. * @return {boolean}
  18154. * - The value of `defaultMuted` from the media element.
  18155. * - True indicates that the media should start muted.
  18156. * - False indicates that the media should not start muted
  18157. *
  18158. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  18159. */
  18160. 'defaultMuted',
  18161. /**
  18162. * Get the value of `autoplay` from the media element. `autoplay` indicates
  18163. * that the media should start to play as soon as the page is ready.
  18164. *
  18165. * @method Html5#autoplay
  18166. * @return {boolean}
  18167. * - The value of `autoplay` from the media element.
  18168. * - True indicates that the media should start as soon as the page loads.
  18169. * - False indicates that the media should not start as soon as the page loads.
  18170. *
  18171. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  18172. */
  18173. 'autoplay',
  18174. /**
  18175. * Get the value of `controls` from the media element. `controls` indicates
  18176. * whether the native media controls should be shown or hidden.
  18177. *
  18178. * @method Html5#controls
  18179. * @return {boolean}
  18180. * - The value of `controls` from the media element.
  18181. * - True indicates that native controls should be showing.
  18182. * - False indicates that native controls should be hidden.
  18183. *
  18184. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
  18185. */
  18186. 'controls',
  18187. /**
  18188. * Get the value of `loop` from the media element. `loop` indicates
  18189. * that the media should return to the start of the media and continue playing once
  18190. * it reaches the end.
  18191. *
  18192. * @method Html5#loop
  18193. * @return {boolean}
  18194. * - The value of `loop` from the media element.
  18195. * - True indicates that playback should seek back to start once
  18196. * the end of a media is reached.
  18197. * - False indicates that playback should not loop back to the start when the
  18198. * end of the media is reached.
  18199. *
  18200. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  18201. */
  18202. 'loop',
  18203. /**
  18204. * Get the value of `playsinline` from the media element. `playsinline` indicates
  18205. * to the browser that non-fullscreen playback is preferred when fullscreen
  18206. * playback is the native default, such as in iOS Safari.
  18207. *
  18208. * @method Html5#playsinline
  18209. * @return {boolean}
  18210. * - The value of `playsinline` from the media element.
  18211. * - True indicates that the media should play inline.
  18212. * - False indicates that the media should not play inline.
  18213. *
  18214. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  18215. */
  18216. 'playsinline'].forEach(function (prop) {
  18217. Html5.prototype[prop] = function () {
  18218. return this.el_[prop] || this.el_.hasAttribute(prop);
  18219. };
  18220. }); // Wrap native boolean attributes with setters that set both property and attribute
  18221. // The list is as followed:
  18222. // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
  18223. // setControls is special-cased above
  18224. [
  18225. /**
  18226. * Set the value of `muted` on the media element. `muted` indicates that the current
  18227. * audio level should be silent.
  18228. *
  18229. * @method Html5#setMuted
  18230. * @param {boolean} muted
  18231. * - True if the audio should be set to silent
  18232. * - False otherwise
  18233. *
  18234. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  18235. */
  18236. 'muted',
  18237. /**
  18238. * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
  18239. * audio level should be silent, but will only effect the muted level on initial playback..
  18240. *
  18241. * @method Html5.prototype.setDefaultMuted
  18242. * @param {boolean} defaultMuted
  18243. * - True if the audio should be set to silent
  18244. * - False otherwise
  18245. *
  18246. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  18247. */
  18248. 'defaultMuted',
  18249. /**
  18250. * Set the value of `autoplay` on the media element. `autoplay` indicates
  18251. * that the media should start to play as soon as the page is ready.
  18252. *
  18253. * @method Html5#setAutoplay
  18254. * @param {boolean} autoplay
  18255. * - True indicates that the media should start as soon as the page loads.
  18256. * - False indicates that the media should not start as soon as the page loads.
  18257. *
  18258. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  18259. */
  18260. 'autoplay',
  18261. /**
  18262. * Set the value of `loop` on the media element. `loop` indicates
  18263. * that the media should return to the start of the media and continue playing once
  18264. * it reaches the end.
  18265. *
  18266. * @method Html5#setLoop
  18267. * @param {boolean} loop
  18268. * - True indicates that playback should seek back to start once
  18269. * the end of a media is reached.
  18270. * - False indicates that playback should not loop back to the start when the
  18271. * end of the media is reached.
  18272. *
  18273. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  18274. */
  18275. 'loop',
  18276. /**
  18277. * Set the value of `playsinline` from the media element. `playsinline` indicates
  18278. * to the browser that non-fullscreen playback is preferred when fullscreen
  18279. * playback is the native default, such as in iOS Safari.
  18280. *
  18281. * @method Html5#setPlaysinline
  18282. * @param {boolean} playsinline
  18283. * - True indicates that the media should play inline.
  18284. * - False indicates that the media should not play inline.
  18285. *
  18286. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  18287. */
  18288. 'playsinline'].forEach(function (prop) {
  18289. Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
  18290. this.el_[prop] = v;
  18291. if (v) {
  18292. this.el_.setAttribute(prop, prop);
  18293. } else {
  18294. this.el_.removeAttribute(prop);
  18295. }
  18296. };
  18297. }); // Wrap native properties with a getter
  18298. // The list is as followed
  18299. // paused, currentTime, buffered, volume, poster, preload, error, seeking
  18300. // seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture
  18301. // played, networkState, readyState, videoWidth, videoHeight, crossOrigin
  18302. [
  18303. /**
  18304. * Get the value of `paused` from the media element. `paused` indicates whether the media element
  18305. * is currently paused or not.
  18306. *
  18307. * @method Html5#paused
  18308. * @return {boolean}
  18309. * The value of `paused` from the media element.
  18310. *
  18311. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
  18312. */
  18313. 'paused',
  18314. /**
  18315. * Get the value of `currentTime` from the media element. `currentTime` indicates
  18316. * the current second that the media is at in playback.
  18317. *
  18318. * @method Html5#currentTime
  18319. * @return {number}
  18320. * The value of `currentTime` from the media element.
  18321. *
  18322. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
  18323. */
  18324. 'currentTime',
  18325. /**
  18326. * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
  18327. * object that represents the parts of the media that are already downloaded and
  18328. * available for playback.
  18329. *
  18330. * @method Html5#buffered
  18331. * @return {TimeRange}
  18332. * The value of `buffered` from the media element.
  18333. *
  18334. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
  18335. */
  18336. 'buffered',
  18337. /**
  18338. * Get the value of `volume` from the media element. `volume` indicates
  18339. * the current playback volume of audio for a media. `volume` will be a value from 0
  18340. * (silent) to 1 (loudest and default).
  18341. *
  18342. * @method Html5#volume
  18343. * @return {number}
  18344. * The value of `volume` from the media element. Value will be between 0-1.
  18345. *
  18346. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  18347. */
  18348. 'volume',
  18349. /**
  18350. * Get the value of `poster` from the media element. `poster` indicates
  18351. * that the url of an image file that can/will be shown when no media data is available.
  18352. *
  18353. * @method Html5#poster
  18354. * @return {string}
  18355. * The value of `poster` from the media element. Value will be a url to an
  18356. * image.
  18357. *
  18358. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
  18359. */
  18360. 'poster',
  18361. /**
  18362. * Get the value of `preload` from the media element. `preload` indicates
  18363. * what should download before the media is interacted with. It can have the following
  18364. * values:
  18365. * - none: nothing should be downloaded
  18366. * - metadata: poster and the first few frames of the media may be downloaded to get
  18367. * media dimensions and other metadata
  18368. * - auto: allow the media and metadata for the media to be downloaded before
  18369. * interaction
  18370. *
  18371. * @method Html5#preload
  18372. * @return {string}
  18373. * The value of `preload` from the media element. Will be 'none', 'metadata',
  18374. * or 'auto'.
  18375. *
  18376. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  18377. */
  18378. 'preload',
  18379. /**
  18380. * Get the value of the `error` from the media element. `error` indicates any
  18381. * MediaError that may have occurred during playback. If error returns null there is no
  18382. * current error.
  18383. *
  18384. * @method Html5#error
  18385. * @return {MediaError|null}
  18386. * The value of `error` from the media element. Will be `MediaError` if there
  18387. * is a current error and null otherwise.
  18388. *
  18389. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
  18390. */
  18391. 'error',
  18392. /**
  18393. * Get the value of `seeking` from the media element. `seeking` indicates whether the
  18394. * media is currently seeking to a new position or not.
  18395. *
  18396. * @method Html5#seeking
  18397. * @return {boolean}
  18398. * - The value of `seeking` from the media element.
  18399. * - True indicates that the media is currently seeking to a new position.
  18400. * - False indicates that the media is not seeking to a new position at this time.
  18401. *
  18402. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
  18403. */
  18404. 'seeking',
  18405. /**
  18406. * Get the value of `seekable` from the media element. `seekable` returns a
  18407. * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
  18408. *
  18409. * @method Html5#seekable
  18410. * @return {TimeRange}
  18411. * The value of `seekable` from the media element. A `TimeRange` object
  18412. * indicating the current ranges of time that can be seeked to.
  18413. *
  18414. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
  18415. */
  18416. 'seekable',
  18417. /**
  18418. * Get the value of `ended` from the media element. `ended` indicates whether
  18419. * the media has reached the end or not.
  18420. *
  18421. * @method Html5#ended
  18422. * @return {boolean}
  18423. * - The value of `ended` from the media element.
  18424. * - True indicates that the media has ended.
  18425. * - False indicates that the media has not ended.
  18426. *
  18427. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
  18428. */
  18429. 'ended',
  18430. /**
  18431. * Get the value of `playbackRate` from the media element. `playbackRate` indicates
  18432. * the rate at which the media is currently playing back. Examples:
  18433. * - if playbackRate is set to 2, media will play twice as fast.
  18434. * - if playbackRate is set to 0.5, media will play half as fast.
  18435. *
  18436. * @method Html5#playbackRate
  18437. * @return {number}
  18438. * The value of `playbackRate` from the media element. A number indicating
  18439. * the current playback speed of the media, where 1 is normal speed.
  18440. *
  18441. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  18442. */
  18443. 'playbackRate',
  18444. /**
  18445. * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
  18446. * the rate at which the media is currently playing back. This value will not indicate the current
  18447. * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
  18448. *
  18449. * Examples:
  18450. * - if defaultPlaybackRate is set to 2, media will play twice as fast.
  18451. * - if defaultPlaybackRate is set to 0.5, media will play half as fast.
  18452. *
  18453. * @method Html5.prototype.defaultPlaybackRate
  18454. * @return {number}
  18455. * The value of `defaultPlaybackRate` from the media element. A number indicating
  18456. * the current playback speed of the media, where 1 is normal speed.
  18457. *
  18458. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  18459. */
  18460. 'defaultPlaybackRate',
  18461. /**
  18462. * Get the value of 'disablePictureInPicture' from the video element.
  18463. *
  18464. * @method Html5#disablePictureInPicture
  18465. * @return {boolean} value
  18466. * - The value of `disablePictureInPicture` from the video element.
  18467. * - True indicates that the video can't be played in Picture-In-Picture mode
  18468. * - False indicates that the video can be played in Picture-In-Picture mode
  18469. *
  18470. * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
  18471. */
  18472. 'disablePictureInPicture',
  18473. /**
  18474. * Get the value of `played` from the media element. `played` returns a `TimeRange`
  18475. * object representing points in the media timeline that have been played.
  18476. *
  18477. * @method Html5#played
  18478. * @return {TimeRange}
  18479. * The value of `played` from the media element. A `TimeRange` object indicating
  18480. * the ranges of time that have been played.
  18481. *
  18482. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
  18483. */
  18484. 'played',
  18485. /**
  18486. * Get the value of `networkState` from the media element. `networkState` indicates
  18487. * the current network state. It returns an enumeration from the following list:
  18488. * - 0: NETWORK_EMPTY
  18489. * - 1: NETWORK_IDLE
  18490. * - 2: NETWORK_LOADING
  18491. * - 3: NETWORK_NO_SOURCE
  18492. *
  18493. * @method Html5#networkState
  18494. * @return {number}
  18495. * The value of `networkState` from the media element. This will be a number
  18496. * from the list in the description.
  18497. *
  18498. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
  18499. */
  18500. 'networkState',
  18501. /**
  18502. * Get the value of `readyState` from the media element. `readyState` indicates
  18503. * the current state of the media element. It returns an enumeration from the
  18504. * following list:
  18505. * - 0: HAVE_NOTHING
  18506. * - 1: HAVE_METADATA
  18507. * - 2: HAVE_CURRENT_DATA
  18508. * - 3: HAVE_FUTURE_DATA
  18509. * - 4: HAVE_ENOUGH_DATA
  18510. *
  18511. * @method Html5#readyState
  18512. * @return {number}
  18513. * The value of `readyState` from the media element. This will be a number
  18514. * from the list in the description.
  18515. *
  18516. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
  18517. */
  18518. 'readyState',
  18519. /**
  18520. * Get the value of `videoWidth` from the video element. `videoWidth` indicates
  18521. * the current width of the video in css pixels.
  18522. *
  18523. * @method Html5#videoWidth
  18524. * @return {number}
  18525. * The value of `videoWidth` from the video element. This will be a number
  18526. * in css pixels.
  18527. *
  18528. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  18529. */
  18530. 'videoWidth',
  18531. /**
  18532. * Get the value of `videoHeight` from the video element. `videoHeight` indicates
  18533. * the current height of the video in css pixels.
  18534. *
  18535. * @method Html5#videoHeight
  18536. * @return {number}
  18537. * The value of `videoHeight` from the video element. This will be a number
  18538. * in css pixels.
  18539. *
  18540. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  18541. */
  18542. 'videoHeight',
  18543. /**
  18544. * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates
  18545. * to the browser that should sent the cookies along with the requests for the
  18546. * different assets/playlists
  18547. *
  18548. * @method Html5#crossOrigin
  18549. * @return {string}
  18550. * - anonymous indicates that the media should not sent cookies.
  18551. * - use-credentials indicates that the media should sent cookies along the requests.
  18552. *
  18553. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
  18554. */
  18555. 'crossOrigin'].forEach(function (prop) {
  18556. Html5.prototype[prop] = function () {
  18557. return this.el_[prop];
  18558. };
  18559. }); // Wrap native properties with a setter in this format:
  18560. // set + toTitleCase(name)
  18561. // The list is as follows:
  18562. // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
  18563. // setDisablePictureInPicture, setCrossOrigin
  18564. [
  18565. /**
  18566. * Set the value of `volume` on the media element. `volume` indicates the current
  18567. * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
  18568. * so on.
  18569. *
  18570. * @method Html5#setVolume
  18571. * @param {number} percentAsDecimal
  18572. * The volume percent as a decimal. Valid range is from 0-1.
  18573. *
  18574. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  18575. */
  18576. 'volume',
  18577. /**
  18578. * Set the value of `src` on the media element. `src` indicates the current
  18579. * {@link Tech~SourceObject} for the media.
  18580. *
  18581. * @method Html5#setSrc
  18582. * @param {Tech~SourceObject} src
  18583. * The source object to set as the current source.
  18584. *
  18585. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
  18586. */
  18587. 'src',
  18588. /**
  18589. * Set the value of `poster` on the media element. `poster` is the url to
  18590. * an image file that can/will be shown when no media data is available.
  18591. *
  18592. * @method Html5#setPoster
  18593. * @param {string} poster
  18594. * The url to an image that should be used as the `poster` for the media
  18595. * element.
  18596. *
  18597. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
  18598. */
  18599. 'poster',
  18600. /**
  18601. * Set the value of `preload` on the media element. `preload` indicates
  18602. * what should download before the media is interacted with. It can have the following
  18603. * values:
  18604. * - none: nothing should be downloaded
  18605. * - metadata: poster and the first few frames of the media may be downloaded to get
  18606. * media dimensions and other metadata
  18607. * - auto: allow the media and metadata for the media to be downloaded before
  18608. * interaction
  18609. *
  18610. * @method Html5#setPreload
  18611. * @param {string} preload
  18612. * The value of `preload` to set on the media element. Must be 'none', 'metadata',
  18613. * or 'auto'.
  18614. *
  18615. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  18616. */
  18617. 'preload',
  18618. /**
  18619. * Set the value of `playbackRate` on the media element. `playbackRate` indicates
  18620. * the rate at which the media should play back. Examples:
  18621. * - if playbackRate is set to 2, media will play twice as fast.
  18622. * - if playbackRate is set to 0.5, media will play half as fast.
  18623. *
  18624. * @method Html5#setPlaybackRate
  18625. * @return {number}
  18626. * The value of `playbackRate` from the media element. A number indicating
  18627. * the current playback speed of the media, where 1 is normal speed.
  18628. *
  18629. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  18630. */
  18631. 'playbackRate',
  18632. /**
  18633. * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
  18634. * the rate at which the media should play back upon initial startup. Changing this value
  18635. * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
  18636. *
  18637. * Example Values:
  18638. * - if playbackRate is set to 2, media will play twice as fast.
  18639. * - if playbackRate is set to 0.5, media will play half as fast.
  18640. *
  18641. * @method Html5.prototype.setDefaultPlaybackRate
  18642. * @return {number}
  18643. * The value of `defaultPlaybackRate` from the media element. A number indicating
  18644. * the current playback speed of the media, where 1 is normal speed.
  18645. *
  18646. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
  18647. */
  18648. 'defaultPlaybackRate',
  18649. /**
  18650. * Prevents the browser from suggesting a Picture-in-Picture context menu
  18651. * or to request Picture-in-Picture automatically in some cases.
  18652. *
  18653. * @method Html5#setDisablePictureInPicture
  18654. * @param {boolean} value
  18655. * The true value will disable Picture-in-Picture mode.
  18656. *
  18657. * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
  18658. */
  18659. 'disablePictureInPicture',
  18660. /**
  18661. * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates
  18662. * to the browser that should sent the cookies along with the requests for the
  18663. * different assets/playlists
  18664. *
  18665. * @method Html5#setCrossOrigin
  18666. * @param {string} crossOrigin
  18667. * - anonymous indicates that the media should not sent cookies.
  18668. * - use-credentials indicates that the media should sent cookies along the requests.
  18669. *
  18670. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
  18671. */
  18672. 'crossOrigin'].forEach(function (prop) {
  18673. Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
  18674. this.el_[prop] = v;
  18675. };
  18676. }); // wrap native functions with a function
  18677. // The list is as follows:
  18678. // pause, load, play
  18679. [
  18680. /**
  18681. * A wrapper around the media elements `pause` function. This will call the `HTML5`
  18682. * media elements `pause` function.
  18683. *
  18684. * @method Html5#pause
  18685. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
  18686. */
  18687. 'pause',
  18688. /**
  18689. * A wrapper around the media elements `load` function. This will call the `HTML5`s
  18690. * media element `load` function.
  18691. *
  18692. * @method Html5#load
  18693. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
  18694. */
  18695. 'load',
  18696. /**
  18697. * A wrapper around the media elements `play` function. This will call the `HTML5`s
  18698. * media element `play` function.
  18699. *
  18700. * @method Html5#play
  18701. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
  18702. */
  18703. 'play'].forEach(function (prop) {
  18704. Html5.prototype[prop] = function () {
  18705. return this.el_[prop]();
  18706. };
  18707. });
  18708. Tech.withSourceHandlers(Html5);
  18709. /**
  18710. * Native source handler for Html5, simply passes the source to the media element.
  18711. *
  18712. * @property {Tech~SourceObject} source
  18713. * The source object
  18714. *
  18715. * @property {Html5} tech
  18716. * The instance of the HTML5 tech.
  18717. */
  18718. Html5.nativeSourceHandler = {};
  18719. /**
  18720. * Check if the media element can play the given mime type.
  18721. *
  18722. * @param {string} type
  18723. * The mimetype to check
  18724. *
  18725. * @return {string}
  18726. * 'probably', 'maybe', or '' (empty string)
  18727. */
  18728. Html5.nativeSourceHandler.canPlayType = function (type) {
  18729. // IE without MediaPlayer throws an error (#519)
  18730. try {
  18731. return Html5.TEST_VID.canPlayType(type);
  18732. } catch (e) {
  18733. return '';
  18734. }
  18735. };
  18736. /**
  18737. * Check if the media element can handle a source natively.
  18738. *
  18739. * @param {Tech~SourceObject} source
  18740. * The source object
  18741. *
  18742. * @param {Object} [options]
  18743. * Options to be passed to the tech.
  18744. *
  18745. * @return {string}
  18746. * 'probably', 'maybe', or '' (empty string).
  18747. */
  18748. Html5.nativeSourceHandler.canHandleSource = function (source, options) {
  18749. // If a type was provided we should rely on that
  18750. if (source.type) {
  18751. return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]'
  18752. } else if (source.src) {
  18753. var ext = getFileExtension(source.src);
  18754. return Html5.nativeSourceHandler.canPlayType("video/" + ext);
  18755. }
  18756. return '';
  18757. };
  18758. /**
  18759. * Pass the source to the native media element.
  18760. *
  18761. * @param {Tech~SourceObject} source
  18762. * The source object
  18763. *
  18764. * @param {Html5} tech
  18765. * The instance of the Html5 tech
  18766. *
  18767. * @param {Object} [options]
  18768. * The options to pass to the source
  18769. */
  18770. Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
  18771. tech.setSrc(source.src);
  18772. };
  18773. /**
  18774. * A noop for the native dispose function, as cleanup is not needed.
  18775. */
  18776. Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler
  18777. Html5.registerSourceHandler(Html5.nativeSourceHandler);
  18778. Tech.registerTech('Html5', Html5);
  18779. // on the player when they happen
  18780. var TECH_EVENTS_RETRIGGER = [
  18781. /**
  18782. * Fired while the user agent is downloading media data.
  18783. *
  18784. * @event Player#progress
  18785. * @type {EventTarget~Event}
  18786. */
  18787. /**
  18788. * Retrigger the `progress` event that was triggered by the {@link Tech}.
  18789. *
  18790. * @private
  18791. * @method Player#handleTechProgress_
  18792. * @fires Player#progress
  18793. * @listens Tech#progress
  18794. */
  18795. 'progress',
  18796. /**
  18797. * Fires when the loading of an audio/video is aborted.
  18798. *
  18799. * @event Player#abort
  18800. * @type {EventTarget~Event}
  18801. */
  18802. /**
  18803. * Retrigger the `abort` event that was triggered by the {@link Tech}.
  18804. *
  18805. * @private
  18806. * @method Player#handleTechAbort_
  18807. * @fires Player#abort
  18808. * @listens Tech#abort
  18809. */
  18810. 'abort',
  18811. /**
  18812. * Fires when the browser is intentionally not getting media data.
  18813. *
  18814. * @event Player#suspend
  18815. * @type {EventTarget~Event}
  18816. */
  18817. /**
  18818. * Retrigger the `suspend` event that was triggered by the {@link Tech}.
  18819. *
  18820. * @private
  18821. * @method Player#handleTechSuspend_
  18822. * @fires Player#suspend
  18823. * @listens Tech#suspend
  18824. */
  18825. 'suspend',
  18826. /**
  18827. * Fires when the current playlist is empty.
  18828. *
  18829. * @event Player#emptied
  18830. * @type {EventTarget~Event}
  18831. */
  18832. /**
  18833. * Retrigger the `emptied` event that was triggered by the {@link Tech}.
  18834. *
  18835. * @private
  18836. * @method Player#handleTechEmptied_
  18837. * @fires Player#emptied
  18838. * @listens Tech#emptied
  18839. */
  18840. 'emptied',
  18841. /**
  18842. * Fires when the browser is trying to get media data, but data is not available.
  18843. *
  18844. * @event Player#stalled
  18845. * @type {EventTarget~Event}
  18846. */
  18847. /**
  18848. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  18849. *
  18850. * @private
  18851. * @method Player#handleTechStalled_
  18852. * @fires Player#stalled
  18853. * @listens Tech#stalled
  18854. */
  18855. 'stalled',
  18856. /**
  18857. * Fires when the browser has loaded meta data for the audio/video.
  18858. *
  18859. * @event Player#loadedmetadata
  18860. * @type {EventTarget~Event}
  18861. */
  18862. /**
  18863. * Retrigger the `loadedmetadata` event that was triggered by the {@link Tech}.
  18864. *
  18865. * @private
  18866. * @method Player#handleTechLoadedmetadata_
  18867. * @fires Player#loadedmetadata
  18868. * @listens Tech#loadedmetadata
  18869. */
  18870. 'loadedmetadata',
  18871. /**
  18872. * Fires when the browser has loaded the current frame of the audio/video.
  18873. *
  18874. * @event Player#loadeddata
  18875. * @type {event}
  18876. */
  18877. /**
  18878. * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
  18879. *
  18880. * @private
  18881. * @method Player#handleTechLoaddeddata_
  18882. * @fires Player#loadeddata
  18883. * @listens Tech#loadeddata
  18884. */
  18885. 'loadeddata',
  18886. /**
  18887. * Fires when the current playback position has changed.
  18888. *
  18889. * @event Player#timeupdate
  18890. * @type {event}
  18891. */
  18892. /**
  18893. * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
  18894. *
  18895. * @private
  18896. * @method Player#handleTechTimeUpdate_
  18897. * @fires Player#timeupdate
  18898. * @listens Tech#timeupdate
  18899. */
  18900. 'timeupdate',
  18901. /**
  18902. * Fires when the video's intrinsic dimensions change
  18903. *
  18904. * @event Player#resize
  18905. * @type {event}
  18906. */
  18907. /**
  18908. * Retrigger the `resize` event that was triggered by the {@link Tech}.
  18909. *
  18910. * @private
  18911. * @method Player#handleTechResize_
  18912. * @fires Player#resize
  18913. * @listens Tech#resize
  18914. */
  18915. 'resize',
  18916. /**
  18917. * Fires when the volume has been changed
  18918. *
  18919. * @event Player#volumechange
  18920. * @type {event}
  18921. */
  18922. /**
  18923. * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
  18924. *
  18925. * @private
  18926. * @method Player#handleTechVolumechange_
  18927. * @fires Player#volumechange
  18928. * @listens Tech#volumechange
  18929. */
  18930. 'volumechange',
  18931. /**
  18932. * Fires when the text track has been changed
  18933. *
  18934. * @event Player#texttrackchange
  18935. * @type {event}
  18936. */
  18937. /**
  18938. * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
  18939. *
  18940. * @private
  18941. * @method Player#handleTechTexttrackchange_
  18942. * @fires Player#texttrackchange
  18943. * @listens Tech#texttrackchange
  18944. */
  18945. 'texttrackchange']; // events to queue when playback rate is zero
  18946. // this is a hash for the sole purpose of mapping non-camel-cased event names
  18947. // to camel-cased function names
  18948. var TECH_EVENTS_QUEUE = {
  18949. canplay: 'CanPlay',
  18950. canplaythrough: 'CanPlayThrough',
  18951. playing: 'Playing',
  18952. seeked: 'Seeked'
  18953. };
  18954. var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
  18955. var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny
  18956. // grep: vjs-layout-x-small
  18957. // grep: vjs-layout-small
  18958. // grep: vjs-layout-medium
  18959. // grep: vjs-layout-large
  18960. // grep: vjs-layout-x-large
  18961. // grep: vjs-layout-huge
  18962. BREAKPOINT_ORDER.forEach(function (k) {
  18963. var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k;
  18964. BREAKPOINT_CLASSES[k] = "vjs-layout-" + v;
  18965. });
  18966. var DEFAULT_BREAKPOINTS = {
  18967. tiny: 210,
  18968. xsmall: 320,
  18969. small: 425,
  18970. medium: 768,
  18971. large: 1440,
  18972. xlarge: 2560,
  18973. huge: Infinity
  18974. };
  18975. /**
  18976. * An instance of the `Player` class is created when any of the Video.js setup methods
  18977. * are used to initialize a video.
  18978. *
  18979. * After an instance has been created it can be accessed globally in two ways:
  18980. * 1. By calling `videojs('example_video_1');`
  18981. * 2. By using it directly via `videojs.players.example_video_1;`
  18982. *
  18983. * @extends Component
  18984. */
  18985. var Player = /*#__PURE__*/function (_Component) {
  18986. _inheritsLoose(Player, _Component);
  18987. /**
  18988. * Create an instance of this class.
  18989. *
  18990. * @param {Element} tag
  18991. * The original video DOM element used for configuring options.
  18992. *
  18993. * @param {Object} [options]
  18994. * Object of option names and values.
  18995. *
  18996. * @param {Component~ReadyCallback} [ready]
  18997. * Ready callback function.
  18998. */
  18999. function Player(tag, options, ready) {
  19000. var _this;
  19001. // Make sure tag ID exists
  19002. tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options
  19003. // The options argument overrides options set in the video tag
  19004. // which overrides globally set options.
  19005. // This latter part coincides with the load order
  19006. // (tag must exist before Player)
  19007. options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up
  19008. // player properties first, and can't use `this` before `super()`
  19009. options.initChildren = false; // Same with creating the element
  19010. options.createEl = false; // don't auto mixin the evented mixin
  19011. options.evented = false; // we don't want the player to report touch activity on itself
  19012. // see enableTouchActivity in Component
  19013. options.reportTouchActivity = false; // If language is not set, get the closest lang attribute
  19014. if (!options.language) {
  19015. if (typeof tag.closest === 'function') {
  19016. var closest = tag.closest('[lang]');
  19017. if (closest && closest.getAttribute) {
  19018. options.language = closest.getAttribute('lang');
  19019. }
  19020. } else {
  19021. var element = tag;
  19022. while (element && element.nodeType === 1) {
  19023. if (getAttributes(element).hasOwnProperty('lang')) {
  19024. options.language = element.getAttribute('lang');
  19025. break;
  19026. }
  19027. element = element.parentNode;
  19028. }
  19029. }
  19030. } // Run base component initializing with new options
  19031. _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners.
  19032. _this.boundDocumentFullscreenChange_ = function (e) {
  19033. return _this.documentFullscreenChange_(e);
  19034. };
  19035. _this.boundFullWindowOnEscKey_ = function (e) {
  19036. return _this.fullWindowOnEscKey(e);
  19037. };
  19038. _this.boundUpdateStyleEl_ = function (e) {
  19039. return _this.updateStyleEl_(e);
  19040. };
  19041. _this.boundApplyInitTime_ = function (e) {
  19042. return _this.applyInitTime_(e);
  19043. };
  19044. _this.boundUpdateCurrentBreakpoint_ = function (e) {
  19045. return _this.updateCurrentBreakpoint_(e);
  19046. };
  19047. _this.boundHandleTechClick_ = function (e) {
  19048. return _this.handleTechClick_(e);
  19049. };
  19050. _this.boundHandleTechDoubleClick_ = function (e) {
  19051. return _this.handleTechDoubleClick_(e);
  19052. };
  19053. _this.boundHandleTechTouchStart_ = function (e) {
  19054. return _this.handleTechTouchStart_(e);
  19055. };
  19056. _this.boundHandleTechTouchMove_ = function (e) {
  19057. return _this.handleTechTouchMove_(e);
  19058. };
  19059. _this.boundHandleTechTouchEnd_ = function (e) {
  19060. return _this.handleTechTouchEnd_(e);
  19061. };
  19062. _this.boundHandleTechTap_ = function (e) {
  19063. return _this.handleTechTap_(e);
  19064. }; // default isFullscreen_ to false
  19065. _this.isFullscreen_ = false; // create logger
  19066. _this.log = createLogger(_this.id_); // Hold our own reference to fullscreen api so it can be mocked in tests
  19067. _this.fsApi_ = FullscreenApi; // Tracks when a tech changes the poster
  19068. _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero
  19069. // and a seek is happening
  19070. _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously
  19071. _this.isReady_ = false; // Init state hasStarted_
  19072. _this.hasStarted_ = false; // Init state userActive_
  19073. _this.userActive_ = false; // Init debugEnabled_
  19074. _this.debugEnabled_ = false; // Init state audioOnlyMode_
  19075. _this.audioOnlyMode_ = false; // Init state audioPosterMode_
  19076. _this.audioPosterMode_ = false; // Init state audioOnlyCache_
  19077. _this.audioOnlyCache_ = {
  19078. playerHeight: null,
  19079. hiddenChildren: []
  19080. }; // if the global option object was accidentally blown away by
  19081. // someone, bail early with an informative error
  19082. if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
  19083. throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
  19084. } // Store the original tag used to set options
  19085. _this.tag = tag; // Store the tag attributes used to restore html5 element
  19086. _this.tagAttributes = tag && getAttributes(tag); // Update current language
  19087. _this.language(_this.options_.language); // Update Supported Languages
  19088. if (options.languages) {
  19089. // Normalise player option languages to lowercase
  19090. var languagesToLower = {};
  19091. Object.getOwnPropertyNames(options.languages).forEach(function (name) {
  19092. languagesToLower[name.toLowerCase()] = options.languages[name];
  19093. });
  19094. _this.languages_ = languagesToLower;
  19095. } else {
  19096. _this.languages_ = Player.prototype.options_.languages;
  19097. }
  19098. _this.resetCache_(); // Set poster
  19099. _this.poster_ = options.poster || ''; // Set controls
  19100. _this.controls_ = !!options.controls; // Original tag settings stored in options
  19101. // now remove immediately so native controls don't flash.
  19102. // May be turned back on by HTML5 tech if nativeControlsForTouch is true
  19103. tag.controls = false;
  19104. tag.removeAttribute('controls');
  19105. _this.changingSrc_ = false;
  19106. _this.playCallbacks_ = [];
  19107. _this.playTerminatedQueue_ = []; // the attribute overrides the option
  19108. if (tag.hasAttribute('autoplay')) {
  19109. _this.autoplay(true);
  19110. } else {
  19111. // otherwise use the setter to validate and
  19112. // set the correct value.
  19113. _this.autoplay(_this.options_.autoplay);
  19114. } // check plugins
  19115. if (options.plugins) {
  19116. Object.keys(options.plugins).forEach(function (name) {
  19117. if (typeof _this[name] !== 'function') {
  19118. throw new Error("plugin \"" + name + "\" does not exist");
  19119. }
  19120. });
  19121. }
  19122. /*
  19123. * Store the internal state of scrubbing
  19124. *
  19125. * @private
  19126. * @return {Boolean} True if the user is scrubbing
  19127. */
  19128. _this.scrubbing_ = false;
  19129. _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus.
  19130. evented(_assertThisInitialized(_this), {
  19131. eventBusKey: 'el_'
  19132. }); // listen to document and player fullscreenchange handlers so we receive those events
  19133. // before a user can receive them so we can update isFullscreen appropriately.
  19134. // make sure that we listen to fullscreenchange events before everything else to make sure that
  19135. // our isFullscreen method is updated properly for internal components as well as external.
  19136. if (_this.fsApi_.requestFullscreen) {
  19137. on(document, _this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_);
  19138. _this.on(_this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_);
  19139. }
  19140. if (_this.fluid_) {
  19141. _this.on(['playerreset', 'resize'], _this.boundUpdateStyleEl_);
  19142. } // We also want to pass the original player options to each component and plugin
  19143. // as well so they don't need to reach back into the player for options later.
  19144. // We also need to do another copy of this.options_ so we don't end up with
  19145. // an infinite loop.
  19146. var playerOptionsCopy = mergeOptions$3(_this.options_); // Load plugins
  19147. if (options.plugins) {
  19148. Object.keys(options.plugins).forEach(function (name) {
  19149. _this[name](options.plugins[name]);
  19150. });
  19151. } // Enable debug mode to fire debugon event for all plugins.
  19152. if (options.debug) {
  19153. _this.debug(true);
  19154. }
  19155. _this.options_.playerOptions = playerOptionsCopy;
  19156. _this.middleware_ = [];
  19157. _this.playbackRates(options.playbackRates);
  19158. _this.initChildren(); // Set isAudio based on whether or not an audio tag was used
  19159. _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially
  19160. // set because the element doesn't exist yet.
  19161. if (_this.controls()) {
  19162. _this.addClass('vjs-controls-enabled');
  19163. } else {
  19164. _this.addClass('vjs-controls-disabled');
  19165. } // Set ARIA label and region role depending on player type
  19166. _this.el_.setAttribute('role', 'region');
  19167. if (_this.isAudio()) {
  19168. _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
  19169. } else {
  19170. _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
  19171. }
  19172. if (_this.isAudio()) {
  19173. _this.addClass('vjs-audio');
  19174. }
  19175. if (_this.flexNotSupported_()) {
  19176. _this.addClass('vjs-no-flex');
  19177. } // TODO: Make this smarter. Toggle user state between touching/mousing
  19178. // using events, since devices can have both touch and mouse events.
  19179. // TODO: Make this check be performed again when the window switches between monitors
  19180. // (See https://github.com/videojs/video.js/issues/5683)
  19181. if (TOUCH_ENABLED) {
  19182. _this.addClass('vjs-touch-enabled');
  19183. } // iOS Safari has broken hover handling
  19184. if (!IS_IOS) {
  19185. _this.addClass('vjs-workinghover');
  19186. } // Make player easily findable by ID
  19187. Player.players[_this.id_] = _assertThisInitialized(_this); // Add a major version class to aid css in plugins
  19188. var majorVersion = version$5.split('.')[0];
  19189. _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components
  19190. // like the control bar show themselves if needed
  19191. _this.userActive(true);
  19192. _this.reportUserActivity();
  19193. _this.one('play', function (e) {
  19194. return _this.listenForUserActivity_(e);
  19195. });
  19196. _this.on('stageclick', function (e) {
  19197. return _this.handleStageClick_(e);
  19198. });
  19199. _this.on('keydown', function (e) {
  19200. return _this.handleKeyDown(e);
  19201. });
  19202. _this.on('languagechange', function (e) {
  19203. return _this.handleLanguagechange(e);
  19204. });
  19205. _this.breakpoints(_this.options_.breakpoints);
  19206. _this.responsive(_this.options_.responsive); // Calling both the audio mode methods after the player is fully
  19207. // setup to be able to listen to the events triggered by them
  19208. _this.on('ready', function () {
  19209. // Calling the audioPosterMode method first so that
  19210. // the audioOnlyMode can take precedence when both options are set to true
  19211. _this.audioPosterMode(_this.options_.audioPosterMode);
  19212. _this.audioOnlyMode(_this.options_.audioOnlyMode);
  19213. });
  19214. return _this;
  19215. }
  19216. /**
  19217. * Destroys the video player and does any necessary cleanup.
  19218. *
  19219. * This is especially helpful if you are dynamically adding and removing videos
  19220. * to/from the DOM.
  19221. *
  19222. * @fires Player#dispose
  19223. */
  19224. var _proto = Player.prototype;
  19225. _proto.dispose = function dispose() {
  19226. var _this2 = this;
  19227. /**
  19228. * Called when the player is being disposed of.
  19229. *
  19230. * @event Player#dispose
  19231. * @type {EventTarget~Event}
  19232. */
  19233. this.trigger('dispose'); // prevent dispose from being called twice
  19234. this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is
  19235. off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
  19236. off(document, 'keydown', this.boundFullWindowOnEscKey_);
  19237. if (this.styleEl_ && this.styleEl_.parentNode) {
  19238. this.styleEl_.parentNode.removeChild(this.styleEl_);
  19239. this.styleEl_ = null;
  19240. } // Kill reference to this player
  19241. Player.players[this.id_] = null;
  19242. if (this.tag && this.tag.player) {
  19243. this.tag.player = null;
  19244. }
  19245. if (this.el_ && this.el_.player) {
  19246. this.el_.player = null;
  19247. }
  19248. if (this.tech_) {
  19249. this.tech_.dispose();
  19250. this.isPosterFromTech_ = false;
  19251. this.poster_ = '';
  19252. }
  19253. if (this.playerElIngest_) {
  19254. this.playerElIngest_ = null;
  19255. }
  19256. if (this.tag) {
  19257. this.tag = null;
  19258. }
  19259. clearCacheForPlayer(this); // remove all event handlers for track lists
  19260. // all tracks and track listeners are removed on
  19261. // tech dispose
  19262. ALL.names.forEach(function (name) {
  19263. var props = ALL[name];
  19264. var list = _this2[props.getterName](); // if it is not a native list
  19265. // we have to manually remove event listeners
  19266. if (list && list.off) {
  19267. list.off();
  19268. }
  19269. }); // the actual .el_ is removed here, or replaced if
  19270. _Component.prototype.dispose.call(this, {
  19271. restoreEl: this.options_.restoreEl
  19272. });
  19273. }
  19274. /**
  19275. * Create the `Player`'s DOM element.
  19276. *
  19277. * @return {Element}
  19278. * The DOM element that gets created.
  19279. */
  19280. ;
  19281. _proto.createEl = function createEl() {
  19282. var tag = this.tag;
  19283. var el;
  19284. var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
  19285. var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
  19286. if (playerElIngest) {
  19287. el = this.el_ = tag.parentNode;
  19288. } else if (!divEmbed) {
  19289. el = this.el_ = _Component.prototype.createEl.call(this, 'div');
  19290. } // Copy over all the attributes from the tag, including ID and class
  19291. // ID will now reference player box, not the video tag
  19292. var attrs = getAttributes(tag);
  19293. if (divEmbed) {
  19294. el = this.el_ = tag;
  19295. tag = this.tag = document.createElement('video');
  19296. while (el.children.length) {
  19297. tag.appendChild(el.firstChild);
  19298. }
  19299. if (!hasClass(el, 'video-js')) {
  19300. addClass(el, 'video-js');
  19301. }
  19302. el.appendChild(tag);
  19303. playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element
  19304. // to our new `video` element. This will move things like
  19305. // `src` or `controls` that were set via js before the player
  19306. // was initialized.
  19307. Object.keys(el).forEach(function (k) {
  19308. try {
  19309. tag[k] = el[k];
  19310. } catch (e) {// we got a a property like outerHTML which we can't actually copy, ignore it
  19311. }
  19312. });
  19313. } // set tabindex to -1 to remove the video element from the focus order
  19314. tag.setAttribute('tabindex', '-1');
  19315. attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and
  19316. // for the same issue with Chrome (on Windows) with JAWS.
  19317. // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
  19318. // Note that we can't detect if JAWS is being used, but this ARIA attribute
  19319. // doesn't change behavior of IE11 or Chrome if JAWS is not being used
  19320. if (IE_VERSION || IS_CHROME && IS_WINDOWS) {
  19321. tag.setAttribute('role', 'application');
  19322. attrs.role = 'application';
  19323. } // Remove width/height attrs from tag so CSS can make it 100% width/height
  19324. tag.removeAttribute('width');
  19325. tag.removeAttribute('height');
  19326. if ('width' in attrs) {
  19327. delete attrs.width;
  19328. }
  19329. if ('height' in attrs) {
  19330. delete attrs.height;
  19331. }
  19332. Object.getOwnPropertyNames(attrs).forEach(function (attr) {
  19333. // don't copy over the class attribute to the player element when we're in a div embed
  19334. // the class is already set up properly in the divEmbed case
  19335. // and we want to make sure that the `video-js` class doesn't get lost
  19336. if (!(divEmbed && attr === 'class')) {
  19337. el.setAttribute(attr, attrs[attr]);
  19338. }
  19339. if (divEmbed) {
  19340. tag.setAttribute(attr, attrs[attr]);
  19341. }
  19342. }); // Update tag id/class for use as HTML5 playback tech
  19343. // Might think we should do this after embedding in container so .vjs-tech class
  19344. // doesn't flash 100% width/height, but class only applies with .video-js parent
  19345. tag.playerId = tag.id;
  19346. tag.id += '_html5_api';
  19347. tag.className = 'vjs-tech'; // Make player findable on elements
  19348. tag.player = el.player = this; // Default state of video is paused
  19349. this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height
  19350. // of the player in a way that's still overrideable by CSS, just like the
  19351. // video element
  19352. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
  19353. this.styleEl_ = createStyleElement('vjs-styles-dimensions');
  19354. var defaultsStyleEl = $('.vjs-styles-defaults');
  19355. var head = $('head');
  19356. head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
  19357. }
  19358. this.fill_ = false;
  19359. this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el
  19360. this.width(this.options_.width);
  19361. this.height(this.options_.height);
  19362. this.fill(this.options_.fill);
  19363. this.fluid(this.options_.fluid);
  19364. this.aspectRatio(this.options_.aspectRatio); // support both crossOrigin and crossorigin to reduce confusion and issues around the name
  19365. this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin); // Hide any links within the video/audio tag,
  19366. // because IE doesn't hide them completely from screen readers.
  19367. var links = tag.getElementsByTagName('a');
  19368. for (var i = 0; i < links.length; i++) {
  19369. var linkEl = links.item(i);
  19370. addClass(linkEl, 'vjs-hidden');
  19371. linkEl.setAttribute('hidden', 'hidden');
  19372. } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
  19373. // keep track of the original for later so we can know if the source originally failed
  19374. tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container
  19375. if (tag.parentNode && !playerElIngest) {
  19376. tag.parentNode.insertBefore(el, tag);
  19377. } // insert the tag as the first child of the player element
  19378. // then manually add it to the children array so that this.addChild
  19379. // will work properly for other components
  19380. //
  19381. // Breaks iPhone, fixed in HTML5 setup.
  19382. prependTo(tag, el);
  19383. this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player
  19384. // if it's been set to something different to the doc
  19385. this.el_.setAttribute('lang', this.language_);
  19386. this.el_.setAttribute('translate', 'no');
  19387. this.el_ = el;
  19388. return el;
  19389. }
  19390. /**
  19391. * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this
  19392. * sets the `crossOrigin` property on the `<video>` tag to control the CORS
  19393. * behavior.
  19394. *
  19395. * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
  19396. *
  19397. * @param {string} [value]
  19398. * The value to set the `Player`'s crossOrigin to. If an argument is
  19399. * given, must be one of `anonymous` or `use-credentials`.
  19400. *
  19401. * @return {string|undefined}
  19402. * - The current crossOrigin value of the `Player` when getting.
  19403. * - undefined when setting
  19404. */
  19405. ;
  19406. _proto.crossOrigin = function crossOrigin(value) {
  19407. if (!value) {
  19408. return this.techGet_('crossOrigin');
  19409. }
  19410. if (value !== 'anonymous' && value !== 'use-credentials') {
  19411. log$1.warn("crossOrigin must be \"anonymous\" or \"use-credentials\", given \"" + value + "\"");
  19412. return;
  19413. }
  19414. this.techCall_('setCrossOrigin', value);
  19415. return;
  19416. }
  19417. /**
  19418. * A getter/setter for the `Player`'s width. Returns the player's configured value.
  19419. * To get the current width use `currentWidth()`.
  19420. *
  19421. * @param {number} [value]
  19422. * The value to set the `Player`'s width to.
  19423. *
  19424. * @return {number}
  19425. * The current width of the `Player` when getting.
  19426. */
  19427. ;
  19428. _proto.width = function width(value) {
  19429. return this.dimension('width', value);
  19430. }
  19431. /**
  19432. * A getter/setter for the `Player`'s height. Returns the player's configured value.
  19433. * To get the current height use `currentheight()`.
  19434. *
  19435. * @param {number} [value]
  19436. * The value to set the `Player`'s heigth to.
  19437. *
  19438. * @return {number}
  19439. * The current height of the `Player` when getting.
  19440. */
  19441. ;
  19442. _proto.height = function height(value) {
  19443. return this.dimension('height', value);
  19444. }
  19445. /**
  19446. * A getter/setter for the `Player`'s width & height.
  19447. *
  19448. * @param {string} dimension
  19449. * This string can be:
  19450. * - 'width'
  19451. * - 'height'
  19452. *
  19453. * @param {number} [value]
  19454. * Value for dimension specified in the first argument.
  19455. *
  19456. * @return {number}
  19457. * The dimension arguments value when getting (width/height).
  19458. */
  19459. ;
  19460. _proto.dimension = function dimension(_dimension, value) {
  19461. var privDimension = _dimension + '_';
  19462. if (value === undefined) {
  19463. return this[privDimension] || 0;
  19464. }
  19465. if (value === '' || value === 'auto') {
  19466. // If an empty string is given, reset the dimension to be automatic
  19467. this[privDimension] = undefined;
  19468. this.updateStyleEl_();
  19469. return;
  19470. }
  19471. var parsedVal = parseFloat(value);
  19472. if (isNaN(parsedVal)) {
  19473. log$1.error("Improper value \"" + value + "\" supplied for for " + _dimension);
  19474. return;
  19475. }
  19476. this[privDimension] = parsedVal;
  19477. this.updateStyleEl_();
  19478. }
  19479. /**
  19480. * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
  19481. *
  19482. * Turning this on will turn off fill mode.
  19483. *
  19484. * @param {boolean} [bool]
  19485. * - A value of true adds the class.
  19486. * - A value of false removes the class.
  19487. * - No value will be a getter.
  19488. *
  19489. * @return {boolean|undefined}
  19490. * - The value of fluid when getting.
  19491. * - `undefined` when setting.
  19492. */
  19493. ;
  19494. _proto.fluid = function fluid(bool) {
  19495. var _this3 = this;
  19496. if (bool === undefined) {
  19497. return !!this.fluid_;
  19498. }
  19499. this.fluid_ = !!bool;
  19500. if (isEvented(this)) {
  19501. this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_);
  19502. }
  19503. if (bool) {
  19504. this.addClass('vjs-fluid');
  19505. this.fill(false);
  19506. addEventedCallback(this, function () {
  19507. _this3.on(['playerreset', 'resize'], _this3.boundUpdateStyleEl_);
  19508. });
  19509. } else {
  19510. this.removeClass('vjs-fluid');
  19511. }
  19512. this.updateStyleEl_();
  19513. }
  19514. /**
  19515. * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
  19516. *
  19517. * Turning this on will turn off fluid mode.
  19518. *
  19519. * @param {boolean} [bool]
  19520. * - A value of true adds the class.
  19521. * - A value of false removes the class.
  19522. * - No value will be a getter.
  19523. *
  19524. * @return {boolean|undefined}
  19525. * - The value of fluid when getting.
  19526. * - `undefined` when setting.
  19527. */
  19528. ;
  19529. _proto.fill = function fill(bool) {
  19530. if (bool === undefined) {
  19531. return !!this.fill_;
  19532. }
  19533. this.fill_ = !!bool;
  19534. if (bool) {
  19535. this.addClass('vjs-fill');
  19536. this.fluid(false);
  19537. } else {
  19538. this.removeClass('vjs-fill');
  19539. }
  19540. }
  19541. /**
  19542. * Get/Set the aspect ratio
  19543. *
  19544. * @param {string} [ratio]
  19545. * Aspect ratio for player
  19546. *
  19547. * @return {string|undefined}
  19548. * returns the current aspect ratio when getting
  19549. */
  19550. /**
  19551. * A getter/setter for the `Player`'s aspect ratio.
  19552. *
  19553. * @param {string} [ratio]
  19554. * The value to set the `Player`'s aspect ratio to.
  19555. *
  19556. * @return {string|undefined}
  19557. * - The current aspect ratio of the `Player` when getting.
  19558. * - undefined when setting
  19559. */
  19560. ;
  19561. _proto.aspectRatio = function aspectRatio(ratio) {
  19562. if (ratio === undefined) {
  19563. return this.aspectRatio_;
  19564. } // Check for width:height format
  19565. if (!/^\d+\:\d+$/.test(ratio)) {
  19566. throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
  19567. }
  19568. this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode,
  19569. // because in fixed mode you could calculate width and height yourself.
  19570. this.fluid(true);
  19571. this.updateStyleEl_();
  19572. }
  19573. /**
  19574. * Update styles of the `Player` element (height, width and aspect ratio).
  19575. *
  19576. * @private
  19577. * @listens Tech#loadedmetadata
  19578. */
  19579. ;
  19580. _proto.updateStyleEl_ = function updateStyleEl_() {
  19581. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
  19582. var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
  19583. var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
  19584. var techEl = this.tech_ && this.tech_.el();
  19585. if (techEl) {
  19586. if (_width >= 0) {
  19587. techEl.width = _width;
  19588. }
  19589. if (_height >= 0) {
  19590. techEl.height = _height;
  19591. }
  19592. }
  19593. return;
  19594. }
  19595. var width;
  19596. var height;
  19597. var aspectRatio;
  19598. var idClass; // The aspect ratio is either used directly or to calculate width and height.
  19599. if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
  19600. // Use any aspectRatio that's been specifically set
  19601. aspectRatio = this.aspectRatio_;
  19602. } else if (this.videoWidth() > 0) {
  19603. // Otherwise try to get the aspect ratio from the video metadata
  19604. aspectRatio = this.videoWidth() + ':' + this.videoHeight();
  19605. } else {
  19606. // Or use a default. The video element's is 2:1, but 16:9 is more common.
  19607. aspectRatio = '16:9';
  19608. } // Get the ratio as a decimal we can use to calculate dimensions
  19609. var ratioParts = aspectRatio.split(':');
  19610. var ratioMultiplier = ratioParts[1] / ratioParts[0];
  19611. if (this.width_ !== undefined) {
  19612. // Use any width that's been specifically set
  19613. width = this.width_;
  19614. } else if (this.height_ !== undefined) {
  19615. // Or calulate the width from the aspect ratio if a height has been set
  19616. width = this.height_ / ratioMultiplier;
  19617. } else {
  19618. // Or use the video's metadata, or use the video el's default of 300
  19619. width = this.videoWidth() || 300;
  19620. }
  19621. if (this.height_ !== undefined) {
  19622. // Use any height that's been specifically set
  19623. height = this.height_;
  19624. } else {
  19625. // Otherwise calculate the height from the ratio and the width
  19626. height = width * ratioMultiplier;
  19627. } // Ensure the CSS class is valid by starting with an alpha character
  19628. if (/^[^a-zA-Z]/.test(this.id())) {
  19629. idClass = 'dimensions-' + this.id();
  19630. } else {
  19631. idClass = this.id() + '-dimensions';
  19632. } // Ensure the right class is still on the player for the style element
  19633. this.addClass(idClass);
  19634. setTextContent(this.styleEl_, "\n ." + idClass + " {\n width: " + width + "px;\n height: " + height + "px;\n }\n\n ." + idClass + ".vjs-fluid:not(.vjs-audio-only-mode) {\n padding-top: " + ratioMultiplier * 100 + "%;\n }\n ");
  19635. }
  19636. /**
  19637. * Load/Create an instance of playback {@link Tech} including element
  19638. * and API methods. Then append the `Tech` element in `Player` as a child.
  19639. *
  19640. * @param {string} techName
  19641. * name of the playback technology
  19642. *
  19643. * @param {string} source
  19644. * video source
  19645. *
  19646. * @private
  19647. */
  19648. ;
  19649. _proto.loadTech_ = function loadTech_(techName, source) {
  19650. var _this4 = this;
  19651. // Pause and remove current playback technology
  19652. if (this.tech_) {
  19653. this.unloadTech_();
  19654. }
  19655. var titleTechName = toTitleCase$1(techName);
  19656. var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech
  19657. if (titleTechName !== 'Html5' && this.tag) {
  19658. Tech.getTech('Html5').disposeMediaElement(this.tag);
  19659. this.tag.player = null;
  19660. this.tag = null;
  19661. }
  19662. this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously
  19663. this.isReady_ = false;
  19664. var autoplay = this.autoplay(); // if autoplay is a string (or `true` with normalizeAutoplay: true) we pass false to the tech
  19665. // because the player is going to handle autoplay on `loadstart`
  19666. if (typeof this.autoplay() === 'string' || this.autoplay() === true && this.options_.normalizeAutoplay) {
  19667. autoplay = false;
  19668. } // Grab tech-specific options from player options and add source and parent element to use.
  19669. var techOptions = {
  19670. source: source,
  19671. autoplay: autoplay,
  19672. 'nativeControlsForTouch': this.options_.nativeControlsForTouch,
  19673. 'playerId': this.id(),
  19674. 'techId': this.id() + "_" + camelTechName + "_api",
  19675. 'playsinline': this.options_.playsinline,
  19676. 'preload': this.options_.preload,
  19677. 'loop': this.options_.loop,
  19678. 'disablePictureInPicture': this.options_.disablePictureInPicture,
  19679. 'muted': this.options_.muted,
  19680. 'poster': this.poster(),
  19681. 'language': this.language(),
  19682. 'playerElIngest': this.playerElIngest_ || false,
  19683. 'vtt.js': this.options_['vtt.js'],
  19684. 'canOverridePoster': !!this.options_.techCanOverridePoster,
  19685. 'enableSourceset': this.options_.enableSourceset,
  19686. 'Promise': this.options_.Promise
  19687. };
  19688. ALL.names.forEach(function (name) {
  19689. var props = ALL[name];
  19690. techOptions[props.getterName] = _this4[props.privateName];
  19691. });
  19692. assign(techOptions, this.options_[titleTechName]);
  19693. assign(techOptions, this.options_[camelTechName]);
  19694. assign(techOptions, this.options_[techName.toLowerCase()]);
  19695. if (this.tag) {
  19696. techOptions.tag = this.tag;
  19697. }
  19698. if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
  19699. techOptions.startTime = this.cache_.currentTime;
  19700. } // Initialize tech instance
  19701. var TechClass = Tech.getTech(techName);
  19702. if (!TechClass) {
  19703. throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'");
  19704. }
  19705. this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async
  19706. this.tech_.ready(bind(this, this.handleTechReady_), true);
  19707. textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player
  19708. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  19709. _this4.on(_this4.tech_, event, function (e) {
  19710. return _this4["handleTech" + toTitleCase$1(event) + "_"](e);
  19711. });
  19712. });
  19713. Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
  19714. _this4.on(_this4.tech_, event, function (eventObj) {
  19715. if (_this4.tech_.playbackRate() === 0 && _this4.tech_.seeking()) {
  19716. _this4.queuedCallbacks_.push({
  19717. callback: _this4["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this4),
  19718. event: eventObj
  19719. });
  19720. return;
  19721. }
  19722. _this4["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj);
  19723. });
  19724. });
  19725. this.on(this.tech_, 'loadstart', function (e) {
  19726. return _this4.handleTechLoadStart_(e);
  19727. });
  19728. this.on(this.tech_, 'sourceset', function (e) {
  19729. return _this4.handleTechSourceset_(e);
  19730. });
  19731. this.on(this.tech_, 'waiting', function (e) {
  19732. return _this4.handleTechWaiting_(e);
  19733. });
  19734. this.on(this.tech_, 'ended', function (e) {
  19735. return _this4.handleTechEnded_(e);
  19736. });
  19737. this.on(this.tech_, 'seeking', function (e) {
  19738. return _this4.handleTechSeeking_(e);
  19739. });
  19740. this.on(this.tech_, 'play', function (e) {
  19741. return _this4.handleTechPlay_(e);
  19742. });
  19743. this.on(this.tech_, 'firstplay', function (e) {
  19744. return _this4.handleTechFirstPlay_(e);
  19745. });
  19746. this.on(this.tech_, 'pause', function (e) {
  19747. return _this4.handleTechPause_(e);
  19748. });
  19749. this.on(this.tech_, 'durationchange', function (e) {
  19750. return _this4.handleTechDurationChange_(e);
  19751. });
  19752. this.on(this.tech_, 'fullscreenchange', function (e, data) {
  19753. return _this4.handleTechFullscreenChange_(e, data);
  19754. });
  19755. this.on(this.tech_, 'fullscreenerror', function (e, err) {
  19756. return _this4.handleTechFullscreenError_(e, err);
  19757. });
  19758. this.on(this.tech_, 'enterpictureinpicture', function (e) {
  19759. return _this4.handleTechEnterPictureInPicture_(e);
  19760. });
  19761. this.on(this.tech_, 'leavepictureinpicture', function (e) {
  19762. return _this4.handleTechLeavePictureInPicture_(e);
  19763. });
  19764. this.on(this.tech_, 'error', function (e) {
  19765. return _this4.handleTechError_(e);
  19766. });
  19767. this.on(this.tech_, 'posterchange', function (e) {
  19768. return _this4.handleTechPosterChange_(e);
  19769. });
  19770. this.on(this.tech_, 'textdata', function (e) {
  19771. return _this4.handleTechTextData_(e);
  19772. });
  19773. this.on(this.tech_, 'ratechange', function (e) {
  19774. return _this4.handleTechRateChange_(e);
  19775. });
  19776. this.on(this.tech_, 'loadedmetadata', this.boundUpdateStyleEl_);
  19777. this.usingNativeControls(this.techGet_('controls'));
  19778. if (this.controls() && !this.usingNativeControls()) {
  19779. this.addTechControlsListeners_();
  19780. } // Add the tech element in the DOM if it was not already there
  19781. // Make sure to not insert the original video element if using Html5
  19782. if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
  19783. prependTo(this.tech_.el(), this.el());
  19784. } // Get rid of the original video tag reference after the first tech is loaded
  19785. if (this.tag) {
  19786. this.tag.player = null;
  19787. this.tag = null;
  19788. }
  19789. }
  19790. /**
  19791. * Unload and dispose of the current playback {@link Tech}.
  19792. *
  19793. * @private
  19794. */
  19795. ;
  19796. _proto.unloadTech_ = function unloadTech_() {
  19797. var _this5 = this;
  19798. // Save the current text tracks so that we can reuse the same text tracks with the next tech
  19799. ALL.names.forEach(function (name) {
  19800. var props = ALL[name];
  19801. _this5[props.privateName] = _this5[props.getterName]();
  19802. });
  19803. this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
  19804. this.isReady_ = false;
  19805. this.tech_.dispose();
  19806. this.tech_ = false;
  19807. if (this.isPosterFromTech_) {
  19808. this.poster_ = '';
  19809. this.trigger('posterchange');
  19810. }
  19811. this.isPosterFromTech_ = false;
  19812. }
  19813. /**
  19814. * Return a reference to the current {@link Tech}.
  19815. * It will print a warning by default about the danger of using the tech directly
  19816. * but any argument that is passed in will silence the warning.
  19817. *
  19818. * @param {*} [safety]
  19819. * Anything passed in to silence the warning
  19820. *
  19821. * @return {Tech}
  19822. * The Tech
  19823. */
  19824. ;
  19825. _proto.tech = function tech(safety) {
  19826. if (safety === undefined) {
  19827. log$1.warn('Using the tech directly can be dangerous. I hope you know what you\'re doing.\n' + 'See https://github.com/videojs/video.js/issues/2617 for more info.\n');
  19828. }
  19829. return this.tech_;
  19830. }
  19831. /**
  19832. * Set up click and touch listeners for the playback element
  19833. *
  19834. * - On desktops: a click on the video itself will toggle playback
  19835. * - On mobile devices: a click on the video toggles controls
  19836. * which is done by toggling the user state between active and
  19837. * inactive
  19838. * - A tap can signal that a user has become active or has become inactive
  19839. * e.g. a quick tap on an iPhone movie should reveal the controls. Another
  19840. * quick tap should hide them again (signaling the user is in an inactive
  19841. * viewing state)
  19842. * - In addition to this, we still want the user to be considered inactive after
  19843. * a few seconds of inactivity.
  19844. *
  19845. * > Note: the only part of iOS interaction we can't mimic with this setup
  19846. * is a touch and hold on the video element counting as activity in order to
  19847. * keep the controls showing, but that shouldn't be an issue. A touch and hold
  19848. * on any controls will still keep the user active
  19849. *
  19850. * @private
  19851. */
  19852. ;
  19853. _proto.addTechControlsListeners_ = function addTechControlsListeners_() {
  19854. // Make sure to remove all the previous listeners in case we are called multiple times.
  19855. this.removeTechControlsListeners_();
  19856. this.on(this.tech_, 'click', this.boundHandleTechClick_);
  19857. this.on(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event
  19858. // so we'll check if the controls were already showing before reporting user
  19859. // activity
  19860. this.on(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
  19861. this.on(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
  19862. this.on(this.tech_, 'touchend', this.boundHandleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap
  19863. // listener cancels out any reportedUserActivity when setting userActive(false)
  19864. this.on(this.tech_, 'tap', this.boundHandleTechTap_);
  19865. }
  19866. /**
  19867. * Remove the listeners used for click and tap controls. This is needed for
  19868. * toggling to controls disabled, where a tap/touch should do nothing.
  19869. *
  19870. * @private
  19871. */
  19872. ;
  19873. _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() {
  19874. // We don't want to just use `this.off()` because there might be other needed
  19875. // listeners added by techs that extend this.
  19876. this.off(this.tech_, 'tap', this.boundHandleTechTap_);
  19877. this.off(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
  19878. this.off(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
  19879. this.off(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
  19880. this.off(this.tech_, 'click', this.boundHandleTechClick_);
  19881. this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
  19882. }
  19883. /**
  19884. * Player waits for the tech to be ready
  19885. *
  19886. * @private
  19887. */
  19888. ;
  19889. _proto.handleTechReady_ = function handleTechReady_() {
  19890. this.triggerReady(); // Keep the same volume as before
  19891. if (this.cache_.volume) {
  19892. this.techCall_('setVolume', this.cache_.volume);
  19893. } // Look if the tech found a higher resolution poster while loading
  19894. this.handleTechPosterChange_(); // Update the duration if available
  19895. this.handleTechDurationChange_();
  19896. }
  19897. /**
  19898. * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
  19899. * function will also trigger {@link Player#firstplay} if it is the first loadstart
  19900. * for a video.
  19901. *
  19902. * @fires Player#loadstart
  19903. * @fires Player#firstplay
  19904. * @listens Tech#loadstart
  19905. * @private
  19906. */
  19907. ;
  19908. _proto.handleTechLoadStart_ = function handleTechLoadStart_() {
  19909. // TODO: Update to use `emptied` event instead. See #1277.
  19910. this.removeClass('vjs-ended');
  19911. this.removeClass('vjs-seeking'); // reset the error state
  19912. this.error(null); // Update the duration
  19913. this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now.
  19914. // The firstplay event relies on both the play and loadstart events
  19915. // which can happen in any order for a new source
  19916. if (!this.paused()) {
  19917. /**
  19918. * Fired when the user agent begins looking for media data
  19919. *
  19920. * @event Player#loadstart
  19921. * @type {EventTarget~Event}
  19922. */
  19923. this.trigger('loadstart');
  19924. this.trigger('firstplay');
  19925. } else {
  19926. // reset the hasStarted state
  19927. this.hasStarted(false);
  19928. this.trigger('loadstart');
  19929. } // autoplay happens after loadstart for the browser,
  19930. // so we mimic that behavior
  19931. this.manualAutoplay_(this.autoplay() === true && this.options_.normalizeAutoplay ? 'play' : this.autoplay());
  19932. }
  19933. /**
  19934. * Handle autoplay string values, rather than the typical boolean
  19935. * values that should be handled by the tech. Note that this is not
  19936. * part of any specification. Valid values and what they do can be
  19937. * found on the autoplay getter at Player#autoplay()
  19938. */
  19939. ;
  19940. _proto.manualAutoplay_ = function manualAutoplay_(type) {
  19941. var _this6 = this;
  19942. if (!this.tech_ || typeof type !== 'string') {
  19943. return;
  19944. } // Save original muted() value, set muted to true, and attempt to play().
  19945. // On promise rejection, restore muted from saved value
  19946. var resolveMuted = function resolveMuted() {
  19947. var previouslyMuted = _this6.muted();
  19948. _this6.muted(true);
  19949. var restoreMuted = function restoreMuted() {
  19950. _this6.muted(previouslyMuted);
  19951. }; // restore muted on play terminatation
  19952. _this6.playTerminatedQueue_.push(restoreMuted);
  19953. var mutedPromise = _this6.play();
  19954. if (!isPromise(mutedPromise)) {
  19955. return;
  19956. }
  19957. return mutedPromise["catch"](function (err) {
  19958. restoreMuted();
  19959. throw new Error("Rejection at manualAutoplay. Restoring muted value. " + (err ? err : ''));
  19960. });
  19961. };
  19962. var promise; // if muted defaults to true
  19963. // the only thing we can do is call play
  19964. if (type === 'any' && !this.muted()) {
  19965. promise = this.play();
  19966. if (isPromise(promise)) {
  19967. promise = promise["catch"](resolveMuted);
  19968. }
  19969. } else if (type === 'muted' && !this.muted()) {
  19970. promise = resolveMuted();
  19971. } else {
  19972. promise = this.play();
  19973. }
  19974. if (!isPromise(promise)) {
  19975. return;
  19976. }
  19977. return promise.then(function () {
  19978. _this6.trigger({
  19979. type: 'autoplay-success',
  19980. autoplay: type
  19981. });
  19982. })["catch"](function () {
  19983. _this6.trigger({
  19984. type: 'autoplay-failure',
  19985. autoplay: type
  19986. });
  19987. });
  19988. }
  19989. /**
  19990. * Update the internal source caches so that we return the correct source from
  19991. * `src()`, `currentSource()`, and `currentSources()`.
  19992. *
  19993. * > Note: `currentSources` will not be updated if the source that is passed in exists
  19994. * in the current `currentSources` cache.
  19995. *
  19996. *
  19997. * @param {Tech~SourceObject} srcObj
  19998. * A string or object source to update our caches to.
  19999. */
  20000. ;
  20001. _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) {
  20002. if (srcObj === void 0) {
  20003. srcObj = '';
  20004. }
  20005. var src = srcObj;
  20006. var type = '';
  20007. if (typeof src !== 'string') {
  20008. src = srcObj.src;
  20009. type = srcObj.type;
  20010. } // make sure all the caches are set to default values
  20011. // to prevent null checking
  20012. this.cache_.source = this.cache_.source || {};
  20013. this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in
  20014. if (src && !type) {
  20015. type = findMimetype(this, src);
  20016. } // update `currentSource` cache always
  20017. this.cache_.source = mergeOptions$3({}, srcObj, {
  20018. src: src,
  20019. type: type
  20020. });
  20021. var matchingSources = this.cache_.sources.filter(function (s) {
  20022. return s.src && s.src === src;
  20023. });
  20024. var sourceElSources = [];
  20025. var sourceEls = this.$$('source');
  20026. var matchingSourceEls = [];
  20027. for (var i = 0; i < sourceEls.length; i++) {
  20028. var sourceObj = getAttributes(sourceEls[i]);
  20029. sourceElSources.push(sourceObj);
  20030. if (sourceObj.src && sourceObj.src === src) {
  20031. matchingSourceEls.push(sourceObj.src);
  20032. }
  20033. } // if we have matching source els but not matching sources
  20034. // the current source cache is not up to date
  20035. if (matchingSourceEls.length && !matchingSources.length) {
  20036. this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the
  20037. // sources cache to the `currentSource` cache
  20038. } else if (!matchingSources.length) {
  20039. this.cache_.sources = [this.cache_.source];
  20040. } // update the tech `src` cache
  20041. this.cache_.src = src;
  20042. }
  20043. /**
  20044. * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
  20045. * causing the media element to reload.
  20046. *
  20047. * It will fire for the initial source and each subsequent source.
  20048. * This event is a custom event from Video.js and is triggered by the {@link Tech}.
  20049. *
  20050. * The event object for this event contains a `src` property that will contain the source
  20051. * that was available when the event was triggered. This is generally only necessary if Video.js
  20052. * is switching techs while the source was being changed.
  20053. *
  20054. * It is also fired when `load` is called on the player (or media element)
  20055. * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
  20056. * says that the resource selection algorithm needs to be aborted and restarted.
  20057. * In this case, it is very likely that the `src` property will be set to the
  20058. * empty string `""` to indicate we do not know what the source will be but
  20059. * that it is changing.
  20060. *
  20061. * *This event is currently still experimental and may change in minor releases.*
  20062. * __To use this, pass `enableSourceset` option to the player.__
  20063. *
  20064. * @event Player#sourceset
  20065. * @type {EventTarget~Event}
  20066. * @prop {string} src
  20067. * The source url available when the `sourceset` was triggered.
  20068. * It will be an empty string if we cannot know what the source is
  20069. * but know that the source will change.
  20070. */
  20071. /**
  20072. * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
  20073. *
  20074. * @fires Player#sourceset
  20075. * @listens Tech#sourceset
  20076. * @private
  20077. */
  20078. ;
  20079. _proto.handleTechSourceset_ = function handleTechSourceset_(event) {
  20080. var _this7 = this;
  20081. // only update the source cache when the source
  20082. // was not updated using the player api
  20083. if (!this.changingSrc_) {
  20084. var updateSourceCaches = function updateSourceCaches(src) {
  20085. return _this7.updateSourceCaches_(src);
  20086. };
  20087. var playerSrc = this.currentSource().src;
  20088. var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob
  20089. if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
  20090. // if both the tech source and the player source were updated we assume
  20091. // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
  20092. if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
  20093. updateSourceCaches = function updateSourceCaches() {};
  20094. }
  20095. } // update the source to the initial source right away
  20096. // in some cases this will be empty string
  20097. updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string
  20098. // wait for a `loadstart` to update the cache to `currentSrc`.
  20099. // If a sourceset happens before a `loadstart`, we reset the state
  20100. if (!event.src) {
  20101. this.tech_.any(['sourceset', 'loadstart'], function (e) {
  20102. // if a sourceset happens before a `loadstart` there
  20103. // is nothing to do as this `handleTechSourceset_`
  20104. // will be called again and this will be handled there.
  20105. if (e.type === 'sourceset') {
  20106. return;
  20107. }
  20108. var techSrc = _this7.techGet('currentSrc');
  20109. _this7.lastSource_.tech = techSrc;
  20110. _this7.updateSourceCaches_(techSrc);
  20111. });
  20112. }
  20113. }
  20114. this.lastSource_ = {
  20115. player: this.currentSource().src,
  20116. tech: event.src
  20117. };
  20118. this.trigger({
  20119. src: event.src,
  20120. type: 'sourceset'
  20121. });
  20122. }
  20123. /**
  20124. * Add/remove the vjs-has-started class
  20125. *
  20126. * @fires Player#firstplay
  20127. *
  20128. * @param {boolean} request
  20129. * - true: adds the class
  20130. * - false: remove the class
  20131. *
  20132. * @return {boolean}
  20133. * the boolean value of hasStarted_
  20134. */
  20135. ;
  20136. _proto.hasStarted = function hasStarted(request) {
  20137. if (request === undefined) {
  20138. // act as getter, if we have no request to change
  20139. return this.hasStarted_;
  20140. }
  20141. if (request === this.hasStarted_) {
  20142. return;
  20143. }
  20144. this.hasStarted_ = request;
  20145. if (this.hasStarted_) {
  20146. this.addClass('vjs-has-started');
  20147. this.trigger('firstplay');
  20148. } else {
  20149. this.removeClass('vjs-has-started');
  20150. }
  20151. }
  20152. /**
  20153. * Fired whenever the media begins or resumes playback
  20154. *
  20155. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
  20156. * @fires Player#play
  20157. * @listens Tech#play
  20158. * @private
  20159. */
  20160. ;
  20161. _proto.handleTechPlay_ = function handleTechPlay_() {
  20162. this.removeClass('vjs-ended');
  20163. this.removeClass('vjs-paused');
  20164. this.addClass('vjs-playing'); // hide the poster when the user hits play
  20165. this.hasStarted(true);
  20166. /**
  20167. * Triggered whenever an {@link Tech#play} event happens. Indicates that
  20168. * playback has started or resumed.
  20169. *
  20170. * @event Player#play
  20171. * @type {EventTarget~Event}
  20172. */
  20173. this.trigger('play');
  20174. }
  20175. /**
  20176. * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
  20177. *
  20178. * If there were any events queued while the playback rate was zero, fire
  20179. * those events now.
  20180. *
  20181. * @private
  20182. * @method Player#handleTechRateChange_
  20183. * @fires Player#ratechange
  20184. * @listens Tech#ratechange
  20185. */
  20186. ;
  20187. _proto.handleTechRateChange_ = function handleTechRateChange_() {
  20188. if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
  20189. this.queuedCallbacks_.forEach(function (queued) {
  20190. return queued.callback(queued.event);
  20191. });
  20192. this.queuedCallbacks_ = [];
  20193. }
  20194. this.cache_.lastPlaybackRate = this.tech_.playbackRate();
  20195. /**
  20196. * Fires when the playing speed of the audio/video is changed
  20197. *
  20198. * @event Player#ratechange
  20199. * @type {event}
  20200. */
  20201. this.trigger('ratechange');
  20202. }
  20203. /**
  20204. * Retrigger the `waiting` event that was triggered by the {@link Tech}.
  20205. *
  20206. * @fires Player#waiting
  20207. * @listens Tech#waiting
  20208. * @private
  20209. */
  20210. ;
  20211. _proto.handleTechWaiting_ = function handleTechWaiting_() {
  20212. var _this8 = this;
  20213. this.addClass('vjs-waiting');
  20214. /**
  20215. * A readyState change on the DOM element has caused playback to stop.
  20216. *
  20217. * @event Player#waiting
  20218. * @type {EventTarget~Event}
  20219. */
  20220. this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent
  20221. // premature removal of the waiting class, wait for the time to change.
  20222. var timeWhenWaiting = this.currentTime();
  20223. var timeUpdateListener = function timeUpdateListener() {
  20224. if (timeWhenWaiting !== _this8.currentTime()) {
  20225. _this8.removeClass('vjs-waiting');
  20226. _this8.off('timeupdate', timeUpdateListener);
  20227. }
  20228. };
  20229. this.on('timeupdate', timeUpdateListener);
  20230. }
  20231. /**
  20232. * Retrigger the `canplay` event that was triggered by the {@link Tech}.
  20233. * > Note: This is not consistent between browsers. See #1351
  20234. *
  20235. * @fires Player#canplay
  20236. * @listens Tech#canplay
  20237. * @private
  20238. */
  20239. ;
  20240. _proto.handleTechCanPlay_ = function handleTechCanPlay_() {
  20241. this.removeClass('vjs-waiting');
  20242. /**
  20243. * The media has a readyState of HAVE_FUTURE_DATA or greater.
  20244. *
  20245. * @event Player#canplay
  20246. * @type {EventTarget~Event}
  20247. */
  20248. this.trigger('canplay');
  20249. }
  20250. /**
  20251. * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
  20252. *
  20253. * @fires Player#canplaythrough
  20254. * @listens Tech#canplaythrough
  20255. * @private
  20256. */
  20257. ;
  20258. _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
  20259. this.removeClass('vjs-waiting');
  20260. /**
  20261. * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
  20262. * entire media file can be played without buffering.
  20263. *
  20264. * @event Player#canplaythrough
  20265. * @type {EventTarget~Event}
  20266. */
  20267. this.trigger('canplaythrough');
  20268. }
  20269. /**
  20270. * Retrigger the `playing` event that was triggered by the {@link Tech}.
  20271. *
  20272. * @fires Player#playing
  20273. * @listens Tech#playing
  20274. * @private
  20275. */
  20276. ;
  20277. _proto.handleTechPlaying_ = function handleTechPlaying_() {
  20278. this.removeClass('vjs-waiting');
  20279. /**
  20280. * The media is no longer blocked from playback, and has started playing.
  20281. *
  20282. * @event Player#playing
  20283. * @type {EventTarget~Event}
  20284. */
  20285. this.trigger('playing');
  20286. }
  20287. /**
  20288. * Retrigger the `seeking` event that was triggered by the {@link Tech}.
  20289. *
  20290. * @fires Player#seeking
  20291. * @listens Tech#seeking
  20292. * @private
  20293. */
  20294. ;
  20295. _proto.handleTechSeeking_ = function handleTechSeeking_() {
  20296. this.addClass('vjs-seeking');
  20297. /**
  20298. * Fired whenever the player is jumping to a new time
  20299. *
  20300. * @event Player#seeking
  20301. * @type {EventTarget~Event}
  20302. */
  20303. this.trigger('seeking');
  20304. }
  20305. /**
  20306. * Retrigger the `seeked` event that was triggered by the {@link Tech}.
  20307. *
  20308. * @fires Player#seeked
  20309. * @listens Tech#seeked
  20310. * @private
  20311. */
  20312. ;
  20313. _proto.handleTechSeeked_ = function handleTechSeeked_() {
  20314. this.removeClass('vjs-seeking');
  20315. this.removeClass('vjs-ended');
  20316. /**
  20317. * Fired when the player has finished jumping to a new time
  20318. *
  20319. * @event Player#seeked
  20320. * @type {EventTarget~Event}
  20321. */
  20322. this.trigger('seeked');
  20323. }
  20324. /**
  20325. * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
  20326. *
  20327. * @fires Player#firstplay
  20328. * @listens Tech#firstplay
  20329. * @deprecated As of 6.0 firstplay event is deprecated.
  20330. * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
  20331. * @private
  20332. */
  20333. ;
  20334. _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() {
  20335. // If the first starttime attribute is specified
  20336. // then we will start at the given offset in seconds
  20337. if (this.options_.starttime) {
  20338. log$1.warn('Passing the `starttime` option to the player will be deprecated in 6.0');
  20339. this.currentTime(this.options_.starttime);
  20340. }
  20341. this.addClass('vjs-has-started');
  20342. /**
  20343. * Fired the first time a video is played. Not part of the HLS spec, and this is
  20344. * probably not the best implementation yet, so use sparingly. If you don't have a
  20345. * reason to prevent playback, use `myPlayer.one('play');` instead.
  20346. *
  20347. * @event Player#firstplay
  20348. * @deprecated As of 6.0 firstplay event is deprecated.
  20349. * @type {EventTarget~Event}
  20350. */
  20351. this.trigger('firstplay');
  20352. }
  20353. /**
  20354. * Retrigger the `pause` event that was triggered by the {@link Tech}.
  20355. *
  20356. * @fires Player#pause
  20357. * @listens Tech#pause
  20358. * @private
  20359. */
  20360. ;
  20361. _proto.handleTechPause_ = function handleTechPause_() {
  20362. this.removeClass('vjs-playing');
  20363. this.addClass('vjs-paused');
  20364. /**
  20365. * Fired whenever the media has been paused
  20366. *
  20367. * @event Player#pause
  20368. * @type {EventTarget~Event}
  20369. */
  20370. this.trigger('pause');
  20371. }
  20372. /**
  20373. * Retrigger the `ended` event that was triggered by the {@link Tech}.
  20374. *
  20375. * @fires Player#ended
  20376. * @listens Tech#ended
  20377. * @private
  20378. */
  20379. ;
  20380. _proto.handleTechEnded_ = function handleTechEnded_() {
  20381. this.addClass('vjs-ended');
  20382. this.removeClass('vjs-waiting');
  20383. if (this.options_.loop) {
  20384. this.currentTime(0);
  20385. this.play();
  20386. } else if (!this.paused()) {
  20387. this.pause();
  20388. }
  20389. /**
  20390. * Fired when the end of the media resource is reached (currentTime == duration)
  20391. *
  20392. * @event Player#ended
  20393. * @type {EventTarget~Event}
  20394. */
  20395. this.trigger('ended');
  20396. }
  20397. /**
  20398. * Fired when the duration of the media resource is first known or changed
  20399. *
  20400. * @listens Tech#durationchange
  20401. * @private
  20402. */
  20403. ;
  20404. _proto.handleTechDurationChange_ = function handleTechDurationChange_() {
  20405. this.duration(this.techGet_('duration'));
  20406. }
  20407. /**
  20408. * Handle a click on the media element to play/pause
  20409. *
  20410. * @param {EventTarget~Event} event
  20411. * the event that caused this function to trigger
  20412. *
  20413. * @listens Tech#click
  20414. * @private
  20415. */
  20416. ;
  20417. _proto.handleTechClick_ = function handleTechClick_(event) {
  20418. // When controls are disabled a click should not toggle playback because
  20419. // the click is considered a control
  20420. if (!this.controls_) {
  20421. return;
  20422. }
  20423. if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.click === undefined || this.options_.userActions.click !== false) {
  20424. if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.click === 'function') {
  20425. this.options_.userActions.click.call(this, event);
  20426. } else if (this.paused()) {
  20427. silencePromise(this.play());
  20428. } else {
  20429. this.pause();
  20430. }
  20431. }
  20432. }
  20433. /**
  20434. * Handle a double-click on the media element to enter/exit fullscreen
  20435. *
  20436. * @param {EventTarget~Event} event
  20437. * the event that caused this function to trigger
  20438. *
  20439. * @listens Tech#dblclick
  20440. * @private
  20441. */
  20442. ;
  20443. _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
  20444. if (!this.controls_) {
  20445. return;
  20446. } // we do not want to toggle fullscreen state
  20447. // when double-clicking inside a control bar or a modal
  20448. var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
  20449. return el.contains(event.target);
  20450. });
  20451. if (!inAllowedEls) {
  20452. /*
  20453. * options.userActions.doubleClick
  20454. *
  20455. * If `undefined` or `true`, double-click toggles fullscreen if controls are present
  20456. * Set to `false` to disable double-click handling
  20457. * Set to a function to substitute an external double-click handler
  20458. */
  20459. if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
  20460. if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
  20461. this.options_.userActions.doubleClick.call(this, event);
  20462. } else if (this.isFullscreen()) {
  20463. this.exitFullscreen();
  20464. } else {
  20465. this.requestFullscreen();
  20466. }
  20467. }
  20468. }
  20469. }
  20470. /**
  20471. * Handle a tap on the media element. It will toggle the user
  20472. * activity state, which hides and shows the controls.
  20473. *
  20474. * @listens Tech#tap
  20475. * @private
  20476. */
  20477. ;
  20478. _proto.handleTechTap_ = function handleTechTap_() {
  20479. this.userActive(!this.userActive());
  20480. }
  20481. /**
  20482. * Handle touch to start
  20483. *
  20484. * @listens Tech#touchstart
  20485. * @private
  20486. */
  20487. ;
  20488. _proto.handleTechTouchStart_ = function handleTechTouchStart_() {
  20489. this.userWasActive = this.userActive();
  20490. }
  20491. /**
  20492. * Handle touch to move
  20493. *
  20494. * @listens Tech#touchmove
  20495. * @private
  20496. */
  20497. ;
  20498. _proto.handleTechTouchMove_ = function handleTechTouchMove_() {
  20499. if (this.userWasActive) {
  20500. this.reportUserActivity();
  20501. }
  20502. }
  20503. /**
  20504. * Handle touch to end
  20505. *
  20506. * @param {EventTarget~Event} event
  20507. * the touchend event that triggered
  20508. * this function
  20509. *
  20510. * @listens Tech#touchend
  20511. * @private
  20512. */
  20513. ;
  20514. _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
  20515. // Stop the mouse events from also happening
  20516. if (event.cancelable) {
  20517. event.preventDefault();
  20518. }
  20519. }
  20520. /**
  20521. * native click events on the SWF aren't triggered on IE11, Win8.1RT
  20522. * use stageclick events triggered from inside the SWF instead
  20523. *
  20524. * @private
  20525. * @listens stageclick
  20526. */
  20527. ;
  20528. _proto.handleStageClick_ = function handleStageClick_() {
  20529. this.reportUserActivity();
  20530. }
  20531. /**
  20532. * @private
  20533. */
  20534. ;
  20535. _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() {
  20536. if (this.isFullscreen()) {
  20537. this.addClass('vjs-fullscreen');
  20538. } else {
  20539. this.removeClass('vjs-fullscreen');
  20540. }
  20541. }
  20542. /**
  20543. * when the document fschange event triggers it calls this
  20544. */
  20545. ;
  20546. _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) {
  20547. var targetPlayer = e.target.player; // if another player was fullscreen
  20548. // do a null check for targetPlayer because older firefox's would put document as e.target
  20549. if (targetPlayer && targetPlayer !== this) {
  20550. return;
  20551. }
  20552. var el = this.el();
  20553. var isFs = document[this.fsApi_.fullscreenElement] === el;
  20554. if (!isFs && el.matches) {
  20555. isFs = el.matches(':' + this.fsApi_.fullscreen);
  20556. } else if (!isFs && el.msMatchesSelector) {
  20557. isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen);
  20558. }
  20559. this.isFullscreen(isFs);
  20560. }
  20561. /**
  20562. * Handle Tech Fullscreen Change
  20563. *
  20564. * @param {EventTarget~Event} event
  20565. * the fullscreenchange event that triggered this function
  20566. *
  20567. * @param {Object} data
  20568. * the data that was sent with the event
  20569. *
  20570. * @private
  20571. * @listens Tech#fullscreenchange
  20572. * @fires Player#fullscreenchange
  20573. */
  20574. ;
  20575. _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
  20576. var _this9 = this;
  20577. if (data) {
  20578. if (data.nativeIOSFullscreen) {
  20579. this.addClass('vjs-ios-native-fs');
  20580. this.tech_.one('webkitendfullscreen', function () {
  20581. _this9.removeClass('vjs-ios-native-fs');
  20582. });
  20583. }
  20584. this.isFullscreen(data.isFullscreen);
  20585. }
  20586. };
  20587. _proto.handleTechFullscreenError_ = function handleTechFullscreenError_(event, err) {
  20588. this.trigger('fullscreenerror', err);
  20589. }
  20590. /**
  20591. * @private
  20592. */
  20593. ;
  20594. _proto.togglePictureInPictureClass_ = function togglePictureInPictureClass_() {
  20595. if (this.isInPictureInPicture()) {
  20596. this.addClass('vjs-picture-in-picture');
  20597. } else {
  20598. this.removeClass('vjs-picture-in-picture');
  20599. }
  20600. }
  20601. /**
  20602. * Handle Tech Enter Picture-in-Picture.
  20603. *
  20604. * @param {EventTarget~Event} event
  20605. * the enterpictureinpicture event that triggered this function
  20606. *
  20607. * @private
  20608. * @listens Tech#enterpictureinpicture
  20609. */
  20610. ;
  20611. _proto.handleTechEnterPictureInPicture_ = function handleTechEnterPictureInPicture_(event) {
  20612. this.isInPictureInPicture(true);
  20613. }
  20614. /**
  20615. * Handle Tech Leave Picture-in-Picture.
  20616. *
  20617. * @param {EventTarget~Event} event
  20618. * the leavepictureinpicture event that triggered this function
  20619. *
  20620. * @private
  20621. * @listens Tech#leavepictureinpicture
  20622. */
  20623. ;
  20624. _proto.handleTechLeavePictureInPicture_ = function handleTechLeavePictureInPicture_(event) {
  20625. this.isInPictureInPicture(false);
  20626. }
  20627. /**
  20628. * Fires when an error occurred during the loading of an audio/video.
  20629. *
  20630. * @private
  20631. * @listens Tech#error
  20632. */
  20633. ;
  20634. _proto.handleTechError_ = function handleTechError_() {
  20635. var error = this.tech_.error();
  20636. this.error(error);
  20637. }
  20638. /**
  20639. * Retrigger the `textdata` event that was triggered by the {@link Tech}.
  20640. *
  20641. * @fires Player#textdata
  20642. * @listens Tech#textdata
  20643. * @private
  20644. */
  20645. ;
  20646. _proto.handleTechTextData_ = function handleTechTextData_() {
  20647. var data = null;
  20648. if (arguments.length > 1) {
  20649. data = arguments[1];
  20650. }
  20651. /**
  20652. * Fires when we get a textdata event from tech
  20653. *
  20654. * @event Player#textdata
  20655. * @type {EventTarget~Event}
  20656. */
  20657. this.trigger('textdata', data);
  20658. }
  20659. /**
  20660. * Get object for cached values.
  20661. *
  20662. * @return {Object}
  20663. * get the current object cache
  20664. */
  20665. ;
  20666. _proto.getCache = function getCache() {
  20667. return this.cache_;
  20668. }
  20669. /**
  20670. * Resets the internal cache object.
  20671. *
  20672. * Using this function outside the player constructor or reset method may
  20673. * have unintended side-effects.
  20674. *
  20675. * @private
  20676. */
  20677. ;
  20678. _proto.resetCache_ = function resetCache_() {
  20679. this.cache_ = {
  20680. // Right now, the currentTime is not _really_ cached because it is always
  20681. // retrieved from the tech (see: currentTime). However, for completeness,
  20682. // we set it to zero here to ensure that if we do start actually caching
  20683. // it, we reset it along with everything else.
  20684. currentTime: 0,
  20685. initTime: 0,
  20686. inactivityTimeout: this.options_.inactivityTimeout,
  20687. duration: NaN,
  20688. lastVolume: 1,
  20689. lastPlaybackRate: this.defaultPlaybackRate(),
  20690. media: null,
  20691. src: '',
  20692. source: {},
  20693. sources: [],
  20694. playbackRates: [],
  20695. volume: 1
  20696. };
  20697. }
  20698. /**
  20699. * Pass values to the playback tech
  20700. *
  20701. * @param {string} [method]
  20702. * the method to call
  20703. *
  20704. * @param {Object} arg
  20705. * the argument to pass
  20706. *
  20707. * @private
  20708. */
  20709. ;
  20710. _proto.techCall_ = function techCall_(method, arg) {
  20711. // If it's not ready yet, call method when it is
  20712. this.ready(function () {
  20713. if (method in allowedSetters) {
  20714. return set(this.middleware_, this.tech_, method, arg);
  20715. } else if (method in allowedMediators) {
  20716. return mediate(this.middleware_, this.tech_, method, arg);
  20717. }
  20718. try {
  20719. if (this.tech_) {
  20720. this.tech_[method](arg);
  20721. }
  20722. } catch (e) {
  20723. log$1(e);
  20724. throw e;
  20725. }
  20726. }, true);
  20727. }
  20728. /**
  20729. * Get calls can't wait for the tech, and sometimes don't need to.
  20730. *
  20731. * @param {string} method
  20732. * Tech method
  20733. *
  20734. * @return {Function|undefined}
  20735. * the method or undefined
  20736. *
  20737. * @private
  20738. */
  20739. ;
  20740. _proto.techGet_ = function techGet_(method) {
  20741. if (!this.tech_ || !this.tech_.isReady_) {
  20742. return;
  20743. }
  20744. if (method in allowedGetters) {
  20745. return get(this.middleware_, this.tech_, method);
  20746. } else if (method in allowedMediators) {
  20747. return mediate(this.middleware_, this.tech_, method);
  20748. } // Flash likes to die and reload when you hide or reposition it.
  20749. // In these cases the object methods go away and we get errors.
  20750. // TODO: Is this needed for techs other than Flash?
  20751. // When that happens we'll catch the errors and inform tech that it's not ready any more.
  20752. try {
  20753. return this.tech_[method]();
  20754. } catch (e) {
  20755. // When building additional tech libs, an expected method may not be defined yet
  20756. if (this.tech_[method] === undefined) {
  20757. log$1("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e);
  20758. throw e;
  20759. } // When a method isn't available on the object it throws a TypeError
  20760. if (e.name === 'TypeError') {
  20761. log$1("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e);
  20762. this.tech_.isReady_ = false;
  20763. throw e;
  20764. } // If error unknown, just log and throw
  20765. log$1(e);
  20766. throw e;
  20767. }
  20768. }
  20769. /**
  20770. * Attempt to begin playback at the first opportunity.
  20771. *
  20772. * @return {Promise|undefined}
  20773. * Returns a promise if the browser supports Promises (or one
  20774. * was passed in as an option). This promise will be resolved on
  20775. * the return value of play. If this is undefined it will fulfill the
  20776. * promise chain otherwise the promise chain will be fulfilled when
  20777. * the promise from play is fulfilled.
  20778. */
  20779. ;
  20780. _proto.play = function play() {
  20781. var _this10 = this;
  20782. var PromiseClass = this.options_.Promise || window$1.Promise;
  20783. if (PromiseClass) {
  20784. return new PromiseClass(function (resolve) {
  20785. _this10.play_(resolve);
  20786. });
  20787. }
  20788. return this.play_();
  20789. }
  20790. /**
  20791. * The actual logic for play, takes a callback that will be resolved on the
  20792. * return value of play. This allows us to resolve to the play promise if there
  20793. * is one on modern browsers.
  20794. *
  20795. * @private
  20796. * @param {Function} [callback]
  20797. * The callback that should be called when the techs play is actually called
  20798. */
  20799. ;
  20800. _proto.play_ = function play_(callback) {
  20801. var _this11 = this;
  20802. if (callback === void 0) {
  20803. callback = silencePromise;
  20804. }
  20805. this.playCallbacks_.push(callback);
  20806. var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc()));
  20807. var isSafariOrIOS = Boolean(IS_ANY_SAFARI || IS_IOS); // treat calls to play_ somewhat like the `one` event function
  20808. if (this.waitToPlay_) {
  20809. this.off(['ready', 'loadstart'], this.waitToPlay_);
  20810. this.waitToPlay_ = null;
  20811. } // if the player/tech is not ready or the src itself is not ready
  20812. // queue up a call to play on `ready` or `loadstart`
  20813. if (!this.isReady_ || !isSrcReady) {
  20814. this.waitToPlay_ = function (e) {
  20815. _this11.play_();
  20816. };
  20817. this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
  20818. // in that case, we need to prime the video element by calling load so it'll be ready in time
  20819. if (!isSrcReady && isSafariOrIOS) {
  20820. this.load();
  20821. }
  20822. return;
  20823. } // If the player/tech is ready and we have a source, we can attempt playback.
  20824. var val = this.techGet_('play'); // For native playback, reset the progress bar if we get a play call from a replay.
  20825. var isNativeReplay = isSafariOrIOS && this.hasClass('vjs-ended');
  20826. if (isNativeReplay) {
  20827. this.resetProgressBar_();
  20828. } // play was terminated if the returned value is null
  20829. if (val === null) {
  20830. this.runPlayTerminatedQueue_();
  20831. } else {
  20832. this.runPlayCallbacks_(val);
  20833. }
  20834. }
  20835. /**
  20836. * These functions will be run when if play is terminated. If play
  20837. * runPlayCallbacks_ is run these function will not be run. This allows us
  20838. * to differenciate between a terminated play and an actual call to play.
  20839. */
  20840. ;
  20841. _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() {
  20842. var queue = this.playTerminatedQueue_.slice(0);
  20843. this.playTerminatedQueue_ = [];
  20844. queue.forEach(function (q) {
  20845. q();
  20846. });
  20847. }
  20848. /**
  20849. * When a callback to play is delayed we have to run these
  20850. * callbacks when play is actually called on the tech. This function
  20851. * runs the callbacks that were delayed and accepts the return value
  20852. * from the tech.
  20853. *
  20854. * @param {undefined|Promise} val
  20855. * The return value from the tech.
  20856. */
  20857. ;
  20858. _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) {
  20859. var callbacks = this.playCallbacks_.slice(0);
  20860. this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play
  20861. this.playTerminatedQueue_ = [];
  20862. callbacks.forEach(function (cb) {
  20863. cb(val);
  20864. });
  20865. }
  20866. /**
  20867. * Pause the video playback
  20868. *
  20869. * @return {Player}
  20870. * A reference to the player object this function was called on
  20871. */
  20872. ;
  20873. _proto.pause = function pause() {
  20874. this.techCall_('pause');
  20875. }
  20876. /**
  20877. * Check if the player is paused or has yet to play
  20878. *
  20879. * @return {boolean}
  20880. * - false: if the media is currently playing
  20881. * - true: if media is not currently playing
  20882. */
  20883. ;
  20884. _proto.paused = function paused() {
  20885. // The initial state of paused should be true (in Safari it's actually false)
  20886. return this.techGet_('paused') === false ? false : true;
  20887. }
  20888. /**
  20889. * Get a TimeRange object representing the current ranges of time that the user
  20890. * has played.
  20891. *
  20892. * @return {TimeRange}
  20893. * A time range object that represents all the increments of time that have
  20894. * been played.
  20895. */
  20896. ;
  20897. _proto.played = function played() {
  20898. return this.techGet_('played') || createTimeRanges(0, 0);
  20899. }
  20900. /**
  20901. * Returns whether or not the user is "scrubbing". Scrubbing is
  20902. * when the user has clicked the progress bar handle and is
  20903. * dragging it along the progress bar.
  20904. *
  20905. * @param {boolean} [isScrubbing]
  20906. * whether the user is or is not scrubbing
  20907. *
  20908. * @return {boolean}
  20909. * The value of scrubbing when getting
  20910. */
  20911. ;
  20912. _proto.scrubbing = function scrubbing(isScrubbing) {
  20913. if (typeof isScrubbing === 'undefined') {
  20914. return this.scrubbing_;
  20915. }
  20916. this.scrubbing_ = !!isScrubbing;
  20917. this.techCall_('setScrubbing', this.scrubbing_);
  20918. if (isScrubbing) {
  20919. this.addClass('vjs-scrubbing');
  20920. } else {
  20921. this.removeClass('vjs-scrubbing');
  20922. }
  20923. }
  20924. /**
  20925. * Get or set the current time (in seconds)
  20926. *
  20927. * @param {number|string} [seconds]
  20928. * The time to seek to in seconds
  20929. *
  20930. * @return {number}
  20931. * - the current time in seconds when getting
  20932. */
  20933. ;
  20934. _proto.currentTime = function currentTime(seconds) {
  20935. if (typeof seconds !== 'undefined') {
  20936. if (seconds < 0) {
  20937. seconds = 0;
  20938. }
  20939. if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) {
  20940. this.cache_.initTime = seconds;
  20941. this.off('canplay', this.boundApplyInitTime_);
  20942. this.one('canplay', this.boundApplyInitTime_);
  20943. return;
  20944. }
  20945. this.techCall_('setCurrentTime', seconds);
  20946. this.cache_.initTime = 0;
  20947. return;
  20948. } // cache last currentTime and return. default to 0 seconds
  20949. //
  20950. // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
  20951. // currentTime when scrubbing, but may not provide much performance benefit afterall.
  20952. // Should be tested. Also something has to read the actual current time or the cache will
  20953. // never get updated.
  20954. this.cache_.currentTime = this.techGet_('currentTime') || 0;
  20955. return this.cache_.currentTime;
  20956. }
  20957. /**
  20958. * Apply the value of initTime stored in cache as currentTime.
  20959. *
  20960. * @private
  20961. */
  20962. ;
  20963. _proto.applyInitTime_ = function applyInitTime_() {
  20964. this.currentTime(this.cache_.initTime);
  20965. }
  20966. /**
  20967. * Normally gets the length in time of the video in seconds;
  20968. * in all but the rarest use cases an argument will NOT be passed to the method
  20969. *
  20970. * > **NOTE**: The video must have started loading before the duration can be
  20971. * known, and depending on preload behaviour may not be known until the video starts
  20972. * playing.
  20973. *
  20974. * @fires Player#durationchange
  20975. *
  20976. * @param {number} [seconds]
  20977. * The duration of the video to set in seconds
  20978. *
  20979. * @return {number}
  20980. * - The duration of the video in seconds when getting
  20981. */
  20982. ;
  20983. _proto.duration = function duration(seconds) {
  20984. if (seconds === undefined) {
  20985. // return NaN if the duration is not known
  20986. return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
  20987. }
  20988. seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live
  20989. if (seconds < 0) {
  20990. seconds = Infinity;
  20991. }
  20992. if (seconds !== this.cache_.duration) {
  20993. // Cache the last set value for optimized scrubbing (esp. Flash)
  20994. // TODO: Required for techs other than Flash?
  20995. this.cache_.duration = seconds;
  20996. if (seconds === Infinity) {
  20997. this.addClass('vjs-live');
  20998. } else {
  20999. this.removeClass('vjs-live');
  21000. }
  21001. if (!isNaN(seconds)) {
  21002. // Do not fire durationchange unless the duration value is known.
  21003. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  21004. /**
  21005. * @event Player#durationchange
  21006. * @type {EventTarget~Event}
  21007. */
  21008. this.trigger('durationchange');
  21009. }
  21010. }
  21011. }
  21012. /**
  21013. * Calculates how much time is left in the video. Not part
  21014. * of the native video API.
  21015. *
  21016. * @return {number}
  21017. * The time remaining in seconds
  21018. */
  21019. ;
  21020. _proto.remainingTime = function remainingTime() {
  21021. return this.duration() - this.currentTime();
  21022. }
  21023. /**
  21024. * A remaining time function that is intented to be used when
  21025. * the time is to be displayed directly to the user.
  21026. *
  21027. * @return {number}
  21028. * The rounded time remaining in seconds
  21029. */
  21030. ;
  21031. _proto.remainingTimeDisplay = function remainingTimeDisplay() {
  21032. return Math.floor(this.duration()) - Math.floor(this.currentTime());
  21033. } //
  21034. // Kind of like an array of portions of the video that have been downloaded.
  21035. /**
  21036. * Get a TimeRange object with an array of the times of the video
  21037. * that have been downloaded. If you just want the percent of the
  21038. * video that's been downloaded, use bufferedPercent.
  21039. *
  21040. * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
  21041. *
  21042. * @return {TimeRange}
  21043. * A mock TimeRange object (following HTML spec)
  21044. */
  21045. ;
  21046. _proto.buffered = function buffered() {
  21047. var buffered = this.techGet_('buffered');
  21048. if (!buffered || !buffered.length) {
  21049. buffered = createTimeRanges(0, 0);
  21050. }
  21051. return buffered;
  21052. }
  21053. /**
  21054. * Get the percent (as a decimal) of the video that's been downloaded.
  21055. * This method is not a part of the native HTML video API.
  21056. *
  21057. * @return {number}
  21058. * A decimal between 0 and 1 representing the percent
  21059. * that is buffered 0 being 0% and 1 being 100%
  21060. */
  21061. ;
  21062. _proto.bufferedPercent = function bufferedPercent$1() {
  21063. return bufferedPercent(this.buffered(), this.duration());
  21064. }
  21065. /**
  21066. * Get the ending time of the last buffered time range
  21067. * This is used in the progress bar to encapsulate all time ranges.
  21068. *
  21069. * @return {number}
  21070. * The end of the last buffered time range
  21071. */
  21072. ;
  21073. _proto.bufferedEnd = function bufferedEnd() {
  21074. var buffered = this.buffered();
  21075. var duration = this.duration();
  21076. var end = buffered.end(buffered.length - 1);
  21077. if (end > duration) {
  21078. end = duration;
  21079. }
  21080. return end;
  21081. }
  21082. /**
  21083. * Get or set the current volume of the media
  21084. *
  21085. * @param {number} [percentAsDecimal]
  21086. * The new volume as a decimal percent:
  21087. * - 0 is muted/0%/off
  21088. * - 1.0 is 100%/full
  21089. * - 0.5 is half volume or 50%
  21090. *
  21091. * @return {number}
  21092. * The current volume as a percent when getting
  21093. */
  21094. ;
  21095. _proto.volume = function volume(percentAsDecimal) {
  21096. var vol;
  21097. if (percentAsDecimal !== undefined) {
  21098. // Force value to between 0 and 1
  21099. vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
  21100. this.cache_.volume = vol;
  21101. this.techCall_('setVolume', vol);
  21102. if (vol > 0) {
  21103. this.lastVolume_(vol);
  21104. }
  21105. return;
  21106. } // Default to 1 when returning current volume.
  21107. vol = parseFloat(this.techGet_('volume'));
  21108. return isNaN(vol) ? 1 : vol;
  21109. }
  21110. /**
  21111. * Get the current muted state, or turn mute on or off
  21112. *
  21113. * @param {boolean} [muted]
  21114. * - true to mute
  21115. * - false to unmute
  21116. *
  21117. * @return {boolean}
  21118. * - true if mute is on and getting
  21119. * - false if mute is off and getting
  21120. */
  21121. ;
  21122. _proto.muted = function muted(_muted) {
  21123. if (_muted !== undefined) {
  21124. this.techCall_('setMuted', _muted);
  21125. return;
  21126. }
  21127. return this.techGet_('muted') || false;
  21128. }
  21129. /**
  21130. * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
  21131. * indicates the state of muted on initial playback.
  21132. *
  21133. * ```js
  21134. * var myPlayer = videojs('some-player-id');
  21135. *
  21136. * myPlayer.src("http://www.example.com/path/to/video.mp4");
  21137. *
  21138. * // get, should be false
  21139. * console.log(myPlayer.defaultMuted());
  21140. * // set to true
  21141. * myPlayer.defaultMuted(true);
  21142. * // get should be true
  21143. * console.log(myPlayer.defaultMuted());
  21144. * ```
  21145. *
  21146. * @param {boolean} [defaultMuted]
  21147. * - true to mute
  21148. * - false to unmute
  21149. *
  21150. * @return {boolean|Player}
  21151. * - true if defaultMuted is on and getting
  21152. * - false if defaultMuted is off and getting
  21153. * - A reference to the current player when setting
  21154. */
  21155. ;
  21156. _proto.defaultMuted = function defaultMuted(_defaultMuted) {
  21157. if (_defaultMuted !== undefined) {
  21158. return this.techCall_('setDefaultMuted', _defaultMuted);
  21159. }
  21160. return this.techGet_('defaultMuted') || false;
  21161. }
  21162. /**
  21163. * Get the last volume, or set it
  21164. *
  21165. * @param {number} [percentAsDecimal]
  21166. * The new last volume as a decimal percent:
  21167. * - 0 is muted/0%/off
  21168. * - 1.0 is 100%/full
  21169. * - 0.5 is half volume or 50%
  21170. *
  21171. * @return {number}
  21172. * the current value of lastVolume as a percent when getting
  21173. *
  21174. * @private
  21175. */
  21176. ;
  21177. _proto.lastVolume_ = function lastVolume_(percentAsDecimal) {
  21178. if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
  21179. this.cache_.lastVolume = percentAsDecimal;
  21180. return;
  21181. }
  21182. return this.cache_.lastVolume;
  21183. }
  21184. /**
  21185. * Check if current tech can support native fullscreen
  21186. * (e.g. with built in controls like iOS)
  21187. *
  21188. * @return {boolean}
  21189. * if native fullscreen is supported
  21190. */
  21191. ;
  21192. _proto.supportsFullScreen = function supportsFullScreen() {
  21193. return this.techGet_('supportsFullScreen') || false;
  21194. }
  21195. /**
  21196. * Check if the player is in fullscreen mode or tell the player that it
  21197. * is or is not in fullscreen mode.
  21198. *
  21199. * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
  21200. * property and instead document.fullscreenElement is used. But isFullscreen is
  21201. * still a valuable property for internal player workings.
  21202. *
  21203. * @param {boolean} [isFS]
  21204. * Set the players current fullscreen state
  21205. *
  21206. * @return {boolean}
  21207. * - true if fullscreen is on and getting
  21208. * - false if fullscreen is off and getting
  21209. */
  21210. ;
  21211. _proto.isFullscreen = function isFullscreen(isFS) {
  21212. if (isFS !== undefined) {
  21213. var oldValue = this.isFullscreen_;
  21214. this.isFullscreen_ = Boolean(isFS); // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange
  21215. // this is the only place where we trigger fullscreenchange events for older browsers
  21216. // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well
  21217. if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) {
  21218. /**
  21219. * @event Player#fullscreenchange
  21220. * @type {EventTarget~Event}
  21221. */
  21222. this.trigger('fullscreenchange');
  21223. }
  21224. this.toggleFullscreenClass_();
  21225. return;
  21226. }
  21227. return this.isFullscreen_;
  21228. }
  21229. /**
  21230. * Increase the size of the video to full screen
  21231. * In some browsers, full screen is not supported natively, so it enters
  21232. * "full window mode", where the video fills the browser window.
  21233. * In browsers and devices that support native full screen, sometimes the
  21234. * browser's default controls will be shown, and not the Video.js custom skin.
  21235. * This includes most mobile devices (iOS, Android) and older versions of
  21236. * Safari.
  21237. *
  21238. * @param {Object} [fullscreenOptions]
  21239. * Override the player fullscreen options
  21240. *
  21241. * @fires Player#fullscreenchange
  21242. */
  21243. ;
  21244. _proto.requestFullscreen = function requestFullscreen(fullscreenOptions) {
  21245. var PromiseClass = this.options_.Promise || window$1.Promise;
  21246. if (PromiseClass) {
  21247. var self = this;
  21248. return new PromiseClass(function (resolve, reject) {
  21249. function offHandler() {
  21250. self.off('fullscreenerror', errorHandler);
  21251. self.off('fullscreenchange', changeHandler);
  21252. }
  21253. function changeHandler() {
  21254. offHandler();
  21255. resolve();
  21256. }
  21257. function errorHandler(e, err) {
  21258. offHandler();
  21259. reject(err);
  21260. }
  21261. self.one('fullscreenchange', changeHandler);
  21262. self.one('fullscreenerror', errorHandler);
  21263. var promise = self.requestFullscreenHelper_(fullscreenOptions);
  21264. if (promise) {
  21265. promise.then(offHandler, offHandler);
  21266. promise.then(resolve, reject);
  21267. }
  21268. });
  21269. }
  21270. return this.requestFullscreenHelper_();
  21271. };
  21272. _proto.requestFullscreenHelper_ = function requestFullscreenHelper_(fullscreenOptions) {
  21273. var _this12 = this;
  21274. var fsOptions; // Only pass fullscreen options to requestFullscreen in spec-compliant browsers.
  21275. // Use defaults or player configured option unless passed directly to this method.
  21276. if (!this.fsApi_.prefixed) {
  21277. fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {};
  21278. if (fullscreenOptions !== undefined) {
  21279. fsOptions = fullscreenOptions;
  21280. }
  21281. } // This method works as follows:
  21282. // 1. if a fullscreen api is available, use it
  21283. // 1. call requestFullscreen with potential options
  21284. // 2. if we got a promise from above, use it to update isFullscreen()
  21285. // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it.
  21286. // This is particularly used for iPhone, older iPads, and non-safari browser on iOS.
  21287. // 3. otherwise, use "fullWindow" mode
  21288. if (this.fsApi_.requestFullscreen) {
  21289. var promise = this.el_[this.fsApi_.requestFullscreen](fsOptions);
  21290. if (promise) {
  21291. promise.then(function () {
  21292. return _this12.isFullscreen(true);
  21293. }, function () {
  21294. return _this12.isFullscreen(false);
  21295. });
  21296. }
  21297. return promise;
  21298. } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
  21299. // we can't take the video.js controls fullscreen but we can go fullscreen
  21300. // with native controls
  21301. this.techCall_('enterFullScreen');
  21302. } else {
  21303. // fullscreen isn't supported so we'll just stretch the video element to
  21304. // fill the viewport
  21305. this.enterFullWindow();
  21306. }
  21307. }
  21308. /**
  21309. * Return the video to its normal size after having been in full screen mode
  21310. *
  21311. * @fires Player#fullscreenchange
  21312. */
  21313. ;
  21314. _proto.exitFullscreen = function exitFullscreen() {
  21315. var PromiseClass = this.options_.Promise || window$1.Promise;
  21316. if (PromiseClass) {
  21317. var self = this;
  21318. return new PromiseClass(function (resolve, reject) {
  21319. function offHandler() {
  21320. self.off('fullscreenerror', errorHandler);
  21321. self.off('fullscreenchange', changeHandler);
  21322. }
  21323. function changeHandler() {
  21324. offHandler();
  21325. resolve();
  21326. }
  21327. function errorHandler(e, err) {
  21328. offHandler();
  21329. reject(err);
  21330. }
  21331. self.one('fullscreenchange', changeHandler);
  21332. self.one('fullscreenerror', errorHandler);
  21333. var promise = self.exitFullscreenHelper_();
  21334. if (promise) {
  21335. promise.then(offHandler, offHandler); // map the promise to our resolve/reject methods
  21336. promise.then(resolve, reject);
  21337. }
  21338. });
  21339. }
  21340. return this.exitFullscreenHelper_();
  21341. };
  21342. _proto.exitFullscreenHelper_ = function exitFullscreenHelper_() {
  21343. var _this13 = this;
  21344. if (this.fsApi_.requestFullscreen) {
  21345. var promise = document[this.fsApi_.exitFullscreen]();
  21346. if (promise) {
  21347. // we're splitting the promise here, so, we want to catch the
  21348. // potential error so that this chain doesn't have unhandled errors
  21349. silencePromise(promise.then(function () {
  21350. return _this13.isFullscreen(false);
  21351. }));
  21352. }
  21353. return promise;
  21354. } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
  21355. this.techCall_('exitFullScreen');
  21356. } else {
  21357. this.exitFullWindow();
  21358. }
  21359. }
  21360. /**
  21361. * When fullscreen isn't supported we can stretch the
  21362. * video container to as wide as the browser will let us.
  21363. *
  21364. * @fires Player#enterFullWindow
  21365. */
  21366. ;
  21367. _proto.enterFullWindow = function enterFullWindow() {
  21368. this.isFullscreen(true);
  21369. this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off
  21370. this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen
  21371. on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars
  21372. document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles
  21373. addClass(document.body, 'vjs-full-window');
  21374. /**
  21375. * @event Player#enterFullWindow
  21376. * @type {EventTarget~Event}
  21377. */
  21378. this.trigger('enterFullWindow');
  21379. }
  21380. /**
  21381. * Check for call to either exit full window or
  21382. * full screen on ESC key
  21383. *
  21384. * @param {string} event
  21385. * Event to check for key press
  21386. */
  21387. ;
  21388. _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
  21389. if (keycode.isEventKey(event, 'Esc')) {
  21390. if (this.isFullscreen() === true) {
  21391. if (!this.isFullWindow) {
  21392. this.exitFullscreen();
  21393. } else {
  21394. this.exitFullWindow();
  21395. }
  21396. }
  21397. }
  21398. }
  21399. /**
  21400. * Exit full window
  21401. *
  21402. * @fires Player#exitFullWindow
  21403. */
  21404. ;
  21405. _proto.exitFullWindow = function exitFullWindow() {
  21406. this.isFullscreen(false);
  21407. this.isFullWindow = false;
  21408. off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars.
  21409. document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles
  21410. removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes
  21411. // this.positionAll();
  21412. /**
  21413. * @event Player#exitFullWindow
  21414. * @type {EventTarget~Event}
  21415. */
  21416. this.trigger('exitFullWindow');
  21417. }
  21418. /**
  21419. * Disable Picture-in-Picture mode.
  21420. *
  21421. * @param {boolean} value
  21422. * - true will disable Picture-in-Picture mode
  21423. * - false will enable Picture-in-Picture mode
  21424. */
  21425. ;
  21426. _proto.disablePictureInPicture = function disablePictureInPicture(value) {
  21427. if (value === undefined) {
  21428. return this.techGet_('disablePictureInPicture');
  21429. }
  21430. this.techCall_('setDisablePictureInPicture', value);
  21431. this.options_.disablePictureInPicture = value;
  21432. this.trigger('disablepictureinpicturechanged');
  21433. }
  21434. /**
  21435. * Check if the player is in Picture-in-Picture mode or tell the player that it
  21436. * is or is not in Picture-in-Picture mode.
  21437. *
  21438. * @param {boolean} [isPiP]
  21439. * Set the players current Picture-in-Picture state
  21440. *
  21441. * @return {boolean}
  21442. * - true if Picture-in-Picture is on and getting
  21443. * - false if Picture-in-Picture is off and getting
  21444. */
  21445. ;
  21446. _proto.isInPictureInPicture = function isInPictureInPicture(isPiP) {
  21447. if (isPiP !== undefined) {
  21448. this.isInPictureInPicture_ = !!isPiP;
  21449. this.togglePictureInPictureClass_();
  21450. return;
  21451. }
  21452. return !!this.isInPictureInPicture_;
  21453. }
  21454. /**
  21455. * Create a floating video window always on top of other windows so that users may
  21456. * continue consuming media while they interact with other content sites, or
  21457. * applications on their device.
  21458. *
  21459. * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
  21460. *
  21461. * @fires Player#enterpictureinpicture
  21462. *
  21463. * @return {Promise}
  21464. * A promise with a Picture-in-Picture window.
  21465. */
  21466. ;
  21467. _proto.requestPictureInPicture = function requestPictureInPicture() {
  21468. if ('pictureInPictureEnabled' in document && this.disablePictureInPicture() === false) {
  21469. /**
  21470. * This event fires when the player enters picture in picture mode
  21471. *
  21472. * @event Player#enterpictureinpicture
  21473. * @type {EventTarget~Event}
  21474. */
  21475. return this.techGet_('requestPictureInPicture');
  21476. }
  21477. }
  21478. /**
  21479. * Exit Picture-in-Picture mode.
  21480. *
  21481. * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
  21482. *
  21483. * @fires Player#leavepictureinpicture
  21484. *
  21485. * @return {Promise}
  21486. * A promise.
  21487. */
  21488. ;
  21489. _proto.exitPictureInPicture = function exitPictureInPicture() {
  21490. if ('pictureInPictureEnabled' in document) {
  21491. /**
  21492. * This event fires when the player leaves picture in picture mode
  21493. *
  21494. * @event Player#leavepictureinpicture
  21495. * @type {EventTarget~Event}
  21496. */
  21497. return document.exitPictureInPicture();
  21498. }
  21499. }
  21500. /**
  21501. * Called when this Player has focus and a key gets pressed down, or when
  21502. * any Component of this player receives a key press that it doesn't handle.
  21503. * This allows player-wide hotkeys (either as defined below, or optionally
  21504. * by an external function).
  21505. *
  21506. * @param {EventTarget~Event} event
  21507. * The `keydown` event that caused this function to be called.
  21508. *
  21509. * @listens keydown
  21510. */
  21511. ;
  21512. _proto.handleKeyDown = function handleKeyDown(event) {
  21513. var userActions = this.options_.userActions; // Bail out if hotkeys are not configured.
  21514. if (!userActions || !userActions.hotkeys) {
  21515. return;
  21516. } // Function that determines whether or not to exclude an element from
  21517. // hotkeys handling.
  21518. var excludeElement = function excludeElement(el) {
  21519. var tagName = el.tagName.toLowerCase(); // The first and easiest test is for `contenteditable` elements.
  21520. if (el.isContentEditable) {
  21521. return true;
  21522. } // Inputs matching these types will still trigger hotkey handling as
  21523. // they are not text inputs.
  21524. var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit'];
  21525. if (tagName === 'input') {
  21526. return allowedInputTypes.indexOf(el.type) === -1;
  21527. } // The final test is by tag name. These tags will be excluded entirely.
  21528. var excludedTags = ['textarea'];
  21529. return excludedTags.indexOf(tagName) !== -1;
  21530. }; // Bail out if the user is focused on an interactive form element.
  21531. if (excludeElement(this.el_.ownerDocument.activeElement)) {
  21532. return;
  21533. }
  21534. if (typeof userActions.hotkeys === 'function') {
  21535. userActions.hotkeys.call(this, event);
  21536. } else {
  21537. this.handleHotkeys(event);
  21538. }
  21539. }
  21540. /**
  21541. * Called when this Player receives a hotkey keydown event.
  21542. * Supported player-wide hotkeys are:
  21543. *
  21544. * f - toggle fullscreen
  21545. * m - toggle mute
  21546. * k or Space - toggle play/pause
  21547. *
  21548. * @param {EventTarget~Event} event
  21549. * The `keydown` event that caused this function to be called.
  21550. */
  21551. ;
  21552. _proto.handleHotkeys = function handleHotkeys(event) {
  21553. var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
  21554. var _hotkeys$fullscreenKe = hotkeys.fullscreenKey,
  21555. fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) {
  21556. return keycode.isEventKey(keydownEvent, 'f');
  21557. } : _hotkeys$fullscreenKe,
  21558. _hotkeys$muteKey = hotkeys.muteKey,
  21559. muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) {
  21560. return keycode.isEventKey(keydownEvent, 'm');
  21561. } : _hotkeys$muteKey,
  21562. _hotkeys$playPauseKey = hotkeys.playPauseKey,
  21563. playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) {
  21564. return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space');
  21565. } : _hotkeys$playPauseKey;
  21566. if (fullscreenKey.call(this, event)) {
  21567. event.preventDefault();
  21568. event.stopPropagation();
  21569. var FSToggle = Component$1.getComponent('FullscreenToggle');
  21570. if (document[this.fsApi_.fullscreenEnabled] !== false) {
  21571. FSToggle.prototype.handleClick.call(this, event);
  21572. }
  21573. } else if (muteKey.call(this, event)) {
  21574. event.preventDefault();
  21575. event.stopPropagation();
  21576. var MuteToggle = Component$1.getComponent('MuteToggle');
  21577. MuteToggle.prototype.handleClick.call(this, event);
  21578. } else if (playPauseKey.call(this, event)) {
  21579. event.preventDefault();
  21580. event.stopPropagation();
  21581. var PlayToggle = Component$1.getComponent('PlayToggle');
  21582. PlayToggle.prototype.handleClick.call(this, event);
  21583. }
  21584. }
  21585. /**
  21586. * Check whether the player can play a given mimetype
  21587. *
  21588. * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
  21589. *
  21590. * @param {string} type
  21591. * The mimetype to check
  21592. *
  21593. * @return {string}
  21594. * 'probably', 'maybe', or '' (empty string)
  21595. */
  21596. ;
  21597. _proto.canPlayType = function canPlayType(type) {
  21598. var can; // Loop through each playback technology in the options order
  21599. for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
  21600. var techName = j[i];
  21601. var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
  21602. // Remove once that deprecated behavior is removed.
  21603. if (!tech) {
  21604. tech = Component$1.getComponent(techName);
  21605. } // Check if the current tech is defined before continuing
  21606. if (!tech) {
  21607. log$1.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
  21608. continue;
  21609. } // Check if the browser supports this technology
  21610. if (tech.isSupported()) {
  21611. can = tech.canPlayType(type);
  21612. if (can) {
  21613. return can;
  21614. }
  21615. }
  21616. }
  21617. return '';
  21618. }
  21619. /**
  21620. * Select source based on tech-order or source-order
  21621. * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
  21622. * defaults to tech-order selection
  21623. *
  21624. * @param {Array} sources
  21625. * The sources for a media asset
  21626. *
  21627. * @return {Object|boolean}
  21628. * Object of source and tech order or false
  21629. */
  21630. ;
  21631. _proto.selectSource = function selectSource(sources) {
  21632. var _this14 = this;
  21633. // Get only the techs specified in `techOrder` that exist and are supported by the
  21634. // current platform
  21635. var techs = this.options_.techOrder.map(function (techName) {
  21636. return [techName, Tech.getTech(techName)];
  21637. }).filter(function (_ref) {
  21638. var techName = _ref[0],
  21639. tech = _ref[1];
  21640. // Check if the current tech is defined before continuing
  21641. if (tech) {
  21642. // Check if the browser supports this technology
  21643. return tech.isSupported();
  21644. }
  21645. log$1.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
  21646. return false;
  21647. }); // Iterate over each `innerArray` element once per `outerArray` element and execute
  21648. // `tester` with both. If `tester` returns a non-falsy value, exit early and return
  21649. // that value.
  21650. var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
  21651. var found;
  21652. outerArray.some(function (outerChoice) {
  21653. return innerArray.some(function (innerChoice) {
  21654. found = tester(outerChoice, innerChoice);
  21655. if (found) {
  21656. return true;
  21657. }
  21658. });
  21659. });
  21660. return found;
  21661. };
  21662. var foundSourceAndTech;
  21663. var flip = function flip(fn) {
  21664. return function (a, b) {
  21665. return fn(b, a);
  21666. };
  21667. };
  21668. var finder = function finder(_ref2, source) {
  21669. var techName = _ref2[0],
  21670. tech = _ref2[1];
  21671. if (tech.canPlaySource(source, _this14.options_[techName.toLowerCase()])) {
  21672. return {
  21673. source: source,
  21674. tech: techName
  21675. };
  21676. }
  21677. }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
  21678. // to select from them based on their priority.
  21679. if (this.options_.sourceOrder) {
  21680. // Source-first ordering
  21681. foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
  21682. } else {
  21683. // Tech-first ordering
  21684. foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
  21685. }
  21686. return foundSourceAndTech || false;
  21687. }
  21688. /**
  21689. * Executes source setting and getting logic
  21690. *
  21691. * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
  21692. * A SourceObject, an array of SourceObjects, or a string referencing
  21693. * a URL to a media source. It is _highly recommended_ that an object
  21694. * or array of objects is used here, so that source selection
  21695. * algorithms can take the `type` into account.
  21696. *
  21697. * If not provided, this method acts as a getter.
  21698. * @param {boolean} isRetry
  21699. * Indicates whether this is being called internally as a result of a retry
  21700. *
  21701. * @return {string|undefined}
  21702. * If the `source` argument is missing, returns the current source
  21703. * URL. Otherwise, returns nothing/undefined.
  21704. */
  21705. ;
  21706. _proto.handleSrc_ = function handleSrc_(source, isRetry) {
  21707. var _this15 = this;
  21708. // getter usage
  21709. if (typeof source === 'undefined') {
  21710. return this.cache_.src || '';
  21711. } // Reset retry behavior for new source
  21712. if (this.resetRetryOnError_) {
  21713. this.resetRetryOnError_();
  21714. } // filter out invalid sources and turn our source into
  21715. // an array of source objects
  21716. var sources = filterSource(source); // if a source was passed in then it is invalid because
  21717. // it was filtered to a zero length Array. So we have to
  21718. // show an error
  21719. if (!sources.length) {
  21720. this.setTimeout(function () {
  21721. this.error({
  21722. code: 4,
  21723. message: this.options_.notSupportedMessage
  21724. });
  21725. }, 0);
  21726. return;
  21727. } // initial sources
  21728. this.changingSrc_ = true; // Only update the cached source list if we are not retrying a new source after error,
  21729. // since in that case we want to include the failed source(s) in the cache
  21730. if (!isRetry) {
  21731. this.cache_.sources = sources;
  21732. }
  21733. this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware
  21734. setSource(this, sources[0], function (middlewareSource, mws) {
  21735. _this15.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
  21736. // the source that is selected could be out of order from the cache update above this callback.
  21737. if (!isRetry) {
  21738. _this15.cache_.sources = sources;
  21739. }
  21740. _this15.updateSourceCaches_(middlewareSource);
  21741. var err = _this15.src_(middlewareSource);
  21742. if (err) {
  21743. if (sources.length > 1) {
  21744. return _this15.handleSrc_(sources.slice(1));
  21745. }
  21746. _this15.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
  21747. _this15.setTimeout(function () {
  21748. this.error({
  21749. code: 4,
  21750. message: this.options_.notSupportedMessage
  21751. });
  21752. }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it
  21753. // this needs a better comment about why this is needed
  21754. _this15.triggerReady();
  21755. return;
  21756. }
  21757. setTech(mws, _this15.tech_);
  21758. }); // Try another available source if this one fails before playback.
  21759. if (this.options_.retryOnError && sources.length > 1) {
  21760. var retry = function retry() {
  21761. // Remove the error modal
  21762. _this15.error(null);
  21763. _this15.handleSrc_(sources.slice(1), true);
  21764. };
  21765. var stopListeningForErrors = function stopListeningForErrors() {
  21766. _this15.off('error', retry);
  21767. };
  21768. this.one('error', retry);
  21769. this.one('playing', stopListeningForErrors);
  21770. this.resetRetryOnError_ = function () {
  21771. _this15.off('error', retry);
  21772. _this15.off('playing', stopListeningForErrors);
  21773. };
  21774. }
  21775. }
  21776. /**
  21777. * Get or set the video source.
  21778. *
  21779. * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
  21780. * A SourceObject, an array of SourceObjects, or a string referencing
  21781. * a URL to a media source. It is _highly recommended_ that an object
  21782. * or array of objects is used here, so that source selection
  21783. * algorithms can take the `type` into account.
  21784. *
  21785. * If not provided, this method acts as a getter.
  21786. *
  21787. * @return {string|undefined}
  21788. * If the `source` argument is missing, returns the current source
  21789. * URL. Otherwise, returns nothing/undefined.
  21790. */
  21791. ;
  21792. _proto.src = function src(source) {
  21793. return this.handleSrc_(source, false);
  21794. }
  21795. /**
  21796. * Set the source object on the tech, returns a boolean that indicates whether
  21797. * there is a tech that can play the source or not
  21798. *
  21799. * @param {Tech~SourceObject} source
  21800. * The source object to set on the Tech
  21801. *
  21802. * @return {boolean}
  21803. * - True if there is no Tech to playback this source
  21804. * - False otherwise
  21805. *
  21806. * @private
  21807. */
  21808. ;
  21809. _proto.src_ = function src_(source) {
  21810. var _this16 = this;
  21811. var sourceTech = this.selectSource([source]);
  21812. if (!sourceTech) {
  21813. return true;
  21814. }
  21815. if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
  21816. this.changingSrc_ = true; // load this technology with the chosen source
  21817. this.loadTech_(sourceTech.tech, sourceTech.source);
  21818. this.tech_.ready(function () {
  21819. _this16.changingSrc_ = false;
  21820. });
  21821. return false;
  21822. } // wait until the tech is ready to set the source
  21823. // and set it synchronously if possible (#2326)
  21824. this.ready(function () {
  21825. // The setSource tech method was added with source handlers
  21826. // so older techs won't support it
  21827. // We need to check the direct prototype for the case where subclasses
  21828. // of the tech do not support source handlers
  21829. if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
  21830. this.techCall_('setSource', source);
  21831. } else {
  21832. this.techCall_('src', source.src);
  21833. }
  21834. this.changingSrc_ = false;
  21835. }, true);
  21836. return false;
  21837. }
  21838. /**
  21839. * Begin loading the src data.
  21840. */
  21841. ;
  21842. _proto.load = function load() {
  21843. this.techCall_('load');
  21844. }
  21845. /**
  21846. * Reset the player. Loads the first tech in the techOrder,
  21847. * removes all the text tracks in the existing `tech`,
  21848. * and calls `reset` on the `tech`.
  21849. */
  21850. ;
  21851. _proto.reset = function reset() {
  21852. var _this17 = this;
  21853. var PromiseClass = this.options_.Promise || window$1.Promise;
  21854. if (this.paused() || !PromiseClass) {
  21855. this.doReset_();
  21856. } else {
  21857. var playPromise = this.play();
  21858. silencePromise(playPromise.then(function () {
  21859. return _this17.doReset_();
  21860. }));
  21861. }
  21862. };
  21863. _proto.doReset_ = function doReset_() {
  21864. if (this.tech_) {
  21865. this.tech_.clearTracks('text');
  21866. }
  21867. this.resetCache_();
  21868. this.poster('');
  21869. this.loadTech_(this.options_.techOrder[0], null);
  21870. this.techCall_('reset');
  21871. this.resetControlBarUI_();
  21872. if (isEvented(this)) {
  21873. this.trigger('playerreset');
  21874. }
  21875. }
  21876. /**
  21877. * Reset Control Bar's UI by calling sub-methods that reset
  21878. * all of Control Bar's components
  21879. */
  21880. ;
  21881. _proto.resetControlBarUI_ = function resetControlBarUI_() {
  21882. this.resetProgressBar_();
  21883. this.resetPlaybackRate_();
  21884. this.resetVolumeBar_();
  21885. }
  21886. /**
  21887. * Reset tech's progress so progress bar is reset in the UI
  21888. */
  21889. ;
  21890. _proto.resetProgressBar_ = function resetProgressBar_() {
  21891. this.currentTime(0);
  21892. var _ref3 = this.controlBar || {},
  21893. durationDisplay = _ref3.durationDisplay,
  21894. remainingTimeDisplay = _ref3.remainingTimeDisplay;
  21895. if (durationDisplay) {
  21896. durationDisplay.updateContent();
  21897. }
  21898. if (remainingTimeDisplay) {
  21899. remainingTimeDisplay.updateContent();
  21900. }
  21901. }
  21902. /**
  21903. * Reset Playback ratio
  21904. */
  21905. ;
  21906. _proto.resetPlaybackRate_ = function resetPlaybackRate_() {
  21907. this.playbackRate(this.defaultPlaybackRate());
  21908. this.handleTechRateChange_();
  21909. }
  21910. /**
  21911. * Reset Volume bar
  21912. */
  21913. ;
  21914. _proto.resetVolumeBar_ = function resetVolumeBar_() {
  21915. this.volume(1.0);
  21916. this.trigger('volumechange');
  21917. }
  21918. /**
  21919. * Returns all of the current source objects.
  21920. *
  21921. * @return {Tech~SourceObject[]}
  21922. * The current source objects
  21923. */
  21924. ;
  21925. _proto.currentSources = function currentSources() {
  21926. var source = this.currentSource();
  21927. var sources = []; // assume `{}` or `{ src }`
  21928. if (Object.keys(source).length !== 0) {
  21929. sources.push(source);
  21930. }
  21931. return this.cache_.sources || sources;
  21932. }
  21933. /**
  21934. * Returns the current source object.
  21935. *
  21936. * @return {Tech~SourceObject}
  21937. * The current source object
  21938. */
  21939. ;
  21940. _proto.currentSource = function currentSource() {
  21941. return this.cache_.source || {};
  21942. }
  21943. /**
  21944. * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
  21945. * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
  21946. *
  21947. * @return {string}
  21948. * The current source
  21949. */
  21950. ;
  21951. _proto.currentSrc = function currentSrc() {
  21952. return this.currentSource() && this.currentSource().src || '';
  21953. }
  21954. /**
  21955. * Get the current source type e.g. video/mp4
  21956. * This can allow you rebuild the current source object so that you could load the same
  21957. * source and tech later
  21958. *
  21959. * @return {string}
  21960. * The source MIME type
  21961. */
  21962. ;
  21963. _proto.currentType = function currentType() {
  21964. return this.currentSource() && this.currentSource().type || '';
  21965. }
  21966. /**
  21967. * Get or set the preload attribute
  21968. *
  21969. * @param {boolean} [value]
  21970. * - true means that we should preload
  21971. * - false means that we should not preload
  21972. *
  21973. * @return {string}
  21974. * The preload attribute value when getting
  21975. */
  21976. ;
  21977. _proto.preload = function preload(value) {
  21978. if (value !== undefined) {
  21979. this.techCall_('setPreload', value);
  21980. this.options_.preload = value;
  21981. return;
  21982. }
  21983. return this.techGet_('preload');
  21984. }
  21985. /**
  21986. * Get or set the autoplay option. When this is a boolean it will
  21987. * modify the attribute on the tech. When this is a string the attribute on
  21988. * the tech will be removed and `Player` will handle autoplay on loadstarts.
  21989. *
  21990. * @param {boolean|string} [value]
  21991. * - true: autoplay using the browser behavior
  21992. * - false: do not autoplay
  21993. * - 'play': call play() on every loadstart
  21994. * - 'muted': call muted() then play() on every loadstart
  21995. * - 'any': call play() on every loadstart. if that fails call muted() then play().
  21996. * - *: values other than those listed here will be set `autoplay` to true
  21997. *
  21998. * @return {boolean|string}
  21999. * The current value of autoplay when getting
  22000. */
  22001. ;
  22002. _proto.autoplay = function autoplay(value) {
  22003. // getter usage
  22004. if (value === undefined) {
  22005. return this.options_.autoplay || false;
  22006. }
  22007. var techAutoplay; // if the value is a valid string set it to that, or normalize `true` to 'play', if need be
  22008. if (typeof value === 'string' && /(any|play|muted)/.test(value) || value === true && this.options_.normalizeAutoplay) {
  22009. this.options_.autoplay = value;
  22010. this.manualAutoplay_(typeof value === 'string' ? value : 'play');
  22011. techAutoplay = false; // any falsy value sets autoplay to false in the browser,
  22012. // lets do the same
  22013. } else if (!value) {
  22014. this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true
  22015. } else {
  22016. this.options_.autoplay = true;
  22017. }
  22018. techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up
  22019. // a setAutoplay call on tech ready. We do this because the
  22020. // autoplay option will be passed in the constructor and we
  22021. // do not need to set it twice
  22022. if (this.tech_) {
  22023. this.techCall_('setAutoplay', techAutoplay);
  22024. }
  22025. }
  22026. /**
  22027. * Set or unset the playsinline attribute.
  22028. * Playsinline tells the browser that non-fullscreen playback is preferred.
  22029. *
  22030. * @param {boolean} [value]
  22031. * - true means that we should try to play inline by default
  22032. * - false means that we should use the browser's default playback mode,
  22033. * which in most cases is inline. iOS Safari is a notable exception
  22034. * and plays fullscreen by default.
  22035. *
  22036. * @return {string|Player}
  22037. * - the current value of playsinline
  22038. * - the player when setting
  22039. *
  22040. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  22041. */
  22042. ;
  22043. _proto.playsinline = function playsinline(value) {
  22044. if (value !== undefined) {
  22045. this.techCall_('setPlaysinline', value);
  22046. this.options_.playsinline = value;
  22047. return this;
  22048. }
  22049. return this.techGet_('playsinline');
  22050. }
  22051. /**
  22052. * Get or set the loop attribute on the video element.
  22053. *
  22054. * @param {boolean} [value]
  22055. * - true means that we should loop the video
  22056. * - false means that we should not loop the video
  22057. *
  22058. * @return {boolean}
  22059. * The current value of loop when getting
  22060. */
  22061. ;
  22062. _proto.loop = function loop(value) {
  22063. if (value !== undefined) {
  22064. this.techCall_('setLoop', value);
  22065. this.options_.loop = value;
  22066. return;
  22067. }
  22068. return this.techGet_('loop');
  22069. }
  22070. /**
  22071. * Get or set the poster image source url
  22072. *
  22073. * @fires Player#posterchange
  22074. *
  22075. * @param {string} [src]
  22076. * Poster image source URL
  22077. *
  22078. * @return {string}
  22079. * The current value of poster when getting
  22080. */
  22081. ;
  22082. _proto.poster = function poster(src) {
  22083. if (src === undefined) {
  22084. return this.poster_;
  22085. } // The correct way to remove a poster is to set as an empty string
  22086. // other falsey values will throw errors
  22087. if (!src) {
  22088. src = '';
  22089. }
  22090. if (src === this.poster_) {
  22091. return;
  22092. } // update the internal poster variable
  22093. this.poster_ = src; // update the tech's poster
  22094. this.techCall_('setPoster', src);
  22095. this.isPosterFromTech_ = false; // alert components that the poster has been set
  22096. /**
  22097. * This event fires when the poster image is changed on the player.
  22098. *
  22099. * @event Player#posterchange
  22100. * @type {EventTarget~Event}
  22101. */
  22102. this.trigger('posterchange');
  22103. }
  22104. /**
  22105. * Some techs (e.g. YouTube) can provide a poster source in an
  22106. * asynchronous way. We want the poster component to use this
  22107. * poster source so that it covers up the tech's controls.
  22108. * (YouTube's play button). However we only want to use this
  22109. * source if the player user hasn't set a poster through
  22110. * the normal APIs.
  22111. *
  22112. * @fires Player#posterchange
  22113. * @listens Tech#posterchange
  22114. * @private
  22115. */
  22116. ;
  22117. _proto.handleTechPosterChange_ = function handleTechPosterChange_() {
  22118. if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
  22119. var newPoster = this.tech_.poster() || '';
  22120. if (newPoster !== this.poster_) {
  22121. this.poster_ = newPoster;
  22122. this.isPosterFromTech_ = true; // Let components know the poster has changed
  22123. this.trigger('posterchange');
  22124. }
  22125. }
  22126. }
  22127. /**
  22128. * Get or set whether or not the controls are showing.
  22129. *
  22130. * @fires Player#controlsenabled
  22131. *
  22132. * @param {boolean} [bool]
  22133. * - true to turn controls on
  22134. * - false to turn controls off
  22135. *
  22136. * @return {boolean}
  22137. * The current value of controls when getting
  22138. */
  22139. ;
  22140. _proto.controls = function controls(bool) {
  22141. if (bool === undefined) {
  22142. return !!this.controls_;
  22143. }
  22144. bool = !!bool; // Don't trigger a change event unless it actually changed
  22145. if (this.controls_ === bool) {
  22146. return;
  22147. }
  22148. this.controls_ = bool;
  22149. if (this.usingNativeControls()) {
  22150. this.techCall_('setControls', bool);
  22151. }
  22152. if (this.controls_) {
  22153. this.removeClass('vjs-controls-disabled');
  22154. this.addClass('vjs-controls-enabled');
  22155. /**
  22156. * @event Player#controlsenabled
  22157. * @type {EventTarget~Event}
  22158. */
  22159. this.trigger('controlsenabled');
  22160. if (!this.usingNativeControls()) {
  22161. this.addTechControlsListeners_();
  22162. }
  22163. } else {
  22164. this.removeClass('vjs-controls-enabled');
  22165. this.addClass('vjs-controls-disabled');
  22166. /**
  22167. * @event Player#controlsdisabled
  22168. * @type {EventTarget~Event}
  22169. */
  22170. this.trigger('controlsdisabled');
  22171. if (!this.usingNativeControls()) {
  22172. this.removeTechControlsListeners_();
  22173. }
  22174. }
  22175. }
  22176. /**
  22177. * Toggle native controls on/off. Native controls are the controls built into
  22178. * devices (e.g. default iPhone controls) or other techs
  22179. * (e.g. Vimeo Controls)
  22180. * **This should only be set by the current tech, because only the tech knows
  22181. * if it can support native controls**
  22182. *
  22183. * @fires Player#usingnativecontrols
  22184. * @fires Player#usingcustomcontrols
  22185. *
  22186. * @param {boolean} [bool]
  22187. * - true to turn native controls on
  22188. * - false to turn native controls off
  22189. *
  22190. * @return {boolean}
  22191. * The current value of native controls when getting
  22192. */
  22193. ;
  22194. _proto.usingNativeControls = function usingNativeControls(bool) {
  22195. if (bool === undefined) {
  22196. return !!this.usingNativeControls_;
  22197. }
  22198. bool = !!bool; // Don't trigger a change event unless it actually changed
  22199. if (this.usingNativeControls_ === bool) {
  22200. return;
  22201. }
  22202. this.usingNativeControls_ = bool;
  22203. if (this.usingNativeControls_) {
  22204. this.addClass('vjs-using-native-controls');
  22205. /**
  22206. * player is using the native device controls
  22207. *
  22208. * @event Player#usingnativecontrols
  22209. * @type {EventTarget~Event}
  22210. */
  22211. this.trigger('usingnativecontrols');
  22212. } else {
  22213. this.removeClass('vjs-using-native-controls');
  22214. /**
  22215. * player is using the custom HTML controls
  22216. *
  22217. * @event Player#usingcustomcontrols
  22218. * @type {EventTarget~Event}
  22219. */
  22220. this.trigger('usingcustomcontrols');
  22221. }
  22222. }
  22223. /**
  22224. * Set or get the current MediaError
  22225. *
  22226. * @fires Player#error
  22227. *
  22228. * @param {MediaError|string|number} [err]
  22229. * A MediaError or a string/number to be turned
  22230. * into a MediaError
  22231. *
  22232. * @return {MediaError|null}
  22233. * The current MediaError when getting (or null)
  22234. */
  22235. ;
  22236. _proto.error = function error(err) {
  22237. var _this18 = this;
  22238. if (err === undefined) {
  22239. return this.error_ || null;
  22240. } // allow hooks to modify error object
  22241. hooks('beforeerror').forEach(function (hookFunction) {
  22242. var newErr = hookFunction(_this18, err);
  22243. if (!(isObject(newErr) && !Array.isArray(newErr) || typeof newErr === 'string' || typeof newErr === 'number' || newErr === null)) {
  22244. _this18.log.error('please return a value that MediaError expects in beforeerror hooks');
  22245. return;
  22246. }
  22247. err = newErr;
  22248. }); // Suppress the first error message for no compatible source until
  22249. // user interaction
  22250. if (this.options_.suppressNotSupportedError && err && err.code === 4) {
  22251. var triggerSuppressedError = function triggerSuppressedError() {
  22252. this.error(err);
  22253. };
  22254. this.options_.suppressNotSupportedError = false;
  22255. this.any(['click', 'touchstart'], triggerSuppressedError);
  22256. this.one('loadstart', function () {
  22257. this.off(['click', 'touchstart'], triggerSuppressedError);
  22258. });
  22259. return;
  22260. } // restoring to default
  22261. if (err === null) {
  22262. this.error_ = err;
  22263. this.removeClass('vjs-error');
  22264. if (this.errorDisplay) {
  22265. this.errorDisplay.close();
  22266. }
  22267. return;
  22268. }
  22269. this.error_ = new MediaError(err); // add the vjs-error classname to the player
  22270. this.addClass('vjs-error'); // log the name of the error type and any message
  22271. // IE11 logs "[object object]" and required you to expand message to see error object
  22272. log$1.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_);
  22273. /**
  22274. * @event Player#error
  22275. * @type {EventTarget~Event}
  22276. */
  22277. this.trigger('error'); // notify hooks of the per player error
  22278. hooks('error').forEach(function (hookFunction) {
  22279. return hookFunction(_this18, _this18.error_);
  22280. });
  22281. return;
  22282. }
  22283. /**
  22284. * Report user activity
  22285. *
  22286. * @param {Object} event
  22287. * Event object
  22288. */
  22289. ;
  22290. _proto.reportUserActivity = function reportUserActivity(event) {
  22291. this.userActivity_ = true;
  22292. }
  22293. /**
  22294. * Get/set if user is active
  22295. *
  22296. * @fires Player#useractive
  22297. * @fires Player#userinactive
  22298. *
  22299. * @param {boolean} [bool]
  22300. * - true if the user is active
  22301. * - false if the user is inactive
  22302. *
  22303. * @return {boolean}
  22304. * The current value of userActive when getting
  22305. */
  22306. ;
  22307. _proto.userActive = function userActive(bool) {
  22308. if (bool === undefined) {
  22309. return this.userActive_;
  22310. }
  22311. bool = !!bool;
  22312. if (bool === this.userActive_) {
  22313. return;
  22314. }
  22315. this.userActive_ = bool;
  22316. if (this.userActive_) {
  22317. this.userActivity_ = true;
  22318. this.removeClass('vjs-user-inactive');
  22319. this.addClass('vjs-user-active');
  22320. /**
  22321. * @event Player#useractive
  22322. * @type {EventTarget~Event}
  22323. */
  22324. this.trigger('useractive');
  22325. return;
  22326. } // Chrome/Safari/IE have bugs where when you change the cursor it can
  22327. // trigger a mousemove event. This causes an issue when you're hiding
  22328. // the cursor when the user is inactive, and a mousemove signals user
  22329. // activity. Making it impossible to go into inactive mode. Specifically
  22330. // this happens in fullscreen when we really need to hide the cursor.
  22331. //
  22332. // When this gets resolved in ALL browsers it can be removed
  22333. // https://code.google.com/p/chromium/issues/detail?id=103041
  22334. if (this.tech_) {
  22335. this.tech_.one('mousemove', function (e) {
  22336. e.stopPropagation();
  22337. e.preventDefault();
  22338. });
  22339. }
  22340. this.userActivity_ = false;
  22341. this.removeClass('vjs-user-active');
  22342. this.addClass('vjs-user-inactive');
  22343. /**
  22344. * @event Player#userinactive
  22345. * @type {EventTarget~Event}
  22346. */
  22347. this.trigger('userinactive');
  22348. }
  22349. /**
  22350. * Listen for user activity based on timeout value
  22351. *
  22352. * @private
  22353. */
  22354. ;
  22355. _proto.listenForUserActivity_ = function listenForUserActivity_() {
  22356. var mouseInProgress;
  22357. var lastMoveX;
  22358. var lastMoveY;
  22359. var handleActivity = bind(this, this.reportUserActivity);
  22360. var handleMouseMove = function handleMouseMove(e) {
  22361. // #1068 - Prevent mousemove spamming
  22362. // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
  22363. if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
  22364. lastMoveX = e.screenX;
  22365. lastMoveY = e.screenY;
  22366. handleActivity();
  22367. }
  22368. };
  22369. var handleMouseDown = function handleMouseDown() {
  22370. handleActivity(); // For as long as the they are touching the device or have their mouse down,
  22371. // we consider them active even if they're not moving their finger or mouse.
  22372. // So we want to continue to update that they are active
  22373. this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time
  22374. // as the activityCheck interval (250) should ensure we never miss the
  22375. // next activityCheck
  22376. mouseInProgress = this.setInterval(handleActivity, 250);
  22377. };
  22378. var handleMouseUpAndMouseLeave = function handleMouseUpAndMouseLeave(event) {
  22379. handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down
  22380. this.clearInterval(mouseInProgress);
  22381. }; // Any mouse movement will be considered user activity
  22382. this.on('mousedown', handleMouseDown);
  22383. this.on('mousemove', handleMouseMove);
  22384. this.on('mouseup', handleMouseUpAndMouseLeave);
  22385. this.on('mouseleave', handleMouseUpAndMouseLeave);
  22386. var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
  22387. // controlBar would no longer be hidden by default timeout.
  22388. if (controlBar && !IS_IOS && !IS_ANDROID) {
  22389. controlBar.on('mouseenter', function (event) {
  22390. if (this.player().options_.inactivityTimeout !== 0) {
  22391. this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
  22392. }
  22393. this.player().options_.inactivityTimeout = 0;
  22394. });
  22395. controlBar.on('mouseleave', function (event) {
  22396. this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
  22397. });
  22398. } // Listen for keyboard navigation
  22399. // Shouldn't need to use inProgress interval because of key repeat
  22400. this.on('keydown', handleActivity);
  22401. this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into
  22402. // the mousemove/touchmove function itself, to prevent performance degradation.
  22403. // `this.reportUserActivity` simply sets this.userActivity_ to true, which
  22404. // then gets picked up by this loop
  22405. // http://ejohn.org/blog/learning-from-twitter/
  22406. var inactivityTimeout;
  22407. this.setInterval(function () {
  22408. // Check to see if mouse/touch activity has happened
  22409. if (!this.userActivity_) {
  22410. return;
  22411. } // Reset the activity tracker
  22412. this.userActivity_ = false; // If the user state was inactive, set the state to active
  22413. this.userActive(true); // Clear any existing inactivity timeout to start the timer over
  22414. this.clearTimeout(inactivityTimeout);
  22415. var timeout = this.options_.inactivityTimeout;
  22416. if (timeout <= 0) {
  22417. return;
  22418. } // In <timeout> milliseconds, if no more activity has occurred the
  22419. // user will be considered inactive
  22420. inactivityTimeout = this.setTimeout(function () {
  22421. // Protect against the case where the inactivityTimeout can trigger just
  22422. // before the next user activity is picked up by the activity check loop
  22423. // causing a flicker
  22424. if (!this.userActivity_) {
  22425. this.userActive(false);
  22426. }
  22427. }, timeout);
  22428. }, 250);
  22429. }
  22430. /**
  22431. * Gets or sets the current playback rate. A playback rate of
  22432. * 1.0 represents normal speed and 0.5 would indicate half-speed
  22433. * playback, for instance.
  22434. *
  22435. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
  22436. *
  22437. * @param {number} [rate]
  22438. * New playback rate to set.
  22439. *
  22440. * @return {number}
  22441. * The current playback rate when getting or 1.0
  22442. */
  22443. ;
  22444. _proto.playbackRate = function playbackRate(rate) {
  22445. if (rate !== undefined) {
  22446. // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
  22447. // that is registered above
  22448. this.techCall_('setPlaybackRate', rate);
  22449. return;
  22450. }
  22451. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  22452. return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
  22453. }
  22454. return 1.0;
  22455. }
  22456. /**
  22457. * Gets or sets the current default playback rate. A default playback rate of
  22458. * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
  22459. * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
  22460. * not the current playbackRate.
  22461. *
  22462. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
  22463. *
  22464. * @param {number} [rate]
  22465. * New default playback rate to set.
  22466. *
  22467. * @return {number|Player}
  22468. * - The default playback rate when getting or 1.0
  22469. * - the player when setting
  22470. */
  22471. ;
  22472. _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) {
  22473. if (rate !== undefined) {
  22474. return this.techCall_('setDefaultPlaybackRate', rate);
  22475. }
  22476. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  22477. return this.techGet_('defaultPlaybackRate');
  22478. }
  22479. return 1.0;
  22480. }
  22481. /**
  22482. * Gets or sets the audio flag
  22483. *
  22484. * @param {boolean} bool
  22485. * - true signals that this is an audio player
  22486. * - false signals that this is not an audio player
  22487. *
  22488. * @return {boolean}
  22489. * The current value of isAudio when getting
  22490. */
  22491. ;
  22492. _proto.isAudio = function isAudio(bool) {
  22493. if (bool !== undefined) {
  22494. this.isAudio_ = !!bool;
  22495. return;
  22496. }
  22497. return !!this.isAudio_;
  22498. };
  22499. _proto.enableAudioOnlyUI_ = function enableAudioOnlyUI_() {
  22500. var _this19 = this;
  22501. // Update styling immediately to show the control bar so we can get its height
  22502. this.addClass('vjs-audio-only-mode');
  22503. var playerChildren = this.children();
  22504. var controlBar = this.getChild('ControlBar');
  22505. var controlBarHeight = controlBar && controlBar.currentHeight(); // Hide all player components except the control bar. Control bar components
  22506. // needed only for video are hidden with CSS
  22507. playerChildren.forEach(function (child) {
  22508. if (child === controlBar) {
  22509. return;
  22510. }
  22511. if (child.el_ && !child.hasClass('vjs-hidden')) {
  22512. child.hide();
  22513. _this19.audioOnlyCache_.hiddenChildren.push(child);
  22514. }
  22515. });
  22516. this.audioOnlyCache_.playerHeight = this.currentHeight(); // Set the player height the same as the control bar
  22517. this.height(controlBarHeight);
  22518. this.trigger('audioonlymodechange');
  22519. };
  22520. _proto.disableAudioOnlyUI_ = function disableAudioOnlyUI_() {
  22521. this.removeClass('vjs-audio-only-mode'); // Show player components that were previously hidden
  22522. this.audioOnlyCache_.hiddenChildren.forEach(function (child) {
  22523. return child.show();
  22524. }); // Reset player height
  22525. this.height(this.audioOnlyCache_.playerHeight);
  22526. this.trigger('audioonlymodechange');
  22527. }
  22528. /**
  22529. * Get the current audioOnlyMode state or set audioOnlyMode to true or false.
  22530. *
  22531. * Setting this to `true` will hide all player components except the control bar,
  22532. * as well as control bar components needed only for video.
  22533. *
  22534. * @param {boolean} [value]
  22535. * The value to set audioOnlyMode to.
  22536. *
  22537. * @return {Promise|boolean}
  22538. * A Promise is returned when setting the state, and a boolean when getting
  22539. * the present state
  22540. */
  22541. ;
  22542. _proto.audioOnlyMode = function audioOnlyMode(value) {
  22543. var _this20 = this;
  22544. if (typeof value !== 'boolean' || value === this.audioOnlyMode_) {
  22545. return this.audioOnlyMode_;
  22546. }
  22547. this.audioOnlyMode_ = value;
  22548. var PromiseClass = this.options_.Promise || window$1.Promise;
  22549. if (PromiseClass) {
  22550. // Enable Audio Only Mode
  22551. if (value) {
  22552. var exitPromises = []; // Fullscreen and PiP are not supported in audioOnlyMode, so exit if we need to.
  22553. if (this.isInPictureInPicture()) {
  22554. exitPromises.push(this.exitPictureInPicture());
  22555. }
  22556. if (this.isFullscreen()) {
  22557. exitPromises.push(this.exitFullscreen());
  22558. }
  22559. if (this.audioPosterMode()) {
  22560. exitPromises.push(this.audioPosterMode(false));
  22561. }
  22562. return PromiseClass.all(exitPromises).then(function () {
  22563. return _this20.enableAudioOnlyUI_();
  22564. });
  22565. } // Disable Audio Only Mode
  22566. return PromiseClass.resolve().then(function () {
  22567. return _this20.disableAudioOnlyUI_();
  22568. });
  22569. }
  22570. if (value) {
  22571. if (this.isInPictureInPicture()) {
  22572. this.exitPictureInPicture();
  22573. }
  22574. if (this.isFullscreen()) {
  22575. this.exitFullscreen();
  22576. }
  22577. this.enableAudioOnlyUI_();
  22578. } else {
  22579. this.disableAudioOnlyUI_();
  22580. }
  22581. };
  22582. _proto.enablePosterModeUI_ = function enablePosterModeUI_() {
  22583. // Hide the video element and show the poster image to enable posterModeUI
  22584. var tech = this.tech_ && this.tech_;
  22585. tech.hide();
  22586. this.addClass('vjs-audio-poster-mode');
  22587. this.trigger('audiopostermodechange');
  22588. };
  22589. _proto.disablePosterModeUI_ = function disablePosterModeUI_() {
  22590. // Show the video element and hide the poster image to disable posterModeUI
  22591. var tech = this.tech_ && this.tech_;
  22592. tech.show();
  22593. this.removeClass('vjs-audio-poster-mode');
  22594. this.trigger('audiopostermodechange');
  22595. }
  22596. /**
  22597. * Get the current audioPosterMode state or set audioPosterMode to true or false
  22598. *
  22599. * @param {boolean} [value]
  22600. * The value to set audioPosterMode to.
  22601. *
  22602. * @return {Promise|boolean}
  22603. * A Promise is returned when setting the state, and a boolean when getting
  22604. * the present state
  22605. */
  22606. ;
  22607. _proto.audioPosterMode = function audioPosterMode(value) {
  22608. var _this21 = this;
  22609. if (typeof value !== 'boolean' || value === this.audioPosterMode_) {
  22610. return this.audioPosterMode_;
  22611. }
  22612. this.audioPosterMode_ = value;
  22613. var PromiseClass = this.options_.Promise || window$1.Promise;
  22614. if (PromiseClass) {
  22615. if (value) {
  22616. if (this.audioOnlyMode()) {
  22617. var audioOnlyModePromise = this.audioOnlyMode(false);
  22618. return audioOnlyModePromise.then(function () {
  22619. // enable audio poster mode after audio only mode is disabled
  22620. _this21.enablePosterModeUI_();
  22621. });
  22622. }
  22623. return PromiseClass.resolve().then(function () {
  22624. // enable audio poster mode
  22625. _this21.enablePosterModeUI_();
  22626. });
  22627. }
  22628. return PromiseClass.resolve().then(function () {
  22629. // disable audio poster mode
  22630. _this21.disablePosterModeUI_();
  22631. });
  22632. }
  22633. if (value) {
  22634. if (this.audioOnlyMode()) {
  22635. this.audioOnlyMode(false);
  22636. }
  22637. this.enablePosterModeUI_();
  22638. return;
  22639. }
  22640. this.disablePosterModeUI_();
  22641. }
  22642. /**
  22643. * A helper method for adding a {@link TextTrack} to our
  22644. * {@link TextTrackList}.
  22645. *
  22646. * In addition to the W3C settings we allow adding additional info through options.
  22647. *
  22648. * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
  22649. *
  22650. * @param {string} [kind]
  22651. * the kind of TextTrack you are adding
  22652. *
  22653. * @param {string} [label]
  22654. * the label to give the TextTrack label
  22655. *
  22656. * @param {string} [language]
  22657. * the language to set on the TextTrack
  22658. *
  22659. * @return {TextTrack|undefined}
  22660. * the TextTrack that was added or undefined
  22661. * if there is no tech
  22662. */
  22663. ;
  22664. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  22665. if (this.tech_) {
  22666. return this.tech_.addTextTrack(kind, label, language);
  22667. }
  22668. }
  22669. /**
  22670. * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}.
  22671. * When manualCleanup is set to false, the track will be automatically removed
  22672. * on source changes.
  22673. *
  22674. * @param {Object} options
  22675. * Options to pass to {@link HTMLTrackElement} during creation. See
  22676. * {@link HTMLTrackElement} for object properties that you should use.
  22677. *
  22678. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  22679. * removed on a source change
  22680. *
  22681. * @return {HtmlTrackElement}
  22682. * the HTMLTrackElement that was created and added
  22683. * to the HtmlTrackElementList and the remote
  22684. * TextTrackList
  22685. *
  22686. * @deprecated The default value of the "manualCleanup" parameter will default
  22687. * to "false" in upcoming versions of Video.js
  22688. */
  22689. ;
  22690. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  22691. if (this.tech_) {
  22692. return this.tech_.addRemoteTextTrack(options, manualCleanup);
  22693. }
  22694. }
  22695. /**
  22696. * Remove a remote {@link TextTrack} from the respective
  22697. * {@link TextTrackList} and {@link HtmlTrackElementList}.
  22698. *
  22699. * @param {Object} track
  22700. * Remote {@link TextTrack} to remove
  22701. *
  22702. * @return {undefined}
  22703. * does not return anything
  22704. */
  22705. ;
  22706. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) {
  22707. if (obj === void 0) {
  22708. obj = {};
  22709. }
  22710. var _obj = obj,
  22711. track = _obj.track;
  22712. if (!track) {
  22713. track = obj;
  22714. } // destructure the input into an object with a track argument, defaulting to arguments[0]
  22715. // default the whole argument to an empty object if nothing was passed in
  22716. if (this.tech_) {
  22717. return this.tech_.removeRemoteTextTrack(track);
  22718. }
  22719. }
  22720. /**
  22721. * Gets available media playback quality metrics as specified by the W3C's Media
  22722. * Playback Quality API.
  22723. *
  22724. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  22725. *
  22726. * @return {Object|undefined}
  22727. * An object with supported media playback quality metrics or undefined if there
  22728. * is no tech or the tech does not support it.
  22729. */
  22730. ;
  22731. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  22732. return this.techGet_('getVideoPlaybackQuality');
  22733. }
  22734. /**
  22735. * Get video width
  22736. *
  22737. * @return {number}
  22738. * current video width
  22739. */
  22740. ;
  22741. _proto.videoWidth = function videoWidth() {
  22742. return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
  22743. }
  22744. /**
  22745. * Get video height
  22746. *
  22747. * @return {number}
  22748. * current video height
  22749. */
  22750. ;
  22751. _proto.videoHeight = function videoHeight() {
  22752. return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
  22753. }
  22754. /**
  22755. * The player's language code.
  22756. *
  22757. * Changing the language will trigger
  22758. * [languagechange]{@link Player#event:languagechange}
  22759. * which Components can use to update control text.
  22760. * ClickableComponent will update its control text by default on
  22761. * [languagechange]{@link Player#event:languagechange}.
  22762. *
  22763. * @fires Player#languagechange
  22764. *
  22765. * @param {string} [code]
  22766. * the language code to set the player to
  22767. *
  22768. * @return {string}
  22769. * The current language code when getting
  22770. */
  22771. ;
  22772. _proto.language = function language(code) {
  22773. if (code === undefined) {
  22774. return this.language_;
  22775. }
  22776. if (this.language_ !== String(code).toLowerCase()) {
  22777. this.language_ = String(code).toLowerCase(); // during first init, it's possible some things won't be evented
  22778. if (isEvented(this)) {
  22779. /**
  22780. * fires when the player language change
  22781. *
  22782. * @event Player#languagechange
  22783. * @type {EventTarget~Event}
  22784. */
  22785. this.trigger('languagechange');
  22786. }
  22787. }
  22788. }
  22789. /**
  22790. * Get the player's language dictionary
  22791. * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
  22792. * Languages specified directly in the player options have precedence
  22793. *
  22794. * @return {Array}
  22795. * An array of of supported languages
  22796. */
  22797. ;
  22798. _proto.languages = function languages() {
  22799. return mergeOptions$3(Player.prototype.options_.languages, this.languages_);
  22800. }
  22801. /**
  22802. * returns a JavaScript object reperesenting the current track
  22803. * information. **DOES not return it as JSON**
  22804. *
  22805. * @return {Object}
  22806. * Object representing the current of track info
  22807. */
  22808. ;
  22809. _proto.toJSON = function toJSON() {
  22810. var options = mergeOptions$3(this.options_);
  22811. var tracks = options.tracks;
  22812. options.tracks = [];
  22813. for (var i = 0; i < tracks.length; i++) {
  22814. var track = tracks[i]; // deep merge tracks and null out player so no circular references
  22815. track = mergeOptions$3(track);
  22816. track.player = undefined;
  22817. options.tracks[i] = track;
  22818. }
  22819. return options;
  22820. }
  22821. /**
  22822. * Creates a simple modal dialog (an instance of the {@link ModalDialog}
  22823. * component) that immediately overlays the player with arbitrary
  22824. * content and removes itself when closed.
  22825. *
  22826. * @param {string|Function|Element|Array|null} content
  22827. * Same as {@link ModalDialog#content}'s param of the same name.
  22828. * The most straight-forward usage is to provide a string or DOM
  22829. * element.
  22830. *
  22831. * @param {Object} [options]
  22832. * Extra options which will be passed on to the {@link ModalDialog}.
  22833. *
  22834. * @return {ModalDialog}
  22835. * the {@link ModalDialog} that was created
  22836. */
  22837. ;
  22838. _proto.createModal = function createModal(content, options) {
  22839. var _this22 = this;
  22840. options = options || {};
  22841. options.content = content || '';
  22842. var modal = new ModalDialog(this, options);
  22843. this.addChild(modal);
  22844. modal.on('dispose', function () {
  22845. _this22.removeChild(modal);
  22846. });
  22847. modal.open();
  22848. return modal;
  22849. }
  22850. /**
  22851. * Change breakpoint classes when the player resizes.
  22852. *
  22853. * @private
  22854. */
  22855. ;
  22856. _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() {
  22857. if (!this.responsive()) {
  22858. return;
  22859. }
  22860. var currentBreakpoint = this.currentBreakpoint();
  22861. var currentWidth = this.currentWidth();
  22862. for (var i = 0; i < BREAKPOINT_ORDER.length; i++) {
  22863. var candidateBreakpoint = BREAKPOINT_ORDER[i];
  22864. var maxWidth = this.breakpoints_[candidateBreakpoint];
  22865. if (currentWidth <= maxWidth) {
  22866. // The current breakpoint did not change, nothing to do.
  22867. if (currentBreakpoint === candidateBreakpoint) {
  22868. return;
  22869. } // Only remove a class if there is a current breakpoint.
  22870. if (currentBreakpoint) {
  22871. this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
  22872. }
  22873. this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
  22874. this.breakpoint_ = candidateBreakpoint;
  22875. break;
  22876. }
  22877. }
  22878. }
  22879. /**
  22880. * Removes the current breakpoint.
  22881. *
  22882. * @private
  22883. */
  22884. ;
  22885. _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() {
  22886. var className = this.currentBreakpointClass();
  22887. this.breakpoint_ = '';
  22888. if (className) {
  22889. this.removeClass(className);
  22890. }
  22891. }
  22892. /**
  22893. * Get or set breakpoints on the player.
  22894. *
  22895. * Calling this method with an object or `true` will remove any previous
  22896. * custom breakpoints and start from the defaults again.
  22897. *
  22898. * @param {Object|boolean} [breakpoints]
  22899. * If an object is given, it can be used to provide custom
  22900. * breakpoints. If `true` is given, will set default breakpoints.
  22901. * If this argument is not given, will simply return the current
  22902. * breakpoints.
  22903. *
  22904. * @param {number} [breakpoints.tiny]
  22905. * The maximum width for the "vjs-layout-tiny" class.
  22906. *
  22907. * @param {number} [breakpoints.xsmall]
  22908. * The maximum width for the "vjs-layout-x-small" class.
  22909. *
  22910. * @param {number} [breakpoints.small]
  22911. * The maximum width for the "vjs-layout-small" class.
  22912. *
  22913. * @param {number} [breakpoints.medium]
  22914. * The maximum width for the "vjs-layout-medium" class.
  22915. *
  22916. * @param {number} [breakpoints.large]
  22917. * The maximum width for the "vjs-layout-large" class.
  22918. *
  22919. * @param {number} [breakpoints.xlarge]
  22920. * The maximum width for the "vjs-layout-x-large" class.
  22921. *
  22922. * @param {number} [breakpoints.huge]
  22923. * The maximum width for the "vjs-layout-huge" class.
  22924. *
  22925. * @return {Object}
  22926. * An object mapping breakpoint names to maximum width values.
  22927. */
  22928. ;
  22929. _proto.breakpoints = function breakpoints(_breakpoints) {
  22930. // Used as a getter.
  22931. if (_breakpoints === undefined) {
  22932. return assign(this.breakpoints_);
  22933. }
  22934. this.breakpoint_ = '';
  22935. this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently
  22936. // selected breakpoint.
  22937. this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning.
  22938. return assign(this.breakpoints_);
  22939. }
  22940. /**
  22941. * Get or set a flag indicating whether or not this player should adjust
  22942. * its UI based on its dimensions.
  22943. *
  22944. * @param {boolean} value
  22945. * Should be `true` if the player should adjust its UI based on its
  22946. * dimensions; otherwise, should be `false`.
  22947. *
  22948. * @return {boolean}
  22949. * Will be `true` if this player should adjust its UI based on its
  22950. * dimensions; otherwise, will be `false`.
  22951. */
  22952. ;
  22953. _proto.responsive = function responsive(value) {
  22954. // Used as a getter.
  22955. if (value === undefined) {
  22956. return this.responsive_;
  22957. }
  22958. value = Boolean(value);
  22959. var current = this.responsive_; // Nothing changed.
  22960. if (value === current) {
  22961. return;
  22962. } // The value actually changed, set it.
  22963. this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the
  22964. // player is now responsive.
  22965. if (value) {
  22966. this.on('playerresize', this.boundUpdateCurrentBreakpoint_);
  22967. this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive.
  22968. } else {
  22969. this.off('playerresize', this.boundUpdateCurrentBreakpoint_);
  22970. this.removeCurrentBreakpoint_();
  22971. }
  22972. return value;
  22973. }
  22974. /**
  22975. * Get current breakpoint name, if any.
  22976. *
  22977. * @return {string}
  22978. * If there is currently a breakpoint set, returns a the key from the
  22979. * breakpoints object matching it. Otherwise, returns an empty string.
  22980. */
  22981. ;
  22982. _proto.currentBreakpoint = function currentBreakpoint() {
  22983. return this.breakpoint_;
  22984. }
  22985. /**
  22986. * Get the current breakpoint class name.
  22987. *
  22988. * @return {string}
  22989. * The matching class name (e.g. `"vjs-layout-tiny"` or
  22990. * `"vjs-layout-large"`) for the current breakpoint. Empty string if
  22991. * there is no current breakpoint.
  22992. */
  22993. ;
  22994. _proto.currentBreakpointClass = function currentBreakpointClass() {
  22995. return BREAKPOINT_CLASSES[this.breakpoint_] || '';
  22996. }
  22997. /**
  22998. * An object that describes a single piece of media.
  22999. *
  23000. * Properties that are not part of this type description will be retained; so,
  23001. * this can be viewed as a generic metadata storage mechanism as well.
  23002. *
  23003. * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
  23004. * @typedef {Object} Player~MediaObject
  23005. *
  23006. * @property {string} [album]
  23007. * Unused, except if this object is passed to the `MediaSession`
  23008. * API.
  23009. *
  23010. * @property {string} [artist]
  23011. * Unused, except if this object is passed to the `MediaSession`
  23012. * API.
  23013. *
  23014. * @property {Object[]} [artwork]
  23015. * Unused, except if this object is passed to the `MediaSession`
  23016. * API. If not specified, will be populated via the `poster`, if
  23017. * available.
  23018. *
  23019. * @property {string} [poster]
  23020. * URL to an image that will display before playback.
  23021. *
  23022. * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
  23023. * A single source object, an array of source objects, or a string
  23024. * referencing a URL to a media source. It is _highly recommended_
  23025. * that an object or array of objects is used here, so that source
  23026. * selection algorithms can take the `type` into account.
  23027. *
  23028. * @property {string} [title]
  23029. * Unused, except if this object is passed to the `MediaSession`
  23030. * API.
  23031. *
  23032. * @property {Object[]} [textTracks]
  23033. * An array of objects to be used to create text tracks, following
  23034. * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
  23035. * For ease of removal, these will be created as "remote" text
  23036. * tracks and set to automatically clean up on source changes.
  23037. *
  23038. * These objects may have properties like `src`, `kind`, `label`,
  23039. * and `language`, see {@link Tech#createRemoteTextTrack}.
  23040. */
  23041. /**
  23042. * Populate the player using a {@link Player~MediaObject|MediaObject}.
  23043. *
  23044. * @param {Player~MediaObject} media
  23045. * A media object.
  23046. *
  23047. * @param {Function} ready
  23048. * A callback to be called when the player is ready.
  23049. */
  23050. ;
  23051. _proto.loadMedia = function loadMedia(media, ready) {
  23052. var _this23 = this;
  23053. if (!media || typeof media !== 'object') {
  23054. return;
  23055. }
  23056. this.reset(); // Clone the media object so it cannot be mutated from outside.
  23057. this.cache_.media = mergeOptions$3(media);
  23058. var _this$cache_$media = this.cache_.media,
  23059. artwork = _this$cache_$media.artwork,
  23060. poster = _this$cache_$media.poster,
  23061. src = _this$cache_$media.src,
  23062. textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`.
  23063. if (!artwork && poster) {
  23064. this.cache_.media.artwork = [{
  23065. src: poster,
  23066. type: getMimetype(poster)
  23067. }];
  23068. }
  23069. if (src) {
  23070. this.src(src);
  23071. }
  23072. if (poster) {
  23073. this.poster(poster);
  23074. }
  23075. if (Array.isArray(textTracks)) {
  23076. textTracks.forEach(function (tt) {
  23077. return _this23.addRemoteTextTrack(tt, false);
  23078. });
  23079. }
  23080. this.ready(ready);
  23081. }
  23082. /**
  23083. * Get a clone of the current {@link Player~MediaObject} for this player.
  23084. *
  23085. * If the `loadMedia` method has not been used, will attempt to return a
  23086. * {@link Player~MediaObject} based on the current state of the player.
  23087. *
  23088. * @return {Player~MediaObject}
  23089. */
  23090. ;
  23091. _proto.getMedia = function getMedia() {
  23092. if (!this.cache_.media) {
  23093. var poster = this.poster();
  23094. var src = this.currentSources();
  23095. var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) {
  23096. return {
  23097. kind: tt.kind,
  23098. label: tt.label,
  23099. language: tt.language,
  23100. src: tt.src
  23101. };
  23102. });
  23103. var media = {
  23104. src: src,
  23105. textTracks: textTracks
  23106. };
  23107. if (poster) {
  23108. media.poster = poster;
  23109. media.artwork = [{
  23110. src: media.poster,
  23111. type: getMimetype(media.poster)
  23112. }];
  23113. }
  23114. return media;
  23115. }
  23116. return mergeOptions$3(this.cache_.media);
  23117. }
  23118. /**
  23119. * Gets tag settings
  23120. *
  23121. * @param {Element} tag
  23122. * The player tag
  23123. *
  23124. * @return {Object}
  23125. * An object containing all of the settings
  23126. * for a player tag
  23127. */
  23128. ;
  23129. Player.getTagSettings = function getTagSettings(tag) {
  23130. var baseOptions = {
  23131. sources: [],
  23132. tracks: []
  23133. };
  23134. var tagOptions = getAttributes(tag);
  23135. var dataSetup = tagOptions['data-setup'];
  23136. if (hasClass(tag, 'vjs-fill')) {
  23137. tagOptions.fill = true;
  23138. }
  23139. if (hasClass(tag, 'vjs-fluid')) {
  23140. tagOptions.fluid = true;
  23141. } // Check if data-setup attr exists.
  23142. if (dataSetup !== null) {
  23143. // Parse options JSON
  23144. // If empty string, make it a parsable json object.
  23145. var _safeParseTuple = safeParseTuple(dataSetup || '{}'),
  23146. err = _safeParseTuple[0],
  23147. data = _safeParseTuple[1];
  23148. if (err) {
  23149. log$1.error(err);
  23150. }
  23151. assign(tagOptions, data);
  23152. }
  23153. assign(baseOptions, tagOptions); // Get tag children settings
  23154. if (tag.hasChildNodes()) {
  23155. var children = tag.childNodes;
  23156. for (var i = 0, j = children.length; i < j; i++) {
  23157. var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
  23158. var childName = child.nodeName.toLowerCase();
  23159. if (childName === 'source') {
  23160. baseOptions.sources.push(getAttributes(child));
  23161. } else if (childName === 'track') {
  23162. baseOptions.tracks.push(getAttributes(child));
  23163. }
  23164. }
  23165. }
  23166. return baseOptions;
  23167. }
  23168. /**
  23169. * Determine whether or not flexbox is supported
  23170. *
  23171. * @return {boolean}
  23172. * - true if flexbox is supported
  23173. * - false if flexbox is not supported
  23174. */
  23175. ;
  23176. _proto.flexNotSupported_ = function flexNotSupported_() {
  23177. var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
  23178. // common flex features that we can rely on when checking for flex support.
  23179. return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style || // IE10-specific (2012 flex spec), available for completeness
  23180. 'msFlexOrder' in elem.style);
  23181. }
  23182. /**
  23183. * Set debug mode to enable/disable logs at info level.
  23184. *
  23185. * @param {boolean} enabled
  23186. * @fires Player#debugon
  23187. * @fires Player#debugoff
  23188. */
  23189. ;
  23190. _proto.debug = function debug(enabled) {
  23191. if (enabled === undefined) {
  23192. return this.debugEnabled_;
  23193. }
  23194. if (enabled) {
  23195. this.trigger('debugon');
  23196. this.previousLogLevel_ = this.log.level;
  23197. this.log.level('debug');
  23198. this.debugEnabled_ = true;
  23199. } else {
  23200. this.trigger('debugoff');
  23201. this.log.level(this.previousLogLevel_);
  23202. this.previousLogLevel_ = undefined;
  23203. this.debugEnabled_ = false;
  23204. }
  23205. }
  23206. /**
  23207. * Set or get current playback rates.
  23208. * Takes an array and updates the playback rates menu with the new items.
  23209. * Pass in an empty array to hide the menu.
  23210. * Values other than arrays are ignored.
  23211. *
  23212. * @fires Player#playbackrateschange
  23213. * @param {number[]} newRates
  23214. * The new rates that the playback rates menu should update to.
  23215. * An empty array will hide the menu
  23216. * @return {number[]} When used as a getter will return the current playback rates
  23217. */
  23218. ;
  23219. _proto.playbackRates = function playbackRates(newRates) {
  23220. if (newRates === undefined) {
  23221. return this.cache_.playbackRates;
  23222. } // ignore any value that isn't an array
  23223. if (!Array.isArray(newRates)) {
  23224. return;
  23225. } // ignore any arrays that don't only contain numbers
  23226. if (!newRates.every(function (rate) {
  23227. return typeof rate === 'number';
  23228. })) {
  23229. return;
  23230. }
  23231. this.cache_.playbackRates = newRates;
  23232. /**
  23233. * fires when the playback rates in a player are changed
  23234. *
  23235. * @event Player#playbackrateschange
  23236. * @type {EventTarget~Event}
  23237. */
  23238. this.trigger('playbackrateschange');
  23239. };
  23240. return Player;
  23241. }(Component$1);
  23242. /**
  23243. * Get the {@link VideoTrackList}
  23244. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
  23245. *
  23246. * @return {VideoTrackList}
  23247. * the current video track list
  23248. *
  23249. * @method Player.prototype.videoTracks
  23250. */
  23251. /**
  23252. * Get the {@link AudioTrackList}
  23253. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
  23254. *
  23255. * @return {AudioTrackList}
  23256. * the current audio track list
  23257. *
  23258. * @method Player.prototype.audioTracks
  23259. */
  23260. /**
  23261. * Get the {@link TextTrackList}
  23262. *
  23263. * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
  23264. *
  23265. * @return {TextTrackList}
  23266. * the current text track list
  23267. *
  23268. * @method Player.prototype.textTracks
  23269. */
  23270. /**
  23271. * Get the remote {@link TextTrackList}
  23272. *
  23273. * @return {TextTrackList}
  23274. * The current remote text track list
  23275. *
  23276. * @method Player.prototype.remoteTextTracks
  23277. */
  23278. /**
  23279. * Get the remote {@link HtmlTrackElementList} tracks.
  23280. *
  23281. * @return {HtmlTrackElementList}
  23282. * The current remote text track element list
  23283. *
  23284. * @method Player.prototype.remoteTextTrackEls
  23285. */
  23286. ALL.names.forEach(function (name) {
  23287. var props = ALL[name];
  23288. Player.prototype[props.getterName] = function () {
  23289. if (this.tech_) {
  23290. return this.tech_[props.getterName]();
  23291. } // if we have not yet loadTech_, we create {video,audio,text}Tracks_
  23292. // these will be passed to the tech during loading
  23293. this[props.privateName] = this[props.privateName] || new props.ListClass();
  23294. return this[props.privateName];
  23295. };
  23296. });
  23297. /**
  23298. * Get or set the `Player`'s crossorigin option. For the HTML5 player, this
  23299. * sets the `crossOrigin` property on the `<video>` tag to control the CORS
  23300. * behavior.
  23301. *
  23302. * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
  23303. *
  23304. * @param {string} [value]
  23305. * The value to set the `Player`'s crossorigin to. If an argument is
  23306. * given, must be one of `anonymous` or `use-credentials`.
  23307. *
  23308. * @return {string|undefined}
  23309. * - The current crossorigin value of the `Player` when getting.
  23310. * - undefined when setting
  23311. */
  23312. Player.prototype.crossorigin = Player.prototype.crossOrigin;
  23313. /**
  23314. * Global enumeration of players.
  23315. *
  23316. * The keys are the player IDs and the values are either the {@link Player}
  23317. * instance or `null` for disposed players.
  23318. *
  23319. * @type {Object}
  23320. */
  23321. Player.players = {};
  23322. var navigator = window$1.navigator;
  23323. /*
  23324. * Player instance options, surfaced using options
  23325. * options = Player.prototype.options_
  23326. * Make changes in options, not here.
  23327. *
  23328. * @type {Object}
  23329. * @private
  23330. */
  23331. Player.prototype.options_ = {
  23332. // Default order of fallback technology
  23333. techOrder: Tech.defaultTechOrder_,
  23334. html5: {},
  23335. // default inactivity timeout
  23336. inactivityTimeout: 2000,
  23337. // default playback rates
  23338. playbackRates: [],
  23339. // Add playback rate selection by adding rates
  23340. // 'playbackRates': [0.5, 1, 1.5, 2],
  23341. liveui: false,
  23342. // Included control sets
  23343. children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
  23344. language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
  23345. // locales and their language translations
  23346. languages: {},
  23347. // Default message to show when a video cannot be played.
  23348. notSupportedMessage: 'No compatible source was found for this media.',
  23349. normalizeAutoplay: false,
  23350. fullscreen: {
  23351. options: {
  23352. navigationUI: 'hide'
  23353. }
  23354. },
  23355. breakpoints: {},
  23356. responsive: false,
  23357. audioOnlyMode: false,
  23358. audioPosterMode: false
  23359. };
  23360. [
  23361. /**
  23362. * Returns whether or not the player is in the "ended" state.
  23363. *
  23364. * @return {Boolean} True if the player is in the ended state, false if not.
  23365. * @method Player#ended
  23366. */
  23367. 'ended',
  23368. /**
  23369. * Returns whether or not the player is in the "seeking" state.
  23370. *
  23371. * @return {Boolean} True if the player is in the seeking state, false if not.
  23372. * @method Player#seeking
  23373. */
  23374. 'seeking',
  23375. /**
  23376. * Returns the TimeRanges of the media that are currently available
  23377. * for seeking to.
  23378. *
  23379. * @return {TimeRanges} the seekable intervals of the media timeline
  23380. * @method Player#seekable
  23381. */
  23382. 'seekable',
  23383. /**
  23384. * Returns the current state of network activity for the element, from
  23385. * the codes in the list below.
  23386. * - NETWORK_EMPTY (numeric value 0)
  23387. * The element has not yet been initialised. All attributes are in
  23388. * their initial states.
  23389. * - NETWORK_IDLE (numeric value 1)
  23390. * The element's resource selection algorithm is active and has
  23391. * selected a resource, but it is not actually using the network at
  23392. * this time.
  23393. * - NETWORK_LOADING (numeric value 2)
  23394. * The user agent is actively trying to download data.
  23395. * - NETWORK_NO_SOURCE (numeric value 3)
  23396. * The element's resource selection algorithm is active, but it has
  23397. * not yet found a resource to use.
  23398. *
  23399. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
  23400. * @return {number} the current network activity state
  23401. * @method Player#networkState
  23402. */
  23403. 'networkState',
  23404. /**
  23405. * Returns a value that expresses the current state of the element
  23406. * with respect to rendering the current playback position, from the
  23407. * codes in the list below.
  23408. * - HAVE_NOTHING (numeric value 0)
  23409. * No information regarding the media resource is available.
  23410. * - HAVE_METADATA (numeric value 1)
  23411. * Enough of the resource has been obtained that the duration of the
  23412. * resource is available.
  23413. * - HAVE_CURRENT_DATA (numeric value 2)
  23414. * Data for the immediate current playback position is available.
  23415. * - HAVE_FUTURE_DATA (numeric value 3)
  23416. * Data for the immediate current playback position is available, as
  23417. * well as enough data for the user agent to advance the current
  23418. * playback position in the direction of playback.
  23419. * - HAVE_ENOUGH_DATA (numeric value 4)
  23420. * The user agent estimates that enough data is available for
  23421. * playback to proceed uninterrupted.
  23422. *
  23423. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
  23424. * @return {number} the current playback rendering state
  23425. * @method Player#readyState
  23426. */
  23427. 'readyState'].forEach(function (fn) {
  23428. Player.prototype[fn] = function () {
  23429. return this.techGet_(fn);
  23430. };
  23431. });
  23432. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  23433. Player.prototype["handleTech" + toTitleCase$1(event) + "_"] = function () {
  23434. return this.trigger(event);
  23435. };
  23436. });
  23437. /**
  23438. * Fired when the player has initial duration and dimension information
  23439. *
  23440. * @event Player#loadedmetadata
  23441. * @type {EventTarget~Event}
  23442. */
  23443. /**
  23444. * Fired when the player has downloaded data at the current playback position
  23445. *
  23446. * @event Player#loadeddata
  23447. * @type {EventTarget~Event}
  23448. */
  23449. /**
  23450. * Fired when the current playback position has changed *
  23451. * During playback this is fired every 15-250 milliseconds, depending on the
  23452. * playback technology in use.
  23453. *
  23454. * @event Player#timeupdate
  23455. * @type {EventTarget~Event}
  23456. */
  23457. /**
  23458. * Fired when the volume changes
  23459. *
  23460. * @event Player#volumechange
  23461. * @type {EventTarget~Event}
  23462. */
  23463. /**
  23464. * Reports whether or not a player has a plugin available.
  23465. *
  23466. * This does not report whether or not the plugin has ever been initialized
  23467. * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
  23468. *
  23469. * @method Player#hasPlugin
  23470. * @param {string} name
  23471. * The name of a plugin.
  23472. *
  23473. * @return {boolean}
  23474. * Whether or not this player has the requested plugin available.
  23475. */
  23476. /**
  23477. * Reports whether or not a player is using a plugin by name.
  23478. *
  23479. * For basic plugins, this only reports whether the plugin has _ever_ been
  23480. * initialized on this player.
  23481. *
  23482. * @method Player#usingPlugin
  23483. * @param {string} name
  23484. * The name of a plugin.
  23485. *
  23486. * @return {boolean}
  23487. * Whether or not this player is using the requested plugin.
  23488. */
  23489. Component$1.registerComponent('Player', Player);
  23490. /**
  23491. * The base plugin name.
  23492. *
  23493. * @private
  23494. * @constant
  23495. * @type {string}
  23496. */
  23497. var BASE_PLUGIN_NAME = 'plugin';
  23498. /**
  23499. * The key on which a player's active plugins cache is stored.
  23500. *
  23501. * @private
  23502. * @constant
  23503. * @type {string}
  23504. */
  23505. var PLUGIN_CACHE_KEY = 'activePlugins_';
  23506. /**
  23507. * Stores registered plugins in a private space.
  23508. *
  23509. * @private
  23510. * @type {Object}
  23511. */
  23512. var pluginStorage = {};
  23513. /**
  23514. * Reports whether or not a plugin has been registered.
  23515. *
  23516. * @private
  23517. * @param {string} name
  23518. * The name of a plugin.
  23519. *
  23520. * @return {boolean}
  23521. * Whether or not the plugin has been registered.
  23522. */
  23523. var pluginExists = function pluginExists(name) {
  23524. return pluginStorage.hasOwnProperty(name);
  23525. };
  23526. /**
  23527. * Get a single registered plugin by name.
  23528. *
  23529. * @private
  23530. * @param {string} name
  23531. * The name of a plugin.
  23532. *
  23533. * @return {Function|undefined}
  23534. * The plugin (or undefined).
  23535. */
  23536. var getPlugin = function getPlugin(name) {
  23537. return pluginExists(name) ? pluginStorage[name] : undefined;
  23538. };
  23539. /**
  23540. * Marks a plugin as "active" on a player.
  23541. *
  23542. * Also, ensures that the player has an object for tracking active plugins.
  23543. *
  23544. * @private
  23545. * @param {Player} player
  23546. * A Video.js player instance.
  23547. *
  23548. * @param {string} name
  23549. * The name of a plugin.
  23550. */
  23551. var markPluginAsActive = function markPluginAsActive(player, name) {
  23552. player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
  23553. player[PLUGIN_CACHE_KEY][name] = true;
  23554. };
  23555. /**
  23556. * Triggers a pair of plugin setup events.
  23557. *
  23558. * @private
  23559. * @param {Player} player
  23560. * A Video.js player instance.
  23561. *
  23562. * @param {Plugin~PluginEventHash} hash
  23563. * A plugin event hash.
  23564. *
  23565. * @param {boolean} [before]
  23566. * If true, prefixes the event name with "before". In other words,
  23567. * use this to trigger "beforepluginsetup" instead of "pluginsetup".
  23568. */
  23569. var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
  23570. var eventName = (before ? 'before' : '') + 'pluginsetup';
  23571. player.trigger(eventName, hash);
  23572. player.trigger(eventName + ':' + hash.name, hash);
  23573. };
  23574. /**
  23575. * Takes a basic plugin function and returns a wrapper function which marks
  23576. * on the player that the plugin has been activated.
  23577. *
  23578. * @private
  23579. * @param {string} name
  23580. * The name of the plugin.
  23581. *
  23582. * @param {Function} plugin
  23583. * The basic plugin.
  23584. *
  23585. * @return {Function}
  23586. * A wrapper function for the given plugin.
  23587. */
  23588. var createBasicPlugin = function createBasicPlugin(name, plugin) {
  23589. var basicPluginWrapper = function basicPluginWrapper() {
  23590. // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
  23591. // regardless, but we want the hash to be consistent with the hash provided
  23592. // for advanced plugins.
  23593. //
  23594. // The only potentially counter-intuitive thing here is the `instance` in
  23595. // the "pluginsetup" event is the value returned by the `plugin` function.
  23596. triggerSetupEvent(this, {
  23597. name: name,
  23598. plugin: plugin,
  23599. instance: null
  23600. }, true);
  23601. var instance = plugin.apply(this, arguments);
  23602. markPluginAsActive(this, name);
  23603. triggerSetupEvent(this, {
  23604. name: name,
  23605. plugin: plugin,
  23606. instance: instance
  23607. });
  23608. return instance;
  23609. };
  23610. Object.keys(plugin).forEach(function (prop) {
  23611. basicPluginWrapper[prop] = plugin[prop];
  23612. });
  23613. return basicPluginWrapper;
  23614. };
  23615. /**
  23616. * Takes a plugin sub-class and returns a factory function for generating
  23617. * instances of it.
  23618. *
  23619. * This factory function will replace itself with an instance of the requested
  23620. * sub-class of Plugin.
  23621. *
  23622. * @private
  23623. * @param {string} name
  23624. * The name of the plugin.
  23625. *
  23626. * @param {Plugin} PluginSubClass
  23627. * The advanced plugin.
  23628. *
  23629. * @return {Function}
  23630. */
  23631. var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
  23632. // Add a `name` property to the plugin prototype so that each plugin can
  23633. // refer to itself by name.
  23634. PluginSubClass.prototype.name = name;
  23635. return function () {
  23636. triggerSetupEvent(this, {
  23637. name: name,
  23638. plugin: PluginSubClass,
  23639. instance: null
  23640. }, true);
  23641. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  23642. args[_key] = arguments[_key];
  23643. }
  23644. var instance = _construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance.
  23645. this[name] = function () {
  23646. return instance;
  23647. };
  23648. triggerSetupEvent(this, instance.getEventHash());
  23649. return instance;
  23650. };
  23651. };
  23652. /**
  23653. * Parent class for all advanced plugins.
  23654. *
  23655. * @mixes module:evented~EventedMixin
  23656. * @mixes module:stateful~StatefulMixin
  23657. * @fires Player#beforepluginsetup
  23658. * @fires Player#beforepluginsetup:$name
  23659. * @fires Player#pluginsetup
  23660. * @fires Player#pluginsetup:$name
  23661. * @listens Player#dispose
  23662. * @throws {Error}
  23663. * If attempting to instantiate the base {@link Plugin} class
  23664. * directly instead of via a sub-class.
  23665. */
  23666. var Plugin = /*#__PURE__*/function () {
  23667. /**
  23668. * Creates an instance of this class.
  23669. *
  23670. * Sub-classes should call `super` to ensure plugins are properly initialized.
  23671. *
  23672. * @param {Player} player
  23673. * A Video.js player instance.
  23674. */
  23675. function Plugin(player) {
  23676. if (this.constructor === Plugin) {
  23677. throw new Error('Plugin must be sub-classed; not directly instantiated.');
  23678. }
  23679. this.player = player;
  23680. if (!this.log) {
  23681. this.log = this.player.log.createLogger(this.name);
  23682. } // Make this object evented, but remove the added `trigger` method so we
  23683. // use the prototype version instead.
  23684. evented(this);
  23685. delete this.trigger;
  23686. stateful(this, this.constructor.defaultState);
  23687. markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind
  23688. // it later easily.
  23689. this.dispose = this.dispose.bind(this); // If the player is disposed, dispose the plugin.
  23690. player.on('dispose', this.dispose);
  23691. }
  23692. /**
  23693. * Get the version of the plugin that was set on <pluginName>.VERSION
  23694. */
  23695. var _proto = Plugin.prototype;
  23696. _proto.version = function version() {
  23697. return this.constructor.VERSION;
  23698. }
  23699. /**
  23700. * Each event triggered by plugins includes a hash of additional data with
  23701. * conventional properties.
  23702. *
  23703. * This returns that object or mutates an existing hash.
  23704. *
  23705. * @param {Object} [hash={}]
  23706. * An object to be used as event an event hash.
  23707. *
  23708. * @return {Plugin~PluginEventHash}
  23709. * An event hash object with provided properties mixed-in.
  23710. */
  23711. ;
  23712. _proto.getEventHash = function getEventHash(hash) {
  23713. if (hash === void 0) {
  23714. hash = {};
  23715. }
  23716. hash.name = this.name;
  23717. hash.plugin = this.constructor;
  23718. hash.instance = this;
  23719. return hash;
  23720. }
  23721. /**
  23722. * Triggers an event on the plugin object and overrides
  23723. * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
  23724. *
  23725. * @param {string|Object} event
  23726. * An event type or an object with a type property.
  23727. *
  23728. * @param {Object} [hash={}]
  23729. * Additional data hash to merge with a
  23730. * {@link Plugin~PluginEventHash|PluginEventHash}.
  23731. *
  23732. * @return {boolean}
  23733. * Whether or not default was prevented.
  23734. */
  23735. ;
  23736. _proto.trigger = function trigger$1(event, hash) {
  23737. if (hash === void 0) {
  23738. hash = {};
  23739. }
  23740. return trigger(this.eventBusEl_, event, this.getEventHash(hash));
  23741. }
  23742. /**
  23743. * Handles "statechanged" events on the plugin. No-op by default, override by
  23744. * subclassing.
  23745. *
  23746. * @abstract
  23747. * @param {Event} e
  23748. * An event object provided by a "statechanged" event.
  23749. *
  23750. * @param {Object} e.changes
  23751. * An object describing changes that occurred with the "statechanged"
  23752. * event.
  23753. */
  23754. ;
  23755. _proto.handleStateChanged = function handleStateChanged(e) {}
  23756. /**
  23757. * Disposes a plugin.
  23758. *
  23759. * Subclasses can override this if they want, but for the sake of safety,
  23760. * it's probably best to subscribe the "dispose" event.
  23761. *
  23762. * @fires Plugin#dispose
  23763. */
  23764. ;
  23765. _proto.dispose = function dispose() {
  23766. var name = this.name,
  23767. player = this.player;
  23768. /**
  23769. * Signals that a advanced plugin is about to be disposed.
  23770. *
  23771. * @event Plugin#dispose
  23772. * @type {EventTarget~Event}
  23773. */
  23774. this.trigger('dispose');
  23775. this.off();
  23776. player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up
  23777. // references between the player and the plugin instance and nulling out
  23778. // the plugin's state and replacing methods with a function that throws.
  23779. player[PLUGIN_CACHE_KEY][name] = false;
  23780. this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory
  23781. // function, so that the plugin is ready to be set up again.
  23782. player[name] = createPluginFactory(name, pluginStorage[name]);
  23783. }
  23784. /**
  23785. * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
  23786. *
  23787. * @param {string|Function} plugin
  23788. * If a string, matches the name of a plugin. If a function, will be
  23789. * tested directly.
  23790. *
  23791. * @return {boolean}
  23792. * Whether or not a plugin is a basic plugin.
  23793. */
  23794. ;
  23795. Plugin.isBasic = function isBasic(plugin) {
  23796. var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
  23797. return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
  23798. }
  23799. /**
  23800. * Register a Video.js plugin.
  23801. *
  23802. * @param {string} name
  23803. * The name of the plugin to be registered. Must be a string and
  23804. * must not match an existing plugin or a method on the `Player`
  23805. * prototype.
  23806. *
  23807. * @param {Function} plugin
  23808. * A sub-class of `Plugin` or a function for basic plugins.
  23809. *
  23810. * @return {Function}
  23811. * For advanced plugins, a factory function for that plugin. For
  23812. * basic plugins, a wrapper function that initializes the plugin.
  23813. */
  23814. ;
  23815. Plugin.registerPlugin = function registerPlugin(name, plugin) {
  23816. if (typeof name !== 'string') {
  23817. throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + ".");
  23818. }
  23819. if (pluginExists(name)) {
  23820. log$1.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!");
  23821. } else if (Player.prototype.hasOwnProperty(name)) {
  23822. throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!");
  23823. }
  23824. if (typeof plugin !== 'function') {
  23825. throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + ".");
  23826. }
  23827. pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for
  23828. // the base Plugin class).
  23829. if (name !== BASE_PLUGIN_NAME) {
  23830. if (Plugin.isBasic(plugin)) {
  23831. Player.prototype[name] = createBasicPlugin(name, plugin);
  23832. } else {
  23833. Player.prototype[name] = createPluginFactory(name, plugin);
  23834. }
  23835. }
  23836. return plugin;
  23837. }
  23838. /**
  23839. * De-register a Video.js plugin.
  23840. *
  23841. * @param {string} name
  23842. * The name of the plugin to be de-registered. Must be a string that
  23843. * matches an existing plugin.
  23844. *
  23845. * @throws {Error}
  23846. * If an attempt is made to de-register the base plugin.
  23847. */
  23848. ;
  23849. Plugin.deregisterPlugin = function deregisterPlugin(name) {
  23850. if (name === BASE_PLUGIN_NAME) {
  23851. throw new Error('Cannot de-register base plugin.');
  23852. }
  23853. if (pluginExists(name)) {
  23854. delete pluginStorage[name];
  23855. delete Player.prototype[name];
  23856. }
  23857. }
  23858. /**
  23859. * Gets an object containing multiple Video.js plugins.
  23860. *
  23861. * @param {Array} [names]
  23862. * If provided, should be an array of plugin names. Defaults to _all_
  23863. * plugin names.
  23864. *
  23865. * @return {Object|undefined}
  23866. * An object containing plugin(s) associated with their name(s) or
  23867. * `undefined` if no matching plugins exist).
  23868. */
  23869. ;
  23870. Plugin.getPlugins = function getPlugins(names) {
  23871. if (names === void 0) {
  23872. names = Object.keys(pluginStorage);
  23873. }
  23874. var result;
  23875. names.forEach(function (name) {
  23876. var plugin = getPlugin(name);
  23877. if (plugin) {
  23878. result = result || {};
  23879. result[name] = plugin;
  23880. }
  23881. });
  23882. return result;
  23883. }
  23884. /**
  23885. * Gets a plugin's version, if available
  23886. *
  23887. * @param {string} name
  23888. * The name of a plugin.
  23889. *
  23890. * @return {string}
  23891. * The plugin's version or an empty string.
  23892. */
  23893. ;
  23894. Plugin.getPluginVersion = function getPluginVersion(name) {
  23895. var plugin = getPlugin(name);
  23896. return plugin && plugin.VERSION || '';
  23897. };
  23898. return Plugin;
  23899. }();
  23900. /**
  23901. * Gets a plugin by name if it exists.
  23902. *
  23903. * @static
  23904. * @method getPlugin
  23905. * @memberOf Plugin
  23906. * @param {string} name
  23907. * The name of a plugin.
  23908. *
  23909. * @returns {Function|undefined}
  23910. * The plugin (or `undefined`).
  23911. */
  23912. Plugin.getPlugin = getPlugin;
  23913. /**
  23914. * The name of the base plugin class as it is registered.
  23915. *
  23916. * @type {string}
  23917. */
  23918. Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
  23919. Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
  23920. /**
  23921. * Documented in player.js
  23922. *
  23923. * @ignore
  23924. */
  23925. Player.prototype.usingPlugin = function (name) {
  23926. return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
  23927. };
  23928. /**
  23929. * Documented in player.js
  23930. *
  23931. * @ignore
  23932. */
  23933. Player.prototype.hasPlugin = function (name) {
  23934. return !!pluginExists(name);
  23935. };
  23936. /**
  23937. * Signals that a plugin is about to be set up on a player.
  23938. *
  23939. * @event Player#beforepluginsetup
  23940. * @type {Plugin~PluginEventHash}
  23941. */
  23942. /**
  23943. * Signals that a plugin is about to be set up on a player - by name. The name
  23944. * is the name of the plugin.
  23945. *
  23946. * @event Player#beforepluginsetup:$name
  23947. * @type {Plugin~PluginEventHash}
  23948. */
  23949. /**
  23950. * Signals that a plugin has just been set up on a player.
  23951. *
  23952. * @event Player#pluginsetup
  23953. * @type {Plugin~PluginEventHash}
  23954. */
  23955. /**
  23956. * Signals that a plugin has just been set up on a player - by name. The name
  23957. * is the name of the plugin.
  23958. *
  23959. * @event Player#pluginsetup:$name
  23960. * @type {Plugin~PluginEventHash}
  23961. */
  23962. /**
  23963. * @typedef {Object} Plugin~PluginEventHash
  23964. *
  23965. * @property {string} instance
  23966. * For basic plugins, the return value of the plugin function. For
  23967. * advanced plugins, the plugin instance on which the event is fired.
  23968. *
  23969. * @property {string} name
  23970. * The name of the plugin.
  23971. *
  23972. * @property {string} plugin
  23973. * For basic plugins, the plugin function. For advanced plugins, the
  23974. * plugin class/constructor.
  23975. */
  23976. /**
  23977. * @file extend.js
  23978. * @module extend
  23979. */
  23980. var hasLogged = false;
  23981. /**
  23982. * Used to subclass an existing class by emulating ES subclassing using the
  23983. * `extends` keyword.
  23984. *
  23985. * @function
  23986. * @deprecated
  23987. * @example
  23988. * var MyComponent = videojs.extend(videojs.getComponent('Component'), {
  23989. * myCustomMethod: function() {
  23990. * // Do things in my method.
  23991. * }
  23992. * });
  23993. *
  23994. * @param {Function} superClass
  23995. * The class to inherit from
  23996. *
  23997. * @param {Object} [subClassMethods={}]
  23998. * Methods of the new class
  23999. *
  24000. * @return {Function}
  24001. * The new class with subClassMethods that inherited superClass.
  24002. */
  24003. var extend = function extend(superClass, subClassMethods) {
  24004. if (subClassMethods === void 0) {
  24005. subClassMethods = {};
  24006. }
  24007. // Log a warning the first time extend is called to note that it is deprecated
  24008. // It was previously deprecated in our documentation (guides, specifically),
  24009. // but was never formally deprecated in code.
  24010. if (!hasLogged) {
  24011. log$1.warn('videojs.extend is deprecated as of Video.js 7.22.0 and will be removed in Video.js 8.0.0');
  24012. hasLogged = true;
  24013. }
  24014. var subClass = function subClass() {
  24015. superClass.apply(this, arguments);
  24016. };
  24017. var methods = {};
  24018. if (typeof subClassMethods === 'object') {
  24019. if (subClassMethods.constructor !== Object.prototype.constructor) {
  24020. subClass = subClassMethods.constructor;
  24021. }
  24022. methods = subClassMethods;
  24023. } else if (typeof subClassMethods === 'function') {
  24024. subClass = subClassMethods;
  24025. }
  24026. _inherits(subClass, superClass); // this is needed for backward-compatibility and node compatibility.
  24027. if (superClass) {
  24028. subClass.super_ = superClass;
  24029. } // Extend subObj's prototype with functions and other properties from props
  24030. for (var name in methods) {
  24031. if (methods.hasOwnProperty(name)) {
  24032. subClass.prototype[name] = methods[name];
  24033. }
  24034. }
  24035. return subClass;
  24036. };
  24037. /**
  24038. * @file video.js
  24039. * @module videojs
  24040. */
  24041. /**
  24042. * Normalize an `id` value by trimming off a leading `#`
  24043. *
  24044. * @private
  24045. * @param {string} id
  24046. * A string, maybe with a leading `#`.
  24047. *
  24048. * @return {string}
  24049. * The string, without any leading `#`.
  24050. */
  24051. var normalizeId = function normalizeId(id) {
  24052. return id.indexOf('#') === 0 ? id.slice(1) : id;
  24053. };
  24054. /**
  24055. * The `videojs()` function doubles as the main function for users to create a
  24056. * {@link Player} instance as well as the main library namespace.
  24057. *
  24058. * It can also be used as a getter for a pre-existing {@link Player} instance.
  24059. * However, we _strongly_ recommend using `videojs.getPlayer()` for this
  24060. * purpose because it avoids any potential for unintended initialization.
  24061. *
  24062. * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
  24063. * of our JSDoc template, we cannot properly document this as both a function
  24064. * and a namespace, so its function signature is documented here.
  24065. *
  24066. * #### Arguments
  24067. * ##### id
  24068. * string|Element, **required**
  24069. *
  24070. * Video element or video element ID.
  24071. *
  24072. * ##### options
  24073. * Object, optional
  24074. *
  24075. * Options object for providing settings.
  24076. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  24077. *
  24078. * ##### ready
  24079. * {@link Component~ReadyCallback}, optional
  24080. *
  24081. * A function to be called when the {@link Player} and {@link Tech} are ready.
  24082. *
  24083. * #### Return Value
  24084. *
  24085. * The `videojs()` function returns a {@link Player} instance.
  24086. *
  24087. * @namespace
  24088. *
  24089. * @borrows AudioTrack as AudioTrack
  24090. * @borrows Component.getComponent as getComponent
  24091. * @borrows module:computed-style~computedStyle as computedStyle
  24092. * @borrows module:events.on as on
  24093. * @borrows module:events.one as one
  24094. * @borrows module:events.off as off
  24095. * @borrows module:events.trigger as trigger
  24096. * @borrows EventTarget as EventTarget
  24097. * @borrows module:extend~extend as extend
  24098. * @borrows module:fn.bind as bind
  24099. * @borrows module:format-time.formatTime as formatTime
  24100. * @borrows module:format-time.resetFormatTime as resetFormatTime
  24101. * @borrows module:format-time.setFormatTime as setFormatTime
  24102. * @borrows module:merge-options.mergeOptions as mergeOptions
  24103. * @borrows module:middleware.use as use
  24104. * @borrows Player.players as players
  24105. * @borrows Plugin.registerPlugin as registerPlugin
  24106. * @borrows Plugin.deregisterPlugin as deregisterPlugin
  24107. * @borrows Plugin.getPlugins as getPlugins
  24108. * @borrows Plugin.getPlugin as getPlugin
  24109. * @borrows Plugin.getPluginVersion as getPluginVersion
  24110. * @borrows Tech.getTech as getTech
  24111. * @borrows Tech.registerTech as registerTech
  24112. * @borrows TextTrack as TextTrack
  24113. * @borrows module:time-ranges.createTimeRanges as createTimeRange
  24114. * @borrows module:time-ranges.createTimeRanges as createTimeRanges
  24115. * @borrows module:url.isCrossOrigin as isCrossOrigin
  24116. * @borrows module:url.parseUrl as parseUrl
  24117. * @borrows VideoTrack as VideoTrack
  24118. *
  24119. * @param {string|Element} id
  24120. * Video element or video element ID.
  24121. *
  24122. * @param {Object} [options]
  24123. * Options object for providing settings.
  24124. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  24125. *
  24126. * @param {Component~ReadyCallback} [ready]
  24127. * A function to be called when the {@link Player} and {@link Tech} are
  24128. * ready.
  24129. *
  24130. * @return {Player}
  24131. * The `videojs()` function returns a {@link Player|Player} instance.
  24132. */
  24133. function videojs(id, options, ready) {
  24134. var player = videojs.getPlayer(id);
  24135. if (player) {
  24136. if (options) {
  24137. log$1.warn("Player \"" + id + "\" is already initialised. Options will not be applied.");
  24138. }
  24139. if (ready) {
  24140. player.ready(ready);
  24141. }
  24142. return player;
  24143. }
  24144. var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
  24145. if (!isEl(el)) {
  24146. throw new TypeError('The element or ID supplied is not valid. (videojs)');
  24147. } // document.body.contains(el) will only check if el is contained within that one document.
  24148. // This causes problems for elements in iframes.
  24149. // Instead, use the element's ownerDocument instead of the global document.
  24150. // This will make sure that the element is indeed in the dom of that document.
  24151. // Additionally, check that the document in question has a default view.
  24152. // If the document is no longer attached to the dom, the defaultView of the document will be null.
  24153. if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) {
  24154. log$1.warn('The element supplied is not included in the DOM');
  24155. }
  24156. options = options || {}; // Store a copy of the el before modification, if it is to be restored in destroy()
  24157. // If div ingest, store the parent div
  24158. if (options.restoreEl === true) {
  24159. options.restoreEl = (el.parentNode && el.parentNode.hasAttribute('data-vjs-player') ? el.parentNode : el).cloneNode(true);
  24160. }
  24161. hooks('beforesetup').forEach(function (hookFunction) {
  24162. var opts = hookFunction(el, mergeOptions$3(options));
  24163. if (!isObject(opts) || Array.isArray(opts)) {
  24164. log$1.error('please return an object in beforesetup hooks');
  24165. return;
  24166. }
  24167. options = mergeOptions$3(options, opts);
  24168. }); // We get the current "Player" component here in case an integration has
  24169. // replaced it with a custom player.
  24170. var PlayerComponent = Component$1.getComponent('Player');
  24171. player = new PlayerComponent(el, options, ready);
  24172. hooks('setup').forEach(function (hookFunction) {
  24173. return hookFunction(player);
  24174. });
  24175. return player;
  24176. }
  24177. videojs.hooks_ = hooks_;
  24178. videojs.hooks = hooks;
  24179. videojs.hook = hook;
  24180. videojs.hookOnce = hookOnce;
  24181. videojs.removeHook = removeHook; // Add default styles
  24182. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
  24183. var style = $('.vjs-styles-defaults');
  24184. if (!style) {
  24185. style = createStyleElement('vjs-styles-defaults');
  24186. var head = $('head');
  24187. if (head) {
  24188. head.insertBefore(style, head.firstChild);
  24189. }
  24190. setTextContent(style, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid:not(.vjs-audio-only-mode) {\n padding-top: 56.25%\n }\n ");
  24191. }
  24192. } // Run Auto-load players
  24193. // You have to wait at least once in case this script is loaded after your
  24194. // video in the DOM (weird behavior only with minified version)
  24195. autoSetupTimeout(1, videojs);
  24196. /**
  24197. * Current Video.js version. Follows [semantic versioning](https://semver.org/).
  24198. *
  24199. * @type {string}
  24200. */
  24201. videojs.VERSION = version$5;
  24202. /**
  24203. * The global options object. These are the settings that take effect
  24204. * if no overrides are specified when the player is created.
  24205. *
  24206. * @type {Object}
  24207. */
  24208. videojs.options = Player.prototype.options_;
  24209. /**
  24210. * Get an object with the currently created players, keyed by player ID
  24211. *
  24212. * @return {Object}
  24213. * The created players
  24214. */
  24215. videojs.getPlayers = function () {
  24216. return Player.players;
  24217. };
  24218. /**
  24219. * Get a single player based on an ID or DOM element.
  24220. *
  24221. * This is useful if you want to check if an element or ID has an associated
  24222. * Video.js player, but not create one if it doesn't.
  24223. *
  24224. * @param {string|Element} id
  24225. * An HTML element - `<video>`, `<audio>`, or `<video-js>` -
  24226. * or a string matching the `id` of such an element.
  24227. *
  24228. * @return {Player|undefined}
  24229. * A player instance or `undefined` if there is no player instance
  24230. * matching the argument.
  24231. */
  24232. videojs.getPlayer = function (id) {
  24233. var players = Player.players;
  24234. var tag;
  24235. if (typeof id === 'string') {
  24236. var nId = normalizeId(id);
  24237. var player = players[nId];
  24238. if (player) {
  24239. return player;
  24240. }
  24241. tag = $('#' + nId);
  24242. } else {
  24243. tag = id;
  24244. }
  24245. if (isEl(tag)) {
  24246. var _tag = tag,
  24247. _player = _tag.player,
  24248. playerId = _tag.playerId; // Element may have a `player` property referring to an already created
  24249. // player instance. If so, return that.
  24250. if (_player || players[playerId]) {
  24251. return _player || players[playerId];
  24252. }
  24253. }
  24254. };
  24255. /**
  24256. * Returns an array of all current players.
  24257. *
  24258. * @return {Array}
  24259. * An array of all players. The array will be in the order that
  24260. * `Object.keys` provides, which could potentially vary between
  24261. * JavaScript engines.
  24262. *
  24263. */
  24264. videojs.getAllPlayers = function () {
  24265. return (// Disposed players leave a key with a `null` value, so we need to make sure
  24266. // we filter those out.
  24267. Object.keys(Player.players).map(function (k) {
  24268. return Player.players[k];
  24269. }).filter(Boolean)
  24270. );
  24271. };
  24272. videojs.players = Player.players;
  24273. videojs.getComponent = Component$1.getComponent;
  24274. /**
  24275. * Register a component so it can referred to by name. Used when adding to other
  24276. * components, either through addChild `component.addChild('myComponent')` or through
  24277. * default children options `{ children: ['myComponent'] }`.
  24278. *
  24279. * > NOTE: You could also just initialize the component before adding.
  24280. * `component.addChild(new MyComponent());`
  24281. *
  24282. * @param {string} name
  24283. * The class name of the component
  24284. *
  24285. * @param {Component} comp
  24286. * The component class
  24287. *
  24288. * @return {Component}
  24289. * The newly registered component
  24290. */
  24291. videojs.registerComponent = function (name, comp) {
  24292. if (Tech.isTech(comp)) {
  24293. log$1.warn("The " + name + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)");
  24294. }
  24295. Component$1.registerComponent.call(Component$1, name, comp);
  24296. };
  24297. videojs.getTech = Tech.getTech;
  24298. videojs.registerTech = Tech.registerTech;
  24299. videojs.use = use;
  24300. /**
  24301. * An object that can be returned by a middleware to signify
  24302. * that the middleware is being terminated.
  24303. *
  24304. * @type {object}
  24305. * @property {object} middleware.TERMINATOR
  24306. */
  24307. Object.defineProperty(videojs, 'middleware', {
  24308. value: {},
  24309. writeable: false,
  24310. enumerable: true
  24311. });
  24312. Object.defineProperty(videojs.middleware, 'TERMINATOR', {
  24313. value: TERMINATOR,
  24314. writeable: false,
  24315. enumerable: true
  24316. });
  24317. /**
  24318. * A reference to the {@link module:browser|browser utility module} as an object.
  24319. *
  24320. * @type {Object}
  24321. * @see {@link module:browser|browser}
  24322. */
  24323. videojs.browser = browser;
  24324. /**
  24325. * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only
  24326. * included for backward-compatibility with 4.x.
  24327. *
  24328. * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead.
  24329. * @type {boolean}
  24330. */
  24331. videojs.TOUCH_ENABLED = TOUCH_ENABLED;
  24332. videojs.extend = extend;
  24333. videojs.mergeOptions = mergeOptions$3;
  24334. videojs.bind = bind;
  24335. videojs.registerPlugin = Plugin.registerPlugin;
  24336. videojs.deregisterPlugin = Plugin.deregisterPlugin;
  24337. /**
  24338. * Deprecated method to register a plugin with Video.js
  24339. *
  24340. * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead
  24341. *
  24342. * @param {string} name
  24343. * The plugin name
  24344. *
  24345. * @param {Plugin|Function} plugin
  24346. * The plugin sub-class or function
  24347. */
  24348. videojs.plugin = function (name, plugin) {
  24349. log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
  24350. return Plugin.registerPlugin(name, plugin);
  24351. };
  24352. videojs.getPlugins = Plugin.getPlugins;
  24353. videojs.getPlugin = Plugin.getPlugin;
  24354. videojs.getPluginVersion = Plugin.getPluginVersion;
  24355. /**
  24356. * Adding languages so that they're available to all players.
  24357. * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
  24358. *
  24359. * @param {string} code
  24360. * The language code or dictionary property
  24361. *
  24362. * @param {Object} data
  24363. * The data values to be translated
  24364. *
  24365. * @return {Object}
  24366. * The resulting language dictionary object
  24367. */
  24368. videojs.addLanguage = function (code, data) {
  24369. var _mergeOptions;
  24370. code = ('' + code).toLowerCase();
  24371. videojs.options.languages = mergeOptions$3(videojs.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
  24372. return videojs.options.languages[code];
  24373. };
  24374. /**
  24375. * A reference to the {@link module:log|log utility module} as an object.
  24376. *
  24377. * @type {Function}
  24378. * @see {@link module:log|log}
  24379. */
  24380. videojs.log = log$1;
  24381. videojs.createLogger = createLogger;
  24382. videojs.createTimeRange = videojs.createTimeRanges = createTimeRanges;
  24383. videojs.formatTime = formatTime;
  24384. videojs.setFormatTime = setFormatTime;
  24385. videojs.resetFormatTime = resetFormatTime;
  24386. videojs.parseUrl = parseUrl;
  24387. videojs.isCrossOrigin = isCrossOrigin;
  24388. videojs.EventTarget = EventTarget$2;
  24389. videojs.on = on;
  24390. videojs.one = one;
  24391. videojs.off = off;
  24392. videojs.trigger = trigger;
  24393. /**
  24394. * A cross-browser XMLHttpRequest wrapper.
  24395. *
  24396. * @function
  24397. * @param {Object} options
  24398. * Settings for the request.
  24399. *
  24400. * @return {XMLHttpRequest|XDomainRequest}
  24401. * The request object.
  24402. *
  24403. * @see https://github.com/Raynos/xhr
  24404. */
  24405. videojs.xhr = XHR;
  24406. videojs.TextTrack = TextTrack;
  24407. videojs.AudioTrack = AudioTrack;
  24408. videojs.VideoTrack = VideoTrack;
  24409. ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
  24410. videojs[k] = function () {
  24411. log$1.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead");
  24412. return Dom[k].apply(null, arguments);
  24413. };
  24414. });
  24415. videojs.computedStyle = computedStyle;
  24416. /**
  24417. * A reference to the {@link module:dom|DOM utility module} as an object.
  24418. *
  24419. * @type {Object}
  24420. * @see {@link module:dom|dom}
  24421. */
  24422. videojs.dom = Dom;
  24423. /**
  24424. * A reference to the {@link module:url|URL utility module} as an object.
  24425. *
  24426. * @type {Object}
  24427. * @see {@link module:url|url}
  24428. */
  24429. videojs.url = Url;
  24430. videojs.defineLazyProperty = defineLazyProperty; // Adding less ambiguous text for fullscreen button.
  24431. // In a major update this could become the default text and key.
  24432. videojs.addLanguage('en', {
  24433. 'Non-Fullscreen': 'Exit Fullscreen'
  24434. });
  24435. /*! @name @videojs/http-streaming @version 2.16.2 @license Apache-2.0 */
  24436. /**
  24437. * @file resolve-url.js - Handling how URLs are resolved and manipulated
  24438. */
  24439. var resolveUrl = _resolveUrl;
  24440. /**
  24441. * Checks whether xhr request was redirected and returns correct url depending
  24442. * on `handleManifestRedirects` option
  24443. *
  24444. * @api private
  24445. *
  24446. * @param {string} url - an url being requested
  24447. * @param {XMLHttpRequest} req - xhr request result
  24448. *
  24449. * @return {string}
  24450. */
  24451. var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) {
  24452. // To understand how the responseURL below is set and generated:
  24453. // - https://fetch.spec.whatwg.org/#concept-response-url
  24454. // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
  24455. if (handleManifestRedirect && req && req.responseURL && url !== req.responseURL) {
  24456. return req.responseURL;
  24457. }
  24458. return url;
  24459. };
  24460. var logger = function logger(source) {
  24461. if (videojs.log.debug) {
  24462. return videojs.log.debug.bind(videojs, 'VHS:', source + " >");
  24463. }
  24464. return function () {};
  24465. };
  24466. /**
  24467. * ranges
  24468. *
  24469. * Utilities for working with TimeRanges.
  24470. *
  24471. */
  24472. var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
  24473. // can be misleading because of precision differences or when the current media has poorly
  24474. // aligned audio and video, which can cause values to be slightly off from what you would
  24475. // expect. This value is what we consider to be safe to use in such comparisons to account
  24476. // for these scenarios.
  24477. var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
  24478. var filterRanges = function filterRanges(timeRanges, predicate) {
  24479. var results = [];
  24480. var i;
  24481. if (timeRanges && timeRanges.length) {
  24482. // Search for ranges that match the predicate
  24483. for (i = 0; i < timeRanges.length; i++) {
  24484. if (predicate(timeRanges.start(i), timeRanges.end(i))) {
  24485. results.push([timeRanges.start(i), timeRanges.end(i)]);
  24486. }
  24487. }
  24488. }
  24489. return videojs.createTimeRanges(results);
  24490. };
  24491. /**
  24492. * Attempts to find the buffered TimeRange that contains the specified
  24493. * time.
  24494. *
  24495. * @param {TimeRanges} buffered - the TimeRanges object to query
  24496. * @param {number} time - the time to filter on.
  24497. * @return {TimeRanges} a new TimeRanges object
  24498. */
  24499. var findRange = function findRange(buffered, time) {
  24500. return filterRanges(buffered, function (start, end) {
  24501. return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time;
  24502. });
  24503. };
  24504. /**
  24505. * Returns the TimeRanges that begin later than the specified time.
  24506. *
  24507. * @param {TimeRanges} timeRanges - the TimeRanges object to query
  24508. * @param {number} time - the time to filter on.
  24509. * @return {TimeRanges} a new TimeRanges object.
  24510. */
  24511. var findNextRange = function findNextRange(timeRanges, time) {
  24512. return filterRanges(timeRanges, function (start) {
  24513. return start - TIME_FUDGE_FACTOR >= time;
  24514. });
  24515. };
  24516. /**
  24517. * Returns gaps within a list of TimeRanges
  24518. *
  24519. * @param {TimeRanges} buffered - the TimeRanges object
  24520. * @return {TimeRanges} a TimeRanges object of gaps
  24521. */
  24522. var findGaps = function findGaps(buffered) {
  24523. if (buffered.length < 2) {
  24524. return videojs.createTimeRanges();
  24525. }
  24526. var ranges = [];
  24527. for (var i = 1; i < buffered.length; i++) {
  24528. var start = buffered.end(i - 1);
  24529. var end = buffered.start(i);
  24530. ranges.push([start, end]);
  24531. }
  24532. return videojs.createTimeRanges(ranges);
  24533. };
  24534. /**
  24535. * Calculate the intersection of two TimeRanges
  24536. *
  24537. * @param {TimeRanges} bufferA
  24538. * @param {TimeRanges} bufferB
  24539. * @return {TimeRanges} The interesection of `bufferA` with `bufferB`
  24540. */
  24541. var bufferIntersection = function bufferIntersection(bufferA, bufferB) {
  24542. var start = null;
  24543. var end = null;
  24544. var arity = 0;
  24545. var extents = [];
  24546. var ranges = [];
  24547. if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
  24548. return videojs.createTimeRange();
  24549. } // Handle the case where we have both buffers and create an
  24550. // intersection of the two
  24551. var count = bufferA.length; // A) Gather up all start and end times
  24552. while (count--) {
  24553. extents.push({
  24554. time: bufferA.start(count),
  24555. type: 'start'
  24556. });
  24557. extents.push({
  24558. time: bufferA.end(count),
  24559. type: 'end'
  24560. });
  24561. }
  24562. count = bufferB.length;
  24563. while (count--) {
  24564. extents.push({
  24565. time: bufferB.start(count),
  24566. type: 'start'
  24567. });
  24568. extents.push({
  24569. time: bufferB.end(count),
  24570. type: 'end'
  24571. });
  24572. } // B) Sort them by time
  24573. extents.sort(function (a, b) {
  24574. return a.time - b.time;
  24575. }); // C) Go along one by one incrementing arity for start and decrementing
  24576. // arity for ends
  24577. for (count = 0; count < extents.length; count++) {
  24578. if (extents[count].type === 'start') {
  24579. arity++; // D) If arity is ever incremented to 2 we are entering an
  24580. // overlapping range
  24581. if (arity === 2) {
  24582. start = extents[count].time;
  24583. }
  24584. } else if (extents[count].type === 'end') {
  24585. arity--; // E) If arity is ever decremented to 1 we leaving an
  24586. // overlapping range
  24587. if (arity === 1) {
  24588. end = extents[count].time;
  24589. }
  24590. } // F) Record overlapping ranges
  24591. if (start !== null && end !== null) {
  24592. ranges.push([start, end]);
  24593. start = null;
  24594. end = null;
  24595. }
  24596. }
  24597. return videojs.createTimeRanges(ranges);
  24598. };
  24599. /**
  24600. * Gets a human readable string for a TimeRange
  24601. *
  24602. * @param {TimeRange} range
  24603. * @return {string} a human readable string
  24604. */
  24605. var printableRange = function printableRange(range) {
  24606. var strArr = [];
  24607. if (!range || !range.length) {
  24608. return '';
  24609. }
  24610. for (var i = 0; i < range.length; i++) {
  24611. strArr.push(range.start(i) + ' => ' + range.end(i));
  24612. }
  24613. return strArr.join(', ');
  24614. };
  24615. /**
  24616. * Calculates the amount of time left in seconds until the player hits the end of the
  24617. * buffer and causes a rebuffer
  24618. *
  24619. * @param {TimeRange} buffered
  24620. * The state of the buffer
  24621. * @param {Numnber} currentTime
  24622. * The current time of the player
  24623. * @param {number} playbackRate
  24624. * The current playback rate of the player. Defaults to 1.
  24625. * @return {number}
  24626. * Time until the player has to start rebuffering in seconds.
  24627. * @function timeUntilRebuffer
  24628. */
  24629. var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime, playbackRate) {
  24630. if (playbackRate === void 0) {
  24631. playbackRate = 1;
  24632. }
  24633. var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
  24634. return (bufferedEnd - currentTime) / playbackRate;
  24635. };
  24636. /**
  24637. * Converts a TimeRanges object into an array representation
  24638. *
  24639. * @param {TimeRanges} timeRanges
  24640. * @return {Array}
  24641. */
  24642. var timeRangesToArray = function timeRangesToArray(timeRanges) {
  24643. var timeRangesList = [];
  24644. for (var i = 0; i < timeRanges.length; i++) {
  24645. timeRangesList.push({
  24646. start: timeRanges.start(i),
  24647. end: timeRanges.end(i)
  24648. });
  24649. }
  24650. return timeRangesList;
  24651. };
  24652. /**
  24653. * Determines if two time range objects are different.
  24654. *
  24655. * @param {TimeRange} a
  24656. * the first time range object to check
  24657. *
  24658. * @param {TimeRange} b
  24659. * the second time range object to check
  24660. *
  24661. * @return {Boolean}
  24662. * Whether the time range objects differ
  24663. */
  24664. var isRangeDifferent = function isRangeDifferent(a, b) {
  24665. // same object
  24666. if (a === b) {
  24667. return false;
  24668. } // one or the other is undefined
  24669. if (!a && b || !b && a) {
  24670. return true;
  24671. } // length is different
  24672. if (a.length !== b.length) {
  24673. return true;
  24674. } // see if any start/end pair is different
  24675. for (var i = 0; i < a.length; i++) {
  24676. if (a.start(i) !== b.start(i) || a.end(i) !== b.end(i)) {
  24677. return true;
  24678. }
  24679. } // if the length and every pair is the same
  24680. // this is the same time range
  24681. return false;
  24682. };
  24683. var lastBufferedEnd = function lastBufferedEnd(a) {
  24684. if (!a || !a.length || !a.end) {
  24685. return;
  24686. }
  24687. return a.end(a.length - 1);
  24688. };
  24689. /**
  24690. * A utility function to add up the amount of time in a timeRange
  24691. * after a specified startTime.
  24692. * ie:[[0, 10], [20, 40], [50, 60]] with a startTime 0
  24693. * would return 40 as there are 40s seconds after 0 in the timeRange
  24694. *
  24695. * @param {TimeRange} range
  24696. * The range to check against
  24697. * @param {number} startTime
  24698. * The time in the time range that you should start counting from
  24699. *
  24700. * @return {number}
  24701. * The number of seconds in the buffer passed the specified time.
  24702. */
  24703. var timeAheadOf = function timeAheadOf(range, startTime) {
  24704. var time = 0;
  24705. if (!range || !range.length) {
  24706. return time;
  24707. }
  24708. for (var i = 0; i < range.length; i++) {
  24709. var start = range.start(i);
  24710. var end = range.end(i); // startTime is after this range entirely
  24711. if (startTime > end) {
  24712. continue;
  24713. } // startTime is within this range
  24714. if (startTime > start && startTime <= end) {
  24715. time += end - startTime;
  24716. continue;
  24717. } // startTime is before this range.
  24718. time += end - start;
  24719. }
  24720. return time;
  24721. };
  24722. /**
  24723. * @file playlist.js
  24724. *
  24725. * Playlist related utilities.
  24726. */
  24727. var createTimeRange = videojs.createTimeRange;
  24728. /**
  24729. * Get the duration of a segment, with special cases for
  24730. * llhls segments that do not have a duration yet.
  24731. *
  24732. * @param {Object} playlist
  24733. * the playlist that the segment belongs to.
  24734. * @param {Object} segment
  24735. * the segment to get a duration for.
  24736. *
  24737. * @return {number}
  24738. * the segment duration
  24739. */
  24740. var segmentDurationWithParts = function segmentDurationWithParts(playlist, segment) {
  24741. // if this isn't a preload segment
  24742. // then we will have a segment duration that is accurate.
  24743. if (!segment.preload) {
  24744. return segment.duration;
  24745. } // otherwise we have to add up parts and preload hints
  24746. // to get an up to date duration.
  24747. var result = 0;
  24748. (segment.parts || []).forEach(function (p) {
  24749. result += p.duration;
  24750. }); // for preload hints we have to use partTargetDuration
  24751. // as they won't even have a duration yet.
  24752. (segment.preloadHints || []).forEach(function (p) {
  24753. if (p.type === 'PART') {
  24754. result += playlist.partTargetDuration;
  24755. }
  24756. });
  24757. return result;
  24758. };
  24759. /**
  24760. * A function to get a combined list of parts and segments with durations
  24761. * and indexes.
  24762. *
  24763. * @param {Playlist} playlist the playlist to get the list for.
  24764. *
  24765. * @return {Array} The part/segment list.
  24766. */
  24767. var getPartsAndSegments = function getPartsAndSegments(playlist) {
  24768. return (playlist.segments || []).reduce(function (acc, segment, si) {
  24769. if (segment.parts) {
  24770. segment.parts.forEach(function (part, pi) {
  24771. acc.push({
  24772. duration: part.duration,
  24773. segmentIndex: si,
  24774. partIndex: pi,
  24775. part: part,
  24776. segment: segment
  24777. });
  24778. });
  24779. } else {
  24780. acc.push({
  24781. duration: segment.duration,
  24782. segmentIndex: si,
  24783. partIndex: null,
  24784. segment: segment,
  24785. part: null
  24786. });
  24787. }
  24788. return acc;
  24789. }, []);
  24790. };
  24791. var getLastParts = function getLastParts(media) {
  24792. var lastSegment = media.segments && media.segments.length && media.segments[media.segments.length - 1];
  24793. return lastSegment && lastSegment.parts || [];
  24794. };
  24795. var getKnownPartCount = function getKnownPartCount(_ref) {
  24796. var preloadSegment = _ref.preloadSegment;
  24797. if (!preloadSegment) {
  24798. return;
  24799. }
  24800. var parts = preloadSegment.parts,
  24801. preloadHints = preloadSegment.preloadHints;
  24802. var partCount = (preloadHints || []).reduce(function (count, hint) {
  24803. return count + (hint.type === 'PART' ? 1 : 0);
  24804. }, 0);
  24805. partCount += parts && parts.length ? parts.length : 0;
  24806. return partCount;
  24807. };
  24808. /**
  24809. * Get the number of seconds to delay from the end of a
  24810. * live playlist.
  24811. *
  24812. * @param {Playlist} master the master playlist
  24813. * @param {Playlist} media the media playlist
  24814. * @return {number} the hold back in seconds.
  24815. */
  24816. var liveEdgeDelay = function liveEdgeDelay(master, media) {
  24817. if (media.endList) {
  24818. return 0;
  24819. } // dash suggestedPresentationDelay trumps everything
  24820. if (master && master.suggestedPresentationDelay) {
  24821. return master.suggestedPresentationDelay;
  24822. }
  24823. var hasParts = getLastParts(media).length > 0; // look for "part" delays from ll-hls first
  24824. if (hasParts && media.serverControl && media.serverControl.partHoldBack) {
  24825. return media.serverControl.partHoldBack;
  24826. } else if (hasParts && media.partTargetDuration) {
  24827. return media.partTargetDuration * 3; // finally look for full segment delays
  24828. } else if (media.serverControl && media.serverControl.holdBack) {
  24829. return media.serverControl.holdBack;
  24830. } else if (media.targetDuration) {
  24831. return media.targetDuration * 3;
  24832. }
  24833. return 0;
  24834. };
  24835. /**
  24836. * walk backward until we find a duration we can use
  24837. * or return a failure
  24838. *
  24839. * @param {Playlist} playlist the playlist to walk through
  24840. * @param {Number} endSequence the mediaSequence to stop walking on
  24841. */
  24842. var backwardDuration = function backwardDuration(playlist, endSequence) {
  24843. var result = 0;
  24844. var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
  24845. // the interval, use it
  24846. var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
  24847. // information that is earlier than endSequence
  24848. if (segment) {
  24849. if (typeof segment.start !== 'undefined') {
  24850. return {
  24851. result: segment.start,
  24852. precise: true
  24853. };
  24854. }
  24855. if (typeof segment.end !== 'undefined') {
  24856. return {
  24857. result: segment.end - segment.duration,
  24858. precise: true
  24859. };
  24860. }
  24861. }
  24862. while (i--) {
  24863. segment = playlist.segments[i];
  24864. if (typeof segment.end !== 'undefined') {
  24865. return {
  24866. result: result + segment.end,
  24867. precise: true
  24868. };
  24869. }
  24870. result += segmentDurationWithParts(playlist, segment);
  24871. if (typeof segment.start !== 'undefined') {
  24872. return {
  24873. result: result + segment.start,
  24874. precise: true
  24875. };
  24876. }
  24877. }
  24878. return {
  24879. result: result,
  24880. precise: false
  24881. };
  24882. };
  24883. /**
  24884. * walk forward until we find a duration we can use
  24885. * or return a failure
  24886. *
  24887. * @param {Playlist} playlist the playlist to walk through
  24888. * @param {number} endSequence the mediaSequence to stop walking on
  24889. */
  24890. var forwardDuration = function forwardDuration(playlist, endSequence) {
  24891. var result = 0;
  24892. var segment;
  24893. var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
  24894. // information
  24895. for (; i < playlist.segments.length; i++) {
  24896. segment = playlist.segments[i];
  24897. if (typeof segment.start !== 'undefined') {
  24898. return {
  24899. result: segment.start - result,
  24900. precise: true
  24901. };
  24902. }
  24903. result += segmentDurationWithParts(playlist, segment);
  24904. if (typeof segment.end !== 'undefined') {
  24905. return {
  24906. result: segment.end - result,
  24907. precise: true
  24908. };
  24909. }
  24910. } // indicate we didn't find a useful duration estimate
  24911. return {
  24912. result: -1,
  24913. precise: false
  24914. };
  24915. };
  24916. /**
  24917. * Calculate the media duration from the segments associated with a
  24918. * playlist. The duration of a subinterval of the available segments
  24919. * may be calculated by specifying an end index.
  24920. *
  24921. * @param {Object} playlist a media playlist object
  24922. * @param {number=} endSequence an exclusive upper boundary
  24923. * for the playlist. Defaults to playlist length.
  24924. * @param {number} expired the amount of time that has dropped
  24925. * off the front of the playlist in a live scenario
  24926. * @return {number} the duration between the first available segment
  24927. * and end index.
  24928. */
  24929. var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
  24930. if (typeof endSequence === 'undefined') {
  24931. endSequence = playlist.mediaSequence + playlist.segments.length;
  24932. }
  24933. if (endSequence < playlist.mediaSequence) {
  24934. return 0;
  24935. } // do a backward walk to estimate the duration
  24936. var backward = backwardDuration(playlist, endSequence);
  24937. if (backward.precise) {
  24938. // if we were able to base our duration estimate on timing
  24939. // information provided directly from the Media Source, return
  24940. // it
  24941. return backward.result;
  24942. } // walk forward to see if a precise duration estimate can be made
  24943. // that way
  24944. var forward = forwardDuration(playlist, endSequence);
  24945. if (forward.precise) {
  24946. // we found a segment that has been buffered and so it's
  24947. // position is known precisely
  24948. return forward.result;
  24949. } // return the less-precise, playlist-based duration estimate
  24950. return backward.result + expired;
  24951. };
  24952. /**
  24953. * Calculates the duration of a playlist. If a start and end index
  24954. * are specified, the duration will be for the subset of the media
  24955. * timeline between those two indices. The total duration for live
  24956. * playlists is always Infinity.
  24957. *
  24958. * @param {Object} playlist a media playlist object
  24959. * @param {number=} endSequence an exclusive upper
  24960. * boundary for the playlist. Defaults to the playlist media
  24961. * sequence number plus its length.
  24962. * @param {number=} expired the amount of time that has
  24963. * dropped off the front of the playlist in a live scenario
  24964. * @return {number} the duration between the start index and end
  24965. * index.
  24966. */
  24967. var duration = function duration(playlist, endSequence, expired) {
  24968. if (!playlist) {
  24969. return 0;
  24970. }
  24971. if (typeof expired !== 'number') {
  24972. expired = 0;
  24973. } // if a slice of the total duration is not requested, use
  24974. // playlist-level duration indicators when they're present
  24975. if (typeof endSequence === 'undefined') {
  24976. // if present, use the duration specified in the playlist
  24977. if (playlist.totalDuration) {
  24978. return playlist.totalDuration;
  24979. } // duration should be Infinity for live playlists
  24980. if (!playlist.endList) {
  24981. return window$1.Infinity;
  24982. }
  24983. } // calculate the total duration based on the segment durations
  24984. return intervalDuration(playlist, endSequence, expired);
  24985. };
  24986. /**
  24987. * Calculate the time between two indexes in the current playlist
  24988. * neight the start- nor the end-index need to be within the current
  24989. * playlist in which case, the targetDuration of the playlist is used
  24990. * to approximate the durations of the segments
  24991. *
  24992. * @param {Array} options.durationList list to iterate over for durations.
  24993. * @param {number} options.defaultDuration duration to use for elements before or after the durationList
  24994. * @param {number} options.startIndex partsAndSegments index to start
  24995. * @param {number} options.endIndex partsAndSegments index to end.
  24996. * @return {number} the number of seconds between startIndex and endIndex
  24997. */
  24998. var sumDurations = function sumDurations(_ref2) {
  24999. var defaultDuration = _ref2.defaultDuration,
  25000. durationList = _ref2.durationList,
  25001. startIndex = _ref2.startIndex,
  25002. endIndex = _ref2.endIndex;
  25003. var durations = 0;
  25004. if (startIndex > endIndex) {
  25005. var _ref3 = [endIndex, startIndex];
  25006. startIndex = _ref3[0];
  25007. endIndex = _ref3[1];
  25008. }
  25009. if (startIndex < 0) {
  25010. for (var i = startIndex; i < Math.min(0, endIndex); i++) {
  25011. durations += defaultDuration;
  25012. }
  25013. startIndex = 0;
  25014. }
  25015. for (var _i = startIndex; _i < endIndex; _i++) {
  25016. durations += durationList[_i].duration;
  25017. }
  25018. return durations;
  25019. };
  25020. /**
  25021. * Calculates the playlist end time
  25022. *
  25023. * @param {Object} playlist a media playlist object
  25024. * @param {number=} expired the amount of time that has
  25025. * dropped off the front of the playlist in a live scenario
  25026. * @param {boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
  25027. * playlist end calculation should consider the safe live end
  25028. * (truncate the playlist end by three segments). This is normally
  25029. * used for calculating the end of the playlist's seekable range.
  25030. * This takes into account the value of liveEdgePadding.
  25031. * Setting liveEdgePadding to 0 is equivalent to setting this to false.
  25032. * @param {number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds.
  25033. * If this is provided, it is used in the safe live end calculation.
  25034. * Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent.
  25035. * Corresponds to suggestedPresentationDelay in DASH manifests.
  25036. * @return {number} the end time of playlist
  25037. * @function playlistEnd
  25038. */
  25039. var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding) {
  25040. if (!playlist || !playlist.segments) {
  25041. return null;
  25042. }
  25043. if (playlist.endList) {
  25044. return duration(playlist);
  25045. }
  25046. if (expired === null) {
  25047. return null;
  25048. }
  25049. expired = expired || 0;
  25050. var lastSegmentEndTime = intervalDuration(playlist, playlist.mediaSequence + playlist.segments.length, expired);
  25051. if (useSafeLiveEnd) {
  25052. liveEdgePadding = typeof liveEdgePadding === 'number' ? liveEdgePadding : liveEdgeDelay(null, playlist);
  25053. lastSegmentEndTime -= liveEdgePadding;
  25054. } // don't return a time less than zero
  25055. return Math.max(0, lastSegmentEndTime);
  25056. };
  25057. /**
  25058. * Calculates the interval of time that is currently seekable in a
  25059. * playlist. The returned time ranges are relative to the earliest
  25060. * moment in the specified playlist that is still available. A full
  25061. * seekable implementation for live streams would need to offset
  25062. * these values by the duration of content that has expired from the
  25063. * stream.
  25064. *
  25065. * @param {Object} playlist a media playlist object
  25066. * dropped off the front of the playlist in a live scenario
  25067. * @param {number=} expired the amount of time that has
  25068. * dropped off the front of the playlist in a live scenario
  25069. * @param {number} liveEdgePadding how far from the end of the playlist we should be in seconds.
  25070. * Corresponds to suggestedPresentationDelay in DASH manifests.
  25071. * @return {TimeRanges} the periods of time that are valid targets
  25072. * for seeking
  25073. */
  25074. var seekable = function seekable(playlist, expired, liveEdgePadding) {
  25075. var useSafeLiveEnd = true;
  25076. var seekableStart = expired || 0;
  25077. var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding);
  25078. if (seekableEnd === null) {
  25079. return createTimeRange();
  25080. }
  25081. return createTimeRange(seekableStart, seekableEnd);
  25082. };
  25083. /**
  25084. * Determine the index and estimated starting time of the segment that
  25085. * contains a specified playback position in a media playlist.
  25086. *
  25087. * @param {Object} options.playlist the media playlist to query
  25088. * @param {number} options.currentTime The number of seconds since the earliest
  25089. * possible position to determine the containing segment for
  25090. * @param {number} options.startTime the time when the segment/part starts
  25091. * @param {number} options.startingSegmentIndex the segment index to start looking at.
  25092. * @param {number?} [options.startingPartIndex] the part index to look at within the segment.
  25093. *
  25094. * @return {Object} an object with partIndex, segmentIndex, and startTime.
  25095. */
  25096. var getMediaInfoForTime = function getMediaInfoForTime(_ref4) {
  25097. var playlist = _ref4.playlist,
  25098. currentTime = _ref4.currentTime,
  25099. startingSegmentIndex = _ref4.startingSegmentIndex,
  25100. startingPartIndex = _ref4.startingPartIndex,
  25101. startTime = _ref4.startTime,
  25102. experimentalExactManifestTimings = _ref4.experimentalExactManifestTimings;
  25103. var time = currentTime - startTime;
  25104. var partsAndSegments = getPartsAndSegments(playlist);
  25105. var startIndex = 0;
  25106. for (var i = 0; i < partsAndSegments.length; i++) {
  25107. var partAndSegment = partsAndSegments[i];
  25108. if (startingSegmentIndex !== partAndSegment.segmentIndex) {
  25109. continue;
  25110. } // skip this if part index does not match.
  25111. if (typeof startingPartIndex === 'number' && typeof partAndSegment.partIndex === 'number' && startingPartIndex !== partAndSegment.partIndex) {
  25112. continue;
  25113. }
  25114. startIndex = i;
  25115. break;
  25116. }
  25117. if (time < 0) {
  25118. // Walk backward from startIndex in the playlist, adding durations
  25119. // until we find a segment that contains `time` and return it
  25120. if (startIndex > 0) {
  25121. for (var _i2 = startIndex - 1; _i2 >= 0; _i2--) {
  25122. var _partAndSegment = partsAndSegments[_i2];
  25123. time += _partAndSegment.duration;
  25124. if (experimentalExactManifestTimings) {
  25125. if (time < 0) {
  25126. continue;
  25127. }
  25128. } else if (time + TIME_FUDGE_FACTOR <= 0) {
  25129. continue;
  25130. }
  25131. return {
  25132. partIndex: _partAndSegment.partIndex,
  25133. segmentIndex: _partAndSegment.segmentIndex,
  25134. startTime: startTime - sumDurations({
  25135. defaultDuration: playlist.targetDuration,
  25136. durationList: partsAndSegments,
  25137. startIndex: startIndex,
  25138. endIndex: _i2
  25139. })
  25140. };
  25141. }
  25142. } // We were unable to find a good segment within the playlist
  25143. // so select the first segment
  25144. return {
  25145. partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
  25146. segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
  25147. startTime: currentTime
  25148. };
  25149. } // When startIndex is negative, we first walk forward to first segment
  25150. // adding target durations. If we "run out of time" before getting to
  25151. // the first segment, return the first segment
  25152. if (startIndex < 0) {
  25153. for (var _i3 = startIndex; _i3 < 0; _i3++) {
  25154. time -= playlist.targetDuration;
  25155. if (time < 0) {
  25156. return {
  25157. partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
  25158. segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
  25159. startTime: currentTime
  25160. };
  25161. }
  25162. }
  25163. startIndex = 0;
  25164. } // Walk forward from startIndex in the playlist, subtracting durations
  25165. // until we find a segment that contains `time` and return it
  25166. for (var _i4 = startIndex; _i4 < partsAndSegments.length; _i4++) {
  25167. var _partAndSegment2 = partsAndSegments[_i4];
  25168. time -= _partAndSegment2.duration;
  25169. if (experimentalExactManifestTimings) {
  25170. if (time > 0) {
  25171. continue;
  25172. }
  25173. } else if (time - TIME_FUDGE_FACTOR >= 0) {
  25174. continue;
  25175. }
  25176. return {
  25177. partIndex: _partAndSegment2.partIndex,
  25178. segmentIndex: _partAndSegment2.segmentIndex,
  25179. startTime: startTime + sumDurations({
  25180. defaultDuration: playlist.targetDuration,
  25181. durationList: partsAndSegments,
  25182. startIndex: startIndex,
  25183. endIndex: _i4
  25184. })
  25185. };
  25186. } // We are out of possible candidates so load the last one...
  25187. return {
  25188. segmentIndex: partsAndSegments[partsAndSegments.length - 1].segmentIndex,
  25189. partIndex: partsAndSegments[partsAndSegments.length - 1].partIndex,
  25190. startTime: currentTime
  25191. };
  25192. };
  25193. /**
  25194. * Check whether the playlist is blacklisted or not.
  25195. *
  25196. * @param {Object} playlist the media playlist object
  25197. * @return {boolean} whether the playlist is blacklisted or not
  25198. * @function isBlacklisted
  25199. */
  25200. var isBlacklisted = function isBlacklisted(playlist) {
  25201. return playlist.excludeUntil && playlist.excludeUntil > Date.now();
  25202. };
  25203. /**
  25204. * Check whether the playlist is compatible with current playback configuration or has
  25205. * been blacklisted permanently for being incompatible.
  25206. *
  25207. * @param {Object} playlist the media playlist object
  25208. * @return {boolean} whether the playlist is incompatible or not
  25209. * @function isIncompatible
  25210. */
  25211. var isIncompatible = function isIncompatible(playlist) {
  25212. return playlist.excludeUntil && playlist.excludeUntil === Infinity;
  25213. };
  25214. /**
  25215. * Check whether the playlist is enabled or not.
  25216. *
  25217. * @param {Object} playlist the media playlist object
  25218. * @return {boolean} whether the playlist is enabled or not
  25219. * @function isEnabled
  25220. */
  25221. var isEnabled = function isEnabled(playlist) {
  25222. var blacklisted = isBlacklisted(playlist);
  25223. return !playlist.disabled && !blacklisted;
  25224. };
  25225. /**
  25226. * Check whether the playlist has been manually disabled through the representations api.
  25227. *
  25228. * @param {Object} playlist the media playlist object
  25229. * @return {boolean} whether the playlist is disabled manually or not
  25230. * @function isDisabled
  25231. */
  25232. var isDisabled = function isDisabled(playlist) {
  25233. return playlist.disabled;
  25234. };
  25235. /**
  25236. * Returns whether the current playlist is an AES encrypted HLS stream
  25237. *
  25238. * @return {boolean} true if it's an AES encrypted HLS stream
  25239. */
  25240. var isAes = function isAes(media) {
  25241. for (var i = 0; i < media.segments.length; i++) {
  25242. if (media.segments[i].key) {
  25243. return true;
  25244. }
  25245. }
  25246. return false;
  25247. };
  25248. /**
  25249. * Checks if the playlist has a value for the specified attribute
  25250. *
  25251. * @param {string} attr
  25252. * Attribute to check for
  25253. * @param {Object} playlist
  25254. * The media playlist object
  25255. * @return {boolean}
  25256. * Whether the playlist contains a value for the attribute or not
  25257. * @function hasAttribute
  25258. */
  25259. var hasAttribute = function hasAttribute(attr, playlist) {
  25260. return playlist.attributes && playlist.attributes[attr];
  25261. };
  25262. /**
  25263. * Estimates the time required to complete a segment download from the specified playlist
  25264. *
  25265. * @param {number} segmentDuration
  25266. * Duration of requested segment
  25267. * @param {number} bandwidth
  25268. * Current measured bandwidth of the player
  25269. * @param {Object} playlist
  25270. * The media playlist object
  25271. * @param {number=} bytesReceived
  25272. * Number of bytes already received for the request. Defaults to 0
  25273. * @return {number|NaN}
  25274. * The estimated time to request the segment. NaN if bandwidth information for
  25275. * the given playlist is unavailable
  25276. * @function estimateSegmentRequestTime
  25277. */
  25278. var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist, bytesReceived) {
  25279. if (bytesReceived === void 0) {
  25280. bytesReceived = 0;
  25281. }
  25282. if (!hasAttribute('BANDWIDTH', playlist)) {
  25283. return NaN;
  25284. }
  25285. var size = segmentDuration * playlist.attributes.BANDWIDTH;
  25286. return (size - bytesReceived * 8) / bandwidth;
  25287. };
  25288. /*
  25289. * Returns whether the current playlist is the lowest rendition
  25290. *
  25291. * @return {Boolean} true if on lowest rendition
  25292. */
  25293. var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
  25294. if (master.playlists.length === 1) {
  25295. return true;
  25296. }
  25297. var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
  25298. return master.playlists.filter(function (playlist) {
  25299. if (!isEnabled(playlist)) {
  25300. return false;
  25301. }
  25302. return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
  25303. }).length === 0;
  25304. };
  25305. var playlistMatch = function playlistMatch(a, b) {
  25306. // both playlits are null
  25307. // or only one playlist is non-null
  25308. // no match
  25309. if (!a && !b || !a && b || a && !b) {
  25310. return false;
  25311. } // playlist objects are the same, match
  25312. if (a === b) {
  25313. return true;
  25314. } // first try to use id as it should be the most
  25315. // accurate
  25316. if (a.id && b.id && a.id === b.id) {
  25317. return true;
  25318. } // next try to use reslovedUri as it should be the
  25319. // second most accurate.
  25320. if (a.resolvedUri && b.resolvedUri && a.resolvedUri === b.resolvedUri) {
  25321. return true;
  25322. } // finally try to use uri as it should be accurate
  25323. // but might miss a few cases for relative uris
  25324. if (a.uri && b.uri && a.uri === b.uri) {
  25325. return true;
  25326. }
  25327. return false;
  25328. };
  25329. var someAudioVariant = function someAudioVariant(master, callback) {
  25330. var AUDIO = master && master.mediaGroups && master.mediaGroups.AUDIO || {};
  25331. var found = false;
  25332. for (var groupName in AUDIO) {
  25333. for (var label in AUDIO[groupName]) {
  25334. found = callback(AUDIO[groupName][label]);
  25335. if (found) {
  25336. break;
  25337. }
  25338. }
  25339. if (found) {
  25340. break;
  25341. }
  25342. }
  25343. return !!found;
  25344. };
  25345. var isAudioOnly = function isAudioOnly(master) {
  25346. // we are audio only if we have no main playlists but do
  25347. // have media group playlists.
  25348. if (!master || !master.playlists || !master.playlists.length) {
  25349. // without audio variants or playlists this
  25350. // is not an audio only master.
  25351. var found = someAudioVariant(master, function (variant) {
  25352. return variant.playlists && variant.playlists.length || variant.uri;
  25353. });
  25354. return found;
  25355. } // if every playlist has only an audio codec it is audio only
  25356. var _loop = function _loop(i) {
  25357. var playlist = master.playlists[i];
  25358. var CODECS = playlist.attributes && playlist.attributes.CODECS; // all codecs are audio, this is an audio playlist.
  25359. if (CODECS && CODECS.split(',').every(function (c) {
  25360. return isAudioCodec(c);
  25361. })) {
  25362. return "continue";
  25363. } // playlist is in an audio group it is audio only
  25364. var found = someAudioVariant(master, function (variant) {
  25365. return playlistMatch(playlist, variant);
  25366. });
  25367. if (found) {
  25368. return "continue";
  25369. } // if we make it here this playlist isn't audio and we
  25370. // are not audio only
  25371. return {
  25372. v: false
  25373. };
  25374. };
  25375. for (var i = 0; i < master.playlists.length; i++) {
  25376. var _ret = _loop(i);
  25377. if (_ret === "continue") continue;
  25378. if (typeof _ret === "object") return _ret.v;
  25379. } // if we make it past every playlist without returning, then
  25380. // this is an audio only playlist.
  25381. return true;
  25382. }; // exports
  25383. var Playlist = {
  25384. liveEdgeDelay: liveEdgeDelay,
  25385. duration: duration,
  25386. seekable: seekable,
  25387. getMediaInfoForTime: getMediaInfoForTime,
  25388. isEnabled: isEnabled,
  25389. isDisabled: isDisabled,
  25390. isBlacklisted: isBlacklisted,
  25391. isIncompatible: isIncompatible,
  25392. playlistEnd: playlistEnd,
  25393. isAes: isAes,
  25394. hasAttribute: hasAttribute,
  25395. estimateSegmentRequestTime: estimateSegmentRequestTime,
  25396. isLowestEnabledRendition: isLowestEnabledRendition,
  25397. isAudioOnly: isAudioOnly,
  25398. playlistMatch: playlistMatch,
  25399. segmentDurationWithParts: segmentDurationWithParts
  25400. };
  25401. var log = videojs.log;
  25402. var createPlaylistID = function createPlaylistID(index, uri) {
  25403. return index + "-" + uri;
  25404. }; // default function for creating a group id
  25405. var groupID = function groupID(type, group, label) {
  25406. return "placeholder-uri-" + type + "-" + group + "-" + label;
  25407. };
  25408. /**
  25409. * Parses a given m3u8 playlist
  25410. *
  25411. * @param {Function} [onwarn]
  25412. * a function to call when the parser triggers a warning event.
  25413. * @param {Function} [oninfo]
  25414. * a function to call when the parser triggers an info event.
  25415. * @param {string} manifestString
  25416. * The downloaded manifest string
  25417. * @param {Object[]} [customTagParsers]
  25418. * An array of custom tag parsers for the m3u8-parser instance
  25419. * @param {Object[]} [customTagMappers]
  25420. * An array of custom tag mappers for the m3u8-parser instance
  25421. * @param {boolean} [experimentalLLHLS=false]
  25422. * Whether to keep ll-hls features in the manifest after parsing.
  25423. * @return {Object}
  25424. * The manifest object
  25425. */
  25426. var parseManifest = function parseManifest(_ref) {
  25427. var onwarn = _ref.onwarn,
  25428. oninfo = _ref.oninfo,
  25429. manifestString = _ref.manifestString,
  25430. _ref$customTagParsers = _ref.customTagParsers,
  25431. customTagParsers = _ref$customTagParsers === void 0 ? [] : _ref$customTagParsers,
  25432. _ref$customTagMappers = _ref.customTagMappers,
  25433. customTagMappers = _ref$customTagMappers === void 0 ? [] : _ref$customTagMappers,
  25434. experimentalLLHLS = _ref.experimentalLLHLS;
  25435. var parser = new Parser();
  25436. if (onwarn) {
  25437. parser.on('warn', onwarn);
  25438. }
  25439. if (oninfo) {
  25440. parser.on('info', oninfo);
  25441. }
  25442. customTagParsers.forEach(function (customParser) {
  25443. return parser.addParser(customParser);
  25444. });
  25445. customTagMappers.forEach(function (mapper) {
  25446. return parser.addTagMapper(mapper);
  25447. });
  25448. parser.push(manifestString);
  25449. parser.end();
  25450. var manifest = parser.manifest; // remove llhls features from the parsed manifest
  25451. // if we don't want llhls support.
  25452. if (!experimentalLLHLS) {
  25453. ['preloadSegment', 'skip', 'serverControl', 'renditionReports', 'partInf', 'partTargetDuration'].forEach(function (k) {
  25454. if (manifest.hasOwnProperty(k)) {
  25455. delete manifest[k];
  25456. }
  25457. });
  25458. if (manifest.segments) {
  25459. manifest.segments.forEach(function (segment) {
  25460. ['parts', 'preloadHints'].forEach(function (k) {
  25461. if (segment.hasOwnProperty(k)) {
  25462. delete segment[k];
  25463. }
  25464. });
  25465. });
  25466. }
  25467. }
  25468. if (!manifest.targetDuration) {
  25469. var targetDuration = 10;
  25470. if (manifest.segments && manifest.segments.length) {
  25471. targetDuration = manifest.segments.reduce(function (acc, s) {
  25472. return Math.max(acc, s.duration);
  25473. }, 0);
  25474. }
  25475. if (onwarn) {
  25476. onwarn("manifest has no targetDuration defaulting to " + targetDuration);
  25477. }
  25478. manifest.targetDuration = targetDuration;
  25479. }
  25480. var parts = getLastParts(manifest);
  25481. if (parts.length && !manifest.partTargetDuration) {
  25482. var partTargetDuration = parts.reduce(function (acc, p) {
  25483. return Math.max(acc, p.duration);
  25484. }, 0);
  25485. if (onwarn) {
  25486. onwarn("manifest has no partTargetDuration defaulting to " + partTargetDuration);
  25487. log.error('LL-HLS manifest has parts but lacks required #EXT-X-PART-INF:PART-TARGET value. See https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-09#section-4.4.3.7. Playback is not guaranteed.');
  25488. }
  25489. manifest.partTargetDuration = partTargetDuration;
  25490. }
  25491. return manifest;
  25492. };
  25493. /**
  25494. * Loops through all supported media groups in master and calls the provided
  25495. * callback for each group
  25496. *
  25497. * @param {Object} master
  25498. * The parsed master manifest object
  25499. * @param {Function} callback
  25500. * Callback to call for each media group
  25501. */
  25502. var forEachMediaGroup = function forEachMediaGroup(master, callback) {
  25503. if (!master.mediaGroups) {
  25504. return;
  25505. }
  25506. ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
  25507. if (!master.mediaGroups[mediaType]) {
  25508. return;
  25509. }
  25510. for (var groupKey in master.mediaGroups[mediaType]) {
  25511. for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
  25512. var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
  25513. callback(mediaProperties, mediaType, groupKey, labelKey);
  25514. }
  25515. }
  25516. });
  25517. };
  25518. /**
  25519. * Adds properties and attributes to the playlist to keep consistent functionality for
  25520. * playlists throughout VHS.
  25521. *
  25522. * @param {Object} config
  25523. * Arguments object
  25524. * @param {Object} config.playlist
  25525. * The media playlist
  25526. * @param {string} [config.uri]
  25527. * The uri to the media playlist (if media playlist is not from within a master
  25528. * playlist)
  25529. * @param {string} id
  25530. * ID to use for the playlist
  25531. */
  25532. var setupMediaPlaylist = function setupMediaPlaylist(_ref2) {
  25533. var playlist = _ref2.playlist,
  25534. uri = _ref2.uri,
  25535. id = _ref2.id;
  25536. playlist.id = id;
  25537. playlist.playlistErrors_ = 0;
  25538. if (uri) {
  25539. // For media playlists, m3u8-parser does not have access to a URI, as HLS media
  25540. // playlists do not contain their own source URI, but one is needed for consistency in
  25541. // VHS.
  25542. playlist.uri = uri;
  25543. } // For HLS master playlists, even though certain attributes MUST be defined, the
  25544. // stream may still be played without them.
  25545. // For HLS media playlists, m3u8-parser does not attach an attributes object to the
  25546. // manifest.
  25547. //
  25548. // To avoid undefined reference errors through the project, and make the code easier
  25549. // to write/read, add an empty attributes object for these cases.
  25550. playlist.attributes = playlist.attributes || {};
  25551. };
  25552. /**
  25553. * Adds ID, resolvedUri, and attributes properties to each playlist of the master, where
  25554. * necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to
  25555. * playlist references to the playlists array.
  25556. *
  25557. * @param {Object} master
  25558. * The master playlist
  25559. */
  25560. var setupMediaPlaylists = function setupMediaPlaylists(master) {
  25561. var i = master.playlists.length;
  25562. while (i--) {
  25563. var playlist = master.playlists[i];
  25564. setupMediaPlaylist({
  25565. playlist: playlist,
  25566. id: createPlaylistID(i, playlist.uri)
  25567. });
  25568. playlist.resolvedUri = resolveUrl(master.uri, playlist.uri);
  25569. master.playlists[playlist.id] = playlist; // URI reference added for backwards compatibility
  25570. master.playlists[playlist.uri] = playlist; // Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute,
  25571. // the stream can be played without it. Although an attributes property may have been
  25572. // added to the playlist to prevent undefined references, issue a warning to fix the
  25573. // manifest.
  25574. if (!playlist.attributes.BANDWIDTH) {
  25575. log.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
  25576. }
  25577. }
  25578. };
  25579. /**
  25580. * Adds resolvedUri properties to each media group.
  25581. *
  25582. * @param {Object} master
  25583. * The master playlist
  25584. */
  25585. var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
  25586. forEachMediaGroup(master, function (properties) {
  25587. if (properties.uri) {
  25588. properties.resolvedUri = resolveUrl(master.uri, properties.uri);
  25589. }
  25590. });
  25591. };
  25592. /**
  25593. * Creates a master playlist wrapper to insert a sole media playlist into.
  25594. *
  25595. * @param {Object} media
  25596. * Media playlist
  25597. * @param {string} uri
  25598. * The media URI
  25599. *
  25600. * @return {Object}
  25601. * Master playlist
  25602. */
  25603. var masterForMedia = function masterForMedia(media, uri) {
  25604. var id = createPlaylistID(0, uri);
  25605. var master = {
  25606. mediaGroups: {
  25607. 'AUDIO': {},
  25608. 'VIDEO': {},
  25609. 'CLOSED-CAPTIONS': {},
  25610. 'SUBTITLES': {}
  25611. },
  25612. uri: window$1.location.href,
  25613. resolvedUri: window$1.location.href,
  25614. playlists: [{
  25615. uri: uri,
  25616. id: id,
  25617. resolvedUri: uri,
  25618. // m3u8-parser does not attach an attributes property to media playlists so make
  25619. // sure that the property is attached to avoid undefined reference errors
  25620. attributes: {}
  25621. }]
  25622. }; // set up ID reference
  25623. master.playlists[id] = master.playlists[0]; // URI reference added for backwards compatibility
  25624. master.playlists[uri] = master.playlists[0];
  25625. return master;
  25626. };
  25627. /**
  25628. * Does an in-place update of the master manifest to add updated playlist URI references
  25629. * as well as other properties needed by VHS that aren't included by the parser.
  25630. *
  25631. * @param {Object} master
  25632. * Master manifest object
  25633. * @param {string} uri
  25634. * The source URI
  25635. * @param {function} createGroupID
  25636. * A function to determine how to create the groupID for mediaGroups
  25637. */
  25638. var addPropertiesToMaster = function addPropertiesToMaster(master, uri, createGroupID) {
  25639. if (createGroupID === void 0) {
  25640. createGroupID = groupID;
  25641. }
  25642. master.uri = uri;
  25643. for (var i = 0; i < master.playlists.length; i++) {
  25644. if (!master.playlists[i].uri) {
  25645. // Set up phony URIs for the playlists since playlists are referenced by their URIs
  25646. // throughout VHS, but some formats (e.g., DASH) don't have external URIs
  25647. // TODO: consider adding dummy URIs in mpd-parser
  25648. var phonyUri = "placeholder-uri-" + i;
  25649. master.playlists[i].uri = phonyUri;
  25650. }
  25651. }
  25652. var audioOnlyMaster = isAudioOnly(master);
  25653. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  25654. // add a playlist array under properties
  25655. if (!properties.playlists || !properties.playlists.length) {
  25656. // If the manifest is audio only and this media group does not have a uri, check
  25657. // if the media group is located in the main list of playlists. If it is, don't add
  25658. // placeholder properties as it shouldn't be considered an alternate audio track.
  25659. if (audioOnlyMaster && mediaType === 'AUDIO' && !properties.uri) {
  25660. for (var _i = 0; _i < master.playlists.length; _i++) {
  25661. var p = master.playlists[_i];
  25662. if (p.attributes && p.attributes.AUDIO && p.attributes.AUDIO === groupKey) {
  25663. return;
  25664. }
  25665. }
  25666. }
  25667. properties.playlists = [_extends({}, properties)];
  25668. }
  25669. properties.playlists.forEach(function (p, i) {
  25670. var groupId = createGroupID(mediaType, groupKey, labelKey, p);
  25671. var id = createPlaylistID(i, groupId);
  25672. if (p.uri) {
  25673. p.resolvedUri = p.resolvedUri || resolveUrl(master.uri, p.uri);
  25674. } else {
  25675. // DEPRECATED, this has been added to prevent a breaking change.
  25676. // previously we only ever had a single media group playlist, so
  25677. // we mark the first playlist uri without prepending the index as we used to
  25678. // ideally we would do all of the playlists the same way.
  25679. p.uri = i === 0 ? groupId : id; // don't resolve a placeholder uri to an absolute url, just use
  25680. // the placeholder again
  25681. p.resolvedUri = p.uri;
  25682. }
  25683. p.id = p.id || id; // add an empty attributes object, all playlists are
  25684. // expected to have this.
  25685. p.attributes = p.attributes || {}; // setup ID and URI references (URI for backwards compatibility)
  25686. master.playlists[p.id] = p;
  25687. master.playlists[p.uri] = p;
  25688. });
  25689. });
  25690. setupMediaPlaylists(master);
  25691. resolveMediaGroupUris(master);
  25692. };
  25693. var mergeOptions$2 = videojs.mergeOptions,
  25694. EventTarget$1 = videojs.EventTarget;
  25695. var addLLHLSQueryDirectives = function addLLHLSQueryDirectives(uri, media) {
  25696. if (media.endList || !media.serverControl) {
  25697. return uri;
  25698. }
  25699. var parameters = {};
  25700. if (media.serverControl.canBlockReload) {
  25701. var preloadSegment = media.preloadSegment; // next msn is a zero based value, length is not.
  25702. var nextMSN = media.mediaSequence + media.segments.length; // If preload segment has parts then it is likely
  25703. // that we are going to request a part of that preload segment.
  25704. // the logic below is used to determine that.
  25705. if (preloadSegment) {
  25706. var parts = preloadSegment.parts || []; // _HLS_part is a zero based index
  25707. var nextPart = getKnownPartCount(media) - 1; // if nextPart is > -1 and not equal to just the
  25708. // length of parts, then we know we had part preload hints
  25709. // and we need to add the _HLS_part= query
  25710. if (nextPart > -1 && nextPart !== parts.length - 1) {
  25711. // add existing parts to our preload hints
  25712. // eslint-disable-next-line
  25713. parameters._HLS_part = nextPart;
  25714. } // this if statement makes sure that we request the msn
  25715. // of the preload segment if:
  25716. // 1. the preload segment had parts (and was not yet a full segment)
  25717. // but was added to our segments array
  25718. // 2. the preload segment had preload hints for parts that are not in
  25719. // the manifest yet.
  25720. // in all other cases we want the segment after the preload segment
  25721. // which will be given by using media.segments.length because it is 1 based
  25722. // rather than 0 based.
  25723. if (nextPart > -1 || parts.length) {
  25724. nextMSN--;
  25725. }
  25726. } // add _HLS_msn= in front of any _HLS_part query
  25727. // eslint-disable-next-line
  25728. parameters._HLS_msn = nextMSN;
  25729. }
  25730. if (media.serverControl && media.serverControl.canSkipUntil) {
  25731. // add _HLS_skip= infront of all other queries.
  25732. // eslint-disable-next-line
  25733. parameters._HLS_skip = media.serverControl.canSkipDateranges ? 'v2' : 'YES';
  25734. }
  25735. if (Object.keys(parameters).length) {
  25736. var parsedUri = new window$1.URL(uri);
  25737. ['_HLS_skip', '_HLS_msn', '_HLS_part'].forEach(function (name) {
  25738. if (!parameters.hasOwnProperty(name)) {
  25739. return;
  25740. }
  25741. parsedUri.searchParams.set(name, parameters[name]);
  25742. });
  25743. uri = parsedUri.toString();
  25744. }
  25745. return uri;
  25746. };
  25747. /**
  25748. * Returns a new segment object with properties and
  25749. * the parts array merged.
  25750. *
  25751. * @param {Object} a the old segment
  25752. * @param {Object} b the new segment
  25753. *
  25754. * @return {Object} the merged segment
  25755. */
  25756. var updateSegment = function updateSegment(a, b) {
  25757. if (!a) {
  25758. return b;
  25759. }
  25760. var result = mergeOptions$2(a, b); // if only the old segment has preload hints
  25761. // and the new one does not, remove preload hints.
  25762. if (a.preloadHints && !b.preloadHints) {
  25763. delete result.preloadHints;
  25764. } // if only the old segment has parts
  25765. // then the parts are no longer valid
  25766. if (a.parts && !b.parts) {
  25767. delete result.parts; // if both segments have parts
  25768. // copy part propeties from the old segment
  25769. // to the new one.
  25770. } else if (a.parts && b.parts) {
  25771. for (var i = 0; i < b.parts.length; i++) {
  25772. if (a.parts && a.parts[i]) {
  25773. result.parts[i] = mergeOptions$2(a.parts[i], b.parts[i]);
  25774. }
  25775. }
  25776. } // set skipped to false for segments that have
  25777. // have had information merged from the old segment.
  25778. if (!a.skipped && b.skipped) {
  25779. result.skipped = false;
  25780. } // set preload to false for segments that have
  25781. // had information added in the new segment.
  25782. if (a.preload && !b.preload) {
  25783. result.preload = false;
  25784. }
  25785. return result;
  25786. };
  25787. /**
  25788. * Returns a new array of segments that is the result of merging
  25789. * properties from an older list of segments onto an updated
  25790. * list. No properties on the updated playlist will be ovewritten.
  25791. *
  25792. * @param {Array} original the outdated list of segments
  25793. * @param {Array} update the updated list of segments
  25794. * @param {number=} offset the index of the first update
  25795. * segment in the original segment list. For non-live playlists,
  25796. * this should always be zero and does not need to be
  25797. * specified. For live playlists, it should be the difference
  25798. * between the media sequence numbers in the original and updated
  25799. * playlists.
  25800. * @return {Array} a list of merged segment objects
  25801. */
  25802. var updateSegments = function updateSegments(original, update, offset) {
  25803. var oldSegments = original.slice();
  25804. var newSegments = update.slice();
  25805. offset = offset || 0;
  25806. var result = [];
  25807. var currentMap;
  25808. for (var newIndex = 0; newIndex < newSegments.length; newIndex++) {
  25809. var oldSegment = oldSegments[newIndex + offset];
  25810. var newSegment = newSegments[newIndex];
  25811. if (oldSegment) {
  25812. currentMap = oldSegment.map || currentMap;
  25813. result.push(updateSegment(oldSegment, newSegment));
  25814. } else {
  25815. // carry over map to new segment if it is missing
  25816. if (currentMap && !newSegment.map) {
  25817. newSegment.map = currentMap;
  25818. }
  25819. result.push(newSegment);
  25820. }
  25821. }
  25822. return result;
  25823. };
  25824. var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
  25825. // preloadSegment will not have a uri at all
  25826. // as the segment isn't actually in the manifest yet, only parts
  25827. if (!segment.resolvedUri && segment.uri) {
  25828. segment.resolvedUri = resolveUrl(baseUri, segment.uri);
  25829. }
  25830. if (segment.key && !segment.key.resolvedUri) {
  25831. segment.key.resolvedUri = resolveUrl(baseUri, segment.key.uri);
  25832. }
  25833. if (segment.map && !segment.map.resolvedUri) {
  25834. segment.map.resolvedUri = resolveUrl(baseUri, segment.map.uri);
  25835. }
  25836. if (segment.map && segment.map.key && !segment.map.key.resolvedUri) {
  25837. segment.map.key.resolvedUri = resolveUrl(baseUri, segment.map.key.uri);
  25838. }
  25839. if (segment.parts && segment.parts.length) {
  25840. segment.parts.forEach(function (p) {
  25841. if (p.resolvedUri) {
  25842. return;
  25843. }
  25844. p.resolvedUri = resolveUrl(baseUri, p.uri);
  25845. });
  25846. }
  25847. if (segment.preloadHints && segment.preloadHints.length) {
  25848. segment.preloadHints.forEach(function (p) {
  25849. if (p.resolvedUri) {
  25850. return;
  25851. }
  25852. p.resolvedUri = resolveUrl(baseUri, p.uri);
  25853. });
  25854. }
  25855. };
  25856. var getAllSegments = function getAllSegments(media) {
  25857. var segments = media.segments || [];
  25858. var preloadSegment = media.preloadSegment; // a preloadSegment with only preloadHints is not currently
  25859. // a usable segment, only include a preloadSegment that has
  25860. // parts.
  25861. if (preloadSegment && preloadSegment.parts && preloadSegment.parts.length) {
  25862. // if preloadHints has a MAP that means that the
  25863. // init segment is going to change. We cannot use any of the parts
  25864. // from this preload segment.
  25865. if (preloadSegment.preloadHints) {
  25866. for (var i = 0; i < preloadSegment.preloadHints.length; i++) {
  25867. if (preloadSegment.preloadHints[i].type === 'MAP') {
  25868. return segments;
  25869. }
  25870. }
  25871. } // set the duration for our preload segment to target duration.
  25872. preloadSegment.duration = media.targetDuration;
  25873. preloadSegment.preload = true;
  25874. segments.push(preloadSegment);
  25875. }
  25876. return segments;
  25877. }; // consider the playlist unchanged if the playlist object is the same or
  25878. // the number of segments is equal, the media sequence number is unchanged,
  25879. // and this playlist hasn't become the end of the playlist
  25880. var isPlaylistUnchanged = function isPlaylistUnchanged(a, b) {
  25881. return a === b || a.segments && b.segments && a.segments.length === b.segments.length && a.endList === b.endList && a.mediaSequence === b.mediaSequence && a.preloadSegment === b.preloadSegment;
  25882. };
  25883. /**
  25884. * Returns a new master playlist that is the result of merging an
  25885. * updated media playlist into the original version. If the
  25886. * updated media playlist does not match any of the playlist
  25887. * entries in the original master playlist, null is returned.
  25888. *
  25889. * @param {Object} master a parsed master M3U8 object
  25890. * @param {Object} media a parsed media M3U8 object
  25891. * @return {Object} a new object that represents the original
  25892. * master playlist with the updated media playlist merged in, or
  25893. * null if the merge produced no change.
  25894. */
  25895. var updateMaster$1 = function updateMaster(master, newMedia, unchangedCheck) {
  25896. if (unchangedCheck === void 0) {
  25897. unchangedCheck = isPlaylistUnchanged;
  25898. }
  25899. var result = mergeOptions$2(master, {});
  25900. var oldMedia = result.playlists[newMedia.id];
  25901. if (!oldMedia) {
  25902. return null;
  25903. }
  25904. if (unchangedCheck(oldMedia, newMedia)) {
  25905. return null;
  25906. }
  25907. newMedia.segments = getAllSegments(newMedia);
  25908. var mergedPlaylist = mergeOptions$2(oldMedia, newMedia); // always use the new media's preload segment
  25909. if (mergedPlaylist.preloadSegment && !newMedia.preloadSegment) {
  25910. delete mergedPlaylist.preloadSegment;
  25911. } // if the update could overlap existing segment information, merge the two segment lists
  25912. if (oldMedia.segments) {
  25913. if (newMedia.skip) {
  25914. newMedia.segments = newMedia.segments || []; // add back in objects for skipped segments, so that we merge
  25915. // old properties into the new segments
  25916. for (var i = 0; i < newMedia.skip.skippedSegments; i++) {
  25917. newMedia.segments.unshift({
  25918. skipped: true
  25919. });
  25920. }
  25921. }
  25922. mergedPlaylist.segments = updateSegments(oldMedia.segments, newMedia.segments, newMedia.mediaSequence - oldMedia.mediaSequence);
  25923. } // resolve any segment URIs to prevent us from having to do it later
  25924. mergedPlaylist.segments.forEach(function (segment) {
  25925. resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
  25926. }); // TODO Right now in the playlists array there are two references to each playlist, one
  25927. // that is referenced by index, and one by URI. The index reference may no longer be
  25928. // necessary.
  25929. for (var _i = 0; _i < result.playlists.length; _i++) {
  25930. if (result.playlists[_i].id === newMedia.id) {
  25931. result.playlists[_i] = mergedPlaylist;
  25932. }
  25933. }
  25934. result.playlists[newMedia.id] = mergedPlaylist; // URI reference added for backwards compatibility
  25935. result.playlists[newMedia.uri] = mergedPlaylist; // update media group playlist references.
  25936. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  25937. if (!properties.playlists) {
  25938. return;
  25939. }
  25940. for (var _i2 = 0; _i2 < properties.playlists.length; _i2++) {
  25941. if (newMedia.id === properties.playlists[_i2].id) {
  25942. properties.playlists[_i2] = mergedPlaylist;
  25943. }
  25944. }
  25945. });
  25946. return result;
  25947. };
  25948. /**
  25949. * Calculates the time to wait before refreshing a live playlist
  25950. *
  25951. * @param {Object} media
  25952. * The current media
  25953. * @param {boolean} update
  25954. * True if there were any updates from the last refresh, false otherwise
  25955. * @return {number}
  25956. * The time in ms to wait before refreshing the live playlist
  25957. */
  25958. var refreshDelay = function refreshDelay(media, update) {
  25959. var segments = media.segments || [];
  25960. var lastSegment = segments[segments.length - 1];
  25961. var lastPart = lastSegment && lastSegment.parts && lastSegment.parts[lastSegment.parts.length - 1];
  25962. var lastDuration = lastPart && lastPart.duration || lastSegment && lastSegment.duration;
  25963. if (update && lastDuration) {
  25964. return lastDuration * 1000;
  25965. } // if the playlist is unchanged since the last reload or last segment duration
  25966. // cannot be determined, try again after half the target duration
  25967. return (media.partTargetDuration || media.targetDuration || 10) * 500;
  25968. };
  25969. /**
  25970. * Load a playlist from a remote location
  25971. *
  25972. * @class PlaylistLoader
  25973. * @extends Stream
  25974. * @param {string|Object} src url or object of manifest
  25975. * @param {boolean} withCredentials the withCredentials xhr option
  25976. * @class
  25977. */
  25978. var PlaylistLoader = /*#__PURE__*/function (_EventTarget) {
  25979. _inheritsLoose(PlaylistLoader, _EventTarget);
  25980. function PlaylistLoader(src, vhs, options) {
  25981. var _this;
  25982. if (options === void 0) {
  25983. options = {};
  25984. }
  25985. _this = _EventTarget.call(this) || this;
  25986. if (!src) {
  25987. throw new Error('A non-empty playlist URL or object is required');
  25988. }
  25989. _this.logger_ = logger('PlaylistLoader');
  25990. var _options = options,
  25991. _options$withCredenti = _options.withCredentials,
  25992. withCredentials = _options$withCredenti === void 0 ? false : _options$withCredenti,
  25993. _options$handleManife = _options.handleManifestRedirects,
  25994. handleManifestRedirects = _options$handleManife === void 0 ? false : _options$handleManife;
  25995. _this.src = src;
  25996. _this.vhs_ = vhs;
  25997. _this.withCredentials = withCredentials;
  25998. _this.handleManifestRedirects = handleManifestRedirects;
  25999. var vhsOptions = vhs.options_;
  26000. _this.customTagParsers = vhsOptions && vhsOptions.customTagParsers || [];
  26001. _this.customTagMappers = vhsOptions && vhsOptions.customTagMappers || [];
  26002. _this.experimentalLLHLS = vhsOptions && vhsOptions.experimentalLLHLS || false; // force experimentalLLHLS for IE 11
  26003. if (videojs.browser.IE_VERSION) {
  26004. _this.experimentalLLHLS = false;
  26005. } // initialize the loader state
  26006. _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
  26007. _this.handleMediaupdatetimeout_ = _this.handleMediaupdatetimeout_.bind(_assertThisInitialized(_this));
  26008. _this.on('mediaupdatetimeout', _this.handleMediaupdatetimeout_);
  26009. return _this;
  26010. }
  26011. var _proto = PlaylistLoader.prototype;
  26012. _proto.handleMediaupdatetimeout_ = function handleMediaupdatetimeout_() {
  26013. var _this2 = this;
  26014. if (this.state !== 'HAVE_METADATA') {
  26015. // only refresh the media playlist if no other activity is going on
  26016. return;
  26017. }
  26018. var media = this.media();
  26019. var uri = resolveUrl(this.master.uri, media.uri);
  26020. if (this.experimentalLLHLS) {
  26021. uri = addLLHLSQueryDirectives(uri, media);
  26022. }
  26023. this.state = 'HAVE_CURRENT_METADATA';
  26024. this.request = this.vhs_.xhr({
  26025. uri: uri,
  26026. withCredentials: this.withCredentials
  26027. }, function (error, req) {
  26028. // disposed
  26029. if (!_this2.request) {
  26030. return;
  26031. }
  26032. if (error) {
  26033. return _this2.playlistRequestError(_this2.request, _this2.media(), 'HAVE_METADATA');
  26034. }
  26035. _this2.haveMetadata({
  26036. playlistString: _this2.request.responseText,
  26037. url: _this2.media().uri,
  26038. id: _this2.media().id
  26039. });
  26040. });
  26041. };
  26042. _proto.playlistRequestError = function playlistRequestError(xhr, playlist, startingState) {
  26043. var uri = playlist.uri,
  26044. id = playlist.id; // any in-flight request is now finished
  26045. this.request = null;
  26046. if (startingState) {
  26047. this.state = startingState;
  26048. }
  26049. this.error = {
  26050. playlist: this.master.playlists[id],
  26051. status: xhr.status,
  26052. message: "HLS playlist request error at URL: " + uri + ".",
  26053. responseText: xhr.responseText,
  26054. code: xhr.status >= 500 ? 4 : 2
  26055. };
  26056. this.trigger('error');
  26057. };
  26058. _proto.parseManifest_ = function parseManifest_(_ref) {
  26059. var _this3 = this;
  26060. var url = _ref.url,
  26061. manifestString = _ref.manifestString;
  26062. return parseManifest({
  26063. onwarn: function onwarn(_ref2) {
  26064. var message = _ref2.message;
  26065. return _this3.logger_("m3u8-parser warn for " + url + ": " + message);
  26066. },
  26067. oninfo: function oninfo(_ref3) {
  26068. var message = _ref3.message;
  26069. return _this3.logger_("m3u8-parser info for " + url + ": " + message);
  26070. },
  26071. manifestString: manifestString,
  26072. customTagParsers: this.customTagParsers,
  26073. customTagMappers: this.customTagMappers,
  26074. experimentalLLHLS: this.experimentalLLHLS
  26075. });
  26076. }
  26077. /**
  26078. * Update the playlist loader's state in response to a new or updated playlist.
  26079. *
  26080. * @param {string} [playlistString]
  26081. * Playlist string (if playlistObject is not provided)
  26082. * @param {Object} [playlistObject]
  26083. * Playlist object (if playlistString is not provided)
  26084. * @param {string} url
  26085. * URL of playlist
  26086. * @param {string} id
  26087. * ID to use for playlist
  26088. */
  26089. ;
  26090. _proto.haveMetadata = function haveMetadata(_ref4) {
  26091. var playlistString = _ref4.playlistString,
  26092. playlistObject = _ref4.playlistObject,
  26093. url = _ref4.url,
  26094. id = _ref4.id; // any in-flight request is now finished
  26095. this.request = null;
  26096. this.state = 'HAVE_METADATA';
  26097. var playlist = playlistObject || this.parseManifest_({
  26098. url: url,
  26099. manifestString: playlistString
  26100. });
  26101. playlist.lastRequest = Date.now();
  26102. setupMediaPlaylist({
  26103. playlist: playlist,
  26104. uri: url,
  26105. id: id
  26106. }); // merge this playlist into the master
  26107. var update = updateMaster$1(this.master, playlist);
  26108. this.targetDuration = playlist.partTargetDuration || playlist.targetDuration;
  26109. this.pendingMedia_ = null;
  26110. if (update) {
  26111. this.master = update;
  26112. this.media_ = this.master.playlists[id];
  26113. } else {
  26114. this.trigger('playlistunchanged');
  26115. }
  26116. this.updateMediaUpdateTimeout_(refreshDelay(this.media(), !!update));
  26117. this.trigger('loadedplaylist');
  26118. }
  26119. /**
  26120. * Abort any outstanding work and clean up.
  26121. */
  26122. ;
  26123. _proto.dispose = function dispose() {
  26124. this.trigger('dispose');
  26125. this.stopRequest();
  26126. window$1.clearTimeout(this.mediaUpdateTimeout);
  26127. window$1.clearTimeout(this.finalRenditionTimeout);
  26128. this.off();
  26129. };
  26130. _proto.stopRequest = function stopRequest() {
  26131. if (this.request) {
  26132. var oldRequest = this.request;
  26133. this.request = null;
  26134. oldRequest.onreadystatechange = null;
  26135. oldRequest.abort();
  26136. }
  26137. }
  26138. /**
  26139. * When called without any arguments, returns the currently
  26140. * active media playlist. When called with a single argument,
  26141. * triggers the playlist loader to asynchronously switch to the
  26142. * specified media playlist. Calling this method while the
  26143. * loader is in the HAVE_NOTHING causes an error to be emitted
  26144. * but otherwise has no effect.
  26145. *
  26146. * @param {Object=} playlist the parsed media playlist
  26147. * object to switch to
  26148. * @param {boolean=} shouldDelay whether we should delay the request by half target duration
  26149. *
  26150. * @return {Playlist} the current loaded media
  26151. */
  26152. ;
  26153. _proto.media = function media(playlist, shouldDelay) {
  26154. var _this4 = this; // getter
  26155. if (!playlist) {
  26156. return this.media_;
  26157. } // setter
  26158. if (this.state === 'HAVE_NOTHING') {
  26159. throw new Error('Cannot switch media playlist from ' + this.state);
  26160. } // find the playlist object if the target playlist has been
  26161. // specified by URI
  26162. if (typeof playlist === 'string') {
  26163. if (!this.master.playlists[playlist]) {
  26164. throw new Error('Unknown playlist URI: ' + playlist);
  26165. }
  26166. playlist = this.master.playlists[playlist];
  26167. }
  26168. window$1.clearTimeout(this.finalRenditionTimeout);
  26169. if (shouldDelay) {
  26170. var delay = (playlist.partTargetDuration || playlist.targetDuration) / 2 * 1000 || 5 * 1000;
  26171. this.finalRenditionTimeout = window$1.setTimeout(this.media.bind(this, playlist, false), delay);
  26172. return;
  26173. }
  26174. var startingState = this.state;
  26175. var mediaChange = !this.media_ || playlist.id !== this.media_.id;
  26176. var masterPlaylistRef = this.master.playlists[playlist.id]; // switch to fully loaded playlists immediately
  26177. if (masterPlaylistRef && masterPlaylistRef.endList || // handle the case of a playlist object (e.g., if using vhs-json with a resolved
  26178. // media playlist or, for the case of demuxed audio, a resolved audio media group)
  26179. playlist.endList && playlist.segments.length) {
  26180. // abort outstanding playlist requests
  26181. if (this.request) {
  26182. this.request.onreadystatechange = null;
  26183. this.request.abort();
  26184. this.request = null;
  26185. }
  26186. this.state = 'HAVE_METADATA';
  26187. this.media_ = playlist; // trigger media change if the active media has been updated
  26188. if (mediaChange) {
  26189. this.trigger('mediachanging');
  26190. if (startingState === 'HAVE_MASTER') {
  26191. // The initial playlist was a master manifest, and the first media selected was
  26192. // also provided (in the form of a resolved playlist object) as part of the
  26193. // source object (rather than just a URL). Therefore, since the media playlist
  26194. // doesn't need to be requested, loadedmetadata won't trigger as part of the
  26195. // normal flow, and needs an explicit trigger here.
  26196. this.trigger('loadedmetadata');
  26197. } else {
  26198. this.trigger('mediachange');
  26199. }
  26200. }
  26201. return;
  26202. } // We update/set the timeout here so that live playlists
  26203. // that are not a media change will "start" the loader as expected.
  26204. // We expect that this function will start the media update timeout
  26205. // cycle again. This also prevents a playlist switch failure from
  26206. // causing us to stall during live.
  26207. this.updateMediaUpdateTimeout_(refreshDelay(playlist, true)); // switching to the active playlist is a no-op
  26208. if (!mediaChange) {
  26209. return;
  26210. }
  26211. this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
  26212. if (this.request) {
  26213. if (playlist.resolvedUri === this.request.url) {
  26214. // requesting to switch to the same playlist multiple times
  26215. // has no effect after the first
  26216. return;
  26217. }
  26218. this.request.onreadystatechange = null;
  26219. this.request.abort();
  26220. this.request = null;
  26221. } // request the new playlist
  26222. if (this.media_) {
  26223. this.trigger('mediachanging');
  26224. }
  26225. this.pendingMedia_ = playlist;
  26226. this.request = this.vhs_.xhr({
  26227. uri: playlist.resolvedUri,
  26228. withCredentials: this.withCredentials
  26229. }, function (error, req) {
  26230. // disposed
  26231. if (!_this4.request) {
  26232. return;
  26233. }
  26234. playlist.lastRequest = Date.now();
  26235. playlist.resolvedUri = resolveManifestRedirect(_this4.handleManifestRedirects, playlist.resolvedUri, req);
  26236. if (error) {
  26237. return _this4.playlistRequestError(_this4.request, playlist, startingState);
  26238. }
  26239. _this4.haveMetadata({
  26240. playlistString: req.responseText,
  26241. url: playlist.uri,
  26242. id: playlist.id
  26243. }); // fire loadedmetadata the first time a media playlist is loaded
  26244. if (startingState === 'HAVE_MASTER') {
  26245. _this4.trigger('loadedmetadata');
  26246. } else {
  26247. _this4.trigger('mediachange');
  26248. }
  26249. });
  26250. }
  26251. /**
  26252. * pause loading of the playlist
  26253. */
  26254. ;
  26255. _proto.pause = function pause() {
  26256. if (this.mediaUpdateTimeout) {
  26257. window$1.clearTimeout(this.mediaUpdateTimeout);
  26258. this.mediaUpdateTimeout = null;
  26259. }
  26260. this.stopRequest();
  26261. if (this.state === 'HAVE_NOTHING') {
  26262. // If we pause the loader before any data has been retrieved, its as if we never
  26263. // started, so reset to an unstarted state.
  26264. this.started = false;
  26265. } // Need to restore state now that no activity is happening
  26266. if (this.state === 'SWITCHING_MEDIA') {
  26267. // if the loader was in the process of switching media, it should either return to
  26268. // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
  26269. // playlist yet. This is determined by the existence of loader.media_
  26270. if (this.media_) {
  26271. this.state = 'HAVE_METADATA';
  26272. } else {
  26273. this.state = 'HAVE_MASTER';
  26274. }
  26275. } else if (this.state === 'HAVE_CURRENT_METADATA') {
  26276. this.state = 'HAVE_METADATA';
  26277. }
  26278. }
  26279. /**
  26280. * start loading of the playlist
  26281. */
  26282. ;
  26283. _proto.load = function load(shouldDelay) {
  26284. var _this5 = this;
  26285. if (this.mediaUpdateTimeout) {
  26286. window$1.clearTimeout(this.mediaUpdateTimeout);
  26287. this.mediaUpdateTimeout = null;
  26288. }
  26289. var media = this.media();
  26290. if (shouldDelay) {
  26291. var delay = media ? (media.partTargetDuration || media.targetDuration) / 2 * 1000 : 5 * 1000;
  26292. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  26293. _this5.mediaUpdateTimeout = null;
  26294. _this5.load();
  26295. }, delay);
  26296. return;
  26297. }
  26298. if (!this.started) {
  26299. this.start();
  26300. return;
  26301. }
  26302. if (media && !media.endList) {
  26303. this.trigger('mediaupdatetimeout');
  26304. } else {
  26305. this.trigger('loadedplaylist');
  26306. }
  26307. };
  26308. _proto.updateMediaUpdateTimeout_ = function updateMediaUpdateTimeout_(delay) {
  26309. var _this6 = this;
  26310. if (this.mediaUpdateTimeout) {
  26311. window$1.clearTimeout(this.mediaUpdateTimeout);
  26312. this.mediaUpdateTimeout = null;
  26313. } // we only have use mediaupdatetimeout for live playlists.
  26314. if (!this.media() || this.media().endList) {
  26315. return;
  26316. }
  26317. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  26318. _this6.mediaUpdateTimeout = null;
  26319. _this6.trigger('mediaupdatetimeout');
  26320. _this6.updateMediaUpdateTimeout_(delay);
  26321. }, delay);
  26322. }
  26323. /**
  26324. * start loading of the playlist
  26325. */
  26326. ;
  26327. _proto.start = function start() {
  26328. var _this7 = this;
  26329. this.started = true;
  26330. if (typeof this.src === 'object') {
  26331. // in the case of an entirely constructed manifest object (meaning there's no actual
  26332. // manifest on a server), default the uri to the page's href
  26333. if (!this.src.uri) {
  26334. this.src.uri = window$1.location.href;
  26335. } // resolvedUri is added on internally after the initial request. Since there's no
  26336. // request for pre-resolved manifests, add on resolvedUri here.
  26337. this.src.resolvedUri = this.src.uri; // Since a manifest object was passed in as the source (instead of a URL), the first
  26338. // request can be skipped (since the top level of the manifest, at a minimum, is
  26339. // already available as a parsed manifest object). However, if the manifest object
  26340. // represents a master playlist, some media playlists may need to be resolved before
  26341. // the starting segment list is available. Therefore, go directly to setup of the
  26342. // initial playlist, and let the normal flow continue from there.
  26343. //
  26344. // Note that the call to setup is asynchronous, as other sections of VHS may assume
  26345. // that the first request is asynchronous.
  26346. setTimeout(function () {
  26347. _this7.setupInitialPlaylist(_this7.src);
  26348. }, 0);
  26349. return;
  26350. } // request the specified URL
  26351. this.request = this.vhs_.xhr({
  26352. uri: this.src,
  26353. withCredentials: this.withCredentials
  26354. }, function (error, req) {
  26355. // disposed
  26356. if (!_this7.request) {
  26357. return;
  26358. } // clear the loader's request reference
  26359. _this7.request = null;
  26360. if (error) {
  26361. _this7.error = {
  26362. status: req.status,
  26363. message: "HLS playlist request error at URL: " + _this7.src + ".",
  26364. responseText: req.responseText,
  26365. // MEDIA_ERR_NETWORK
  26366. code: 2
  26367. };
  26368. if (_this7.state === 'HAVE_NOTHING') {
  26369. _this7.started = false;
  26370. }
  26371. return _this7.trigger('error');
  26372. }
  26373. _this7.src = resolveManifestRedirect(_this7.handleManifestRedirects, _this7.src, req);
  26374. var manifest = _this7.parseManifest_({
  26375. manifestString: req.responseText,
  26376. url: _this7.src
  26377. });
  26378. _this7.setupInitialPlaylist(manifest);
  26379. });
  26380. };
  26381. _proto.srcUri = function srcUri() {
  26382. return typeof this.src === 'string' ? this.src : this.src.uri;
  26383. }
  26384. /**
  26385. * Given a manifest object that's either a master or media playlist, trigger the proper
  26386. * events and set the state of the playlist loader.
  26387. *
  26388. * If the manifest object represents a master playlist, `loadedplaylist` will be
  26389. * triggered to allow listeners to select a playlist. If none is selected, the loader
  26390. * will default to the first one in the playlists array.
  26391. *
  26392. * If the manifest object represents a media playlist, `loadedplaylist` will be
  26393. * triggered followed by `loadedmetadata`, as the only available playlist is loaded.
  26394. *
  26395. * In the case of a media playlist, a master playlist object wrapper with one playlist
  26396. * will be created so that all logic can handle playlists in the same fashion (as an
  26397. * assumed manifest object schema).
  26398. *
  26399. * @param {Object} manifest
  26400. * The parsed manifest object
  26401. */
  26402. ;
  26403. _proto.setupInitialPlaylist = function setupInitialPlaylist(manifest) {
  26404. this.state = 'HAVE_MASTER';
  26405. if (manifest.playlists) {
  26406. this.master = manifest;
  26407. addPropertiesToMaster(this.master, this.srcUri()); // If the initial master playlist has playlists wtih segments already resolved,
  26408. // then resolve URIs in advance, as they are usually done after a playlist request,
  26409. // which may not happen if the playlist is resolved.
  26410. manifest.playlists.forEach(function (playlist) {
  26411. playlist.segments = getAllSegments(playlist);
  26412. playlist.segments.forEach(function (segment) {
  26413. resolveSegmentUris(segment, playlist.resolvedUri);
  26414. });
  26415. });
  26416. this.trigger('loadedplaylist');
  26417. if (!this.request) {
  26418. // no media playlist was specifically selected so start
  26419. // from the first listed one
  26420. this.media(this.master.playlists[0]);
  26421. }
  26422. return;
  26423. } // In order to support media playlists passed in as vhs-json, the case where the uri
  26424. // is not provided as part of the manifest should be considered, and an appropriate
  26425. // default used.
  26426. var uri = this.srcUri() || window$1.location.href;
  26427. this.master = masterForMedia(manifest, uri);
  26428. this.haveMetadata({
  26429. playlistObject: manifest,
  26430. url: uri,
  26431. id: this.master.playlists[0].id
  26432. });
  26433. this.trigger('loadedmetadata');
  26434. };
  26435. return PlaylistLoader;
  26436. }(EventTarget$1);
  26437. /**
  26438. * @file xhr.js
  26439. */
  26440. var videojsXHR = videojs.xhr,
  26441. mergeOptions$1 = videojs.mergeOptions;
  26442. var callbackWrapper = function callbackWrapper(request, error, response, callback) {
  26443. var reqResponse = request.responseType === 'arraybuffer' ? request.response : request.responseText;
  26444. if (!error && reqResponse) {
  26445. request.responseTime = Date.now();
  26446. request.roundTripTime = request.responseTime - request.requestTime;
  26447. request.bytesReceived = reqResponse.byteLength || reqResponse.length;
  26448. if (!request.bandwidth) {
  26449. request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
  26450. }
  26451. }
  26452. if (response.headers) {
  26453. request.responseHeaders = response.headers;
  26454. } // videojs.xhr now uses a specific code on the error
  26455. // object to signal that a request has timed out instead
  26456. // of setting a boolean on the request object
  26457. if (error && error.code === 'ETIMEDOUT') {
  26458. request.timedout = true;
  26459. } // videojs.xhr no longer considers status codes outside of 200 and 0
  26460. // (for file uris) to be errors, but the old XHR did, so emulate that
  26461. // behavior. Status 206 may be used in response to byterange requests.
  26462. if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
  26463. error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
  26464. }
  26465. callback(error, request);
  26466. };
  26467. var xhrFactory = function xhrFactory() {
  26468. var xhr = function XhrFunction(options, callback) {
  26469. // Add a default timeout
  26470. options = mergeOptions$1({
  26471. timeout: 45e3
  26472. }, options); // Allow an optional user-specified function to modify the option
  26473. // object before we construct the xhr request
  26474. var beforeRequest = XhrFunction.beforeRequest || videojs.Vhs.xhr.beforeRequest;
  26475. if (beforeRequest && typeof beforeRequest === 'function') {
  26476. var newOptions = beforeRequest(options);
  26477. if (newOptions) {
  26478. options = newOptions;
  26479. }
  26480. } // Use the standard videojs.xhr() method unless `videojs.Vhs.xhr` has been overriden
  26481. // TODO: switch back to videojs.Vhs.xhr.name === 'XhrFunction' when we drop IE11
  26482. var xhrMethod = videojs.Vhs.xhr.original === true ? videojsXHR : videojs.Vhs.xhr;
  26483. var request = xhrMethod(options, function (error, response) {
  26484. return callbackWrapper(request, error, response, callback);
  26485. });
  26486. var originalAbort = request.abort;
  26487. request.abort = function () {
  26488. request.aborted = true;
  26489. return originalAbort.apply(request, arguments);
  26490. };
  26491. request.uri = options.uri;
  26492. request.requestTime = Date.now();
  26493. return request;
  26494. };
  26495. xhr.original = true;
  26496. return xhr;
  26497. };
  26498. /**
  26499. * Turns segment byterange into a string suitable for use in
  26500. * HTTP Range requests
  26501. *
  26502. * @param {Object} byterange - an object with two values defining the start and end
  26503. * of a byte-range
  26504. */
  26505. var byterangeStr = function byterangeStr(byterange) {
  26506. // `byterangeEnd` is one less than `offset + length` because the HTTP range
  26507. // header uses inclusive ranges
  26508. var byterangeEnd;
  26509. var byterangeStart = byterange.offset;
  26510. if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
  26511. byterangeEnd = window$1.BigInt(byterange.offset) + window$1.BigInt(byterange.length) - window$1.BigInt(1);
  26512. } else {
  26513. byterangeEnd = byterange.offset + byterange.length - 1;
  26514. }
  26515. return 'bytes=' + byterangeStart + '-' + byterangeEnd;
  26516. };
  26517. /**
  26518. * Defines headers for use in the xhr request for a particular segment.
  26519. *
  26520. * @param {Object} segment - a simplified copy of the segmentInfo object
  26521. * from SegmentLoader
  26522. */
  26523. var segmentXhrHeaders = function segmentXhrHeaders(segment) {
  26524. var headers = {};
  26525. if (segment.byterange) {
  26526. headers.Range = byterangeStr(segment.byterange);
  26527. }
  26528. return headers;
  26529. };
  26530. /**
  26531. * @file bin-utils.js
  26532. */
  26533. /**
  26534. * convert a TimeRange to text
  26535. *
  26536. * @param {TimeRange} range the timerange to use for conversion
  26537. * @param {number} i the iterator on the range to convert
  26538. * @return {string} the range in string format
  26539. */
  26540. var textRange = function textRange(range, i) {
  26541. return range.start(i) + '-' + range.end(i);
  26542. };
  26543. /**
  26544. * format a number as hex string
  26545. *
  26546. * @param {number} e The number
  26547. * @param {number} i the iterator
  26548. * @return {string} the hex formatted number as a string
  26549. */
  26550. var formatHexString = function formatHexString(e, i) {
  26551. var value = e.toString(16);
  26552. return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
  26553. };
  26554. var formatAsciiString = function formatAsciiString(e) {
  26555. if (e >= 0x20 && e < 0x7e) {
  26556. return String.fromCharCode(e);
  26557. }
  26558. return '.';
  26559. };
  26560. /**
  26561. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  26562. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  26563. *
  26564. * @param {Object} message
  26565. * Object of properties and values to send to the web worker
  26566. * @return {Object}
  26567. * Modified message with TypedArray values expanded
  26568. * @function createTransferableMessage
  26569. */
  26570. var createTransferableMessage = function createTransferableMessage(message) {
  26571. var transferable = {};
  26572. Object.keys(message).forEach(function (key) {
  26573. var value = message[key];
  26574. if (isArrayBufferView(value)) {
  26575. transferable[key] = {
  26576. bytes: value.buffer,
  26577. byteOffset: value.byteOffset,
  26578. byteLength: value.byteLength
  26579. };
  26580. } else {
  26581. transferable[key] = value;
  26582. }
  26583. });
  26584. return transferable;
  26585. };
  26586. /**
  26587. * Returns a unique string identifier for a media initialization
  26588. * segment.
  26589. *
  26590. * @param {Object} initSegment
  26591. * the init segment object.
  26592. *
  26593. * @return {string} the generated init segment id
  26594. */
  26595. var initSegmentId = function initSegmentId(initSegment) {
  26596. var byterange = initSegment.byterange || {
  26597. length: Infinity,
  26598. offset: 0
  26599. };
  26600. return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
  26601. };
  26602. /**
  26603. * Returns a unique string identifier for a media segment key.
  26604. *
  26605. * @param {Object} key the encryption key
  26606. * @return {string} the unique id for the media segment key.
  26607. */
  26608. var segmentKeyId = function segmentKeyId(key) {
  26609. return key.resolvedUri;
  26610. };
  26611. /**
  26612. * utils to help dump binary data to the console
  26613. *
  26614. * @param {Array|TypedArray} data
  26615. * data to dump to a string
  26616. *
  26617. * @return {string} the data as a hex string.
  26618. */
  26619. var hexDump = function hexDump(data) {
  26620. var bytes = Array.prototype.slice.call(data);
  26621. var step = 16;
  26622. var result = '';
  26623. var hex;
  26624. var ascii;
  26625. for (var j = 0; j < bytes.length / step; j++) {
  26626. hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
  26627. ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
  26628. result += hex + ' ' + ascii + '\n';
  26629. }
  26630. return result;
  26631. };
  26632. var tagDump = function tagDump(_ref) {
  26633. var bytes = _ref.bytes;
  26634. return hexDump(bytes);
  26635. };
  26636. var textRanges = function textRanges(ranges) {
  26637. var result = '';
  26638. var i;
  26639. for (i = 0; i < ranges.length; i++) {
  26640. result += textRange(ranges, i) + ' ';
  26641. }
  26642. return result;
  26643. };
  26644. var utils = /*#__PURE__*/Object.freeze({
  26645. __proto__: null,
  26646. createTransferableMessage: createTransferableMessage,
  26647. initSegmentId: initSegmentId,
  26648. segmentKeyId: segmentKeyId,
  26649. hexDump: hexDump,
  26650. tagDump: tagDump,
  26651. textRanges: textRanges
  26652. }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
  26653. // 25% was arbitrarily chosen, and may need to be refined over time.
  26654. var SEGMENT_END_FUDGE_PERCENT = 0.25;
  26655. /**
  26656. * Converts a player time (any time that can be gotten/set from player.currentTime(),
  26657. * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
  26658. * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
  26659. *
  26660. * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
  26661. * point" (a point where we have a mapping from program time to player time, with player
  26662. * time being the post transmux start of the segment).
  26663. *
  26664. * For more details, see [this doc](../../docs/program-time-from-player-time.md).
  26665. *
  26666. * @param {number} playerTime the player time
  26667. * @param {Object} segment the segment which contains the player time
  26668. * @return {Date} program time
  26669. */
  26670. var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) {
  26671. if (!segment.dateTimeObject) {
  26672. // Can't convert without an "anchor point" for the program time (i.e., a time that can
  26673. // be used to map the start of a segment with a real world time).
  26674. return null;
  26675. }
  26676. var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
  26677. var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
  26678. var startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
  26679. var offsetFromSegmentStart = playerTime - startOfSegment;
  26680. return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
  26681. };
  26682. var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) {
  26683. return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
  26684. };
  26685. /**
  26686. * Finds a segment that contains the time requested given as an ISO-8601 string. The
  26687. * returned segment might be an estimate or an accurate match.
  26688. *
  26689. * @param {string} programTime The ISO-8601 programTime to find a match for
  26690. * @param {Object} playlist A playlist object to search within
  26691. */
  26692. var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) {
  26693. // Assumptions:
  26694. // - verifyProgramDateTimeTags has already been run
  26695. // - live streams have been started
  26696. var dateTimeObject;
  26697. try {
  26698. dateTimeObject = new Date(programTime);
  26699. } catch (e) {
  26700. return null;
  26701. }
  26702. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  26703. return null;
  26704. }
  26705. var segment = playlist.segments[0];
  26706. if (dateTimeObject < segment.dateTimeObject) {
  26707. // Requested time is before stream start.
  26708. return null;
  26709. }
  26710. for (var i = 0; i < playlist.segments.length - 1; i++) {
  26711. segment = playlist.segments[i];
  26712. var nextSegmentStart = playlist.segments[i + 1].dateTimeObject;
  26713. if (dateTimeObject < nextSegmentStart) {
  26714. break;
  26715. }
  26716. }
  26717. var lastSegment = playlist.segments[playlist.segments.length - 1];
  26718. var lastSegmentStart = lastSegment.dateTimeObject;
  26719. var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
  26720. var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
  26721. if (dateTimeObject > lastSegmentEnd) {
  26722. // Beyond the end of the stream, or our best guess of the end of the stream.
  26723. return null;
  26724. }
  26725. if (dateTimeObject > lastSegmentStart) {
  26726. segment = lastSegment;
  26727. }
  26728. return {
  26729. segment: segment,
  26730. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
  26731. // Although, given that all segments have accurate date time objects, the segment
  26732. // selected should be accurate, unless the video has been transmuxed at some point
  26733. // (determined by the presence of the videoTimingInfo object), the segment's "player
  26734. // time" (the start time in the player) can't be considered accurate.
  26735. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  26736. };
  26737. };
  26738. /**
  26739. * Finds a segment that contains the given player time(in seconds).
  26740. *
  26741. * @param {number} time The player time to find a match for
  26742. * @param {Object} playlist A playlist object to search within
  26743. */
  26744. var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) {
  26745. // Assumptions:
  26746. // - there will always be a segment.duration
  26747. // - we can start from zero
  26748. // - segments are in time order
  26749. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  26750. return null;
  26751. }
  26752. var segmentEnd = 0;
  26753. var segment;
  26754. for (var i = 0; i < playlist.segments.length; i++) {
  26755. segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
  26756. // should contain the most accurate values we have for the segment's player times.
  26757. //
  26758. // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
  26759. // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
  26760. // calculate an end value.
  26761. segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
  26762. if (time <= segmentEnd) {
  26763. break;
  26764. }
  26765. }
  26766. var lastSegment = playlist.segments[playlist.segments.length - 1];
  26767. if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
  26768. // The time requested is beyond the stream end.
  26769. return null;
  26770. }
  26771. if (time > segmentEnd) {
  26772. // The time is within or beyond the last segment.
  26773. //
  26774. // Check to see if the time is beyond a reasonable guess of the end of the stream.
  26775. if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
  26776. // Technically, because the duration value is only an estimate, the time may still
  26777. // exist in the last segment, however, there isn't enough information to make even
  26778. // a reasonable estimate.
  26779. return null;
  26780. }
  26781. segment = lastSegment;
  26782. }
  26783. return {
  26784. segment: segment,
  26785. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
  26786. // Because videoTimingInfo is only set after transmux, it is the only way to get
  26787. // accurate timing values.
  26788. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  26789. };
  26790. };
  26791. /**
  26792. * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
  26793. * If the offset returned is positive, the programTime occurs after the
  26794. * comparisonTimestamp.
  26795. * If the offset is negative, the programTime occurs before the comparisonTimestamp.
  26796. *
  26797. * @param {string} comparisonTimeStamp An ISO-8601 timestamp to compare against
  26798. * @param {string} programTime The programTime as an ISO-8601 string
  26799. * @return {number} offset
  26800. */
  26801. var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) {
  26802. var segmentDateTime;
  26803. var programDateTime;
  26804. try {
  26805. segmentDateTime = new Date(comparisonTimeStamp);
  26806. programDateTime = new Date(programTime);
  26807. } catch (e) {// TODO handle error
  26808. }
  26809. var segmentTimeEpoch = segmentDateTime.getTime();
  26810. var programTimeEpoch = programDateTime.getTime();
  26811. return (programTimeEpoch - segmentTimeEpoch) / 1000;
  26812. };
  26813. /**
  26814. * Checks that all segments in this playlist have programDateTime tags.
  26815. *
  26816. * @param {Object} playlist A playlist object
  26817. */
  26818. var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) {
  26819. if (!playlist.segments || playlist.segments.length === 0) {
  26820. return false;
  26821. }
  26822. for (var i = 0; i < playlist.segments.length; i++) {
  26823. var segment = playlist.segments[i];
  26824. if (!segment.dateTimeObject) {
  26825. return false;
  26826. }
  26827. }
  26828. return true;
  26829. };
  26830. /**
  26831. * Returns the programTime of the media given a playlist and a playerTime.
  26832. * The playlist must have programDateTime tags for a programDateTime tag to be returned.
  26833. * If the segments containing the time requested have not been buffered yet, an estimate
  26834. * may be returned to the callback.
  26835. *
  26836. * @param {Object} args
  26837. * @param {Object} args.playlist A playlist object to search within
  26838. * @param {number} time A playerTime in seconds
  26839. * @param {Function} callback(err, programTime)
  26840. * @return {string} err.message A detailed error message
  26841. * @return {Object} programTime
  26842. * @return {number} programTime.mediaSeconds The streamTime in seconds
  26843. * @return {string} programTime.programDateTime The programTime as an ISO-8601 String
  26844. */
  26845. var getProgramTime = function getProgramTime(_ref) {
  26846. var playlist = _ref.playlist,
  26847. _ref$time = _ref.time,
  26848. time = _ref$time === void 0 ? undefined : _ref$time,
  26849. callback = _ref.callback;
  26850. if (!callback) {
  26851. throw new Error('getProgramTime: callback must be provided');
  26852. }
  26853. if (!playlist || time === undefined) {
  26854. return callback({
  26855. message: 'getProgramTime: playlist and time must be provided'
  26856. });
  26857. }
  26858. var matchedSegment = findSegmentForPlayerTime(time, playlist);
  26859. if (!matchedSegment) {
  26860. return callback({
  26861. message: 'valid programTime was not found'
  26862. });
  26863. }
  26864. if (matchedSegment.type === 'estimate') {
  26865. return callback({
  26866. message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
  26867. seekTime: matchedSegment.estimatedStart
  26868. });
  26869. }
  26870. var programTimeObject = {
  26871. mediaSeconds: time
  26872. };
  26873. var programTime = playerTimeToProgramTime(time, matchedSegment.segment);
  26874. if (programTime) {
  26875. programTimeObject.programDateTime = programTime.toISOString();
  26876. }
  26877. return callback(null, programTimeObject);
  26878. };
  26879. /**
  26880. * Seeks in the player to a time that matches the given programTime ISO-8601 string.
  26881. *
  26882. * @param {Object} args
  26883. * @param {string} args.programTime A programTime to seek to as an ISO-8601 String
  26884. * @param {Object} args.playlist A playlist to look within
  26885. * @param {number} args.retryCount The number of times to try for an accurate seek. Default is 2.
  26886. * @param {Function} args.seekTo A method to perform a seek
  26887. * @param {boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
  26888. * @param {Object} args.tech The tech to seek on
  26889. * @param {Function} args.callback(err, newTime) A callback to return the new time to
  26890. * @return {string} err.message A detailed error message
  26891. * @return {number} newTime The exact time that was seeked to in seconds
  26892. */
  26893. var seekToProgramTime = function seekToProgramTime(_ref2) {
  26894. var programTime = _ref2.programTime,
  26895. playlist = _ref2.playlist,
  26896. _ref2$retryCount = _ref2.retryCount,
  26897. retryCount = _ref2$retryCount === void 0 ? 2 : _ref2$retryCount,
  26898. seekTo = _ref2.seekTo,
  26899. _ref2$pauseAfterSeek = _ref2.pauseAfterSeek,
  26900. pauseAfterSeek = _ref2$pauseAfterSeek === void 0 ? true : _ref2$pauseAfterSeek,
  26901. tech = _ref2.tech,
  26902. callback = _ref2.callback;
  26903. if (!callback) {
  26904. throw new Error('seekToProgramTime: callback must be provided');
  26905. }
  26906. if (typeof programTime === 'undefined' || !playlist || !seekTo) {
  26907. return callback({
  26908. message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
  26909. });
  26910. }
  26911. if (!playlist.endList && !tech.hasStarted_) {
  26912. return callback({
  26913. message: 'player must be playing a live stream to start buffering'
  26914. });
  26915. }
  26916. if (!verifyProgramDateTimeTags(playlist)) {
  26917. return callback({
  26918. message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
  26919. });
  26920. }
  26921. var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
  26922. if (!matchedSegment) {
  26923. return callback({
  26924. message: programTime + " was not found in the stream"
  26925. });
  26926. }
  26927. var segment = matchedSegment.segment;
  26928. var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
  26929. if (matchedSegment.type === 'estimate') {
  26930. // we've run out of retries
  26931. if (retryCount === 0) {
  26932. return callback({
  26933. message: programTime + " is not buffered yet. Try again"
  26934. });
  26935. }
  26936. seekTo(matchedSegment.estimatedStart + mediaOffset);
  26937. tech.one('seeked', function () {
  26938. seekToProgramTime({
  26939. programTime: programTime,
  26940. playlist: playlist,
  26941. retryCount: retryCount - 1,
  26942. seekTo: seekTo,
  26943. pauseAfterSeek: pauseAfterSeek,
  26944. tech: tech,
  26945. callback: callback
  26946. });
  26947. });
  26948. return;
  26949. } // Since the segment.start value is determined from the buffered end or ending time
  26950. // of the prior segment, the seekToTime doesn't need to account for any transmuxer
  26951. // modifications.
  26952. var seekToTime = segment.start + mediaOffset;
  26953. var seekedCallback = function seekedCallback() {
  26954. return callback(null, tech.currentTime());
  26955. }; // listen for seeked event
  26956. tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
  26957. if (pauseAfterSeek) {
  26958. tech.pause();
  26959. }
  26960. seekTo(seekToTime);
  26961. }; // which will only happen if the request is complete.
  26962. var callbackOnCompleted = function callbackOnCompleted(request, cb) {
  26963. if (request.readyState === 4) {
  26964. return cb();
  26965. }
  26966. return;
  26967. };
  26968. var containerRequest = function containerRequest(uri, xhr, cb) {
  26969. var bytes = [];
  26970. var id3Offset;
  26971. var finished = false;
  26972. var endRequestAndCallback = function endRequestAndCallback(err, req, type, _bytes) {
  26973. req.abort();
  26974. finished = true;
  26975. return cb(err, req, type, _bytes);
  26976. };
  26977. var progressListener = function progressListener(error, request) {
  26978. if (finished) {
  26979. return;
  26980. }
  26981. if (error) {
  26982. return endRequestAndCallback(error, request, '', bytes);
  26983. } // grap the new part of content that was just downloaded
  26984. var newPart = request.responseText.substring(bytes && bytes.byteLength || 0, request.responseText.length); // add that onto bytes
  26985. bytes = concatTypedArrays(bytes, stringToBytes(newPart, true));
  26986. id3Offset = id3Offset || getId3Offset(bytes); // we need at least 10 bytes to determine a type
  26987. // or we need at least two bytes after an id3Offset
  26988. if (bytes.length < 10 || id3Offset && bytes.length < id3Offset + 2) {
  26989. return callbackOnCompleted(request, function () {
  26990. return endRequestAndCallback(error, request, '', bytes);
  26991. });
  26992. }
  26993. var type = detectContainerForBytes(bytes); // if this looks like a ts segment but we don't have enough data
  26994. // to see the second sync byte, wait until we have enough data
  26995. // before declaring it ts
  26996. if (type === 'ts' && bytes.length < 188) {
  26997. return callbackOnCompleted(request, function () {
  26998. return endRequestAndCallback(error, request, '', bytes);
  26999. });
  27000. } // this may be an unsynced ts segment
  27001. // wait for 376 bytes before detecting no container
  27002. if (!type && bytes.length < 376) {
  27003. return callbackOnCompleted(request, function () {
  27004. return endRequestAndCallback(error, request, '', bytes);
  27005. });
  27006. }
  27007. return endRequestAndCallback(null, request, type, bytes);
  27008. };
  27009. var options = {
  27010. uri: uri,
  27011. beforeSend: function beforeSend(request) {
  27012. // this forces the browser to pass the bytes to us unprocessed
  27013. request.overrideMimeType('text/plain; charset=x-user-defined');
  27014. request.addEventListener('progress', function (_ref) {
  27015. _ref.total;
  27016. _ref.loaded;
  27017. return callbackWrapper(request, null, {
  27018. statusCode: request.status
  27019. }, progressListener);
  27020. });
  27021. }
  27022. };
  27023. var request = xhr(options, function (error, response) {
  27024. return callbackWrapper(request, error, response, progressListener);
  27025. });
  27026. return request;
  27027. };
  27028. var EventTarget = videojs.EventTarget,
  27029. mergeOptions = videojs.mergeOptions;
  27030. var dashPlaylistUnchanged = function dashPlaylistUnchanged(a, b) {
  27031. if (!isPlaylistUnchanged(a, b)) {
  27032. return false;
  27033. } // for dash the above check will often return true in scenarios where
  27034. // the playlist actually has changed because mediaSequence isn't a
  27035. // dash thing, and we often set it to 1. So if the playlists have the same amount
  27036. // of segments we return true.
  27037. // So for dash we need to make sure that the underlying segments are different.
  27038. // if sidx changed then the playlists are different.
  27039. if (a.sidx && b.sidx && (a.sidx.offset !== b.sidx.offset || a.sidx.length !== b.sidx.length)) {
  27040. return false;
  27041. } else if (!a.sidx && b.sidx || a.sidx && !b.sidx) {
  27042. return false;
  27043. } // one or the other does not have segments
  27044. // there was a change.
  27045. if (a.segments && !b.segments || !a.segments && b.segments) {
  27046. return false;
  27047. } // neither has segments nothing changed
  27048. if (!a.segments && !b.segments) {
  27049. return true;
  27050. } // check segments themselves
  27051. for (var i = 0; i < a.segments.length; i++) {
  27052. var aSegment = a.segments[i];
  27053. var bSegment = b.segments[i]; // if uris are different between segments there was a change
  27054. if (aSegment.uri !== bSegment.uri) {
  27055. return false;
  27056. } // neither segment has a byterange, there will be no byterange change.
  27057. if (!aSegment.byterange && !bSegment.byterange) {
  27058. continue;
  27059. }
  27060. var aByterange = aSegment.byterange;
  27061. var bByterange = bSegment.byterange; // if byterange only exists on one of the segments, there was a change.
  27062. if (aByterange && !bByterange || !aByterange && bByterange) {
  27063. return false;
  27064. } // if both segments have byterange with different offsets, there was a change.
  27065. if (aByterange.offset !== bByterange.offset || aByterange.length !== bByterange.length) {
  27066. return false;
  27067. }
  27068. } // if everything was the same with segments, this is the same playlist.
  27069. return true;
  27070. };
  27071. /**
  27072. * Use the representation IDs from the mpd object to create groupIDs, the NAME is set to mandatory representation
  27073. * ID in the parser. This allows for continuous playout across periods with the same representation IDs
  27074. * (continuous periods as defined in DASH-IF 3.2.12). This is assumed in the mpd-parser as well. If we want to support
  27075. * periods without continuous playback this function may need modification as well as the parser.
  27076. */
  27077. var dashGroupId = function dashGroupId(type, group, label, playlist) {
  27078. // If the manifest somehow does not have an ID (non-dash compliant), use the label.
  27079. var playlistId = playlist.attributes.NAME || label;
  27080. return "placeholder-uri-" + type + "-" + group + "-" + playlistId;
  27081. };
  27082. /**
  27083. * Parses the master XML string and updates playlist URI references.
  27084. *
  27085. * @param {Object} config
  27086. * Object of arguments
  27087. * @param {string} config.masterXml
  27088. * The mpd XML
  27089. * @param {string} config.srcUrl
  27090. * The mpd URL
  27091. * @param {Date} config.clientOffset
  27092. * A time difference between server and client
  27093. * @param {Object} config.sidxMapping
  27094. * SIDX mappings for moof/mdat URIs and byte ranges
  27095. * @return {Object}
  27096. * The parsed mpd manifest object
  27097. */
  27098. var parseMasterXml = function parseMasterXml(_ref) {
  27099. var masterXml = _ref.masterXml,
  27100. srcUrl = _ref.srcUrl,
  27101. clientOffset = _ref.clientOffset,
  27102. sidxMapping = _ref.sidxMapping,
  27103. previousManifest = _ref.previousManifest;
  27104. var manifest = parse(masterXml, {
  27105. manifestUri: srcUrl,
  27106. clientOffset: clientOffset,
  27107. sidxMapping: sidxMapping,
  27108. previousManifest: previousManifest
  27109. });
  27110. addPropertiesToMaster(manifest, srcUrl, dashGroupId);
  27111. return manifest;
  27112. };
  27113. /**
  27114. * Removes any mediaGroup labels that no longer exist in the newMaster
  27115. *
  27116. * @param {Object} update
  27117. * The previous mpd object being updated
  27118. * @param {Object} newMaster
  27119. * The new mpd object
  27120. */
  27121. var removeOldMediaGroupLabels = function removeOldMediaGroupLabels(update, newMaster) {
  27122. forEachMediaGroup(update, function (properties, type, group, label) {
  27123. if (!(label in newMaster.mediaGroups[type][group])) {
  27124. delete update.mediaGroups[type][group][label];
  27125. }
  27126. });
  27127. };
  27128. /**
  27129. * Returns a new master manifest that is the result of merging an updated master manifest
  27130. * into the original version.
  27131. *
  27132. * @param {Object} oldMaster
  27133. * The old parsed mpd object
  27134. * @param {Object} newMaster
  27135. * The updated parsed mpd object
  27136. * @return {Object}
  27137. * A new object representing the original master manifest with the updated media
  27138. * playlists merged in
  27139. */
  27140. var updateMaster = function updateMaster(oldMaster, newMaster, sidxMapping) {
  27141. var noChanges = true;
  27142. var update = mergeOptions(oldMaster, {
  27143. // These are top level properties that can be updated
  27144. duration: newMaster.duration,
  27145. minimumUpdatePeriod: newMaster.minimumUpdatePeriod,
  27146. timelineStarts: newMaster.timelineStarts
  27147. }); // First update the playlists in playlist list
  27148. for (var i = 0; i < newMaster.playlists.length; i++) {
  27149. var playlist = newMaster.playlists[i];
  27150. if (playlist.sidx) {
  27151. var sidxKey = generateSidxKey(playlist.sidx); // add sidx segments to the playlist if we have all the sidx info already
  27152. if (sidxMapping && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx) {
  27153. addSidxSegmentsToPlaylist(playlist, sidxMapping[sidxKey].sidx, playlist.sidx.resolvedUri);
  27154. }
  27155. }
  27156. var playlistUpdate = updateMaster$1(update, playlist, dashPlaylistUnchanged);
  27157. if (playlistUpdate) {
  27158. update = playlistUpdate;
  27159. noChanges = false;
  27160. }
  27161. } // Then update media group playlists
  27162. forEachMediaGroup(newMaster, function (properties, type, group, label) {
  27163. if (properties.playlists && properties.playlists.length) {
  27164. var id = properties.playlists[0].id;
  27165. var _playlistUpdate = updateMaster$1(update, properties.playlists[0], dashPlaylistUnchanged);
  27166. if (_playlistUpdate) {
  27167. update = _playlistUpdate; // add new mediaGroup label if it doesn't exist and assign the new mediaGroup.
  27168. if (!(label in update.mediaGroups[type][group])) {
  27169. update.mediaGroups[type][group][label] = properties;
  27170. } // update the playlist reference within media groups
  27171. update.mediaGroups[type][group][label].playlists[0] = update.playlists[id];
  27172. noChanges = false;
  27173. }
  27174. }
  27175. }); // remove mediaGroup labels and references that no longer exist in the newMaster
  27176. removeOldMediaGroupLabels(update, newMaster);
  27177. if (newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod) {
  27178. noChanges = false;
  27179. }
  27180. if (noChanges) {
  27181. return null;
  27182. }
  27183. return update;
  27184. }; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
  27185. // If the SIDXs have maps, the two maps should match,
  27186. // both `a` and `b` missing SIDXs is considered matching.
  27187. // If `a` or `b` but not both have a map, they aren't matching.
  27188. var equivalentSidx = function equivalentSidx(a, b) {
  27189. var neitherMap = Boolean(!a.map && !b.map);
  27190. var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
  27191. return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
  27192. }; // exported for testing
  27193. var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) {
  27194. var newSidxMapping = {};
  27195. for (var id in playlists) {
  27196. var playlist = playlists[id];
  27197. var currentSidxInfo = playlist.sidx;
  27198. if (currentSidxInfo) {
  27199. var key = generateSidxKey(currentSidxInfo);
  27200. if (!oldSidxMapping[key]) {
  27201. break;
  27202. }
  27203. var savedSidxInfo = oldSidxMapping[key].sidxInfo;
  27204. if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
  27205. newSidxMapping[key] = oldSidxMapping[key];
  27206. }
  27207. }
  27208. }
  27209. return newSidxMapping;
  27210. };
  27211. /**
  27212. * A function that filters out changed items as they need to be requested separately.
  27213. *
  27214. * The method is exported for testing
  27215. *
  27216. * @param {Object} master the parsed mpd XML returned via mpd-parser
  27217. * @param {Object} oldSidxMapping the SIDX to compare against
  27218. */
  27219. var filterChangedSidxMappings = function filterChangedSidxMappings(master, oldSidxMapping) {
  27220. var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping);
  27221. var mediaGroupSidx = videoSidx;
  27222. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  27223. if (properties.playlists && properties.playlists.length) {
  27224. var playlists = properties.playlists;
  27225. mediaGroupSidx = mergeOptions(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
  27226. }
  27227. });
  27228. return mediaGroupSidx;
  27229. };
  27230. var DashPlaylistLoader = /*#__PURE__*/function (_EventTarget) {
  27231. _inheritsLoose(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent
  27232. // playlist loader setups from media groups will expect to be able to pass a playlist
  27233. // (since there aren't external URLs to media playlists with DASH)
  27234. function DashPlaylistLoader(srcUrlOrPlaylist, vhs, options, masterPlaylistLoader) {
  27235. var _this;
  27236. if (options === void 0) {
  27237. options = {};
  27238. }
  27239. _this = _EventTarget.call(this) || this;
  27240. _this.masterPlaylistLoader_ = masterPlaylistLoader || _assertThisInitialized(_this);
  27241. if (!masterPlaylistLoader) {
  27242. _this.isMaster_ = true;
  27243. }
  27244. var _options = options,
  27245. _options$withCredenti = _options.withCredentials,
  27246. withCredentials = _options$withCredenti === void 0 ? false : _options$withCredenti,
  27247. _options$handleManife = _options.handleManifestRedirects,
  27248. handleManifestRedirects = _options$handleManife === void 0 ? false : _options$handleManife;
  27249. _this.vhs_ = vhs;
  27250. _this.withCredentials = withCredentials;
  27251. _this.handleManifestRedirects = handleManifestRedirects;
  27252. if (!srcUrlOrPlaylist) {
  27253. throw new Error('A non-empty playlist URL or object is required');
  27254. } // event naming?
  27255. _this.on('minimumUpdatePeriod', function () {
  27256. _this.refreshXml_();
  27257. }); // live playlist staleness timeout
  27258. _this.on('mediaupdatetimeout', function () {
  27259. _this.refreshMedia_(_this.media().id);
  27260. });
  27261. _this.state = 'HAVE_NOTHING';
  27262. _this.loadedPlaylists_ = {};
  27263. _this.logger_ = logger('DashPlaylistLoader'); // initialize the loader state
  27264. // The masterPlaylistLoader will be created with a string
  27265. if (_this.isMaster_) {
  27266. _this.masterPlaylistLoader_.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
  27267. // once multi-period is refactored
  27268. _this.masterPlaylistLoader_.sidxMapping_ = {};
  27269. } else {
  27270. _this.childPlaylist_ = srcUrlOrPlaylist;
  27271. }
  27272. return _this;
  27273. }
  27274. var _proto = DashPlaylistLoader.prototype;
  27275. _proto.requestErrored_ = function requestErrored_(err, request, startingState) {
  27276. // disposed
  27277. if (!this.request) {
  27278. return true;
  27279. } // pending request is cleared
  27280. this.request = null;
  27281. if (err) {
  27282. // use the provided error object or create one
  27283. // based on the request/response
  27284. this.error = typeof err === 'object' && !(err instanceof Error) ? err : {
  27285. status: request.status,
  27286. message: 'DASH request error at URL: ' + request.uri,
  27287. response: request.response,
  27288. // MEDIA_ERR_NETWORK
  27289. code: 2
  27290. };
  27291. if (startingState) {
  27292. this.state = startingState;
  27293. }
  27294. this.trigger('error');
  27295. return true;
  27296. }
  27297. }
  27298. /**
  27299. * Verify that the container of the sidx segment can be parsed
  27300. * and if it can, get and parse that segment.
  27301. */
  27302. ;
  27303. _proto.addSidxSegments_ = function addSidxSegments_(playlist, startingState, cb) {
  27304. var _this2 = this;
  27305. var sidxKey = playlist.sidx && generateSidxKey(playlist.sidx); // playlist lacks sidx or sidx segments were added to this playlist already.
  27306. if (!playlist.sidx || !sidxKey || this.masterPlaylistLoader_.sidxMapping_[sidxKey]) {
  27307. // keep this function async
  27308. this.mediaRequest_ = window$1.setTimeout(function () {
  27309. return cb(false);
  27310. }, 0);
  27311. return;
  27312. } // resolve the segment URL relative to the playlist
  27313. var uri = resolveManifestRedirect(this.handleManifestRedirects, playlist.sidx.resolvedUri);
  27314. var fin = function fin(err, request) {
  27315. if (_this2.requestErrored_(err, request, startingState)) {
  27316. return;
  27317. }
  27318. var sidxMapping = _this2.masterPlaylistLoader_.sidxMapping_;
  27319. var sidx;
  27320. try {
  27321. sidx = parseSidx(toUint8(request.response).subarray(8));
  27322. } catch (e) {
  27323. // sidx parsing failed.
  27324. _this2.requestErrored_(e, request, startingState);
  27325. return;
  27326. }
  27327. sidxMapping[sidxKey] = {
  27328. sidxInfo: playlist.sidx,
  27329. sidx: sidx
  27330. };
  27331. addSidxSegmentsToPlaylist(playlist, sidx, playlist.sidx.resolvedUri);
  27332. return cb(true);
  27333. };
  27334. this.request = containerRequest(uri, this.vhs_.xhr, function (err, request, container, bytes) {
  27335. if (err) {
  27336. return fin(err, request);
  27337. }
  27338. if (!container || container !== 'mp4') {
  27339. return fin({
  27340. status: request.status,
  27341. message: "Unsupported " + (container || 'unknown') + " container type for sidx segment at URL: " + uri,
  27342. // response is just bytes in this case
  27343. // but we really don't want to return that.
  27344. response: '',
  27345. playlist: playlist,
  27346. internal: true,
  27347. blacklistDuration: Infinity,
  27348. // MEDIA_ERR_NETWORK
  27349. code: 2
  27350. }, request);
  27351. } // if we already downloaded the sidx bytes in the container request, use them
  27352. var _playlist$sidx$bytera = playlist.sidx.byterange,
  27353. offset = _playlist$sidx$bytera.offset,
  27354. length = _playlist$sidx$bytera.length;
  27355. if (bytes.length >= length + offset) {
  27356. return fin(err, {
  27357. response: bytes.subarray(offset, offset + length),
  27358. status: request.status,
  27359. uri: request.uri
  27360. });
  27361. } // otherwise request sidx bytes
  27362. _this2.request = _this2.vhs_.xhr({
  27363. uri: uri,
  27364. responseType: 'arraybuffer',
  27365. headers: segmentXhrHeaders({
  27366. byterange: playlist.sidx.byterange
  27367. })
  27368. }, fin);
  27369. });
  27370. };
  27371. _proto.dispose = function dispose() {
  27372. this.trigger('dispose');
  27373. this.stopRequest();
  27374. this.loadedPlaylists_ = {};
  27375. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  27376. window$1.clearTimeout(this.mediaRequest_);
  27377. window$1.clearTimeout(this.mediaUpdateTimeout);
  27378. this.mediaUpdateTimeout = null;
  27379. this.mediaRequest_ = null;
  27380. this.minimumUpdatePeriodTimeout_ = null;
  27381. if (this.masterPlaylistLoader_.createMupOnMedia_) {
  27382. this.off('loadedmetadata', this.masterPlaylistLoader_.createMupOnMedia_);
  27383. this.masterPlaylistLoader_.createMupOnMedia_ = null;
  27384. }
  27385. this.off();
  27386. };
  27387. _proto.hasPendingRequest = function hasPendingRequest() {
  27388. return this.request || this.mediaRequest_;
  27389. };
  27390. _proto.stopRequest = function stopRequest() {
  27391. if (this.request) {
  27392. var oldRequest = this.request;
  27393. this.request = null;
  27394. oldRequest.onreadystatechange = null;
  27395. oldRequest.abort();
  27396. }
  27397. };
  27398. _proto.media = function media(playlist) {
  27399. var _this3 = this; // getter
  27400. if (!playlist) {
  27401. return this.media_;
  27402. } // setter
  27403. if (this.state === 'HAVE_NOTHING') {
  27404. throw new Error('Cannot switch media playlist from ' + this.state);
  27405. }
  27406. var startingState = this.state; // find the playlist object if the target playlist has been specified by URI
  27407. if (typeof playlist === 'string') {
  27408. if (!this.masterPlaylistLoader_.master.playlists[playlist]) {
  27409. throw new Error('Unknown playlist URI: ' + playlist);
  27410. }
  27411. playlist = this.masterPlaylistLoader_.master.playlists[playlist];
  27412. }
  27413. var mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to previously loaded playlists immediately
  27414. if (mediaChange && this.loadedPlaylists_[playlist.id] && this.loadedPlaylists_[playlist.id].endList) {
  27415. this.state = 'HAVE_METADATA';
  27416. this.media_ = playlist; // trigger media change if the active media has been updated
  27417. if (mediaChange) {
  27418. this.trigger('mediachanging');
  27419. this.trigger('mediachange');
  27420. }
  27421. return;
  27422. } // switching to the active playlist is a no-op
  27423. if (!mediaChange) {
  27424. return;
  27425. } // switching from an already loaded playlist
  27426. if (this.media_) {
  27427. this.trigger('mediachanging');
  27428. }
  27429. this.addSidxSegments_(playlist, startingState, function (sidxChanged) {
  27430. // everything is ready just continue to haveMetadata
  27431. _this3.haveMetadata({
  27432. startingState: startingState,
  27433. playlist: playlist
  27434. });
  27435. });
  27436. };
  27437. _proto.haveMetadata = function haveMetadata(_ref2) {
  27438. var startingState = _ref2.startingState,
  27439. playlist = _ref2.playlist;
  27440. this.state = 'HAVE_METADATA';
  27441. this.loadedPlaylists_[playlist.id] = playlist;
  27442. this.mediaRequest_ = null; // This will trigger loadedplaylist
  27443. this.refreshMedia_(playlist.id); // fire loadedmetadata the first time a media playlist is loaded
  27444. // to resolve setup of media groups
  27445. if (startingState === 'HAVE_MASTER') {
  27446. this.trigger('loadedmetadata');
  27447. } else {
  27448. // trigger media change if the active media has been updated
  27449. this.trigger('mediachange');
  27450. }
  27451. };
  27452. _proto.pause = function pause() {
  27453. if (this.masterPlaylistLoader_.createMupOnMedia_) {
  27454. this.off('loadedmetadata', this.masterPlaylistLoader_.createMupOnMedia_);
  27455. this.masterPlaylistLoader_.createMupOnMedia_ = null;
  27456. }
  27457. this.stopRequest();
  27458. window$1.clearTimeout(this.mediaUpdateTimeout);
  27459. this.mediaUpdateTimeout = null;
  27460. if (this.isMaster_) {
  27461. window$1.clearTimeout(this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_);
  27462. this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_ = null;
  27463. }
  27464. if (this.state === 'HAVE_NOTHING') {
  27465. // If we pause the loader before any data has been retrieved, its as if we never
  27466. // started, so reset to an unstarted state.
  27467. this.started = false;
  27468. }
  27469. };
  27470. _proto.load = function load(isFinalRendition) {
  27471. var _this4 = this;
  27472. window$1.clearTimeout(this.mediaUpdateTimeout);
  27473. this.mediaUpdateTimeout = null;
  27474. var media = this.media();
  27475. if (isFinalRendition) {
  27476. var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
  27477. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  27478. return _this4.load();
  27479. }, delay);
  27480. return;
  27481. } // because the playlists are internal to the manifest, load should either load the
  27482. // main manifest, or do nothing but trigger an event
  27483. if (!this.started) {
  27484. this.start();
  27485. return;
  27486. }
  27487. if (media && !media.endList) {
  27488. // Check to see if this is the master loader and the MUP was cleared (this happens
  27489. // when the loader was paused). `media` should be set at this point since one is always
  27490. // set during `start()`.
  27491. if (this.isMaster_ && !this.minimumUpdatePeriodTimeout_) {
  27492. // Trigger minimumUpdatePeriod to refresh the master manifest
  27493. this.trigger('minimumUpdatePeriod'); // Since there was no prior minimumUpdatePeriodTimeout it should be recreated
  27494. this.updateMinimumUpdatePeriodTimeout_();
  27495. }
  27496. this.trigger('mediaupdatetimeout');
  27497. } else {
  27498. this.trigger('loadedplaylist');
  27499. }
  27500. };
  27501. _proto.start = function start() {
  27502. var _this5 = this;
  27503. this.started = true; // We don't need to request the master manifest again
  27504. // Call this asynchronously to match the xhr request behavior below
  27505. if (!this.isMaster_) {
  27506. this.mediaRequest_ = window$1.setTimeout(function () {
  27507. return _this5.haveMaster_();
  27508. }, 0);
  27509. return;
  27510. }
  27511. this.requestMaster_(function (req, masterChanged) {
  27512. _this5.haveMaster_();
  27513. if (!_this5.hasPendingRequest() && !_this5.media_) {
  27514. _this5.media(_this5.masterPlaylistLoader_.master.playlists[0]);
  27515. }
  27516. });
  27517. };
  27518. _proto.requestMaster_ = function requestMaster_(cb) {
  27519. var _this6 = this;
  27520. this.request = this.vhs_.xhr({
  27521. uri: this.masterPlaylistLoader_.srcUrl,
  27522. withCredentials: this.withCredentials
  27523. }, function (error, req) {
  27524. if (_this6.requestErrored_(error, req)) {
  27525. if (_this6.state === 'HAVE_NOTHING') {
  27526. _this6.started = false;
  27527. }
  27528. return;
  27529. }
  27530. var masterChanged = req.responseText !== _this6.masterPlaylistLoader_.masterXml_;
  27531. _this6.masterPlaylistLoader_.masterXml_ = req.responseText;
  27532. if (req.responseHeaders && req.responseHeaders.date) {
  27533. _this6.masterLoaded_ = Date.parse(req.responseHeaders.date);
  27534. } else {
  27535. _this6.masterLoaded_ = Date.now();
  27536. }
  27537. _this6.masterPlaylistLoader_.srcUrl = resolveManifestRedirect(_this6.handleManifestRedirects, _this6.masterPlaylistLoader_.srcUrl, req);
  27538. if (masterChanged) {
  27539. _this6.handleMaster_();
  27540. _this6.syncClientServerClock_(function () {
  27541. return cb(req, masterChanged);
  27542. });
  27543. return;
  27544. }
  27545. return cb(req, masterChanged);
  27546. });
  27547. }
  27548. /**
  27549. * Parses the master xml for UTCTiming node to sync the client clock to the server
  27550. * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
  27551. *
  27552. * @param {Function} done
  27553. * Function to call when clock sync has completed
  27554. */
  27555. ;
  27556. _proto.syncClientServerClock_ = function syncClientServerClock_(done) {
  27557. var _this7 = this;
  27558. var utcTiming = parseUTCTiming(this.masterPlaylistLoader_.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
  27559. // server clock
  27560. if (utcTiming === null) {
  27561. this.masterPlaylistLoader_.clientOffset_ = this.masterLoaded_ - Date.now();
  27562. return done();
  27563. }
  27564. if (utcTiming.method === 'DIRECT') {
  27565. this.masterPlaylistLoader_.clientOffset_ = utcTiming.value - Date.now();
  27566. return done();
  27567. }
  27568. this.request = this.vhs_.xhr({
  27569. uri: resolveUrl(this.masterPlaylistLoader_.srcUrl, utcTiming.value),
  27570. method: utcTiming.method,
  27571. withCredentials: this.withCredentials
  27572. }, function (error, req) {
  27573. // disposed
  27574. if (!_this7.request) {
  27575. return;
  27576. }
  27577. if (error) {
  27578. // sync request failed, fall back to using date header from mpd
  27579. // TODO: log warning
  27580. _this7.masterPlaylistLoader_.clientOffset_ = _this7.masterLoaded_ - Date.now();
  27581. return done();
  27582. }
  27583. var serverTime;
  27584. if (utcTiming.method === 'HEAD') {
  27585. if (!req.responseHeaders || !req.responseHeaders.date) {
  27586. // expected date header not preset, fall back to using date header from mpd
  27587. // TODO: log warning
  27588. serverTime = _this7.masterLoaded_;
  27589. } else {
  27590. serverTime = Date.parse(req.responseHeaders.date);
  27591. }
  27592. } else {
  27593. serverTime = Date.parse(req.responseText);
  27594. }
  27595. _this7.masterPlaylistLoader_.clientOffset_ = serverTime - Date.now();
  27596. done();
  27597. });
  27598. };
  27599. _proto.haveMaster_ = function haveMaster_() {
  27600. this.state = 'HAVE_MASTER';
  27601. if (this.isMaster_) {
  27602. // We have the master playlist at this point, so
  27603. // trigger this to allow MasterPlaylistController
  27604. // to make an initial playlist selection
  27605. this.trigger('loadedplaylist');
  27606. } else if (!this.media_) {
  27607. // no media playlist was specifically selected so select
  27608. // the one the child playlist loader was created with
  27609. this.media(this.childPlaylist_);
  27610. }
  27611. };
  27612. _proto.handleMaster_ = function handleMaster_() {
  27613. // clear media request
  27614. this.mediaRequest_ = null;
  27615. var oldMaster = this.masterPlaylistLoader_.master;
  27616. var newMaster = parseMasterXml({
  27617. masterXml: this.masterPlaylistLoader_.masterXml_,
  27618. srcUrl: this.masterPlaylistLoader_.srcUrl,
  27619. clientOffset: this.masterPlaylistLoader_.clientOffset_,
  27620. sidxMapping: this.masterPlaylistLoader_.sidxMapping_,
  27621. previousManifest: oldMaster
  27622. }); // if we have an old master to compare the new master against
  27623. if (oldMaster) {
  27624. newMaster = updateMaster(oldMaster, newMaster, this.masterPlaylistLoader_.sidxMapping_);
  27625. } // only update master if we have a new master
  27626. this.masterPlaylistLoader_.master = newMaster ? newMaster : oldMaster;
  27627. var location = this.masterPlaylistLoader_.master.locations && this.masterPlaylistLoader_.master.locations[0];
  27628. if (location && location !== this.masterPlaylistLoader_.srcUrl) {
  27629. this.masterPlaylistLoader_.srcUrl = location;
  27630. }
  27631. if (!oldMaster || newMaster && newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod) {
  27632. this.updateMinimumUpdatePeriodTimeout_();
  27633. }
  27634. return Boolean(newMaster);
  27635. };
  27636. _proto.updateMinimumUpdatePeriodTimeout_ = function updateMinimumUpdatePeriodTimeout_() {
  27637. var mpl = this.masterPlaylistLoader_; // cancel any pending creation of mup on media
  27638. // a new one will be added if needed.
  27639. if (mpl.createMupOnMedia_) {
  27640. mpl.off('loadedmetadata', mpl.createMupOnMedia_);
  27641. mpl.createMupOnMedia_ = null;
  27642. } // clear any pending timeouts
  27643. if (mpl.minimumUpdatePeriodTimeout_) {
  27644. window$1.clearTimeout(mpl.minimumUpdatePeriodTimeout_);
  27645. mpl.minimumUpdatePeriodTimeout_ = null;
  27646. }
  27647. var mup = mpl.master && mpl.master.minimumUpdatePeriod; // If the minimumUpdatePeriod has a value of 0, that indicates that the current
  27648. // MPD has no future validity, so a new one will need to be acquired when new
  27649. // media segments are to be made available. Thus, we use the target duration
  27650. // in this case
  27651. if (mup === 0) {
  27652. if (mpl.media()) {
  27653. mup = mpl.media().targetDuration * 1000;
  27654. } else {
  27655. mpl.createMupOnMedia_ = mpl.updateMinimumUpdatePeriodTimeout_;
  27656. mpl.one('loadedmetadata', mpl.createMupOnMedia_);
  27657. }
  27658. } // if minimumUpdatePeriod is invalid or <= zero, which
  27659. // can happen when a live video becomes VOD. skip timeout
  27660. // creation.
  27661. if (typeof mup !== 'number' || mup <= 0) {
  27662. if (mup < 0) {
  27663. this.logger_("found invalid minimumUpdatePeriod of " + mup + ", not setting a timeout");
  27664. }
  27665. return;
  27666. }
  27667. this.createMUPTimeout_(mup);
  27668. };
  27669. _proto.createMUPTimeout_ = function createMUPTimeout_(mup) {
  27670. var mpl = this.masterPlaylistLoader_;
  27671. mpl.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  27672. mpl.minimumUpdatePeriodTimeout_ = null;
  27673. mpl.trigger('minimumUpdatePeriod');
  27674. mpl.createMUPTimeout_(mup);
  27675. }, mup);
  27676. }
  27677. /**
  27678. * Sends request to refresh the master xml and updates the parsed master manifest
  27679. */
  27680. ;
  27681. _proto.refreshXml_ = function refreshXml_() {
  27682. var _this8 = this;
  27683. this.requestMaster_(function (req, masterChanged) {
  27684. if (!masterChanged) {
  27685. return;
  27686. }
  27687. if (_this8.media_) {
  27688. _this8.media_ = _this8.masterPlaylistLoader_.master.playlists[_this8.media_.id];
  27689. } // This will filter out updated sidx info from the mapping
  27690. _this8.masterPlaylistLoader_.sidxMapping_ = filterChangedSidxMappings(_this8.masterPlaylistLoader_.master, _this8.masterPlaylistLoader_.sidxMapping_);
  27691. _this8.addSidxSegments_(_this8.media(), _this8.state, function (sidxChanged) {
  27692. // TODO: do we need to reload the current playlist?
  27693. _this8.refreshMedia_(_this8.media().id);
  27694. });
  27695. });
  27696. }
  27697. /**
  27698. * Refreshes the media playlist by re-parsing the master xml and updating playlist
  27699. * references. If this is an alternate loader, the updated parsed manifest is retrieved
  27700. * from the master loader.
  27701. */
  27702. ;
  27703. _proto.refreshMedia_ = function refreshMedia_(mediaID) {
  27704. var _this9 = this;
  27705. if (!mediaID) {
  27706. throw new Error('refreshMedia_ must take a media id');
  27707. } // for master we have to reparse the master xml
  27708. // to re-create segments based on current timing values
  27709. // which may change media. We only skip updating master
  27710. // if this is the first time this.media_ is being set.
  27711. // as master was just parsed in that case.
  27712. if (this.media_ && this.isMaster_) {
  27713. this.handleMaster_();
  27714. }
  27715. var playlists = this.masterPlaylistLoader_.master.playlists;
  27716. var mediaChanged = !this.media_ || this.media_ !== playlists[mediaID];
  27717. if (mediaChanged) {
  27718. this.media_ = playlists[mediaID];
  27719. } else {
  27720. this.trigger('playlistunchanged');
  27721. }
  27722. if (!this.mediaUpdateTimeout) {
  27723. var createMediaUpdateTimeout = function createMediaUpdateTimeout() {
  27724. if (_this9.media().endList) {
  27725. return;
  27726. }
  27727. _this9.mediaUpdateTimeout = window$1.setTimeout(function () {
  27728. _this9.trigger('mediaupdatetimeout');
  27729. createMediaUpdateTimeout();
  27730. }, refreshDelay(_this9.media(), Boolean(mediaChanged)));
  27731. };
  27732. createMediaUpdateTimeout();
  27733. }
  27734. this.trigger('loadedplaylist');
  27735. };
  27736. return DashPlaylistLoader;
  27737. }(EventTarget);
  27738. var Config = {
  27739. GOAL_BUFFER_LENGTH: 30,
  27740. MAX_GOAL_BUFFER_LENGTH: 60,
  27741. BACK_BUFFER_LENGTH: 30,
  27742. GOAL_BUFFER_LENGTH_RATE: 1,
  27743. // 0.5 MB/s
  27744. INITIAL_BANDWIDTH: 4194304,
  27745. // A fudge factor to apply to advertised playlist bitrates to account for
  27746. // temporary flucations in client bandwidth
  27747. BANDWIDTH_VARIANCE: 1.2,
  27748. // How much of the buffer must be filled before we consider upswitching
  27749. BUFFER_LOW_WATER_LINE: 0,
  27750. MAX_BUFFER_LOW_WATER_LINE: 30,
  27751. // TODO: Remove this when experimentalBufferBasedABR is removed
  27752. EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE: 16,
  27753. BUFFER_LOW_WATER_LINE_RATE: 1,
  27754. // If the buffer is greater than the high water line, we won't switch down
  27755. BUFFER_HIGH_WATER_LINE: 30
  27756. };
  27757. var stringToArrayBuffer = function stringToArrayBuffer(string) {
  27758. var view = new Uint8Array(new ArrayBuffer(string.length));
  27759. for (var i = 0; i < string.length; i++) {
  27760. view[i] = string.charCodeAt(i);
  27761. }
  27762. return view.buffer;
  27763. };
  27764. /* global Blob, BlobBuilder, Worker */
  27765. // unify worker interface
  27766. var browserWorkerPolyFill = function browserWorkerPolyFill(workerObj) {
  27767. // node only supports on/off
  27768. workerObj.on = workerObj.addEventListener;
  27769. workerObj.off = workerObj.removeEventListener;
  27770. return workerObj;
  27771. };
  27772. var createObjectURL = function createObjectURL(str) {
  27773. try {
  27774. return URL.createObjectURL(new Blob([str], {
  27775. type: 'application/javascript'
  27776. }));
  27777. } catch (e) {
  27778. var blob = new BlobBuilder();
  27779. blob.append(str);
  27780. return URL.createObjectURL(blob.getBlob());
  27781. }
  27782. };
  27783. var factory = function factory(code) {
  27784. return function () {
  27785. var objectUrl = createObjectURL(code);
  27786. var worker = browserWorkerPolyFill(new Worker(objectUrl));
  27787. worker.objURL = objectUrl;
  27788. var terminate = worker.terminate;
  27789. worker.on = worker.addEventListener;
  27790. worker.off = worker.removeEventListener;
  27791. worker.terminate = function () {
  27792. URL.revokeObjectURL(objectUrl);
  27793. return terminate.call(this);
  27794. };
  27795. return worker;
  27796. };
  27797. };
  27798. var transform = function transform(code) {
  27799. return "var browserWorkerPolyFill = " + browserWorkerPolyFill.toString() + ";\n" + 'browserWorkerPolyFill(self);\n' + code;
  27800. };
  27801. var getWorkerString = function getWorkerString(fn) {
  27802. return fn.toString().replace(/^function.+?{/, '').slice(0, -1);
  27803. };
  27804. /* rollup-plugin-worker-factory start for worker!/Users/ddashkevich/projects/http-streaming/src/transmuxer-worker.js */
  27805. var workerCode$1 = transform(getWorkerString(function () {
  27806. /**
  27807. * mux.js
  27808. *
  27809. * Copyright (c) Brightcove
  27810. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  27811. *
  27812. * A lightweight readable stream implemention that handles event dispatching.
  27813. * Objects that inherit from streams should call init in their constructors.
  27814. */
  27815. var Stream = function Stream() {
  27816. this.init = function () {
  27817. var listeners = {};
  27818. /**
  27819. * Add a listener for a specified event type.
  27820. * @param type {string} the event name
  27821. * @param listener {function} the callback to be invoked when an event of
  27822. * the specified type occurs
  27823. */
  27824. this.on = function (type, listener) {
  27825. if (!listeners[type]) {
  27826. listeners[type] = [];
  27827. }
  27828. listeners[type] = listeners[type].concat(listener);
  27829. };
  27830. /**
  27831. * Remove a listener for a specified event type.
  27832. * @param type {string} the event name
  27833. * @param listener {function} a function previously registered for this
  27834. * type of event through `on`
  27835. */
  27836. this.off = function (type, listener) {
  27837. var index;
  27838. if (!listeners[type]) {
  27839. return false;
  27840. }
  27841. index = listeners[type].indexOf(listener);
  27842. listeners[type] = listeners[type].slice();
  27843. listeners[type].splice(index, 1);
  27844. return index > -1;
  27845. };
  27846. /**
  27847. * Trigger an event of the specified type on this stream. Any additional
  27848. * arguments to this function are passed as parameters to event listeners.
  27849. * @param type {string} the event name
  27850. */
  27851. this.trigger = function (type) {
  27852. var callbacks, i, length, args;
  27853. callbacks = listeners[type];
  27854. if (!callbacks) {
  27855. return;
  27856. } // Slicing the arguments on every invocation of this method
  27857. // can add a significant amount of overhead. Avoid the
  27858. // intermediate object creation for the common case of a
  27859. // single callback argument
  27860. if (arguments.length === 2) {
  27861. length = callbacks.length;
  27862. for (i = 0; i < length; ++i) {
  27863. callbacks[i].call(this, arguments[1]);
  27864. }
  27865. } else {
  27866. args = [];
  27867. i = arguments.length;
  27868. for (i = 1; i < arguments.length; ++i) {
  27869. args.push(arguments[i]);
  27870. }
  27871. length = callbacks.length;
  27872. for (i = 0; i < length; ++i) {
  27873. callbacks[i].apply(this, args);
  27874. }
  27875. }
  27876. };
  27877. /**
  27878. * Destroys the stream and cleans up.
  27879. */
  27880. this.dispose = function () {
  27881. listeners = {};
  27882. };
  27883. };
  27884. };
  27885. /**
  27886. * Forwards all `data` events on this stream to the destination stream. The
  27887. * destination stream should provide a method `push` to receive the data
  27888. * events as they arrive.
  27889. * @param destination {stream} the stream that will receive all `data` events
  27890. * @param autoFlush {boolean} if false, we will not call `flush` on the destination
  27891. * when the current stream emits a 'done' event
  27892. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  27893. */
  27894. Stream.prototype.pipe = function (destination) {
  27895. this.on('data', function (data) {
  27896. destination.push(data);
  27897. });
  27898. this.on('done', function (flushSource) {
  27899. destination.flush(flushSource);
  27900. });
  27901. this.on('partialdone', function (flushSource) {
  27902. destination.partialFlush(flushSource);
  27903. });
  27904. this.on('endedtimeline', function (flushSource) {
  27905. destination.endTimeline(flushSource);
  27906. });
  27907. this.on('reset', function (flushSource) {
  27908. destination.reset(flushSource);
  27909. });
  27910. return destination;
  27911. }; // Default stream functions that are expected to be overridden to perform
  27912. // actual work. These are provided by the prototype as a sort of no-op
  27913. // implementation so that we don't have to check for their existence in the
  27914. // `pipe` function above.
  27915. Stream.prototype.push = function (data) {
  27916. this.trigger('data', data);
  27917. };
  27918. Stream.prototype.flush = function (flushSource) {
  27919. this.trigger('done', flushSource);
  27920. };
  27921. Stream.prototype.partialFlush = function (flushSource) {
  27922. this.trigger('partialdone', flushSource);
  27923. };
  27924. Stream.prototype.endTimeline = function (flushSource) {
  27925. this.trigger('endedtimeline', flushSource);
  27926. };
  27927. Stream.prototype.reset = function (flushSource) {
  27928. this.trigger('reset', flushSource);
  27929. };
  27930. var stream = Stream;
  27931. var MAX_UINT32$1 = Math.pow(2, 32);
  27932. var getUint64$2 = function getUint64(uint8) {
  27933. var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
  27934. var value;
  27935. if (dv.getBigUint64) {
  27936. value = dv.getBigUint64(0);
  27937. if (value < Number.MAX_SAFE_INTEGER) {
  27938. return Number(value);
  27939. }
  27940. return value;
  27941. }
  27942. return dv.getUint32(0) * MAX_UINT32$1 + dv.getUint32(4);
  27943. };
  27944. var numbers = {
  27945. getUint64: getUint64$2,
  27946. MAX_UINT32: MAX_UINT32$1
  27947. };
  27948. var MAX_UINT32 = numbers.MAX_UINT32;
  27949. var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun$1, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants
  27950. (function () {
  27951. var i;
  27952. types = {
  27953. avc1: [],
  27954. // codingname
  27955. avcC: [],
  27956. btrt: [],
  27957. dinf: [],
  27958. dref: [],
  27959. esds: [],
  27960. ftyp: [],
  27961. hdlr: [],
  27962. mdat: [],
  27963. mdhd: [],
  27964. mdia: [],
  27965. mfhd: [],
  27966. minf: [],
  27967. moof: [],
  27968. moov: [],
  27969. mp4a: [],
  27970. // codingname
  27971. mvex: [],
  27972. mvhd: [],
  27973. pasp: [],
  27974. sdtp: [],
  27975. smhd: [],
  27976. stbl: [],
  27977. stco: [],
  27978. stsc: [],
  27979. stsd: [],
  27980. stsz: [],
  27981. stts: [],
  27982. styp: [],
  27983. tfdt: [],
  27984. tfhd: [],
  27985. traf: [],
  27986. trak: [],
  27987. trun: [],
  27988. trex: [],
  27989. tkhd: [],
  27990. vmhd: []
  27991. }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
  27992. // don't throw an error
  27993. if (typeof Uint8Array === 'undefined') {
  27994. return;
  27995. }
  27996. for (i in types) {
  27997. if (types.hasOwnProperty(i)) {
  27998. types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
  27999. }
  28000. }
  28001. MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
  28002. AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
  28003. MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
  28004. VIDEO_HDLR = new Uint8Array([0x00, // version 0
  28005. 0x00, 0x00, 0x00, // flags
  28006. 0x00, 0x00, 0x00, 0x00, // pre_defined
  28007. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  28008. 0x00, 0x00, 0x00, 0x00, // reserved
  28009. 0x00, 0x00, 0x00, 0x00, // reserved
  28010. 0x00, 0x00, 0x00, 0x00, // reserved
  28011. 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  28012. ]);
  28013. AUDIO_HDLR = new Uint8Array([0x00, // version 0
  28014. 0x00, 0x00, 0x00, // flags
  28015. 0x00, 0x00, 0x00, 0x00, // pre_defined
  28016. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  28017. 0x00, 0x00, 0x00, 0x00, // reserved
  28018. 0x00, 0x00, 0x00, 0x00, // reserved
  28019. 0x00, 0x00, 0x00, 0x00, // reserved
  28020. 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  28021. ]);
  28022. HDLR_TYPES = {
  28023. video: VIDEO_HDLR,
  28024. audio: AUDIO_HDLR
  28025. };
  28026. DREF = new Uint8Array([0x00, // version 0
  28027. 0x00, 0x00, 0x00, // flags
  28028. 0x00, 0x00, 0x00, 0x01, // entry_count
  28029. 0x00, 0x00, 0x00, 0x0c, // entry_size
  28030. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  28031. 0x00, // version 0
  28032. 0x00, 0x00, 0x01 // entry_flags
  28033. ]);
  28034. SMHD = new Uint8Array([0x00, // version
  28035. 0x00, 0x00, 0x00, // flags
  28036. 0x00, 0x00, // balance, 0 means centered
  28037. 0x00, 0x00 // reserved
  28038. ]);
  28039. STCO = new Uint8Array([0x00, // version
  28040. 0x00, 0x00, 0x00, // flags
  28041. 0x00, 0x00, 0x00, 0x00 // entry_count
  28042. ]);
  28043. STSC = STCO;
  28044. STSZ = new Uint8Array([0x00, // version
  28045. 0x00, 0x00, 0x00, // flags
  28046. 0x00, 0x00, 0x00, 0x00, // sample_size
  28047. 0x00, 0x00, 0x00, 0x00 // sample_count
  28048. ]);
  28049. STTS = STCO;
  28050. VMHD = new Uint8Array([0x00, // version
  28051. 0x00, 0x00, 0x01, // flags
  28052. 0x00, 0x00, // graphicsmode
  28053. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
  28054. ]);
  28055. })();
  28056. box = function box(type) {
  28057. var payload = [],
  28058. size = 0,
  28059. i,
  28060. result,
  28061. view;
  28062. for (i = 1; i < arguments.length; i++) {
  28063. payload.push(arguments[i]);
  28064. }
  28065. i = payload.length; // calculate the total size we need to allocate
  28066. while (i--) {
  28067. size += payload[i].byteLength;
  28068. }
  28069. result = new Uint8Array(size + 8);
  28070. view = new DataView(result.buffer, result.byteOffset, result.byteLength);
  28071. view.setUint32(0, result.byteLength);
  28072. result.set(type, 4); // copy the payload into the result
  28073. for (i = 0, size = 8; i < payload.length; i++) {
  28074. result.set(payload[i], size);
  28075. size += payload[i].byteLength;
  28076. }
  28077. return result;
  28078. };
  28079. dinf = function dinf() {
  28080. return box(types.dinf, box(types.dref, DREF));
  28081. };
  28082. esds = function esds(track) {
  28083. return box(types.esds, new Uint8Array([0x00, // version
  28084. 0x00, 0x00, 0x00, // flags
  28085. // ES_Descriptor
  28086. 0x03, // tag, ES_DescrTag
  28087. 0x19, // length
  28088. 0x00, 0x00, // ES_ID
  28089. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  28090. // DecoderConfigDescriptor
  28091. 0x04, // tag, DecoderConfigDescrTag
  28092. 0x11, // length
  28093. 0x40, // object type
  28094. 0x15, // streamType
  28095. 0x00, 0x06, 0x00, // bufferSizeDB
  28096. 0x00, 0x00, 0xda, 0xc0, // maxBitrate
  28097. 0x00, 0x00, 0xda, 0xc0, // avgBitrate
  28098. // DecoderSpecificInfo
  28099. 0x05, // tag, DecoderSpecificInfoTag
  28100. 0x02, // length
  28101. // ISO/IEC 14496-3, AudioSpecificConfig
  28102. // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  28103. track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
  28104. ]));
  28105. };
  28106. ftyp = function ftyp() {
  28107. return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
  28108. };
  28109. hdlr = function hdlr(type) {
  28110. return box(types.hdlr, HDLR_TYPES[type]);
  28111. };
  28112. mdat = function mdat(data) {
  28113. return box(types.mdat, data);
  28114. };
  28115. mdhd = function mdhd(track) {
  28116. var result = new Uint8Array([0x00, // version 0
  28117. 0x00, 0x00, 0x00, // flags
  28118. 0x00, 0x00, 0x00, 0x02, // creation_time
  28119. 0x00, 0x00, 0x00, 0x03, // modification_time
  28120. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  28121. track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
  28122. 0x55, 0xc4, // 'und' language (undetermined)
  28123. 0x00, 0x00]); // Use the sample rate from the track metadata, when it is
  28124. // defined. The sample rate can be parsed out of an ADTS header, for
  28125. // instance.
  28126. if (track.samplerate) {
  28127. result[12] = track.samplerate >>> 24 & 0xFF;
  28128. result[13] = track.samplerate >>> 16 & 0xFF;
  28129. result[14] = track.samplerate >>> 8 & 0xFF;
  28130. result[15] = track.samplerate & 0xFF;
  28131. }
  28132. return box(types.mdhd, result);
  28133. };
  28134. mdia = function mdia(track) {
  28135. return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
  28136. };
  28137. mfhd = function mfhd(sequenceNumber) {
  28138. return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
  28139. (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
  28140. ]));
  28141. };
  28142. minf = function minf(track) {
  28143. return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
  28144. };
  28145. moof = function moof(sequenceNumber, tracks) {
  28146. var trackFragments = [],
  28147. i = tracks.length; // build traf boxes for each track fragment
  28148. while (i--) {
  28149. trackFragments[i] = traf(tracks[i]);
  28150. }
  28151. return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
  28152. };
  28153. /**
  28154. * Returns a movie box.
  28155. * @param tracks {array} the tracks associated with this movie
  28156. * @see ISO/IEC 14496-12:2012(E), section 8.2.1
  28157. */
  28158. moov = function moov(tracks) {
  28159. var i = tracks.length,
  28160. boxes = [];
  28161. while (i--) {
  28162. boxes[i] = trak(tracks[i]);
  28163. }
  28164. return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
  28165. };
  28166. mvex = function mvex(tracks) {
  28167. var i = tracks.length,
  28168. boxes = [];
  28169. while (i--) {
  28170. boxes[i] = trex(tracks[i]);
  28171. }
  28172. return box.apply(null, [types.mvex].concat(boxes));
  28173. };
  28174. mvhd = function mvhd(duration) {
  28175. var bytes = new Uint8Array([0x00, // version 0
  28176. 0x00, 0x00, 0x00, // flags
  28177. 0x00, 0x00, 0x00, 0x01, // creation_time
  28178. 0x00, 0x00, 0x00, 0x02, // modification_time
  28179. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  28180. (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
  28181. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  28182. 0x01, 0x00, // 1.0 volume
  28183. 0x00, 0x00, // reserved
  28184. 0x00, 0x00, 0x00, 0x00, // reserved
  28185. 0x00, 0x00, 0x00, 0x00, // reserved
  28186. 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  28187. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  28188. 0xff, 0xff, 0xff, 0xff // next_track_ID
  28189. ]);
  28190. return box(types.mvhd, bytes);
  28191. };
  28192. sdtp = function sdtp(track) {
  28193. var samples = track.samples || [],
  28194. bytes = new Uint8Array(4 + samples.length),
  28195. flags,
  28196. i; // leave the full box header (4 bytes) all zero
  28197. // write the sample table
  28198. for (i = 0; i < samples.length; i++) {
  28199. flags = samples[i].flags;
  28200. bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
  28201. }
  28202. return box(types.sdtp, bytes);
  28203. };
  28204. stbl = function stbl(track) {
  28205. return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
  28206. };
  28207. (function () {
  28208. var videoSample, audioSample;
  28209. stsd = function stsd(track) {
  28210. return box(types.stsd, new Uint8Array([0x00, // version 0
  28211. 0x00, 0x00, 0x00, // flags
  28212. 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
  28213. };
  28214. videoSample = function videoSample(track) {
  28215. var sps = track.sps || [],
  28216. pps = track.pps || [],
  28217. sequenceParameterSets = [],
  28218. pictureParameterSets = [],
  28219. i,
  28220. avc1Box; // assemble the SPSs
  28221. for (i = 0; i < sps.length; i++) {
  28222. sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
  28223. sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
  28224. sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
  28225. } // assemble the PPSs
  28226. for (i = 0; i < pps.length; i++) {
  28227. pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
  28228. pictureParameterSets.push(pps[i].byteLength & 0xFF);
  28229. pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
  28230. }
  28231. avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  28232. 0x00, 0x01, // data_reference_index
  28233. 0x00, 0x00, // pre_defined
  28234. 0x00, 0x00, // reserved
  28235. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  28236. (track.width & 0xff00) >> 8, track.width & 0xff, // width
  28237. (track.height & 0xff00) >> 8, track.height & 0xff, // height
  28238. 0x00, 0x48, 0x00, 0x00, // horizresolution
  28239. 0x00, 0x48, 0x00, 0x00, // vertresolution
  28240. 0x00, 0x00, 0x00, 0x00, // reserved
  28241. 0x00, 0x01, // frame_count
  28242. 0x13, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6a, 0x73, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x2d, 0x68, 0x6c, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compressorname
  28243. 0x00, 0x18, // depth = 24
  28244. 0x11, 0x11 // pre_defined = -1
  28245. ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
  28246. track.profileIdc, // AVCProfileIndication
  28247. track.profileCompatibility, // profile_compatibility
  28248. track.levelIdc, // AVCLevelIndication
  28249. 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
  28250. ].concat([sps.length], // numOfSequenceParameterSets
  28251. sequenceParameterSets, // "SPS"
  28252. [pps.length], // numOfPictureParameterSets
  28253. pictureParameterSets // "PPS"
  28254. ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  28255. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  28256. 0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
  28257. ]))];
  28258. if (track.sarRatio) {
  28259. var hSpacing = track.sarRatio[0],
  28260. vSpacing = track.sarRatio[1];
  28261. avc1Box.push(box(types.pasp, new Uint8Array([(hSpacing & 0xFF000000) >> 24, (hSpacing & 0xFF0000) >> 16, (hSpacing & 0xFF00) >> 8, hSpacing & 0xFF, (vSpacing & 0xFF000000) >> 24, (vSpacing & 0xFF0000) >> 16, (vSpacing & 0xFF00) >> 8, vSpacing & 0xFF])));
  28262. }
  28263. return box.apply(null, avc1Box);
  28264. };
  28265. audioSample = function audioSample(track) {
  28266. return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
  28267. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  28268. 0x00, 0x01, // data_reference_index
  28269. // AudioSampleEntry, ISO/IEC 14496-12
  28270. 0x00, 0x00, 0x00, 0x00, // reserved
  28271. 0x00, 0x00, 0x00, 0x00, // reserved
  28272. (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
  28273. (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
  28274. 0x00, 0x00, // pre_defined
  28275. 0x00, 0x00, // reserved
  28276. (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
  28277. // MP4AudioSampleEntry, ISO/IEC 14496-14
  28278. ]), esds(track));
  28279. };
  28280. })();
  28281. tkhd = function tkhd(track) {
  28282. var result = new Uint8Array([0x00, // version 0
  28283. 0x00, 0x00, 0x07, // flags
  28284. 0x00, 0x00, 0x00, 0x00, // creation_time
  28285. 0x00, 0x00, 0x00, 0x00, // modification_time
  28286. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  28287. 0x00, 0x00, 0x00, 0x00, // reserved
  28288. (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
  28289. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  28290. 0x00, 0x00, // layer
  28291. 0x00, 0x00, // alternate_group
  28292. 0x01, 0x00, // non-audio track volume
  28293. 0x00, 0x00, // reserved
  28294. 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  28295. (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
  28296. (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
  28297. ]);
  28298. return box(types.tkhd, result);
  28299. };
  28300. /**
  28301. * Generate a track fragment (traf) box. A traf box collects metadata
  28302. * about tracks in a movie fragment (moof) box.
  28303. */
  28304. traf = function traf(track) {
  28305. var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
  28306. trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
  28307. 0x00, 0x00, 0x3a, // flags
  28308. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  28309. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  28310. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  28311. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  28312. 0x00, 0x00, 0x00, 0x00 // default_sample_flags
  28313. ]));
  28314. upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / MAX_UINT32);
  28315. lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % MAX_UINT32);
  28316. trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
  28317. 0x00, 0x00, 0x00, // flags
  28318. // baseMediaDecodeTime
  28319. upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 0xFF])); // the data offset specifies the number of bytes from the start of
  28320. // the containing moof to the first payload byte of the associated
  28321. // mdat
  28322. dataOffset = 32 + // tfhd
  28323. 20 + // tfdt
  28324. 8 + // traf header
  28325. 16 + // mfhd
  28326. 8 + // moof header
  28327. 8; // mdat header
  28328. // audio tracks require less metadata
  28329. if (track.type === 'audio') {
  28330. trackFragmentRun = trun$1(track, dataOffset);
  28331. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
  28332. } // video tracks should contain an independent and disposable samples
  28333. // box (sdtp)
  28334. // generate one and adjust offsets to match
  28335. sampleDependencyTable = sdtp(track);
  28336. trackFragmentRun = trun$1(track, sampleDependencyTable.length + dataOffset);
  28337. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
  28338. };
  28339. /**
  28340. * Generate a track box.
  28341. * @param track {object} a track definition
  28342. * @return {Uint8Array} the track box
  28343. */
  28344. trak = function trak(track) {
  28345. track.duration = track.duration || 0xffffffff;
  28346. return box(types.trak, tkhd(track), mdia(track));
  28347. };
  28348. trex = function trex(track) {
  28349. var result = new Uint8Array([0x00, // version 0
  28350. 0x00, 0x00, 0x00, // flags
  28351. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  28352. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  28353. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  28354. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  28355. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  28356. ]); // the last two bytes of default_sample_flags is the sample
  28357. // degradation priority, a hint about the importance of this sample
  28358. // relative to others. Lower the degradation priority for all sample
  28359. // types other than video.
  28360. if (track.type !== 'video') {
  28361. result[result.length - 1] = 0x00;
  28362. }
  28363. return box(types.trex, result);
  28364. };
  28365. (function () {
  28366. var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
  28367. // duration is present for the first sample, it will be present for
  28368. // all subsequent samples.
  28369. // see ISO/IEC 14496-12:2012, Section 8.8.8.1
  28370. trunHeader = function trunHeader(samples, offset) {
  28371. var durationPresent = 0,
  28372. sizePresent = 0,
  28373. flagsPresent = 0,
  28374. compositionTimeOffset = 0; // trun flag constants
  28375. if (samples.length) {
  28376. if (samples[0].duration !== undefined) {
  28377. durationPresent = 0x1;
  28378. }
  28379. if (samples[0].size !== undefined) {
  28380. sizePresent = 0x2;
  28381. }
  28382. if (samples[0].flags !== undefined) {
  28383. flagsPresent = 0x4;
  28384. }
  28385. if (samples[0].compositionTimeOffset !== undefined) {
  28386. compositionTimeOffset = 0x8;
  28387. }
  28388. }
  28389. return [0x00, // version 0
  28390. 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
  28391. (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
  28392. (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
  28393. ];
  28394. };
  28395. videoTrun = function videoTrun(track, offset) {
  28396. var bytesOffest, bytes, header, samples, sample, i;
  28397. samples = track.samples || [];
  28398. offset += 8 + 12 + 16 * samples.length;
  28399. header = trunHeader(samples, offset);
  28400. bytes = new Uint8Array(header.length + samples.length * 16);
  28401. bytes.set(header);
  28402. bytesOffest = header.length;
  28403. for (i = 0; i < samples.length; i++) {
  28404. sample = samples[i];
  28405. bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
  28406. bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
  28407. bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
  28408. bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
  28409. bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
  28410. bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
  28411. bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
  28412. bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
  28413. bytes[bytesOffest++] = sample.flags.isLeading << 2 | sample.flags.dependsOn;
  28414. bytes[bytesOffest++] = sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample;
  28415. bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8;
  28416. bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags
  28417. bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24;
  28418. bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16;
  28419. bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8;
  28420. bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset
  28421. }
  28422. return box(types.trun, bytes);
  28423. };
  28424. audioTrun = function audioTrun(track, offset) {
  28425. var bytes, bytesOffest, header, samples, sample, i;
  28426. samples = track.samples || [];
  28427. offset += 8 + 12 + 8 * samples.length;
  28428. header = trunHeader(samples, offset);
  28429. bytes = new Uint8Array(header.length + samples.length * 8);
  28430. bytes.set(header);
  28431. bytesOffest = header.length;
  28432. for (i = 0; i < samples.length; i++) {
  28433. sample = samples[i];
  28434. bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
  28435. bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
  28436. bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
  28437. bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
  28438. bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
  28439. bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
  28440. bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
  28441. bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
  28442. }
  28443. return box(types.trun, bytes);
  28444. };
  28445. trun$1 = function trun(track, offset) {
  28446. if (track.type === 'audio') {
  28447. return audioTrun(track, offset);
  28448. }
  28449. return videoTrun(track, offset);
  28450. };
  28451. })();
  28452. var mp4Generator = {
  28453. ftyp: ftyp,
  28454. mdat: mdat,
  28455. moof: moof,
  28456. moov: moov,
  28457. initSegment: function initSegment(tracks) {
  28458. var fileType = ftyp(),
  28459. movie = moov(tracks),
  28460. result;
  28461. result = new Uint8Array(fileType.byteLength + movie.byteLength);
  28462. result.set(fileType);
  28463. result.set(movie, fileType.byteLength);
  28464. return result;
  28465. }
  28466. };
  28467. /**
  28468. * mux.js
  28469. *
  28470. * Copyright (c) Brightcove
  28471. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  28472. */
  28473. // Convert an array of nal units into an array of frames with each frame being
  28474. // composed of the nal units that make up that frame
  28475. // Also keep track of cummulative data about the frame from the nal units such
  28476. // as the frame duration, starting pts, etc.
  28477. var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
  28478. var i,
  28479. currentNal,
  28480. currentFrame = [],
  28481. frames = []; // TODO added for LHLS, make sure this is OK
  28482. frames.byteLength = 0;
  28483. frames.nalCount = 0;
  28484. frames.duration = 0;
  28485. currentFrame.byteLength = 0;
  28486. for (i = 0; i < nalUnits.length; i++) {
  28487. currentNal = nalUnits[i]; // Split on 'aud'-type nal units
  28488. if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  28489. // Since the very first nal unit is expected to be an AUD
  28490. // only push to the frames array when currentFrame is not empty
  28491. if (currentFrame.length) {
  28492. currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK
  28493. frames.byteLength += currentFrame.byteLength;
  28494. frames.nalCount += currentFrame.length;
  28495. frames.duration += currentFrame.duration;
  28496. frames.push(currentFrame);
  28497. }
  28498. currentFrame = [currentNal];
  28499. currentFrame.byteLength = currentNal.data.byteLength;
  28500. currentFrame.pts = currentNal.pts;
  28501. currentFrame.dts = currentNal.dts;
  28502. } else {
  28503. // Specifically flag key frames for ease of use later
  28504. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  28505. currentFrame.keyFrame = true;
  28506. }
  28507. currentFrame.duration = currentNal.dts - currentFrame.dts;
  28508. currentFrame.byteLength += currentNal.data.byteLength;
  28509. currentFrame.push(currentNal);
  28510. }
  28511. } // For the last frame, use the duration of the previous frame if we
  28512. // have nothing better to go on
  28513. if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
  28514. currentFrame.duration = frames[frames.length - 1].duration;
  28515. } // Push the final frame
  28516. // TODO added for LHLS, make sure this is OK
  28517. frames.byteLength += currentFrame.byteLength;
  28518. frames.nalCount += currentFrame.length;
  28519. frames.duration += currentFrame.duration;
  28520. frames.push(currentFrame);
  28521. return frames;
  28522. }; // Convert an array of frames into an array of Gop with each Gop being composed
  28523. // of the frames that make up that Gop
  28524. // Also keep track of cummulative data about the Gop from the frames such as the
  28525. // Gop duration, starting pts, etc.
  28526. var groupFramesIntoGops = function groupFramesIntoGops(frames) {
  28527. var i,
  28528. currentFrame,
  28529. currentGop = [],
  28530. gops = []; // We must pre-set some of the values on the Gop since we
  28531. // keep running totals of these values
  28532. currentGop.byteLength = 0;
  28533. currentGop.nalCount = 0;
  28534. currentGop.duration = 0;
  28535. currentGop.pts = frames[0].pts;
  28536. currentGop.dts = frames[0].dts; // store some metadata about all the Gops
  28537. gops.byteLength = 0;
  28538. gops.nalCount = 0;
  28539. gops.duration = 0;
  28540. gops.pts = frames[0].pts;
  28541. gops.dts = frames[0].dts;
  28542. for (i = 0; i < frames.length; i++) {
  28543. currentFrame = frames[i];
  28544. if (currentFrame.keyFrame) {
  28545. // Since the very first frame is expected to be an keyframe
  28546. // only push to the gops array when currentGop is not empty
  28547. if (currentGop.length) {
  28548. gops.push(currentGop);
  28549. gops.byteLength += currentGop.byteLength;
  28550. gops.nalCount += currentGop.nalCount;
  28551. gops.duration += currentGop.duration;
  28552. }
  28553. currentGop = [currentFrame];
  28554. currentGop.nalCount = currentFrame.length;
  28555. currentGop.byteLength = currentFrame.byteLength;
  28556. currentGop.pts = currentFrame.pts;
  28557. currentGop.dts = currentFrame.dts;
  28558. currentGop.duration = currentFrame.duration;
  28559. } else {
  28560. currentGop.duration += currentFrame.duration;
  28561. currentGop.nalCount += currentFrame.length;
  28562. currentGop.byteLength += currentFrame.byteLength;
  28563. currentGop.push(currentFrame);
  28564. }
  28565. }
  28566. if (gops.length && currentGop.duration <= 0) {
  28567. currentGop.duration = gops[gops.length - 1].duration;
  28568. }
  28569. gops.byteLength += currentGop.byteLength;
  28570. gops.nalCount += currentGop.nalCount;
  28571. gops.duration += currentGop.duration; // push the final Gop
  28572. gops.push(currentGop);
  28573. return gops;
  28574. };
  28575. /*
  28576. * Search for the first keyframe in the GOPs and throw away all frames
  28577. * until that keyframe. Then extend the duration of the pulled keyframe
  28578. * and pull the PTS and DTS of the keyframe so that it covers the time
  28579. * range of the frames that were disposed.
  28580. *
  28581. * @param {Array} gops video GOPs
  28582. * @returns {Array} modified video GOPs
  28583. */
  28584. var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
  28585. var currentGop;
  28586. if (!gops[0][0].keyFrame && gops.length > 1) {
  28587. // Remove the first GOP
  28588. currentGop = gops.shift();
  28589. gops.byteLength -= currentGop.byteLength;
  28590. gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
  28591. // first gop to cover the time period of the
  28592. // frames we just removed
  28593. gops[0][0].dts = currentGop.dts;
  28594. gops[0][0].pts = currentGop.pts;
  28595. gops[0][0].duration += currentGop.duration;
  28596. }
  28597. return gops;
  28598. };
  28599. /**
  28600. * Default sample object
  28601. * see ISO/IEC 14496-12:2012, section 8.6.4.3
  28602. */
  28603. var createDefaultSample = function createDefaultSample() {
  28604. return {
  28605. size: 0,
  28606. flags: {
  28607. isLeading: 0,
  28608. dependsOn: 1,
  28609. isDependedOn: 0,
  28610. hasRedundancy: 0,
  28611. degradationPriority: 0,
  28612. isNonSyncSample: 1
  28613. }
  28614. };
  28615. };
  28616. /*
  28617. * Collates information from a video frame into an object for eventual
  28618. * entry into an MP4 sample table.
  28619. *
  28620. * @param {Object} frame the video frame
  28621. * @param {Number} dataOffset the byte offset to position the sample
  28622. * @return {Object} object containing sample table info for a frame
  28623. */
  28624. var sampleForFrame = function sampleForFrame(frame, dataOffset) {
  28625. var sample = createDefaultSample();
  28626. sample.dataOffset = dataOffset;
  28627. sample.compositionTimeOffset = frame.pts - frame.dts;
  28628. sample.duration = frame.duration;
  28629. sample.size = 4 * frame.length; // Space for nal unit size
  28630. sample.size += frame.byteLength;
  28631. if (frame.keyFrame) {
  28632. sample.flags.dependsOn = 2;
  28633. sample.flags.isNonSyncSample = 0;
  28634. }
  28635. return sample;
  28636. }; // generate the track's sample table from an array of gops
  28637. var generateSampleTable$1 = function generateSampleTable(gops, baseDataOffset) {
  28638. var h,
  28639. i,
  28640. sample,
  28641. currentGop,
  28642. currentFrame,
  28643. dataOffset = baseDataOffset || 0,
  28644. samples = [];
  28645. for (h = 0; h < gops.length; h++) {
  28646. currentGop = gops[h];
  28647. for (i = 0; i < currentGop.length; i++) {
  28648. currentFrame = currentGop[i];
  28649. sample = sampleForFrame(currentFrame, dataOffset);
  28650. dataOffset += sample.size;
  28651. samples.push(sample);
  28652. }
  28653. }
  28654. return samples;
  28655. }; // generate the track's raw mdat data from an array of gops
  28656. var concatenateNalData = function concatenateNalData(gops) {
  28657. var h,
  28658. i,
  28659. j,
  28660. currentGop,
  28661. currentFrame,
  28662. currentNal,
  28663. dataOffset = 0,
  28664. nalsByteLength = gops.byteLength,
  28665. numberOfNals = gops.nalCount,
  28666. totalByteLength = nalsByteLength + 4 * numberOfNals,
  28667. data = new Uint8Array(totalByteLength),
  28668. view = new DataView(data.buffer); // For each Gop..
  28669. for (h = 0; h < gops.length; h++) {
  28670. currentGop = gops[h]; // For each Frame..
  28671. for (i = 0; i < currentGop.length; i++) {
  28672. currentFrame = currentGop[i]; // For each NAL..
  28673. for (j = 0; j < currentFrame.length; j++) {
  28674. currentNal = currentFrame[j];
  28675. view.setUint32(dataOffset, currentNal.data.byteLength);
  28676. dataOffset += 4;
  28677. data.set(currentNal.data, dataOffset);
  28678. dataOffset += currentNal.data.byteLength;
  28679. }
  28680. }
  28681. }
  28682. return data;
  28683. }; // generate the track's sample table from a frame
  28684. var generateSampleTableForFrame = function generateSampleTableForFrame(frame, baseDataOffset) {
  28685. var sample,
  28686. dataOffset = baseDataOffset || 0,
  28687. samples = [];
  28688. sample = sampleForFrame(frame, dataOffset);
  28689. samples.push(sample);
  28690. return samples;
  28691. }; // generate the track's raw mdat data from a frame
  28692. var concatenateNalDataForFrame = function concatenateNalDataForFrame(frame) {
  28693. var i,
  28694. currentNal,
  28695. dataOffset = 0,
  28696. nalsByteLength = frame.byteLength,
  28697. numberOfNals = frame.length,
  28698. totalByteLength = nalsByteLength + 4 * numberOfNals,
  28699. data = new Uint8Array(totalByteLength),
  28700. view = new DataView(data.buffer); // For each NAL..
  28701. for (i = 0; i < frame.length; i++) {
  28702. currentNal = frame[i];
  28703. view.setUint32(dataOffset, currentNal.data.byteLength);
  28704. dataOffset += 4;
  28705. data.set(currentNal.data, dataOffset);
  28706. dataOffset += currentNal.data.byteLength;
  28707. }
  28708. return data;
  28709. };
  28710. var frameUtils = {
  28711. groupNalsIntoFrames: groupNalsIntoFrames,
  28712. groupFramesIntoGops: groupFramesIntoGops,
  28713. extendFirstKeyFrame: extendFirstKeyFrame,
  28714. generateSampleTable: generateSampleTable$1,
  28715. concatenateNalData: concatenateNalData,
  28716. generateSampleTableForFrame: generateSampleTableForFrame,
  28717. concatenateNalDataForFrame: concatenateNalDataForFrame
  28718. };
  28719. /**
  28720. * mux.js
  28721. *
  28722. * Copyright (c) Brightcove
  28723. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  28724. */
  28725. var highPrefix = [33, 16, 5, 32, 164, 27];
  28726. var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
  28727. var zeroFill = function zeroFill(count) {
  28728. var a = [];
  28729. while (count--) {
  28730. a.push(0);
  28731. }
  28732. return a;
  28733. };
  28734. var makeTable = function makeTable(metaTable) {
  28735. return Object.keys(metaTable).reduce(function (obj, key) {
  28736. obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
  28737. return arr.concat(part);
  28738. }, []));
  28739. return obj;
  28740. }, {});
  28741. };
  28742. var silence;
  28743. var silence_1 = function silence_1() {
  28744. if (!silence) {
  28745. // Frames-of-silence to use for filling in missing AAC frames
  28746. var coneOfSilence = {
  28747. 96000: [highPrefix, [227, 64], zeroFill(154), [56]],
  28748. 88200: [highPrefix, [231], zeroFill(170), [56]],
  28749. 64000: [highPrefix, [248, 192], zeroFill(240), [56]],
  28750. 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
  28751. 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
  28752. 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
  28753. 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
  28754. 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
  28755. 12000: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 253, 128], zeroFill(259), [56]],
  28756. 11025: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 255, 192], zeroFill(268), [55, 175, 128], zeroFill(108), [112]],
  28757. 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
  28758. };
  28759. silence = makeTable(coneOfSilence);
  28760. }
  28761. return silence;
  28762. };
  28763. /**
  28764. * mux.js
  28765. *
  28766. * Copyright (c) Brightcove
  28767. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  28768. */
  28769. var ONE_SECOND_IN_TS$4 = 90000,
  28770. // 90kHz clock
  28771. secondsToVideoTs,
  28772. secondsToAudioTs,
  28773. videoTsToSeconds,
  28774. audioTsToSeconds,
  28775. audioTsToVideoTs,
  28776. videoTsToAudioTs,
  28777. metadataTsToSeconds;
  28778. secondsToVideoTs = function secondsToVideoTs(seconds) {
  28779. return seconds * ONE_SECOND_IN_TS$4;
  28780. };
  28781. secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
  28782. return seconds * sampleRate;
  28783. };
  28784. videoTsToSeconds = function videoTsToSeconds(timestamp) {
  28785. return timestamp / ONE_SECOND_IN_TS$4;
  28786. };
  28787. audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
  28788. return timestamp / sampleRate;
  28789. };
  28790. audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
  28791. return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
  28792. };
  28793. videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
  28794. return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
  28795. };
  28796. /**
  28797. * Adjust ID3 tag or caption timing information by the timeline pts values
  28798. * (if keepOriginalTimestamps is false) and convert to seconds
  28799. */
  28800. metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) {
  28801. return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
  28802. };
  28803. var clock = {
  28804. ONE_SECOND_IN_TS: ONE_SECOND_IN_TS$4,
  28805. secondsToVideoTs: secondsToVideoTs,
  28806. secondsToAudioTs: secondsToAudioTs,
  28807. videoTsToSeconds: videoTsToSeconds,
  28808. audioTsToSeconds: audioTsToSeconds,
  28809. audioTsToVideoTs: audioTsToVideoTs,
  28810. videoTsToAudioTs: videoTsToAudioTs,
  28811. metadataTsToSeconds: metadataTsToSeconds
  28812. };
  28813. /**
  28814. * mux.js
  28815. *
  28816. * Copyright (c) Brightcove
  28817. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  28818. */
  28819. /**
  28820. * Sum the `byteLength` properties of the data in each AAC frame
  28821. */
  28822. var sumFrameByteLengths = function sumFrameByteLengths(array) {
  28823. var i,
  28824. currentObj,
  28825. sum = 0; // sum the byteLength's all each nal unit in the frame
  28826. for (i = 0; i < array.length; i++) {
  28827. currentObj = array[i];
  28828. sum += currentObj.data.byteLength;
  28829. }
  28830. return sum;
  28831. }; // Possibly pad (prefix) the audio track with silence if appending this track
  28832. // would lead to the introduction of a gap in the audio buffer
  28833. var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
  28834. var baseMediaDecodeTimeTs,
  28835. frameDuration = 0,
  28836. audioGapDuration = 0,
  28837. audioFillFrameCount = 0,
  28838. audioFillDuration = 0,
  28839. silentFrame,
  28840. i,
  28841. firstFrame;
  28842. if (!frames.length) {
  28843. return;
  28844. }
  28845. baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
  28846. frameDuration = Math.ceil(clock.ONE_SECOND_IN_TS / (track.samplerate / 1024));
  28847. if (audioAppendStartTs && videoBaseMediaDecodeTime) {
  28848. // insert the shortest possible amount (audio gap or audio to video gap)
  28849. audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
  28850. audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
  28851. audioFillDuration = audioFillFrameCount * frameDuration;
  28852. } // don't attempt to fill gaps smaller than a single frame or larger
  28853. // than a half second
  28854. if (audioFillFrameCount < 1 || audioFillDuration > clock.ONE_SECOND_IN_TS / 2) {
  28855. return;
  28856. }
  28857. silentFrame = silence_1()[track.samplerate];
  28858. if (!silentFrame) {
  28859. // we don't have a silent frame pregenerated for the sample rate, so use a frame
  28860. // from the content instead
  28861. silentFrame = frames[0].data;
  28862. }
  28863. for (i = 0; i < audioFillFrameCount; i++) {
  28864. firstFrame = frames[0];
  28865. frames.splice(0, 0, {
  28866. data: silentFrame,
  28867. dts: firstFrame.dts - frameDuration,
  28868. pts: firstFrame.pts - frameDuration
  28869. });
  28870. }
  28871. track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
  28872. return audioFillDuration;
  28873. }; // If the audio segment extends before the earliest allowed dts
  28874. // value, remove AAC frames until starts at or after the earliest
  28875. // allowed DTS so that we don't end up with a negative baseMedia-
  28876. // DecodeTime for the audio track
  28877. var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
  28878. if (track.minSegmentDts >= earliestAllowedDts) {
  28879. return adtsFrames;
  28880. } // We will need to recalculate the earliest segment Dts
  28881. track.minSegmentDts = Infinity;
  28882. return adtsFrames.filter(function (currentFrame) {
  28883. // If this is an allowed frame, keep it and record it's Dts
  28884. if (currentFrame.dts >= earliestAllowedDts) {
  28885. track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
  28886. track.minSegmentPts = track.minSegmentDts;
  28887. return true;
  28888. } // Otherwise, discard it
  28889. return false;
  28890. });
  28891. }; // generate the track's raw mdat data from an array of frames
  28892. var generateSampleTable = function generateSampleTable(frames) {
  28893. var i,
  28894. currentFrame,
  28895. samples = [];
  28896. for (i = 0; i < frames.length; i++) {
  28897. currentFrame = frames[i];
  28898. samples.push({
  28899. size: currentFrame.data.byteLength,
  28900. duration: 1024 // For AAC audio, all samples contain 1024 samples
  28901. });
  28902. }
  28903. return samples;
  28904. }; // generate the track's sample table from an array of frames
  28905. var concatenateFrameData = function concatenateFrameData(frames) {
  28906. var i,
  28907. currentFrame,
  28908. dataOffset = 0,
  28909. data = new Uint8Array(sumFrameByteLengths(frames));
  28910. for (i = 0; i < frames.length; i++) {
  28911. currentFrame = frames[i];
  28912. data.set(currentFrame.data, dataOffset);
  28913. dataOffset += currentFrame.data.byteLength;
  28914. }
  28915. return data;
  28916. };
  28917. var audioFrameUtils = {
  28918. prefixWithSilence: prefixWithSilence,
  28919. trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
  28920. generateSampleTable: generateSampleTable,
  28921. concatenateFrameData: concatenateFrameData
  28922. };
  28923. /**
  28924. * mux.js
  28925. *
  28926. * Copyright (c) Brightcove
  28927. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  28928. */
  28929. var ONE_SECOND_IN_TS$3 = clock.ONE_SECOND_IN_TS;
  28930. /**
  28931. * Store information about the start and end of the track and the
  28932. * duration for each frame/sample we process in order to calculate
  28933. * the baseMediaDecodeTime
  28934. */
  28935. var collectDtsInfo = function collectDtsInfo(track, data) {
  28936. if (typeof data.pts === 'number') {
  28937. if (track.timelineStartInfo.pts === undefined) {
  28938. track.timelineStartInfo.pts = data.pts;
  28939. }
  28940. if (track.minSegmentPts === undefined) {
  28941. track.minSegmentPts = data.pts;
  28942. } else {
  28943. track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
  28944. }
  28945. if (track.maxSegmentPts === undefined) {
  28946. track.maxSegmentPts = data.pts;
  28947. } else {
  28948. track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
  28949. }
  28950. }
  28951. if (typeof data.dts === 'number') {
  28952. if (track.timelineStartInfo.dts === undefined) {
  28953. track.timelineStartInfo.dts = data.dts;
  28954. }
  28955. if (track.minSegmentDts === undefined) {
  28956. track.minSegmentDts = data.dts;
  28957. } else {
  28958. track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
  28959. }
  28960. if (track.maxSegmentDts === undefined) {
  28961. track.maxSegmentDts = data.dts;
  28962. } else {
  28963. track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
  28964. }
  28965. }
  28966. };
  28967. /**
  28968. * Clear values used to calculate the baseMediaDecodeTime between
  28969. * tracks
  28970. */
  28971. var clearDtsInfo = function clearDtsInfo(track) {
  28972. delete track.minSegmentDts;
  28973. delete track.maxSegmentDts;
  28974. delete track.minSegmentPts;
  28975. delete track.maxSegmentPts;
  28976. };
  28977. /**
  28978. * Calculate the track's baseMediaDecodeTime based on the earliest
  28979. * DTS the transmuxer has ever seen and the minimum DTS for the
  28980. * current track
  28981. * @param track {object} track metadata configuration
  28982. * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
  28983. * in the source; false to adjust the first segment to start at 0.
  28984. */
  28985. var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
  28986. var baseMediaDecodeTime,
  28987. scale,
  28988. minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
  28989. if (!keepOriginalTimestamps) {
  28990. minSegmentDts -= track.timelineStartInfo.dts;
  28991. } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
  28992. // we want the start of the first segment to be placed
  28993. baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
  28994. baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
  28995. baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
  28996. if (track.type === 'audio') {
  28997. // Audio has a different clock equal to the sampling_rate so we need to
  28998. // scale the PTS values into the clock rate of the track
  28999. scale = track.samplerate / ONE_SECOND_IN_TS$3;
  29000. baseMediaDecodeTime *= scale;
  29001. baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
  29002. }
  29003. return baseMediaDecodeTime;
  29004. };
  29005. var trackDecodeInfo = {
  29006. clearDtsInfo: clearDtsInfo,
  29007. calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
  29008. collectDtsInfo: collectDtsInfo
  29009. };
  29010. /**
  29011. * mux.js
  29012. *
  29013. * Copyright (c) Brightcove
  29014. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  29015. *
  29016. * Reads in-band caption information from a video elementary
  29017. * stream. Captions must follow the CEA-708 standard for injection
  29018. * into an MPEG-2 transport streams.
  29019. * @see https://en.wikipedia.org/wiki/CEA-708
  29020. * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
  29021. */
  29022. // payload type field to indicate how they are to be
  29023. // interpreted. CEAS-708 caption content is always transmitted with
  29024. // payload type 0x04.
  29025. var USER_DATA_REGISTERED_ITU_T_T35 = 4,
  29026. RBSP_TRAILING_BITS = 128;
  29027. /**
  29028. * Parse a supplemental enhancement information (SEI) NAL unit.
  29029. * Stops parsing once a message of type ITU T T35 has been found.
  29030. *
  29031. * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  29032. * @return {object} the parsed SEI payload
  29033. * @see Rec. ITU-T H.264, 7.3.2.3.1
  29034. */
  29035. var parseSei = function parseSei(bytes) {
  29036. var i = 0,
  29037. result = {
  29038. payloadType: -1,
  29039. payloadSize: 0
  29040. },
  29041. payloadType = 0,
  29042. payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
  29043. while (i < bytes.byteLength) {
  29044. // stop once we have hit the end of the sei_rbsp
  29045. if (bytes[i] === RBSP_TRAILING_BITS) {
  29046. break;
  29047. } // Parse payload type
  29048. while (bytes[i] === 0xFF) {
  29049. payloadType += 255;
  29050. i++;
  29051. }
  29052. payloadType += bytes[i++]; // Parse payload size
  29053. while (bytes[i] === 0xFF) {
  29054. payloadSize += 255;
  29055. i++;
  29056. }
  29057. payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
  29058. // there can only ever be one caption message in a frame's sei
  29059. if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
  29060. var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]);
  29061. if (userIdentifier === 'GA94') {
  29062. result.payloadType = payloadType;
  29063. result.payloadSize = payloadSize;
  29064. result.payload = bytes.subarray(i, i + payloadSize);
  29065. break;
  29066. } else {
  29067. result.payload = void 0;
  29068. }
  29069. } // skip the payload and parse the next message
  29070. i += payloadSize;
  29071. payloadType = 0;
  29072. payloadSize = 0;
  29073. }
  29074. return result;
  29075. }; // see ANSI/SCTE 128-1 (2013), section 8.1
  29076. var parseUserData = function parseUserData(sei) {
  29077. // itu_t_t35_contry_code must be 181 (United States) for
  29078. // captions
  29079. if (sei.payload[0] !== 181) {
  29080. return null;
  29081. } // itu_t_t35_provider_code should be 49 (ATSC) for captions
  29082. if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
  29083. return null;
  29084. } // the user_identifier should be "GA94" to indicate ATSC1 data
  29085. if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
  29086. return null;
  29087. } // finally, user_data_type_code should be 0x03 for caption data
  29088. if (sei.payload[7] !== 0x03) {
  29089. return null;
  29090. } // return the user_data_type_structure and strip the trailing
  29091. // marker bits
  29092. return sei.payload.subarray(8, sei.payload.length - 1);
  29093. }; // see CEA-708-D, section 4.4
  29094. var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  29095. var results = [],
  29096. i,
  29097. count,
  29098. offset,
  29099. data; // if this is just filler, return immediately
  29100. if (!(userData[0] & 0x40)) {
  29101. return results;
  29102. } // parse out the cc_data_1 and cc_data_2 fields
  29103. count = userData[0] & 0x1f;
  29104. for (i = 0; i < count; i++) {
  29105. offset = i * 3;
  29106. data = {
  29107. type: userData[offset + 2] & 0x03,
  29108. pts: pts
  29109. }; // capture cc data when cc_valid is 1
  29110. if (userData[offset + 2] & 0x04) {
  29111. data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
  29112. results.push(data);
  29113. }
  29114. }
  29115. return results;
  29116. };
  29117. var discardEmulationPreventionBytes$1 = function discardEmulationPreventionBytes(data) {
  29118. var length = data.byteLength,
  29119. emulationPreventionBytesPositions = [],
  29120. i = 1,
  29121. newLength,
  29122. newData; // Find all `Emulation Prevention Bytes`
  29123. while (i < length - 2) {
  29124. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  29125. emulationPreventionBytesPositions.push(i + 2);
  29126. i += 2;
  29127. } else {
  29128. i++;
  29129. }
  29130. } // If no Emulation Prevention Bytes were found just return the original
  29131. // array
  29132. if (emulationPreventionBytesPositions.length === 0) {
  29133. return data;
  29134. } // Create a new array to hold the NAL unit data
  29135. newLength = length - emulationPreventionBytesPositions.length;
  29136. newData = new Uint8Array(newLength);
  29137. var sourceIndex = 0;
  29138. for (i = 0; i < newLength; sourceIndex++, i++) {
  29139. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  29140. // Skip this byte
  29141. sourceIndex++; // Remove this position index
  29142. emulationPreventionBytesPositions.shift();
  29143. }
  29144. newData[i] = data[sourceIndex];
  29145. }
  29146. return newData;
  29147. }; // exports
  29148. var captionPacketParser = {
  29149. parseSei: parseSei,
  29150. parseUserData: parseUserData,
  29151. parseCaptionPackets: parseCaptionPackets,
  29152. discardEmulationPreventionBytes: discardEmulationPreventionBytes$1,
  29153. USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
  29154. }; // Link To Transport
  29155. // -----------------
  29156. var CaptionStream$1 = function CaptionStream(options) {
  29157. options = options || {};
  29158. CaptionStream.prototype.init.call(this); // parse708captions flag, default to true
  29159. this.parse708captions_ = typeof options.parse708captions === 'boolean' ? options.parse708captions : true;
  29160. this.captionPackets_ = [];
  29161. this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
  29162. new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
  29163. new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
  29164. new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
  29165. ];
  29166. if (this.parse708captions_) {
  29167. this.cc708Stream_ = new Cea708Stream({
  29168. captionServices: options.captionServices
  29169. }); // eslint-disable-line no-use-before-define
  29170. }
  29171. this.reset(); // forward data and done events from CCs to this CaptionStream
  29172. this.ccStreams_.forEach(function (cc) {
  29173. cc.on('data', this.trigger.bind(this, 'data'));
  29174. cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
  29175. cc.on('done', this.trigger.bind(this, 'done'));
  29176. }, this);
  29177. if (this.parse708captions_) {
  29178. this.cc708Stream_.on('data', this.trigger.bind(this, 'data'));
  29179. this.cc708Stream_.on('partialdone', this.trigger.bind(this, 'partialdone'));
  29180. this.cc708Stream_.on('done', this.trigger.bind(this, 'done'));
  29181. }
  29182. };
  29183. CaptionStream$1.prototype = new stream();
  29184. CaptionStream$1.prototype.push = function (event) {
  29185. var sei, userData, newCaptionPackets; // only examine SEI NALs
  29186. if (event.nalUnitType !== 'sei_rbsp') {
  29187. return;
  29188. } // parse the sei
  29189. sei = captionPacketParser.parseSei(event.escapedRBSP); // no payload data, skip
  29190. if (!sei.payload) {
  29191. return;
  29192. } // ignore everything but user_data_registered_itu_t_t35
  29193. if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
  29194. return;
  29195. } // parse out the user data payload
  29196. userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
  29197. if (!userData) {
  29198. return;
  29199. } // Sometimes, the same segment # will be downloaded twice. To stop the
  29200. // caption data from being processed twice, we track the latest dts we've
  29201. // received and ignore everything with a dts before that. However, since
  29202. // data for a specific dts can be split across packets on either side of
  29203. // a segment boundary, we need to make sure we *don't* ignore the packets
  29204. // from the *next* segment that have dts === this.latestDts_. By constantly
  29205. // tracking the number of packets received with dts === this.latestDts_, we
  29206. // know how many should be ignored once we start receiving duplicates.
  29207. if (event.dts < this.latestDts_) {
  29208. // We've started getting older data, so set the flag.
  29209. this.ignoreNextEqualDts_ = true;
  29210. return;
  29211. } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
  29212. this.numSameDts_--;
  29213. if (!this.numSameDts_) {
  29214. // We've received the last duplicate packet, time to start processing again
  29215. this.ignoreNextEqualDts_ = false;
  29216. }
  29217. return;
  29218. } // parse out CC data packets and save them for later
  29219. newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
  29220. this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
  29221. if (this.latestDts_ !== event.dts) {
  29222. this.numSameDts_ = 0;
  29223. }
  29224. this.numSameDts_++;
  29225. this.latestDts_ = event.dts;
  29226. };
  29227. CaptionStream$1.prototype.flushCCStreams = function (flushType) {
  29228. this.ccStreams_.forEach(function (cc) {
  29229. return flushType === 'flush' ? cc.flush() : cc.partialFlush();
  29230. }, this);
  29231. };
  29232. CaptionStream$1.prototype.flushStream = function (flushType) {
  29233. // make sure we actually parsed captions before proceeding
  29234. if (!this.captionPackets_.length) {
  29235. this.flushCCStreams(flushType);
  29236. return;
  29237. } // In Chrome, the Array#sort function is not stable so add a
  29238. // presortIndex that we can use to ensure we get a stable-sort
  29239. this.captionPackets_.forEach(function (elem, idx) {
  29240. elem.presortIndex = idx;
  29241. }); // sort caption byte-pairs based on their PTS values
  29242. this.captionPackets_.sort(function (a, b) {
  29243. if (a.pts === b.pts) {
  29244. return a.presortIndex - b.presortIndex;
  29245. }
  29246. return a.pts - b.pts;
  29247. });
  29248. this.captionPackets_.forEach(function (packet) {
  29249. if (packet.type < 2) {
  29250. // Dispatch packet to the right Cea608Stream
  29251. this.dispatchCea608Packet(packet);
  29252. } else {
  29253. // Dispatch packet to the Cea708Stream
  29254. this.dispatchCea708Packet(packet);
  29255. }
  29256. }, this);
  29257. this.captionPackets_.length = 0;
  29258. this.flushCCStreams(flushType);
  29259. };
  29260. CaptionStream$1.prototype.flush = function () {
  29261. return this.flushStream('flush');
  29262. }; // Only called if handling partial data
  29263. CaptionStream$1.prototype.partialFlush = function () {
  29264. return this.flushStream('partialFlush');
  29265. };
  29266. CaptionStream$1.prototype.reset = function () {
  29267. this.latestDts_ = null;
  29268. this.ignoreNextEqualDts_ = false;
  29269. this.numSameDts_ = 0;
  29270. this.activeCea608Channel_ = [null, null];
  29271. this.ccStreams_.forEach(function (ccStream) {
  29272. ccStream.reset();
  29273. });
  29274. }; // From the CEA-608 spec:
  29275. /*
  29276. * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
  29277. * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
  29278. * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
  29279. * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
  29280. * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
  29281. * to switch to captioning or Text.
  29282. */
  29283. // With that in mind, we ignore any data between an XDS control code and a
  29284. // subsequent closed-captioning control code.
  29285. CaptionStream$1.prototype.dispatchCea608Packet = function (packet) {
  29286. // NOTE: packet.type is the CEA608 field
  29287. if (this.setsTextOrXDSActive(packet)) {
  29288. this.activeCea608Channel_[packet.type] = null;
  29289. } else if (this.setsChannel1Active(packet)) {
  29290. this.activeCea608Channel_[packet.type] = 0;
  29291. } else if (this.setsChannel2Active(packet)) {
  29292. this.activeCea608Channel_[packet.type] = 1;
  29293. }
  29294. if (this.activeCea608Channel_[packet.type] === null) {
  29295. // If we haven't received anything to set the active channel, or the
  29296. // packets are Text/XDS data, discard the data; we don't want jumbled
  29297. // captions
  29298. return;
  29299. }
  29300. this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
  29301. };
  29302. CaptionStream$1.prototype.setsChannel1Active = function (packet) {
  29303. return (packet.ccData & 0x7800) === 0x1000;
  29304. };
  29305. CaptionStream$1.prototype.setsChannel2Active = function (packet) {
  29306. return (packet.ccData & 0x7800) === 0x1800;
  29307. };
  29308. CaptionStream$1.prototype.setsTextOrXDSActive = function (packet) {
  29309. return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
  29310. };
  29311. CaptionStream$1.prototype.dispatchCea708Packet = function (packet) {
  29312. if (this.parse708captions_) {
  29313. this.cc708Stream_.push(packet);
  29314. }
  29315. }; // ----------------------
  29316. // Session to Application
  29317. // ----------------------
  29318. // This hash maps special and extended character codes to their
  29319. // proper Unicode equivalent. The first one-byte key is just a
  29320. // non-standard character code. The two-byte keys that follow are
  29321. // the extended CEA708 character codes, along with the preceding
  29322. // 0x10 extended character byte to distinguish these codes from
  29323. // non-extended character codes. Every CEA708 character code that
  29324. // is not in this object maps directly to a standard unicode
  29325. // character code.
  29326. // The transparent space and non-breaking transparent space are
  29327. // technically not fully supported since there is no code to
  29328. // make them transparent, so they have normal non-transparent
  29329. // stand-ins.
  29330. // The special closed caption (CC) character isn't a standard
  29331. // unicode character, so a fairly similar unicode character was
  29332. // chosen in it's place.
  29333. var CHARACTER_TRANSLATION_708 = {
  29334. 0x7f: 0x266a,
  29335. // ♪
  29336. 0x1020: 0x20,
  29337. // Transparent Space
  29338. 0x1021: 0xa0,
  29339. // Nob-breaking Transparent Space
  29340. 0x1025: 0x2026,
  29341. // …
  29342. 0x102a: 0x0160,
  29343. // Š
  29344. 0x102c: 0x0152,
  29345. // Œ
  29346. 0x1030: 0x2588,
  29347. // █
  29348. 0x1031: 0x2018,
  29349. // ‘
  29350. 0x1032: 0x2019,
  29351. // ’
  29352. 0x1033: 0x201c,
  29353. // “
  29354. 0x1034: 0x201d,
  29355. // ”
  29356. 0x1035: 0x2022,
  29357. // •
  29358. 0x1039: 0x2122,
  29359. // ™
  29360. 0x103a: 0x0161,
  29361. // š
  29362. 0x103c: 0x0153,
  29363. // œ
  29364. 0x103d: 0x2120,
  29365. // ℠
  29366. 0x103f: 0x0178,
  29367. // Ÿ
  29368. 0x1076: 0x215b,
  29369. // ⅛
  29370. 0x1077: 0x215c,
  29371. // ⅜
  29372. 0x1078: 0x215d,
  29373. // ⅝
  29374. 0x1079: 0x215e,
  29375. // ⅞
  29376. 0x107a: 0x23d0,
  29377. // ⏐
  29378. 0x107b: 0x23a4,
  29379. // ⎤
  29380. 0x107c: 0x23a3,
  29381. // ⎣
  29382. 0x107d: 0x23af,
  29383. // ⎯
  29384. 0x107e: 0x23a6,
  29385. // ⎦
  29386. 0x107f: 0x23a1,
  29387. // ⎡
  29388. 0x10a0: 0x3138 // ㄸ (CC char)
  29389. };
  29390. var get708CharFromCode = function get708CharFromCode(code) {
  29391. var newCode = CHARACTER_TRANSLATION_708[code] || code;
  29392. if (code & 0x1000 && code === newCode) {
  29393. // Invalid extended code
  29394. return '';
  29395. }
  29396. return String.fromCharCode(newCode);
  29397. };
  29398. var within708TextBlock = function within708TextBlock(b) {
  29399. return 0x20 <= b && b <= 0x7f || 0xa0 <= b && b <= 0xff;
  29400. };
  29401. var Cea708Window = function Cea708Window(windowNum) {
  29402. this.windowNum = windowNum;
  29403. this.reset();
  29404. };
  29405. Cea708Window.prototype.reset = function () {
  29406. this.clearText();
  29407. this.pendingNewLine = false;
  29408. this.winAttr = {};
  29409. this.penAttr = {};
  29410. this.penLoc = {};
  29411. this.penColor = {}; // These default values are arbitrary,
  29412. // defineWindow will usually override them
  29413. this.visible = 0;
  29414. this.rowLock = 0;
  29415. this.columnLock = 0;
  29416. this.priority = 0;
  29417. this.relativePositioning = 0;
  29418. this.anchorVertical = 0;
  29419. this.anchorHorizontal = 0;
  29420. this.anchorPoint = 0;
  29421. this.rowCount = 1;
  29422. this.virtualRowCount = this.rowCount + 1;
  29423. this.columnCount = 41;
  29424. this.windowStyle = 0;
  29425. this.penStyle = 0;
  29426. };
  29427. Cea708Window.prototype.getText = function () {
  29428. return this.rows.join('\n');
  29429. };
  29430. Cea708Window.prototype.clearText = function () {
  29431. this.rows = [''];
  29432. this.rowIdx = 0;
  29433. };
  29434. Cea708Window.prototype.newLine = function (pts) {
  29435. if (this.rows.length >= this.virtualRowCount && typeof this.beforeRowOverflow === 'function') {
  29436. this.beforeRowOverflow(pts);
  29437. }
  29438. if (this.rows.length > 0) {
  29439. this.rows.push('');
  29440. this.rowIdx++;
  29441. } // Show all virtual rows since there's no visible scrolling
  29442. while (this.rows.length > this.virtualRowCount) {
  29443. this.rows.shift();
  29444. this.rowIdx--;
  29445. }
  29446. };
  29447. Cea708Window.prototype.isEmpty = function () {
  29448. if (this.rows.length === 0) {
  29449. return true;
  29450. } else if (this.rows.length === 1) {
  29451. return this.rows[0] === '';
  29452. }
  29453. return false;
  29454. };
  29455. Cea708Window.prototype.addText = function (text) {
  29456. this.rows[this.rowIdx] += text;
  29457. };
  29458. Cea708Window.prototype.backspace = function () {
  29459. if (!this.isEmpty()) {
  29460. var row = this.rows[this.rowIdx];
  29461. this.rows[this.rowIdx] = row.substr(0, row.length - 1);
  29462. }
  29463. };
  29464. var Cea708Service = function Cea708Service(serviceNum, encoding, stream) {
  29465. this.serviceNum = serviceNum;
  29466. this.text = '';
  29467. this.currentWindow = new Cea708Window(-1);
  29468. this.windows = [];
  29469. this.stream = stream; // Try to setup a TextDecoder if an `encoding` value was provided
  29470. if (typeof encoding === 'string') {
  29471. this.createTextDecoder(encoding);
  29472. }
  29473. };
  29474. /**
  29475. * Initialize service windows
  29476. * Must be run before service use
  29477. *
  29478. * @param {Integer} pts PTS value
  29479. * @param {Function} beforeRowOverflow Function to execute before row overflow of a window
  29480. */
  29481. Cea708Service.prototype.init = function (pts, beforeRowOverflow) {
  29482. this.startPts = pts;
  29483. for (var win = 0; win < 8; win++) {
  29484. this.windows[win] = new Cea708Window(win);
  29485. if (typeof beforeRowOverflow === 'function') {
  29486. this.windows[win].beforeRowOverflow = beforeRowOverflow;
  29487. }
  29488. }
  29489. };
  29490. /**
  29491. * Set current window of service to be affected by commands
  29492. *
  29493. * @param {Integer} windowNum Window number
  29494. */
  29495. Cea708Service.prototype.setCurrentWindow = function (windowNum) {
  29496. this.currentWindow = this.windows[windowNum];
  29497. };
  29498. /**
  29499. * Try to create a TextDecoder if it is natively supported
  29500. */
  29501. Cea708Service.prototype.createTextDecoder = function (encoding) {
  29502. if (typeof TextDecoder === 'undefined') {
  29503. this.stream.trigger('log', {
  29504. level: 'warn',
  29505. message: 'The `encoding` option is unsupported without TextDecoder support'
  29506. });
  29507. } else {
  29508. try {
  29509. this.textDecoder_ = new TextDecoder(encoding);
  29510. } catch (error) {
  29511. this.stream.trigger('log', {
  29512. level: 'warn',
  29513. message: 'TextDecoder could not be created with ' + encoding + ' encoding. ' + error
  29514. });
  29515. }
  29516. }
  29517. };
  29518. var Cea708Stream = function Cea708Stream(options) {
  29519. options = options || {};
  29520. Cea708Stream.prototype.init.call(this);
  29521. var self = this;
  29522. var captionServices = options.captionServices || {};
  29523. var captionServiceEncodings = {};
  29524. var serviceProps; // Get service encodings from captionServices option block
  29525. Object.keys(captionServices).forEach(function (serviceName) {
  29526. serviceProps = captionServices[serviceName];
  29527. if (/^SERVICE/.test(serviceName)) {
  29528. captionServiceEncodings[serviceName] = serviceProps.encoding;
  29529. }
  29530. });
  29531. this.serviceEncodings = captionServiceEncodings;
  29532. this.current708Packet = null;
  29533. this.services = {};
  29534. this.push = function (packet) {
  29535. if (packet.type === 3) {
  29536. // 708 packet start
  29537. self.new708Packet();
  29538. self.add708Bytes(packet);
  29539. } else {
  29540. if (self.current708Packet === null) {
  29541. // This should only happen at the start of a file if there's no packet start.
  29542. self.new708Packet();
  29543. }
  29544. self.add708Bytes(packet);
  29545. }
  29546. };
  29547. };
  29548. Cea708Stream.prototype = new stream();
  29549. /**
  29550. * Push current 708 packet, create new 708 packet.
  29551. */
  29552. Cea708Stream.prototype.new708Packet = function () {
  29553. if (this.current708Packet !== null) {
  29554. this.push708Packet();
  29555. }
  29556. this.current708Packet = {
  29557. data: [],
  29558. ptsVals: []
  29559. };
  29560. };
  29561. /**
  29562. * Add pts and both bytes from packet into current 708 packet.
  29563. */
  29564. Cea708Stream.prototype.add708Bytes = function (packet) {
  29565. var data = packet.ccData;
  29566. var byte0 = data >>> 8;
  29567. var byte1 = data & 0xff; // I would just keep a list of packets instead of bytes, but it isn't clear in the spec
  29568. // that service blocks will always line up with byte pairs.
  29569. this.current708Packet.ptsVals.push(packet.pts);
  29570. this.current708Packet.data.push(byte0);
  29571. this.current708Packet.data.push(byte1);
  29572. };
  29573. /**
  29574. * Parse completed 708 packet into service blocks and push each service block.
  29575. */
  29576. Cea708Stream.prototype.push708Packet = function () {
  29577. var packet708 = this.current708Packet;
  29578. var packetData = packet708.data;
  29579. var serviceNum = null;
  29580. var blockSize = null;
  29581. var i = 0;
  29582. var b = packetData[i++];
  29583. packet708.seq = b >> 6;
  29584. packet708.sizeCode = b & 0x3f; // 0b00111111;
  29585. for (; i < packetData.length; i++) {
  29586. b = packetData[i++];
  29587. serviceNum = b >> 5;
  29588. blockSize = b & 0x1f; // 0b00011111
  29589. if (serviceNum === 7 && blockSize > 0) {
  29590. // Extended service num
  29591. b = packetData[i++];
  29592. serviceNum = b;
  29593. }
  29594. this.pushServiceBlock(serviceNum, i, blockSize);
  29595. if (blockSize > 0) {
  29596. i += blockSize - 1;
  29597. }
  29598. }
  29599. };
  29600. /**
  29601. * Parse service block, execute commands, read text.
  29602. *
  29603. * Note: While many of these commands serve important purposes,
  29604. * many others just parse out the parameters or attributes, but
  29605. * nothing is done with them because this is not a full and complete
  29606. * implementation of the entire 708 spec.
  29607. *
  29608. * @param {Integer} serviceNum Service number
  29609. * @param {Integer} start Start index of the 708 packet data
  29610. * @param {Integer} size Block size
  29611. */
  29612. Cea708Stream.prototype.pushServiceBlock = function (serviceNum, start, size) {
  29613. var b;
  29614. var i = start;
  29615. var packetData = this.current708Packet.data;
  29616. var service = this.services[serviceNum];
  29617. if (!service) {
  29618. service = this.initService(serviceNum, i);
  29619. }
  29620. for (; i < start + size && i < packetData.length; i++) {
  29621. b = packetData[i];
  29622. if (within708TextBlock(b)) {
  29623. i = this.handleText(i, service);
  29624. } else if (b === 0x18) {
  29625. i = this.multiByteCharacter(i, service);
  29626. } else if (b === 0x10) {
  29627. i = this.extendedCommands(i, service);
  29628. } else if (0x80 <= b && b <= 0x87) {
  29629. i = this.setCurrentWindow(i, service);
  29630. } else if (0x98 <= b && b <= 0x9f) {
  29631. i = this.defineWindow(i, service);
  29632. } else if (b === 0x88) {
  29633. i = this.clearWindows(i, service);
  29634. } else if (b === 0x8c) {
  29635. i = this.deleteWindows(i, service);
  29636. } else if (b === 0x89) {
  29637. i = this.displayWindows(i, service);
  29638. } else if (b === 0x8a) {
  29639. i = this.hideWindows(i, service);
  29640. } else if (b === 0x8b) {
  29641. i = this.toggleWindows(i, service);
  29642. } else if (b === 0x97) {
  29643. i = this.setWindowAttributes(i, service);
  29644. } else if (b === 0x90) {
  29645. i = this.setPenAttributes(i, service);
  29646. } else if (b === 0x91) {
  29647. i = this.setPenColor(i, service);
  29648. } else if (b === 0x92) {
  29649. i = this.setPenLocation(i, service);
  29650. } else if (b === 0x8f) {
  29651. service = this.reset(i, service);
  29652. } else if (b === 0x08) {
  29653. // BS: Backspace
  29654. service.currentWindow.backspace();
  29655. } else if (b === 0x0c) {
  29656. // FF: Form feed
  29657. service.currentWindow.clearText();
  29658. } else if (b === 0x0d) {
  29659. // CR: Carriage return
  29660. service.currentWindow.pendingNewLine = true;
  29661. } else if (b === 0x0e) {
  29662. // HCR: Horizontal carriage return
  29663. service.currentWindow.clearText();
  29664. } else if (b === 0x8d) {
  29665. // DLY: Delay, nothing to do
  29666. i++;
  29667. } else ;
  29668. }
  29669. };
  29670. /**
  29671. * Execute an extended command
  29672. *
  29673. * @param {Integer} i Current index in the 708 packet
  29674. * @param {Service} service The service object to be affected
  29675. * @return {Integer} New index after parsing
  29676. */
  29677. Cea708Stream.prototype.extendedCommands = function (i, service) {
  29678. var packetData = this.current708Packet.data;
  29679. var b = packetData[++i];
  29680. if (within708TextBlock(b)) {
  29681. i = this.handleText(i, service, {
  29682. isExtended: true
  29683. });
  29684. }
  29685. return i;
  29686. };
  29687. /**
  29688. * Get PTS value of a given byte index
  29689. *
  29690. * @param {Integer} byteIndex Index of the byte
  29691. * @return {Integer} PTS
  29692. */
  29693. Cea708Stream.prototype.getPts = function (byteIndex) {
  29694. // There's 1 pts value per 2 bytes
  29695. return this.current708Packet.ptsVals[Math.floor(byteIndex / 2)];
  29696. };
  29697. /**
  29698. * Initializes a service
  29699. *
  29700. * @param {Integer} serviceNum Service number
  29701. * @return {Service} Initialized service object
  29702. */
  29703. Cea708Stream.prototype.initService = function (serviceNum, i) {
  29704. var serviceName = 'SERVICE' + serviceNum;
  29705. var self = this;
  29706. var serviceName;
  29707. var encoding;
  29708. if (serviceName in this.serviceEncodings) {
  29709. encoding = this.serviceEncodings[serviceName];
  29710. }
  29711. this.services[serviceNum] = new Cea708Service(serviceNum, encoding, self);
  29712. this.services[serviceNum].init(this.getPts(i), function (pts) {
  29713. self.flushDisplayed(pts, self.services[serviceNum]);
  29714. });
  29715. return this.services[serviceNum];
  29716. };
  29717. /**
  29718. * Execute text writing to current window
  29719. *
  29720. * @param {Integer} i Current index in the 708 packet
  29721. * @param {Service} service The service object to be affected
  29722. * @return {Integer} New index after parsing
  29723. */
  29724. Cea708Stream.prototype.handleText = function (i, service, options) {
  29725. var isExtended = options && options.isExtended;
  29726. var isMultiByte = options && options.isMultiByte;
  29727. var packetData = this.current708Packet.data;
  29728. var extended = isExtended ? 0x1000 : 0x0000;
  29729. var currentByte = packetData[i];
  29730. var nextByte = packetData[i + 1];
  29731. var win = service.currentWindow;
  29732. var _char;
  29733. var charCodeArray; // Use the TextDecoder if one was created for this service
  29734. if (service.textDecoder_ && !isExtended) {
  29735. if (isMultiByte) {
  29736. charCodeArray = [currentByte, nextByte];
  29737. i++;
  29738. } else {
  29739. charCodeArray = [currentByte];
  29740. }
  29741. _char = service.textDecoder_.decode(new Uint8Array(charCodeArray));
  29742. } else {
  29743. _char = get708CharFromCode(extended | currentByte);
  29744. }
  29745. if (win.pendingNewLine && !win.isEmpty()) {
  29746. win.newLine(this.getPts(i));
  29747. }
  29748. win.pendingNewLine = false;
  29749. win.addText(_char);
  29750. return i;
  29751. };
  29752. /**
  29753. * Handle decoding of multibyte character
  29754. *
  29755. * @param {Integer} i Current index in the 708 packet
  29756. * @param {Service} service The service object to be affected
  29757. * @return {Integer} New index after parsing
  29758. */
  29759. Cea708Stream.prototype.multiByteCharacter = function (i, service) {
  29760. var packetData = this.current708Packet.data;
  29761. var firstByte = packetData[i + 1];
  29762. var secondByte = packetData[i + 2];
  29763. if (within708TextBlock(firstByte) && within708TextBlock(secondByte)) {
  29764. i = this.handleText(++i, service, {
  29765. isMultiByte: true
  29766. });
  29767. }
  29768. return i;
  29769. };
  29770. /**
  29771. * Parse and execute the CW# command.
  29772. *
  29773. * Set the current window.
  29774. *
  29775. * @param {Integer} i Current index in the 708 packet
  29776. * @param {Service} service The service object to be affected
  29777. * @return {Integer} New index after parsing
  29778. */
  29779. Cea708Stream.prototype.setCurrentWindow = function (i, service) {
  29780. var packetData = this.current708Packet.data;
  29781. var b = packetData[i];
  29782. var windowNum = b & 0x07;
  29783. service.setCurrentWindow(windowNum);
  29784. return i;
  29785. };
  29786. /**
  29787. * Parse and execute the DF# command.
  29788. *
  29789. * Define a window and set it as the current window.
  29790. *
  29791. * @param {Integer} i Current index in the 708 packet
  29792. * @param {Service} service The service object to be affected
  29793. * @return {Integer} New index after parsing
  29794. */
  29795. Cea708Stream.prototype.defineWindow = function (i, service) {
  29796. var packetData = this.current708Packet.data;
  29797. var b = packetData[i];
  29798. var windowNum = b & 0x07;
  29799. service.setCurrentWindow(windowNum);
  29800. var win = service.currentWindow;
  29801. b = packetData[++i];
  29802. win.visible = (b & 0x20) >> 5; // v
  29803. win.rowLock = (b & 0x10) >> 4; // rl
  29804. win.columnLock = (b & 0x08) >> 3; // cl
  29805. win.priority = b & 0x07; // p
  29806. b = packetData[++i];
  29807. win.relativePositioning = (b & 0x80) >> 7; // rp
  29808. win.anchorVertical = b & 0x7f; // av
  29809. b = packetData[++i];
  29810. win.anchorHorizontal = b; // ah
  29811. b = packetData[++i];
  29812. win.anchorPoint = (b & 0xf0) >> 4; // ap
  29813. win.rowCount = b & 0x0f; // rc
  29814. b = packetData[++i];
  29815. win.columnCount = b & 0x3f; // cc
  29816. b = packetData[++i];
  29817. win.windowStyle = (b & 0x38) >> 3; // ws
  29818. win.penStyle = b & 0x07; // ps
  29819. // The spec says there are (rowCount+1) "virtual rows"
  29820. win.virtualRowCount = win.rowCount + 1;
  29821. return i;
  29822. };
  29823. /**
  29824. * Parse and execute the SWA command.
  29825. *
  29826. * Set attributes of the current window.
  29827. *
  29828. * @param {Integer} i Current index in the 708 packet
  29829. * @param {Service} service The service object to be affected
  29830. * @return {Integer} New index after parsing
  29831. */
  29832. Cea708Stream.prototype.setWindowAttributes = function (i, service) {
  29833. var packetData = this.current708Packet.data;
  29834. var b = packetData[i];
  29835. var winAttr = service.currentWindow.winAttr;
  29836. b = packetData[++i];
  29837. winAttr.fillOpacity = (b & 0xc0) >> 6; // fo
  29838. winAttr.fillRed = (b & 0x30) >> 4; // fr
  29839. winAttr.fillGreen = (b & 0x0c) >> 2; // fg
  29840. winAttr.fillBlue = b & 0x03; // fb
  29841. b = packetData[++i];
  29842. winAttr.borderType = (b & 0xc0) >> 6; // bt
  29843. winAttr.borderRed = (b & 0x30) >> 4; // br
  29844. winAttr.borderGreen = (b & 0x0c) >> 2; // bg
  29845. winAttr.borderBlue = b & 0x03; // bb
  29846. b = packetData[++i];
  29847. winAttr.borderType += (b & 0x80) >> 5; // bt
  29848. winAttr.wordWrap = (b & 0x40) >> 6; // ww
  29849. winAttr.printDirection = (b & 0x30) >> 4; // pd
  29850. winAttr.scrollDirection = (b & 0x0c) >> 2; // sd
  29851. winAttr.justify = b & 0x03; // j
  29852. b = packetData[++i];
  29853. winAttr.effectSpeed = (b & 0xf0) >> 4; // es
  29854. winAttr.effectDirection = (b & 0x0c) >> 2; // ed
  29855. winAttr.displayEffect = b & 0x03; // de
  29856. return i;
  29857. };
  29858. /**
  29859. * Gather text from all displayed windows and push a caption to output.
  29860. *
  29861. * @param {Integer} i Current index in the 708 packet
  29862. * @param {Service} service The service object to be affected
  29863. */
  29864. Cea708Stream.prototype.flushDisplayed = function (pts, service) {
  29865. var displayedText = []; // TODO: Positioning not supported, displaying multiple windows will not necessarily
  29866. // display text in the correct order, but sample files so far have not shown any issue.
  29867. for (var winId = 0; winId < 8; winId++) {
  29868. if (service.windows[winId].visible && !service.windows[winId].isEmpty()) {
  29869. displayedText.push(service.windows[winId].getText());
  29870. }
  29871. }
  29872. service.endPts = pts;
  29873. service.text = displayedText.join('\n\n');
  29874. this.pushCaption(service);
  29875. service.startPts = pts;
  29876. };
  29877. /**
  29878. * Push a caption to output if the caption contains text.
  29879. *
  29880. * @param {Service} service The service object to be affected
  29881. */
  29882. Cea708Stream.prototype.pushCaption = function (service) {
  29883. if (service.text !== '') {
  29884. this.trigger('data', {
  29885. startPts: service.startPts,
  29886. endPts: service.endPts,
  29887. text: service.text,
  29888. stream: 'cc708_' + service.serviceNum
  29889. });
  29890. service.text = '';
  29891. service.startPts = service.endPts;
  29892. }
  29893. };
  29894. /**
  29895. * Parse and execute the DSW command.
  29896. *
  29897. * Set visible property of windows based on the parsed bitmask.
  29898. *
  29899. * @param {Integer} i Current index in the 708 packet
  29900. * @param {Service} service The service object to be affected
  29901. * @return {Integer} New index after parsing
  29902. */
  29903. Cea708Stream.prototype.displayWindows = function (i, service) {
  29904. var packetData = this.current708Packet.data;
  29905. var b = packetData[++i];
  29906. var pts = this.getPts(i);
  29907. this.flushDisplayed(pts, service);
  29908. for (var winId = 0; winId < 8; winId++) {
  29909. if (b & 0x01 << winId) {
  29910. service.windows[winId].visible = 1;
  29911. }
  29912. }
  29913. return i;
  29914. };
  29915. /**
  29916. * Parse and execute the HDW command.
  29917. *
  29918. * Set visible property of windows based on the parsed bitmask.
  29919. *
  29920. * @param {Integer} i Current index in the 708 packet
  29921. * @param {Service} service The service object to be affected
  29922. * @return {Integer} New index after parsing
  29923. */
  29924. Cea708Stream.prototype.hideWindows = function (i, service) {
  29925. var packetData = this.current708Packet.data;
  29926. var b = packetData[++i];
  29927. var pts = this.getPts(i);
  29928. this.flushDisplayed(pts, service);
  29929. for (var winId = 0; winId < 8; winId++) {
  29930. if (b & 0x01 << winId) {
  29931. service.windows[winId].visible = 0;
  29932. }
  29933. }
  29934. return i;
  29935. };
  29936. /**
  29937. * Parse and execute the TGW command.
  29938. *
  29939. * Set visible property of windows based on the parsed bitmask.
  29940. *
  29941. * @param {Integer} i Current index in the 708 packet
  29942. * @param {Service} service The service object to be affected
  29943. * @return {Integer} New index after parsing
  29944. */
  29945. Cea708Stream.prototype.toggleWindows = function (i, service) {
  29946. var packetData = this.current708Packet.data;
  29947. var b = packetData[++i];
  29948. var pts = this.getPts(i);
  29949. this.flushDisplayed(pts, service);
  29950. for (var winId = 0; winId < 8; winId++) {
  29951. if (b & 0x01 << winId) {
  29952. service.windows[winId].visible ^= 1;
  29953. }
  29954. }
  29955. return i;
  29956. };
  29957. /**
  29958. * Parse and execute the CLW command.
  29959. *
  29960. * Clear text of windows based on the parsed bitmask.
  29961. *
  29962. * @param {Integer} i Current index in the 708 packet
  29963. * @param {Service} service The service object to be affected
  29964. * @return {Integer} New index after parsing
  29965. */
  29966. Cea708Stream.prototype.clearWindows = function (i, service) {
  29967. var packetData = this.current708Packet.data;
  29968. var b = packetData[++i];
  29969. var pts = this.getPts(i);
  29970. this.flushDisplayed(pts, service);
  29971. for (var winId = 0; winId < 8; winId++) {
  29972. if (b & 0x01 << winId) {
  29973. service.windows[winId].clearText();
  29974. }
  29975. }
  29976. return i;
  29977. };
  29978. /**
  29979. * Parse and execute the DLW command.
  29980. *
  29981. * Re-initialize windows based on the parsed bitmask.
  29982. *
  29983. * @param {Integer} i Current index in the 708 packet
  29984. * @param {Service} service The service object to be affected
  29985. * @return {Integer} New index after parsing
  29986. */
  29987. Cea708Stream.prototype.deleteWindows = function (i, service) {
  29988. var packetData = this.current708Packet.data;
  29989. var b = packetData[++i];
  29990. var pts = this.getPts(i);
  29991. this.flushDisplayed(pts, service);
  29992. for (var winId = 0; winId < 8; winId++) {
  29993. if (b & 0x01 << winId) {
  29994. service.windows[winId].reset();
  29995. }
  29996. }
  29997. return i;
  29998. };
  29999. /**
  30000. * Parse and execute the SPA command.
  30001. *
  30002. * Set pen attributes of the current window.
  30003. *
  30004. * @param {Integer} i Current index in the 708 packet
  30005. * @param {Service} service The service object to be affected
  30006. * @return {Integer} New index after parsing
  30007. */
  30008. Cea708Stream.prototype.setPenAttributes = function (i, service) {
  30009. var packetData = this.current708Packet.data;
  30010. var b = packetData[i];
  30011. var penAttr = service.currentWindow.penAttr;
  30012. b = packetData[++i];
  30013. penAttr.textTag = (b & 0xf0) >> 4; // tt
  30014. penAttr.offset = (b & 0x0c) >> 2; // o
  30015. penAttr.penSize = b & 0x03; // s
  30016. b = packetData[++i];
  30017. penAttr.italics = (b & 0x80) >> 7; // i
  30018. penAttr.underline = (b & 0x40) >> 6; // u
  30019. penAttr.edgeType = (b & 0x38) >> 3; // et
  30020. penAttr.fontStyle = b & 0x07; // fs
  30021. return i;
  30022. };
  30023. /**
  30024. * Parse and execute the SPC command.
  30025. *
  30026. * Set pen color of the current window.
  30027. *
  30028. * @param {Integer} i Current index in the 708 packet
  30029. * @param {Service} service The service object to be affected
  30030. * @return {Integer} New index after parsing
  30031. */
  30032. Cea708Stream.prototype.setPenColor = function (i, service) {
  30033. var packetData = this.current708Packet.data;
  30034. var b = packetData[i];
  30035. var penColor = service.currentWindow.penColor;
  30036. b = packetData[++i];
  30037. penColor.fgOpacity = (b & 0xc0) >> 6; // fo
  30038. penColor.fgRed = (b & 0x30) >> 4; // fr
  30039. penColor.fgGreen = (b & 0x0c) >> 2; // fg
  30040. penColor.fgBlue = b & 0x03; // fb
  30041. b = packetData[++i];
  30042. penColor.bgOpacity = (b & 0xc0) >> 6; // bo
  30043. penColor.bgRed = (b & 0x30) >> 4; // br
  30044. penColor.bgGreen = (b & 0x0c) >> 2; // bg
  30045. penColor.bgBlue = b & 0x03; // bb
  30046. b = packetData[++i];
  30047. penColor.edgeRed = (b & 0x30) >> 4; // er
  30048. penColor.edgeGreen = (b & 0x0c) >> 2; // eg
  30049. penColor.edgeBlue = b & 0x03; // eb
  30050. return i;
  30051. };
  30052. /**
  30053. * Parse and execute the SPL command.
  30054. *
  30055. * Set pen location of the current window.
  30056. *
  30057. * @param {Integer} i Current index in the 708 packet
  30058. * @param {Service} service The service object to be affected
  30059. * @return {Integer} New index after parsing
  30060. */
  30061. Cea708Stream.prototype.setPenLocation = function (i, service) {
  30062. var packetData = this.current708Packet.data;
  30063. var b = packetData[i];
  30064. var penLoc = service.currentWindow.penLoc; // Positioning isn't really supported at the moment, so this essentially just inserts a linebreak
  30065. service.currentWindow.pendingNewLine = true;
  30066. b = packetData[++i];
  30067. penLoc.row = b & 0x0f; // r
  30068. b = packetData[++i];
  30069. penLoc.column = b & 0x3f; // c
  30070. return i;
  30071. };
  30072. /**
  30073. * Execute the RST command.
  30074. *
  30075. * Reset service to a clean slate. Re-initialize.
  30076. *
  30077. * @param {Integer} i Current index in the 708 packet
  30078. * @param {Service} service The service object to be affected
  30079. * @return {Service} Re-initialized service
  30080. */
  30081. Cea708Stream.prototype.reset = function (i, service) {
  30082. var pts = this.getPts(i);
  30083. this.flushDisplayed(pts, service);
  30084. return this.initService(service.serviceNum, i);
  30085. }; // This hash maps non-ASCII, special, and extended character codes to their
  30086. // proper Unicode equivalent. The first keys that are only a single byte
  30087. // are the non-standard ASCII characters, which simply map the CEA608 byte
  30088. // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
  30089. // character codes, but have their MSB bitmasked with 0x03 so that a lookup
  30090. // can be performed regardless of the field and data channel on which the
  30091. // character code was received.
  30092. var CHARACTER_TRANSLATION = {
  30093. 0x2a: 0xe1,
  30094. // á
  30095. 0x5c: 0xe9,
  30096. // é
  30097. 0x5e: 0xed,
  30098. // í
  30099. 0x5f: 0xf3,
  30100. // ó
  30101. 0x60: 0xfa,
  30102. // ú
  30103. 0x7b: 0xe7,
  30104. // ç
  30105. 0x7c: 0xf7,
  30106. // ÷
  30107. 0x7d: 0xd1,
  30108. // Ñ
  30109. 0x7e: 0xf1,
  30110. // ñ
  30111. 0x7f: 0x2588,
  30112. // █
  30113. 0x0130: 0xae,
  30114. // ®
  30115. 0x0131: 0xb0,
  30116. // °
  30117. 0x0132: 0xbd,
  30118. // ½
  30119. 0x0133: 0xbf,
  30120. // ¿
  30121. 0x0134: 0x2122,
  30122. // ™
  30123. 0x0135: 0xa2,
  30124. // ¢
  30125. 0x0136: 0xa3,
  30126. // £
  30127. 0x0137: 0x266a,
  30128. // ♪
  30129. 0x0138: 0xe0,
  30130. // à
  30131. 0x0139: 0xa0,
  30132. //
  30133. 0x013a: 0xe8,
  30134. // è
  30135. 0x013b: 0xe2,
  30136. // â
  30137. 0x013c: 0xea,
  30138. // ê
  30139. 0x013d: 0xee,
  30140. // î
  30141. 0x013e: 0xf4,
  30142. // ô
  30143. 0x013f: 0xfb,
  30144. // û
  30145. 0x0220: 0xc1,
  30146. // Á
  30147. 0x0221: 0xc9,
  30148. // É
  30149. 0x0222: 0xd3,
  30150. // Ó
  30151. 0x0223: 0xda,
  30152. // Ú
  30153. 0x0224: 0xdc,
  30154. // Ü
  30155. 0x0225: 0xfc,
  30156. // ü
  30157. 0x0226: 0x2018,
  30158. // ‘
  30159. 0x0227: 0xa1,
  30160. // ¡
  30161. 0x0228: 0x2a,
  30162. // *
  30163. 0x0229: 0x27,
  30164. // '
  30165. 0x022a: 0x2014,
  30166. // —
  30167. 0x022b: 0xa9,
  30168. // ©
  30169. 0x022c: 0x2120,
  30170. // ℠
  30171. 0x022d: 0x2022,
  30172. // •
  30173. 0x022e: 0x201c,
  30174. // “
  30175. 0x022f: 0x201d,
  30176. // ”
  30177. 0x0230: 0xc0,
  30178. // À
  30179. 0x0231: 0xc2,
  30180. // Â
  30181. 0x0232: 0xc7,
  30182. // Ç
  30183. 0x0233: 0xc8,
  30184. // È
  30185. 0x0234: 0xca,
  30186. // Ê
  30187. 0x0235: 0xcb,
  30188. // Ë
  30189. 0x0236: 0xeb,
  30190. // ë
  30191. 0x0237: 0xce,
  30192. // Î
  30193. 0x0238: 0xcf,
  30194. // Ï
  30195. 0x0239: 0xef,
  30196. // ï
  30197. 0x023a: 0xd4,
  30198. // Ô
  30199. 0x023b: 0xd9,
  30200. // Ù
  30201. 0x023c: 0xf9,
  30202. // ù
  30203. 0x023d: 0xdb,
  30204. // Û
  30205. 0x023e: 0xab,
  30206. // «
  30207. 0x023f: 0xbb,
  30208. // »
  30209. 0x0320: 0xc3,
  30210. // Ã
  30211. 0x0321: 0xe3,
  30212. // ã
  30213. 0x0322: 0xcd,
  30214. // Í
  30215. 0x0323: 0xcc,
  30216. // Ì
  30217. 0x0324: 0xec,
  30218. // ì
  30219. 0x0325: 0xd2,
  30220. // Ò
  30221. 0x0326: 0xf2,
  30222. // ò
  30223. 0x0327: 0xd5,
  30224. // Õ
  30225. 0x0328: 0xf5,
  30226. // õ
  30227. 0x0329: 0x7b,
  30228. // {
  30229. 0x032a: 0x7d,
  30230. // }
  30231. 0x032b: 0x5c,
  30232. // \
  30233. 0x032c: 0x5e,
  30234. // ^
  30235. 0x032d: 0x5f,
  30236. // _
  30237. 0x032e: 0x7c,
  30238. // |
  30239. 0x032f: 0x7e,
  30240. // ~
  30241. 0x0330: 0xc4,
  30242. // Ä
  30243. 0x0331: 0xe4,
  30244. // ä
  30245. 0x0332: 0xd6,
  30246. // Ö
  30247. 0x0333: 0xf6,
  30248. // ö
  30249. 0x0334: 0xdf,
  30250. // ß
  30251. 0x0335: 0xa5,
  30252. // ¥
  30253. 0x0336: 0xa4,
  30254. // ¤
  30255. 0x0337: 0x2502,
  30256. // │
  30257. 0x0338: 0xc5,
  30258. // Å
  30259. 0x0339: 0xe5,
  30260. // å
  30261. 0x033a: 0xd8,
  30262. // Ø
  30263. 0x033b: 0xf8,
  30264. // ø
  30265. 0x033c: 0x250c,
  30266. // ┌
  30267. 0x033d: 0x2510,
  30268. // ┐
  30269. 0x033e: 0x2514,
  30270. // └
  30271. 0x033f: 0x2518 // ┘
  30272. };
  30273. var getCharFromCode = function getCharFromCode(code) {
  30274. if (code === null) {
  30275. return '';
  30276. }
  30277. code = CHARACTER_TRANSLATION[code] || code;
  30278. return String.fromCharCode(code);
  30279. }; // the index of the last row in a CEA-608 display buffer
  30280. var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
  30281. // getting it through bit logic.
  30282. var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620, 0x1700, 0x1720, 0x1000, 0x1300, 0x1320, 0x1400, 0x1420]; // CEA-608 captions are rendered onto a 34x15 matrix of character
  30283. // cells. The "bottom" row is the last element in the outer array.
  30284. var createDisplayBuffer = function createDisplayBuffer() {
  30285. var result = [],
  30286. i = BOTTOM_ROW + 1;
  30287. while (i--) {
  30288. result.push('');
  30289. }
  30290. return result;
  30291. };
  30292. var Cea608Stream = function Cea608Stream(field, dataChannel) {
  30293. Cea608Stream.prototype.init.call(this);
  30294. this.field_ = field || 0;
  30295. this.dataChannel_ = dataChannel || 0;
  30296. this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
  30297. this.setConstants();
  30298. this.reset();
  30299. this.push = function (packet) {
  30300. var data, swap, char0, char1, text; // remove the parity bits
  30301. data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
  30302. if (data === this.lastControlCode_) {
  30303. this.lastControlCode_ = null;
  30304. return;
  30305. } // Store control codes
  30306. if ((data & 0xf000) === 0x1000) {
  30307. this.lastControlCode_ = data;
  30308. } else if (data !== this.PADDING_) {
  30309. this.lastControlCode_ = null;
  30310. }
  30311. char0 = data >>> 8;
  30312. char1 = data & 0xff;
  30313. if (data === this.PADDING_) {
  30314. return;
  30315. } else if (data === this.RESUME_CAPTION_LOADING_) {
  30316. this.mode_ = 'popOn';
  30317. } else if (data === this.END_OF_CAPTION_) {
  30318. // If an EOC is received while in paint-on mode, the displayed caption
  30319. // text should be swapped to non-displayed memory as if it was a pop-on
  30320. // caption. Because of that, we should explicitly switch back to pop-on
  30321. // mode
  30322. this.mode_ = 'popOn';
  30323. this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
  30324. this.flushDisplayed(packet.pts); // flip memory
  30325. swap = this.displayed_;
  30326. this.displayed_ = this.nonDisplayed_;
  30327. this.nonDisplayed_ = swap; // start measuring the time to display the caption
  30328. this.startPts_ = packet.pts;
  30329. } else if (data === this.ROLL_UP_2_ROWS_) {
  30330. this.rollUpRows_ = 2;
  30331. this.setRollUp(packet.pts);
  30332. } else if (data === this.ROLL_UP_3_ROWS_) {
  30333. this.rollUpRows_ = 3;
  30334. this.setRollUp(packet.pts);
  30335. } else if (data === this.ROLL_UP_4_ROWS_) {
  30336. this.rollUpRows_ = 4;
  30337. this.setRollUp(packet.pts);
  30338. } else if (data === this.CARRIAGE_RETURN_) {
  30339. this.clearFormatting(packet.pts);
  30340. this.flushDisplayed(packet.pts);
  30341. this.shiftRowsUp_();
  30342. this.startPts_ = packet.pts;
  30343. } else if (data === this.BACKSPACE_) {
  30344. if (this.mode_ === 'popOn') {
  30345. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  30346. } else {
  30347. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  30348. }
  30349. } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
  30350. this.flushDisplayed(packet.pts);
  30351. this.displayed_ = createDisplayBuffer();
  30352. } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
  30353. this.nonDisplayed_ = createDisplayBuffer();
  30354. } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
  30355. if (this.mode_ !== 'paintOn') {
  30356. // NOTE: This should be removed when proper caption positioning is
  30357. // implemented
  30358. this.flushDisplayed(packet.pts);
  30359. this.displayed_ = createDisplayBuffer();
  30360. }
  30361. this.mode_ = 'paintOn';
  30362. this.startPts_ = packet.pts; // Append special characters to caption text
  30363. } else if (this.isSpecialCharacter(char0, char1)) {
  30364. // Bitmask char0 so that we can apply character transformations
  30365. // regardless of field and data channel.
  30366. // Then byte-shift to the left and OR with char1 so we can pass the
  30367. // entire character code to `getCharFromCode`.
  30368. char0 = (char0 & 0x03) << 8;
  30369. text = getCharFromCode(char0 | char1);
  30370. this[this.mode_](packet.pts, text);
  30371. this.column_++; // Append extended characters to caption text
  30372. } else if (this.isExtCharacter(char0, char1)) {
  30373. // Extended characters always follow their "non-extended" equivalents.
  30374. // IE if a "è" is desired, you'll always receive "eè"; non-compliant
  30375. // decoders are supposed to drop the "è", while compliant decoders
  30376. // backspace the "e" and insert "è".
  30377. // Delete the previous character
  30378. if (this.mode_ === 'popOn') {
  30379. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  30380. } else {
  30381. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  30382. } // Bitmask char0 so that we can apply character transformations
  30383. // regardless of field and data channel.
  30384. // Then byte-shift to the left and OR with char1 so we can pass the
  30385. // entire character code to `getCharFromCode`.
  30386. char0 = (char0 & 0x03) << 8;
  30387. text = getCharFromCode(char0 | char1);
  30388. this[this.mode_](packet.pts, text);
  30389. this.column_++; // Process mid-row codes
  30390. } else if (this.isMidRowCode(char0, char1)) {
  30391. // Attributes are not additive, so clear all formatting
  30392. this.clearFormatting(packet.pts); // According to the standard, mid-row codes
  30393. // should be replaced with spaces, so add one now
  30394. this[this.mode_](packet.pts, ' ');
  30395. this.column_++;
  30396. if ((char1 & 0xe) === 0xe) {
  30397. this.addFormatting(packet.pts, ['i']);
  30398. }
  30399. if ((char1 & 0x1) === 0x1) {
  30400. this.addFormatting(packet.pts, ['u']);
  30401. } // Detect offset control codes and adjust cursor
  30402. } else if (this.isOffsetControlCode(char0, char1)) {
  30403. // Cursor position is set by indent PAC (see below) in 4-column
  30404. // increments, with an additional offset code of 1-3 to reach any
  30405. // of the 32 columns specified by CEA-608. So all we need to do
  30406. // here is increment the column cursor by the given offset.
  30407. this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
  30408. } else if (this.isPAC(char0, char1)) {
  30409. // There's no logic for PAC -> row mapping, so we have to just
  30410. // find the row code in an array and use its index :(
  30411. var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
  30412. if (this.mode_ === 'rollUp') {
  30413. // This implies that the base row is incorrectly set.
  30414. // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
  30415. // of roll-up rows set.
  30416. if (row - this.rollUpRows_ + 1 < 0) {
  30417. row = this.rollUpRows_ - 1;
  30418. }
  30419. this.setRollUp(packet.pts, row);
  30420. }
  30421. if (row !== this.row_) {
  30422. // formatting is only persistent for current row
  30423. this.clearFormatting(packet.pts);
  30424. this.row_ = row;
  30425. } // All PACs can apply underline, so detect and apply
  30426. // (All odd-numbered second bytes set underline)
  30427. if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
  30428. this.addFormatting(packet.pts, ['u']);
  30429. }
  30430. if ((data & 0x10) === 0x10) {
  30431. // We've got an indent level code. Each successive even number
  30432. // increments the column cursor by 4, so we can get the desired
  30433. // column position by bit-shifting to the right (to get n/2)
  30434. // and multiplying by 4.
  30435. this.column_ = ((data & 0xe) >> 1) * 4;
  30436. }
  30437. if (this.isColorPAC(char1)) {
  30438. // it's a color code, though we only support white, which
  30439. // can be either normal or italicized. white italics can be
  30440. // either 0x4e or 0x6e depending on the row, so we just
  30441. // bitwise-and with 0xe to see if italics should be turned on
  30442. if ((char1 & 0xe) === 0xe) {
  30443. this.addFormatting(packet.pts, ['i']);
  30444. }
  30445. } // We have a normal character in char0, and possibly one in char1
  30446. } else if (this.isNormalChar(char0)) {
  30447. if (char1 === 0x00) {
  30448. char1 = null;
  30449. }
  30450. text = getCharFromCode(char0);
  30451. text += getCharFromCode(char1);
  30452. this[this.mode_](packet.pts, text);
  30453. this.column_ += text.length;
  30454. } // finish data processing
  30455. };
  30456. };
  30457. Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
  30458. // display buffer
  30459. Cea608Stream.prototype.flushDisplayed = function (pts) {
  30460. var content = this.displayed_ // remove spaces from the start and end of the string
  30461. .map(function (row, index) {
  30462. try {
  30463. return row.trim();
  30464. } catch (e) {
  30465. // Ordinarily, this shouldn't happen. However, caption
  30466. // parsing errors should not throw exceptions and
  30467. // break playback.
  30468. this.trigger('log', {
  30469. level: 'warn',
  30470. message: 'Skipping a malformed 608 caption at index ' + index + '.'
  30471. });
  30472. return '';
  30473. }
  30474. }, this) // combine all text rows to display in one cue
  30475. .join('\n') // and remove blank rows from the start and end, but not the middle
  30476. .replace(/^\n+|\n+$/g, '');
  30477. if (content.length) {
  30478. this.trigger('data', {
  30479. startPts: this.startPts_,
  30480. endPts: pts,
  30481. text: content,
  30482. stream: this.name_
  30483. });
  30484. }
  30485. };
  30486. /**
  30487. * Zero out the data, used for startup and on seek
  30488. */
  30489. Cea608Stream.prototype.reset = function () {
  30490. this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
  30491. // actually display captions. If a caption is shifted to a row
  30492. // with a lower index than this, it is cleared from the display
  30493. // buffer
  30494. this.topRow_ = 0;
  30495. this.startPts_ = 0;
  30496. this.displayed_ = createDisplayBuffer();
  30497. this.nonDisplayed_ = createDisplayBuffer();
  30498. this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
  30499. this.column_ = 0;
  30500. this.row_ = BOTTOM_ROW;
  30501. this.rollUpRows_ = 2; // This variable holds currently-applied formatting
  30502. this.formatting_ = [];
  30503. };
  30504. /**
  30505. * Sets up control code and related constants for this instance
  30506. */
  30507. Cea608Stream.prototype.setConstants = function () {
  30508. // The following attributes have these uses:
  30509. // ext_ : char0 for mid-row codes, and the base for extended
  30510. // chars (ext_+0, ext_+1, and ext_+2 are char0s for
  30511. // extended codes)
  30512. // control_: char0 for control codes, except byte-shifted to the
  30513. // left so that we can do this.control_ | CONTROL_CODE
  30514. // offset_: char0 for tab offset codes
  30515. //
  30516. // It's also worth noting that control codes, and _only_ control codes,
  30517. // differ between field 1 and field2. Field 2 control codes are always
  30518. // their field 1 value plus 1. That's why there's the "| field" on the
  30519. // control value.
  30520. if (this.dataChannel_ === 0) {
  30521. this.BASE_ = 0x10;
  30522. this.EXT_ = 0x11;
  30523. this.CONTROL_ = (0x14 | this.field_) << 8;
  30524. this.OFFSET_ = 0x17;
  30525. } else if (this.dataChannel_ === 1) {
  30526. this.BASE_ = 0x18;
  30527. this.EXT_ = 0x19;
  30528. this.CONTROL_ = (0x1c | this.field_) << 8;
  30529. this.OFFSET_ = 0x1f;
  30530. } // Constants for the LSByte command codes recognized by Cea608Stream. This
  30531. // list is not exhaustive. For a more comprehensive listing and semantics see
  30532. // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
  30533. // Padding
  30534. this.PADDING_ = 0x0000; // Pop-on Mode
  30535. this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
  30536. this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
  30537. this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
  30538. this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
  30539. this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
  30540. this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
  30541. this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
  30542. this.BACKSPACE_ = this.CONTROL_ | 0x21;
  30543. this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
  30544. this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
  30545. };
  30546. /**
  30547. * Detects if the 2-byte packet data is a special character
  30548. *
  30549. * Special characters have a second byte in the range 0x30 to 0x3f,
  30550. * with the first byte being 0x11 (for data channel 1) or 0x19 (for
  30551. * data channel 2).
  30552. *
  30553. * @param {Integer} char0 The first byte
  30554. * @param {Integer} char1 The second byte
  30555. * @return {Boolean} Whether the 2 bytes are an special character
  30556. */
  30557. Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
  30558. return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
  30559. };
  30560. /**
  30561. * Detects if the 2-byte packet data is an extended character
  30562. *
  30563. * Extended characters have a second byte in the range 0x20 to 0x3f,
  30564. * with the first byte being 0x12 or 0x13 (for data channel 1) or
  30565. * 0x1a or 0x1b (for data channel 2).
  30566. *
  30567. * @param {Integer} char0 The first byte
  30568. * @param {Integer} char1 The second byte
  30569. * @return {Boolean} Whether the 2 bytes are an extended character
  30570. */
  30571. Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
  30572. return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
  30573. };
  30574. /**
  30575. * Detects if the 2-byte packet is a mid-row code
  30576. *
  30577. * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
  30578. * the first byte being 0x11 (for data channel 1) or 0x19 (for data
  30579. * channel 2).
  30580. *
  30581. * @param {Integer} char0 The first byte
  30582. * @param {Integer} char1 The second byte
  30583. * @return {Boolean} Whether the 2 bytes are a mid-row code
  30584. */
  30585. Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
  30586. return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
  30587. };
  30588. /**
  30589. * Detects if the 2-byte packet is an offset control code
  30590. *
  30591. * Offset control codes have a second byte in the range 0x21 to 0x23,
  30592. * with the first byte being 0x17 (for data channel 1) or 0x1f (for
  30593. * data channel 2).
  30594. *
  30595. * @param {Integer} char0 The first byte
  30596. * @param {Integer} char1 The second byte
  30597. * @return {Boolean} Whether the 2 bytes are an offset control code
  30598. */
  30599. Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
  30600. return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
  30601. };
  30602. /**
  30603. * Detects if the 2-byte packet is a Preamble Address Code
  30604. *
  30605. * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
  30606. * or 0x18 to 0x1f (for data channel 2), with the second byte in the
  30607. * range 0x40 to 0x7f.
  30608. *
  30609. * @param {Integer} char0 The first byte
  30610. * @param {Integer} char1 The second byte
  30611. * @return {Boolean} Whether the 2 bytes are a PAC
  30612. */
  30613. Cea608Stream.prototype.isPAC = function (char0, char1) {
  30614. return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
  30615. };
  30616. /**
  30617. * Detects if a packet's second byte is in the range of a PAC color code
  30618. *
  30619. * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
  30620. * 0x60 to 0x6f.
  30621. *
  30622. * @param {Integer} char1 The second byte
  30623. * @return {Boolean} Whether the byte is a color PAC
  30624. */
  30625. Cea608Stream.prototype.isColorPAC = function (char1) {
  30626. return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
  30627. };
  30628. /**
  30629. * Detects if a single byte is in the range of a normal character
  30630. *
  30631. * Normal text bytes are in the range 0x20 to 0x7f.
  30632. *
  30633. * @param {Integer} char The byte
  30634. * @return {Boolean} Whether the byte is a normal character
  30635. */
  30636. Cea608Stream.prototype.isNormalChar = function (_char2) {
  30637. return _char2 >= 0x20 && _char2 <= 0x7f;
  30638. };
  30639. /**
  30640. * Configures roll-up
  30641. *
  30642. * @param {Integer} pts Current PTS
  30643. * @param {Integer} newBaseRow Used by PACs to slide the current window to
  30644. * a new position
  30645. */
  30646. Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
  30647. // Reset the base row to the bottom row when switching modes
  30648. if (this.mode_ !== 'rollUp') {
  30649. this.row_ = BOTTOM_ROW;
  30650. this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
  30651. this.flushDisplayed(pts);
  30652. this.nonDisplayed_ = createDisplayBuffer();
  30653. this.displayed_ = createDisplayBuffer();
  30654. }
  30655. if (newBaseRow !== undefined && newBaseRow !== this.row_) {
  30656. // move currently displayed captions (up or down) to the new base row
  30657. for (var i = 0; i < this.rollUpRows_; i++) {
  30658. this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
  30659. this.displayed_[this.row_ - i] = '';
  30660. }
  30661. }
  30662. if (newBaseRow === undefined) {
  30663. newBaseRow = this.row_;
  30664. }
  30665. this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
  30666. }; // Adds the opening HTML tag for the passed character to the caption text,
  30667. // and keeps track of it for later closing
  30668. Cea608Stream.prototype.addFormatting = function (pts, format) {
  30669. this.formatting_ = this.formatting_.concat(format);
  30670. var text = format.reduce(function (text, format) {
  30671. return text + '<' + format + '>';
  30672. }, '');
  30673. this[this.mode_](pts, text);
  30674. }; // Adds HTML closing tags for current formatting to caption text and
  30675. // clears remembered formatting
  30676. Cea608Stream.prototype.clearFormatting = function (pts) {
  30677. if (!this.formatting_.length) {
  30678. return;
  30679. }
  30680. var text = this.formatting_.reverse().reduce(function (text, format) {
  30681. return text + '</' + format + '>';
  30682. }, '');
  30683. this.formatting_ = [];
  30684. this[this.mode_](pts, text);
  30685. }; // Mode Implementations
  30686. Cea608Stream.prototype.popOn = function (pts, text) {
  30687. var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
  30688. baseRow += text;
  30689. this.nonDisplayed_[this.row_] = baseRow;
  30690. };
  30691. Cea608Stream.prototype.rollUp = function (pts, text) {
  30692. var baseRow = this.displayed_[this.row_];
  30693. baseRow += text;
  30694. this.displayed_[this.row_] = baseRow;
  30695. };
  30696. Cea608Stream.prototype.shiftRowsUp_ = function () {
  30697. var i; // clear out inactive rows
  30698. for (i = 0; i < this.topRow_; i++) {
  30699. this.displayed_[i] = '';
  30700. }
  30701. for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
  30702. this.displayed_[i] = '';
  30703. } // shift displayed rows up
  30704. for (i = this.topRow_; i < this.row_; i++) {
  30705. this.displayed_[i] = this.displayed_[i + 1];
  30706. } // clear out the bottom row
  30707. this.displayed_[this.row_] = '';
  30708. };
  30709. Cea608Stream.prototype.paintOn = function (pts, text) {
  30710. var baseRow = this.displayed_[this.row_];
  30711. baseRow += text;
  30712. this.displayed_[this.row_] = baseRow;
  30713. }; // exports
  30714. var captionStream = {
  30715. CaptionStream: CaptionStream$1,
  30716. Cea608Stream: Cea608Stream,
  30717. Cea708Stream: Cea708Stream
  30718. };
  30719. /**
  30720. * mux.js
  30721. *
  30722. * Copyright (c) Brightcove
  30723. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  30724. */
  30725. var streamTypes = {
  30726. H264_STREAM_TYPE: 0x1B,
  30727. ADTS_STREAM_TYPE: 0x0F,
  30728. METADATA_STREAM_TYPE: 0x15
  30729. };
  30730. var MAX_TS = 8589934592;
  30731. var RO_THRESH = 4294967296;
  30732. var TYPE_SHARED = 'shared';
  30733. var handleRollover$1 = function handleRollover(value, reference) {
  30734. var direction = 1;
  30735. if (value > reference) {
  30736. // If the current timestamp value is greater than our reference timestamp and we detect a
  30737. // timestamp rollover, this means the roll over is happening in the opposite direction.
  30738. // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
  30739. // point will be set to a small number, e.g. 1. The user then seeks backwards over the
  30740. // rollover point. In loading this segment, the timestamp values will be very large,
  30741. // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
  30742. // the time stamp to be `value - 2^33`.
  30743. direction = -1;
  30744. } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
  30745. // cause an incorrect adjustment.
  30746. while (Math.abs(reference - value) > RO_THRESH) {
  30747. value += direction * MAX_TS;
  30748. }
  30749. return value;
  30750. };
  30751. var TimestampRolloverStream$1 = function TimestampRolloverStream(type) {
  30752. var lastDTS, referenceDTS;
  30753. TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
  30754. // video and audio. We could use `undefined` here, but having a string
  30755. // makes debugging a little clearer.
  30756. this.type_ = type || TYPE_SHARED;
  30757. this.push = function (data) {
  30758. // Any "shared" rollover streams will accept _all_ data. Otherwise,
  30759. // streams will only accept data that matches their type.
  30760. if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
  30761. return;
  30762. }
  30763. if (referenceDTS === undefined) {
  30764. referenceDTS = data.dts;
  30765. }
  30766. data.dts = handleRollover$1(data.dts, referenceDTS);
  30767. data.pts = handleRollover$1(data.pts, referenceDTS);
  30768. lastDTS = data.dts;
  30769. this.trigger('data', data);
  30770. };
  30771. this.flush = function () {
  30772. referenceDTS = lastDTS;
  30773. this.trigger('done');
  30774. };
  30775. this.endTimeline = function () {
  30776. this.flush();
  30777. this.trigger('endedtimeline');
  30778. };
  30779. this.discontinuity = function () {
  30780. referenceDTS = void 0;
  30781. lastDTS = void 0;
  30782. };
  30783. this.reset = function () {
  30784. this.discontinuity();
  30785. this.trigger('reset');
  30786. };
  30787. };
  30788. TimestampRolloverStream$1.prototype = new stream();
  30789. var timestampRolloverStream = {
  30790. TimestampRolloverStream: TimestampRolloverStream$1,
  30791. handleRollover: handleRollover$1
  30792. };
  30793. var percentEncode$1 = function percentEncode(bytes, start, end) {
  30794. var i,
  30795. result = '';
  30796. for (i = start; i < end; i++) {
  30797. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  30798. }
  30799. return result;
  30800. },
  30801. // return the string representation of the specified byte range,
  30802. // interpreted as UTf-8.
  30803. parseUtf8 = function parseUtf8(bytes, start, end) {
  30804. return decodeURIComponent(percentEncode$1(bytes, start, end));
  30805. },
  30806. // return the string representation of the specified byte range,
  30807. // interpreted as ISO-8859-1.
  30808. parseIso88591$1 = function parseIso88591(bytes, start, end) {
  30809. return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
  30810. },
  30811. parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
  30812. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  30813. },
  30814. tagParsers = {
  30815. TXXX: function TXXX(tag) {
  30816. var i;
  30817. if (tag.data[0] !== 3) {
  30818. // ignore frames with unrecognized character encodings
  30819. return;
  30820. }
  30821. for (i = 1; i < tag.data.length; i++) {
  30822. if (tag.data[i] === 0) {
  30823. // parse the text fields
  30824. tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
  30825. tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
  30826. break;
  30827. }
  30828. }
  30829. tag.data = tag.value;
  30830. },
  30831. WXXX: function WXXX(tag) {
  30832. var i;
  30833. if (tag.data[0] !== 3) {
  30834. // ignore frames with unrecognized character encodings
  30835. return;
  30836. }
  30837. for (i = 1; i < tag.data.length; i++) {
  30838. if (tag.data[i] === 0) {
  30839. // parse the description and URL fields
  30840. tag.description = parseUtf8(tag.data, 1, i);
  30841. tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
  30842. break;
  30843. }
  30844. }
  30845. },
  30846. PRIV: function PRIV(tag) {
  30847. var i;
  30848. for (i = 0; i < tag.data.length; i++) {
  30849. if (tag.data[i] === 0) {
  30850. // parse the description and URL fields
  30851. tag.owner = parseIso88591$1(tag.data, 0, i);
  30852. break;
  30853. }
  30854. }
  30855. tag.privateData = tag.data.subarray(i + 1);
  30856. tag.data = tag.privateData;
  30857. }
  30858. },
  30859. _MetadataStream;
  30860. _MetadataStream = function MetadataStream(options) {
  30861. var settings = {
  30862. // the bytes of the program-level descriptor field in MP2T
  30863. // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
  30864. // program element descriptors"
  30865. descriptor: options && options.descriptor
  30866. },
  30867. // the total size in bytes of the ID3 tag being parsed
  30868. tagSize = 0,
  30869. // tag data that is not complete enough to be parsed
  30870. buffer = [],
  30871. // the total number of bytes currently in the buffer
  30872. bufferSize = 0,
  30873. i;
  30874. _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
  30875. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  30876. this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
  30877. if (settings.descriptor) {
  30878. for (i = 0; i < settings.descriptor.length; i++) {
  30879. this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
  30880. }
  30881. }
  30882. this.push = function (chunk) {
  30883. var tag, frameStart, frameSize, frame, i, frameHeader;
  30884. if (chunk.type !== 'timed-metadata') {
  30885. return;
  30886. } // if data_alignment_indicator is set in the PES header,
  30887. // we must have the start of a new ID3 tag. Assume anything
  30888. // remaining in the buffer was malformed and throw it out
  30889. if (chunk.dataAlignmentIndicator) {
  30890. bufferSize = 0;
  30891. buffer.length = 0;
  30892. } // ignore events that don't look like ID3 data
  30893. if (buffer.length === 0 && (chunk.data.length < 10 || chunk.data[0] !== 'I'.charCodeAt(0) || chunk.data[1] !== 'D'.charCodeAt(0) || chunk.data[2] !== '3'.charCodeAt(0))) {
  30894. this.trigger('log', {
  30895. level: 'warn',
  30896. message: 'Skipping unrecognized metadata packet'
  30897. });
  30898. return;
  30899. } // add this chunk to the data we've collected so far
  30900. buffer.push(chunk);
  30901. bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
  30902. if (buffer.length === 1) {
  30903. // the frame size is transmitted as a 28-bit integer in the
  30904. // last four bytes of the ID3 header.
  30905. // The most significant bit of each byte is dropped and the
  30906. // results concatenated to recover the actual value.
  30907. tagSize = parseSyncSafeInteger$1(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  30908. // convenient for our comparisons to include it
  30909. tagSize += 10;
  30910. } // if the entire frame has not arrived, wait for more data
  30911. if (bufferSize < tagSize) {
  30912. return;
  30913. } // collect the entire frame so it can be parsed
  30914. tag = {
  30915. data: new Uint8Array(tagSize),
  30916. frames: [],
  30917. pts: buffer[0].pts,
  30918. dts: buffer[0].dts
  30919. };
  30920. for (i = 0; i < tagSize;) {
  30921. tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
  30922. i += buffer[0].data.byteLength;
  30923. bufferSize -= buffer[0].data.byteLength;
  30924. buffer.shift();
  30925. } // find the start of the first frame and the end of the tag
  30926. frameStart = 10;
  30927. if (tag.data[5] & 0x40) {
  30928. // advance the frame start past the extended header
  30929. frameStart += 4; // header size field
  30930. frameStart += parseSyncSafeInteger$1(tag.data.subarray(10, 14)); // clip any padding off the end
  30931. tagSize -= parseSyncSafeInteger$1(tag.data.subarray(16, 20));
  30932. } // parse one or more ID3 frames
  30933. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  30934. do {
  30935. // determine the number of bytes in this frame
  30936. frameSize = parseSyncSafeInteger$1(tag.data.subarray(frameStart + 4, frameStart + 8));
  30937. if (frameSize < 1) {
  30938. this.trigger('log', {
  30939. level: 'warn',
  30940. message: 'Malformed ID3 frame encountered. Skipping metadata parsing.'
  30941. });
  30942. return;
  30943. }
  30944. frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
  30945. frame = {
  30946. id: frameHeader,
  30947. data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
  30948. };
  30949. frame.key = frame.id;
  30950. if (tagParsers[frame.id]) {
  30951. tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
  30952. // time for raw AAC data
  30953. if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
  30954. var d = frame.data,
  30955. size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  30956. size *= 4;
  30957. size += d[7] & 0x03;
  30958. frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
  30959. // on the value of this frame
  30960. // we couldn't have known the appropriate pts and dts before
  30961. // parsing this ID3 tag so set those values now
  30962. if (tag.pts === undefined && tag.dts === undefined) {
  30963. tag.pts = frame.timeStamp;
  30964. tag.dts = frame.timeStamp;
  30965. }
  30966. this.trigger('timestamp', frame);
  30967. }
  30968. }
  30969. tag.frames.push(frame);
  30970. frameStart += 10; // advance past the frame header
  30971. frameStart += frameSize; // advance past the frame body
  30972. } while (frameStart < tagSize);
  30973. this.trigger('data', tag);
  30974. };
  30975. };
  30976. _MetadataStream.prototype = new stream();
  30977. var metadataStream = _MetadataStream;
  30978. var TimestampRolloverStream = timestampRolloverStream.TimestampRolloverStream; // object types
  30979. var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
  30980. var MP2T_PACKET_LENGTH$1 = 188,
  30981. // bytes
  30982. SYNC_BYTE$1 = 0x47;
  30983. /**
  30984. * Splits an incoming stream of binary data into MPEG-2 Transport
  30985. * Stream packets.
  30986. */
  30987. _TransportPacketStream = function TransportPacketStream() {
  30988. var buffer = new Uint8Array(MP2T_PACKET_LENGTH$1),
  30989. bytesInBuffer = 0;
  30990. _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
  30991. /**
  30992. * Split a stream of data into M2TS packets
  30993. **/
  30994. this.push = function (bytes) {
  30995. var startIndex = 0,
  30996. endIndex = MP2T_PACKET_LENGTH$1,
  30997. everything; // If there are bytes remaining from the last segment, prepend them to the
  30998. // bytes that were pushed in
  30999. if (bytesInBuffer) {
  31000. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  31001. everything.set(buffer.subarray(0, bytesInBuffer));
  31002. everything.set(bytes, bytesInBuffer);
  31003. bytesInBuffer = 0;
  31004. } else {
  31005. everything = bytes;
  31006. } // While we have enough data for a packet
  31007. while (endIndex < everything.byteLength) {
  31008. // Look for a pair of start and end sync bytes in the data..
  31009. if (everything[startIndex] === SYNC_BYTE$1 && everything[endIndex] === SYNC_BYTE$1) {
  31010. // We found a packet so emit it and jump one whole packet forward in
  31011. // the stream
  31012. this.trigger('data', everything.subarray(startIndex, endIndex));
  31013. startIndex += MP2T_PACKET_LENGTH$1;
  31014. endIndex += MP2T_PACKET_LENGTH$1;
  31015. continue;
  31016. } // If we get here, we have somehow become de-synchronized and we need to step
  31017. // forward one byte at a time until we find a pair of sync bytes that denote
  31018. // a packet
  31019. startIndex++;
  31020. endIndex++;
  31021. } // If there was some data left over at the end of the segment that couldn't
  31022. // possibly be a whole packet, keep it because it might be the start of a packet
  31023. // that continues in the next segment
  31024. if (startIndex < everything.byteLength) {
  31025. buffer.set(everything.subarray(startIndex), 0);
  31026. bytesInBuffer = everything.byteLength - startIndex;
  31027. }
  31028. };
  31029. /**
  31030. * Passes identified M2TS packets to the TransportParseStream to be parsed
  31031. **/
  31032. this.flush = function () {
  31033. // If the buffer contains a whole packet when we are being flushed, emit it
  31034. // and empty the buffer. Otherwise hold onto the data because it may be
  31035. // important for decoding the next segment
  31036. if (bytesInBuffer === MP2T_PACKET_LENGTH$1 && buffer[0] === SYNC_BYTE$1) {
  31037. this.trigger('data', buffer);
  31038. bytesInBuffer = 0;
  31039. }
  31040. this.trigger('done');
  31041. };
  31042. this.endTimeline = function () {
  31043. this.flush();
  31044. this.trigger('endedtimeline');
  31045. };
  31046. this.reset = function () {
  31047. bytesInBuffer = 0;
  31048. this.trigger('reset');
  31049. };
  31050. };
  31051. _TransportPacketStream.prototype = new stream();
  31052. /**
  31053. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  31054. * forms of the individual transport stream packets.
  31055. */
  31056. _TransportParseStream = function TransportParseStream() {
  31057. var parsePsi, parsePat, parsePmt, self;
  31058. _TransportParseStream.prototype.init.call(this);
  31059. self = this;
  31060. this.packetsWaitingForPmt = [];
  31061. this.programMapTable = undefined;
  31062. parsePsi = function parsePsi(payload, psi) {
  31063. var offset = 0; // PSI packets may be split into multiple sections and those
  31064. // sections may be split into multiple packets. If a PSI
  31065. // section starts in this packet, the payload_unit_start_indicator
  31066. // will be true and the first byte of the payload will indicate
  31067. // the offset from the current position to the start of the
  31068. // section.
  31069. if (psi.payloadUnitStartIndicator) {
  31070. offset += payload[offset] + 1;
  31071. }
  31072. if (psi.type === 'pat') {
  31073. parsePat(payload.subarray(offset), psi);
  31074. } else {
  31075. parsePmt(payload.subarray(offset), psi);
  31076. }
  31077. };
  31078. parsePat = function parsePat(payload, pat) {
  31079. pat.section_number = payload[7]; // eslint-disable-line camelcase
  31080. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  31081. // skip the PSI header and parse the first PMT entry
  31082. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  31083. pat.pmtPid = self.pmtPid;
  31084. };
  31085. /**
  31086. * Parse out the relevant fields of a Program Map Table (PMT).
  31087. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  31088. * packet. The first byte in this array should be the table_id
  31089. * field.
  31090. * @param pmt {object} the object that should be decorated with
  31091. * fields parsed from the PMT.
  31092. */
  31093. parsePmt = function parsePmt(payload, pmt) {
  31094. var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
  31095. // take effect. We don't believe this should ever be the case
  31096. // for HLS but we'll ignore "forward" PMT declarations if we see
  31097. // them. Future PMT declarations have the current_next_indicator
  31098. // set to zero.
  31099. if (!(payload[5] & 0x01)) {
  31100. return;
  31101. } // overwrite any existing program map table
  31102. self.programMapTable = {
  31103. video: null,
  31104. audio: null,
  31105. 'timed-metadata': {}
  31106. }; // the mapping table ends at the end of the current section
  31107. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  31108. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  31109. // long the program info descriptors are
  31110. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
  31111. offset = 12 + programInfoLength;
  31112. while (offset < tableEnd) {
  31113. var streamType = payload[offset];
  31114. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
  31115. // TODO: should this be done for metadata too? for now maintain behavior of
  31116. // multiple metadata streams
  31117. if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
  31118. self.programMapTable.video = pid;
  31119. } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
  31120. self.programMapTable.audio = pid;
  31121. } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
  31122. // map pid to stream type for metadata streams
  31123. self.programMapTable['timed-metadata'][pid] = streamType;
  31124. } // move to the next table entry
  31125. // skip past the elementary stream descriptors, if present
  31126. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  31127. } // record the map on the packet as well
  31128. pmt.programMapTable = self.programMapTable;
  31129. };
  31130. /**
  31131. * Deliver a new MP2T packet to the next stream in the pipeline.
  31132. */
  31133. this.push = function (packet) {
  31134. var result = {},
  31135. offset = 4;
  31136. result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
  31137. result.pid = packet[1] & 0x1f;
  31138. result.pid <<= 8;
  31139. result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
  31140. // fifth byte of the TS packet header. The adaptation field is
  31141. // used to add stuffing to PES packets that don't fill a complete
  31142. // TS packet, and to specify some forms of timing and control data
  31143. // that we do not currently use.
  31144. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  31145. offset += packet[offset] + 1;
  31146. } // parse the rest of the packet based on the type
  31147. if (result.pid === 0) {
  31148. result.type = 'pat';
  31149. parsePsi(packet.subarray(offset), result);
  31150. this.trigger('data', result);
  31151. } else if (result.pid === this.pmtPid) {
  31152. result.type = 'pmt';
  31153. parsePsi(packet.subarray(offset), result);
  31154. this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
  31155. while (this.packetsWaitingForPmt.length) {
  31156. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  31157. }
  31158. } else if (this.programMapTable === undefined) {
  31159. // When we have not seen a PMT yet, defer further processing of
  31160. // PES packets until one has been parsed
  31161. this.packetsWaitingForPmt.push([packet, offset, result]);
  31162. } else {
  31163. this.processPes_(packet, offset, result);
  31164. }
  31165. };
  31166. this.processPes_ = function (packet, offset, result) {
  31167. // set the appropriate stream type
  31168. if (result.pid === this.programMapTable.video) {
  31169. result.streamType = streamTypes.H264_STREAM_TYPE;
  31170. } else if (result.pid === this.programMapTable.audio) {
  31171. result.streamType = streamTypes.ADTS_STREAM_TYPE;
  31172. } else {
  31173. // if not video or audio, it is timed-metadata or unknown
  31174. // if unknown, streamType will be undefined
  31175. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  31176. }
  31177. result.type = 'pes';
  31178. result.data = packet.subarray(offset);
  31179. this.trigger('data', result);
  31180. };
  31181. };
  31182. _TransportParseStream.prototype = new stream();
  31183. _TransportParseStream.STREAM_TYPES = {
  31184. h264: 0x1b,
  31185. adts: 0x0f
  31186. };
  31187. /**
  31188. * Reconsistutes program elementary stream (PES) packets from parsed
  31189. * transport stream packets. That is, if you pipe an
  31190. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  31191. * events will be events which capture the bytes for individual PES
  31192. * packets plus relevant metadata that has been extracted from the
  31193. * container.
  31194. */
  31195. _ElementaryStream = function ElementaryStream() {
  31196. var self = this,
  31197. segmentHadPmt = false,
  31198. // PES packet fragments
  31199. video = {
  31200. data: [],
  31201. size: 0
  31202. },
  31203. audio = {
  31204. data: [],
  31205. size: 0
  31206. },
  31207. timedMetadata = {
  31208. data: [],
  31209. size: 0
  31210. },
  31211. programMapTable,
  31212. parsePes = function parsePes(payload, pes) {
  31213. var ptsDtsFlags;
  31214. var startPrefix = payload[0] << 16 | payload[1] << 8 | payload[2]; // default to an empty array
  31215. pes.data = new Uint8Array(); // In certain live streams, the start of a TS fragment has ts packets
  31216. // that are frame data that is continuing from the previous fragment. This
  31217. // is to check that the pes data is the start of a new pes payload
  31218. if (startPrefix !== 1) {
  31219. return;
  31220. } // get the packet length, this will be 0 for video
  31221. pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
  31222. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
  31223. // and a DTS value. Determine what combination of values is
  31224. // available to work with.
  31225. ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  31226. // performs all bitwise operations on 32-bit integers but javascript
  31227. // supports a much greater range (52-bits) of integer using standard
  31228. // mathematical operations.
  31229. // We construct a 31-bit value using bitwise operators over the 31
  31230. // most significant bits and then multiply by 4 (equal to a left-shift
  31231. // of 2) before we add the final 2 least significant bits of the
  31232. // timestamp (equal to an OR.)
  31233. if (ptsDtsFlags & 0xC0) {
  31234. // the PTS and DTS are not written out directly. For information
  31235. // on how they are encoded, see
  31236. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  31237. pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
  31238. pes.pts *= 4; // Left shift by 2
  31239. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  31240. pes.dts = pes.pts;
  31241. if (ptsDtsFlags & 0x40) {
  31242. pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
  31243. pes.dts *= 4; // Left shift by 2
  31244. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  31245. }
  31246. } // the data section starts immediately after the PES header.
  31247. // pes_header_data_length specifies the number of header bytes
  31248. // that follow the last byte of the field.
  31249. pes.data = payload.subarray(9 + payload[8]);
  31250. },
  31251. /**
  31252. * Pass completely parsed PES packets to the next stream in the pipeline
  31253. **/
  31254. flushStream = function flushStream(stream, type, forceFlush) {
  31255. var packetData = new Uint8Array(stream.size),
  31256. event = {
  31257. type: type
  31258. },
  31259. i = 0,
  31260. offset = 0,
  31261. packetFlushable = false,
  31262. fragment; // do nothing if there is not enough buffered data for a complete
  31263. // PES header
  31264. if (!stream.data.length || stream.size < 9) {
  31265. return;
  31266. }
  31267. event.trackId = stream.data[0].pid; // reassemble the packet
  31268. for (i = 0; i < stream.data.length; i++) {
  31269. fragment = stream.data[i];
  31270. packetData.set(fragment.data, offset);
  31271. offset += fragment.data.byteLength;
  31272. } // parse assembled packet's PES header
  31273. parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
  31274. // check that there is enough stream data to fill the packet
  31275. packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right
  31276. if (forceFlush || packetFlushable) {
  31277. stream.size = 0;
  31278. stream.data.length = 0;
  31279. } // only emit packets that are complete. this is to avoid assembling
  31280. // incomplete PES packets due to poor segmentation
  31281. if (packetFlushable) {
  31282. self.trigger('data', event);
  31283. }
  31284. };
  31285. _ElementaryStream.prototype.init.call(this);
  31286. /**
  31287. * Identifies M2TS packet types and parses PES packets using metadata
  31288. * parsed from the PMT
  31289. **/
  31290. this.push = function (data) {
  31291. ({
  31292. pat: function pat() {// we have to wait for the PMT to arrive as well before we
  31293. // have any meaningful metadata
  31294. },
  31295. pes: function pes() {
  31296. var stream, streamType;
  31297. switch (data.streamType) {
  31298. case streamTypes.H264_STREAM_TYPE:
  31299. stream = video;
  31300. streamType = 'video';
  31301. break;
  31302. case streamTypes.ADTS_STREAM_TYPE:
  31303. stream = audio;
  31304. streamType = 'audio';
  31305. break;
  31306. case streamTypes.METADATA_STREAM_TYPE:
  31307. stream = timedMetadata;
  31308. streamType = 'timed-metadata';
  31309. break;
  31310. default:
  31311. // ignore unknown stream types
  31312. return;
  31313. } // if a new packet is starting, we can flush the completed
  31314. // packet
  31315. if (data.payloadUnitStartIndicator) {
  31316. flushStream(stream, streamType, true);
  31317. } // buffer this fragment until we are sure we've received the
  31318. // complete payload
  31319. stream.data.push(data);
  31320. stream.size += data.data.byteLength;
  31321. },
  31322. pmt: function pmt() {
  31323. var event = {
  31324. type: 'metadata',
  31325. tracks: []
  31326. };
  31327. programMapTable = data.programMapTable; // translate audio and video streams to tracks
  31328. if (programMapTable.video !== null) {
  31329. event.tracks.push({
  31330. timelineStartInfo: {
  31331. baseMediaDecodeTime: 0
  31332. },
  31333. id: +programMapTable.video,
  31334. codec: 'avc',
  31335. type: 'video'
  31336. });
  31337. }
  31338. if (programMapTable.audio !== null) {
  31339. event.tracks.push({
  31340. timelineStartInfo: {
  31341. baseMediaDecodeTime: 0
  31342. },
  31343. id: +programMapTable.audio,
  31344. codec: 'adts',
  31345. type: 'audio'
  31346. });
  31347. }
  31348. segmentHadPmt = true;
  31349. self.trigger('data', event);
  31350. }
  31351. })[data.type]();
  31352. };
  31353. this.reset = function () {
  31354. video.size = 0;
  31355. video.data.length = 0;
  31356. audio.size = 0;
  31357. audio.data.length = 0;
  31358. this.trigger('reset');
  31359. };
  31360. /**
  31361. * Flush any remaining input. Video PES packets may be of variable
  31362. * length. Normally, the start of a new video packet can trigger the
  31363. * finalization of the previous packet. That is not possible if no
  31364. * more video is forthcoming, however. In that case, some other
  31365. * mechanism (like the end of the file) has to be employed. When it is
  31366. * clear that no additional data is forthcoming, calling this method
  31367. * will flush the buffered packets.
  31368. */
  31369. this.flushStreams_ = function () {
  31370. // !!THIS ORDER IS IMPORTANT!!
  31371. // video first then audio
  31372. flushStream(video, 'video');
  31373. flushStream(audio, 'audio');
  31374. flushStream(timedMetadata, 'timed-metadata');
  31375. };
  31376. this.flush = function () {
  31377. // if on flush we haven't had a pmt emitted
  31378. // and we have a pmt to emit. emit the pmt
  31379. // so that we trigger a trackinfo downstream.
  31380. if (!segmentHadPmt && programMapTable) {
  31381. var pmt = {
  31382. type: 'metadata',
  31383. tracks: []
  31384. }; // translate audio and video streams to tracks
  31385. if (programMapTable.video !== null) {
  31386. pmt.tracks.push({
  31387. timelineStartInfo: {
  31388. baseMediaDecodeTime: 0
  31389. },
  31390. id: +programMapTable.video,
  31391. codec: 'avc',
  31392. type: 'video'
  31393. });
  31394. }
  31395. if (programMapTable.audio !== null) {
  31396. pmt.tracks.push({
  31397. timelineStartInfo: {
  31398. baseMediaDecodeTime: 0
  31399. },
  31400. id: +programMapTable.audio,
  31401. codec: 'adts',
  31402. type: 'audio'
  31403. });
  31404. }
  31405. self.trigger('data', pmt);
  31406. }
  31407. segmentHadPmt = false;
  31408. this.flushStreams_();
  31409. this.trigger('done');
  31410. };
  31411. };
  31412. _ElementaryStream.prototype = new stream();
  31413. var m2ts = {
  31414. PAT_PID: 0x0000,
  31415. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH$1,
  31416. TransportPacketStream: _TransportPacketStream,
  31417. TransportParseStream: _TransportParseStream,
  31418. ElementaryStream: _ElementaryStream,
  31419. TimestampRolloverStream: TimestampRolloverStream,
  31420. CaptionStream: captionStream.CaptionStream,
  31421. Cea608Stream: captionStream.Cea608Stream,
  31422. Cea708Stream: captionStream.Cea708Stream,
  31423. MetadataStream: metadataStream
  31424. };
  31425. for (var type in streamTypes) {
  31426. if (streamTypes.hasOwnProperty(type)) {
  31427. m2ts[type] = streamTypes[type];
  31428. }
  31429. }
  31430. var m2ts_1 = m2ts;
  31431. var ONE_SECOND_IN_TS$2 = clock.ONE_SECOND_IN_TS;
  31432. var _AdtsStream;
  31433. var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  31434. /*
  31435. * Accepts a ElementaryStream and emits data events with parsed
  31436. * AAC Audio Frames of the individual packets. Input audio in ADTS
  31437. * format is unpacked and re-emitted as AAC frames.
  31438. *
  31439. * @see http://wiki.multimedia.cx/index.php?title=ADTS
  31440. * @see http://wiki.multimedia.cx/?title=Understanding_AAC
  31441. */
  31442. _AdtsStream = function AdtsStream(handlePartialSegments) {
  31443. var buffer,
  31444. frameNum = 0;
  31445. _AdtsStream.prototype.init.call(this);
  31446. this.skipWarn_ = function (start, end) {
  31447. this.trigger('log', {
  31448. level: 'warn',
  31449. message: "adts skiping bytes " + start + " to " + end + " in frame " + frameNum + " outside syncword"
  31450. });
  31451. };
  31452. this.push = function (packet) {
  31453. var i = 0,
  31454. frameLength,
  31455. protectionSkipBytes,
  31456. oldBuffer,
  31457. sampleCount,
  31458. adtsFrameDuration;
  31459. if (!handlePartialSegments) {
  31460. frameNum = 0;
  31461. }
  31462. if (packet.type !== 'audio') {
  31463. // ignore non-audio data
  31464. return;
  31465. } // Prepend any data in the buffer to the input data so that we can parse
  31466. // aac frames the cross a PES packet boundary
  31467. if (buffer && buffer.length) {
  31468. oldBuffer = buffer;
  31469. buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
  31470. buffer.set(oldBuffer);
  31471. buffer.set(packet.data, oldBuffer.byteLength);
  31472. } else {
  31473. buffer = packet.data;
  31474. } // unpack any ADTS frames which have been fully received
  31475. // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
  31476. var skip; // We use i + 7 here because we want to be able to parse the entire header.
  31477. // If we don't have enough bytes to do that, then we definitely won't have a full frame.
  31478. while (i + 7 < buffer.length) {
  31479. // Look for the start of an ADTS header..
  31480. if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
  31481. if (typeof skip !== 'number') {
  31482. skip = i;
  31483. } // If a valid header was not found, jump one forward and attempt to
  31484. // find a valid ADTS header starting at the next byte
  31485. i++;
  31486. continue;
  31487. }
  31488. if (typeof skip === 'number') {
  31489. this.skipWarn_(skip, i);
  31490. skip = null;
  31491. } // The protection skip bit tells us if we have 2 bytes of CRC data at the
  31492. // end of the ADTS header
  31493. protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
  31494. // end of the sync sequence
  31495. // NOTE: frame length includes the size of the header
  31496. frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
  31497. sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
  31498. adtsFrameDuration = sampleCount * ONE_SECOND_IN_TS$2 / ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2]; // If we don't have enough data to actually finish this ADTS frame,
  31499. // then we have to wait for more data
  31500. if (buffer.byteLength - i < frameLength) {
  31501. break;
  31502. } // Otherwise, deliver the complete AAC frame
  31503. this.trigger('data', {
  31504. pts: packet.pts + frameNum * adtsFrameDuration,
  31505. dts: packet.dts + frameNum * adtsFrameDuration,
  31506. sampleCount: sampleCount,
  31507. audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
  31508. channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
  31509. samplerate: ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2],
  31510. samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
  31511. // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
  31512. samplesize: 16,
  31513. // data is the frame without it's header
  31514. data: buffer.subarray(i + 7 + protectionSkipBytes, i + frameLength)
  31515. });
  31516. frameNum++;
  31517. i += frameLength;
  31518. }
  31519. if (typeof skip === 'number') {
  31520. this.skipWarn_(skip, i);
  31521. skip = null;
  31522. } // remove processed bytes from the buffer.
  31523. buffer = buffer.subarray(i);
  31524. };
  31525. this.flush = function () {
  31526. frameNum = 0;
  31527. this.trigger('done');
  31528. };
  31529. this.reset = function () {
  31530. buffer = void 0;
  31531. this.trigger('reset');
  31532. };
  31533. this.endTimeline = function () {
  31534. buffer = void 0;
  31535. this.trigger('endedtimeline');
  31536. };
  31537. };
  31538. _AdtsStream.prototype = new stream();
  31539. var adts = _AdtsStream;
  31540. /**
  31541. * mux.js
  31542. *
  31543. * Copyright (c) Brightcove
  31544. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  31545. */
  31546. var ExpGolomb;
  31547. /**
  31548. * Parser for exponential Golomb codes, a variable-bitwidth number encoding
  31549. * scheme used by h264.
  31550. */
  31551. ExpGolomb = function ExpGolomb(workingData) {
  31552. var // the number of bytes left to examine in workingData
  31553. workingBytesAvailable = workingData.byteLength,
  31554. // the current word being examined
  31555. workingWord = 0,
  31556. // :uint
  31557. // the number of bits left to examine in the current word
  31558. workingBitsAvailable = 0; // :uint;
  31559. // ():uint
  31560. this.length = function () {
  31561. return 8 * workingBytesAvailable;
  31562. }; // ():uint
  31563. this.bitsAvailable = function () {
  31564. return 8 * workingBytesAvailable + workingBitsAvailable;
  31565. }; // ():void
  31566. this.loadWord = function () {
  31567. var position = workingData.byteLength - workingBytesAvailable,
  31568. workingBytes = new Uint8Array(4),
  31569. availableBytes = Math.min(4, workingBytesAvailable);
  31570. if (availableBytes === 0) {
  31571. throw new Error('no bytes available');
  31572. }
  31573. workingBytes.set(workingData.subarray(position, position + availableBytes));
  31574. workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
  31575. workingBitsAvailable = availableBytes * 8;
  31576. workingBytesAvailable -= availableBytes;
  31577. }; // (count:int):void
  31578. this.skipBits = function (count) {
  31579. var skipBytes; // :int
  31580. if (workingBitsAvailable > count) {
  31581. workingWord <<= count;
  31582. workingBitsAvailable -= count;
  31583. } else {
  31584. count -= workingBitsAvailable;
  31585. skipBytes = Math.floor(count / 8);
  31586. count -= skipBytes * 8;
  31587. workingBytesAvailable -= skipBytes;
  31588. this.loadWord();
  31589. workingWord <<= count;
  31590. workingBitsAvailable -= count;
  31591. }
  31592. }; // (size:int):uint
  31593. this.readBits = function (size) {
  31594. var bits = Math.min(workingBitsAvailable, size),
  31595. // :uint
  31596. valu = workingWord >>> 32 - bits; // :uint
  31597. // if size > 31, handle error
  31598. workingBitsAvailable -= bits;
  31599. if (workingBitsAvailable > 0) {
  31600. workingWord <<= bits;
  31601. } else if (workingBytesAvailable > 0) {
  31602. this.loadWord();
  31603. }
  31604. bits = size - bits;
  31605. if (bits > 0) {
  31606. return valu << bits | this.readBits(bits);
  31607. }
  31608. return valu;
  31609. }; // ():uint
  31610. this.skipLeadingZeros = function () {
  31611. var leadingZeroCount; // :uint
  31612. for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
  31613. if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
  31614. // the first bit of working word is 1
  31615. workingWord <<= leadingZeroCount;
  31616. workingBitsAvailable -= leadingZeroCount;
  31617. return leadingZeroCount;
  31618. }
  31619. } // we exhausted workingWord and still have not found a 1
  31620. this.loadWord();
  31621. return leadingZeroCount + this.skipLeadingZeros();
  31622. }; // ():void
  31623. this.skipUnsignedExpGolomb = function () {
  31624. this.skipBits(1 + this.skipLeadingZeros());
  31625. }; // ():void
  31626. this.skipExpGolomb = function () {
  31627. this.skipBits(1 + this.skipLeadingZeros());
  31628. }; // ():uint
  31629. this.readUnsignedExpGolomb = function () {
  31630. var clz = this.skipLeadingZeros(); // :uint
  31631. return this.readBits(clz + 1) - 1;
  31632. }; // ():int
  31633. this.readExpGolomb = function () {
  31634. var valu = this.readUnsignedExpGolomb(); // :int
  31635. if (0x01 & valu) {
  31636. // the number is odd if the low order bit is set
  31637. return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
  31638. }
  31639. return -1 * (valu >>> 1); // divide by two then make it negative
  31640. }; // Some convenience functions
  31641. // :Boolean
  31642. this.readBoolean = function () {
  31643. return this.readBits(1) === 1;
  31644. }; // ():int
  31645. this.readUnsignedByte = function () {
  31646. return this.readBits(8);
  31647. };
  31648. this.loadWord();
  31649. };
  31650. var expGolomb = ExpGolomb;
  31651. var _H264Stream, _NalByteStream;
  31652. var PROFILES_WITH_OPTIONAL_SPS_DATA;
  31653. /**
  31654. * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
  31655. */
  31656. _NalByteStream = function NalByteStream() {
  31657. var syncPoint = 0,
  31658. i,
  31659. buffer;
  31660. _NalByteStream.prototype.init.call(this);
  31661. /*
  31662. * Scans a byte stream and triggers a data event with the NAL units found.
  31663. * @param {Object} data Event received from H264Stream
  31664. * @param {Uint8Array} data.data The h264 byte stream to be scanned
  31665. *
  31666. * @see H264Stream.push
  31667. */
  31668. this.push = function (data) {
  31669. var swapBuffer;
  31670. if (!buffer) {
  31671. buffer = data.data;
  31672. } else {
  31673. swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
  31674. swapBuffer.set(buffer);
  31675. swapBuffer.set(data.data, buffer.byteLength);
  31676. buffer = swapBuffer;
  31677. }
  31678. var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B
  31679. // scan for NAL unit boundaries
  31680. // a match looks like this:
  31681. // 0 0 1 .. NAL .. 0 0 1
  31682. // ^ sync point ^ i
  31683. // or this:
  31684. // 0 0 1 .. NAL .. 0 0 0
  31685. // ^ sync point ^ i
  31686. // advance the sync point to a NAL start, if necessary
  31687. for (; syncPoint < len - 3; syncPoint++) {
  31688. if (buffer[syncPoint + 2] === 1) {
  31689. // the sync point is properly aligned
  31690. i = syncPoint + 5;
  31691. break;
  31692. }
  31693. }
  31694. while (i < len) {
  31695. // look at the current byte to determine if we've hit the end of
  31696. // a NAL unit boundary
  31697. switch (buffer[i]) {
  31698. case 0:
  31699. // skip past non-sync sequences
  31700. if (buffer[i - 1] !== 0) {
  31701. i += 2;
  31702. break;
  31703. } else if (buffer[i - 2] !== 0) {
  31704. i++;
  31705. break;
  31706. } // deliver the NAL unit if it isn't empty
  31707. if (syncPoint + 3 !== i - 2) {
  31708. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  31709. } // drop trailing zeroes
  31710. do {
  31711. i++;
  31712. } while (buffer[i] !== 1 && i < len);
  31713. syncPoint = i - 2;
  31714. i += 3;
  31715. break;
  31716. case 1:
  31717. // skip past non-sync sequences
  31718. if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
  31719. i += 3;
  31720. break;
  31721. } // deliver the NAL unit
  31722. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  31723. syncPoint = i - 2;
  31724. i += 3;
  31725. break;
  31726. default:
  31727. // the current byte isn't a one or zero, so it cannot be part
  31728. // of a sync sequence
  31729. i += 3;
  31730. break;
  31731. }
  31732. } // filter out the NAL units that were delivered
  31733. buffer = buffer.subarray(syncPoint);
  31734. i -= syncPoint;
  31735. syncPoint = 0;
  31736. };
  31737. this.reset = function () {
  31738. buffer = null;
  31739. syncPoint = 0;
  31740. this.trigger('reset');
  31741. };
  31742. this.flush = function () {
  31743. // deliver the last buffered NAL unit
  31744. if (buffer && buffer.byteLength > 3) {
  31745. this.trigger('data', buffer.subarray(syncPoint + 3));
  31746. } // reset the stream state
  31747. buffer = null;
  31748. syncPoint = 0;
  31749. this.trigger('done');
  31750. };
  31751. this.endTimeline = function () {
  31752. this.flush();
  31753. this.trigger('endedtimeline');
  31754. };
  31755. };
  31756. _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
  31757. // see Recommendation ITU-T H.264 (4/2013),
  31758. // 7.3.2.1.1 Sequence parameter set data syntax
  31759. PROFILES_WITH_OPTIONAL_SPS_DATA = {
  31760. 100: true,
  31761. 110: true,
  31762. 122: true,
  31763. 244: true,
  31764. 44: true,
  31765. 83: true,
  31766. 86: true,
  31767. 118: true,
  31768. 128: true,
  31769. // TODO: the three profiles below don't
  31770. // appear to have sps data in the specificiation anymore?
  31771. 138: true,
  31772. 139: true,
  31773. 134: true
  31774. };
  31775. /**
  31776. * Accepts input from a ElementaryStream and produces H.264 NAL unit data
  31777. * events.
  31778. */
  31779. _H264Stream = function H264Stream() {
  31780. var nalByteStream = new _NalByteStream(),
  31781. self,
  31782. trackId,
  31783. currentPts,
  31784. currentDts,
  31785. discardEmulationPreventionBytes,
  31786. readSequenceParameterSet,
  31787. skipScalingList;
  31788. _H264Stream.prototype.init.call(this);
  31789. self = this;
  31790. /*
  31791. * Pushes a packet from a stream onto the NalByteStream
  31792. *
  31793. * @param {Object} packet - A packet received from a stream
  31794. * @param {Uint8Array} packet.data - The raw bytes of the packet
  31795. * @param {Number} packet.dts - Decode timestamp of the packet
  31796. * @param {Number} packet.pts - Presentation timestamp of the packet
  31797. * @param {Number} packet.trackId - The id of the h264 track this packet came from
  31798. * @param {('video'|'audio')} packet.type - The type of packet
  31799. *
  31800. */
  31801. this.push = function (packet) {
  31802. if (packet.type !== 'video') {
  31803. return;
  31804. }
  31805. trackId = packet.trackId;
  31806. currentPts = packet.pts;
  31807. currentDts = packet.dts;
  31808. nalByteStream.push(packet);
  31809. };
  31810. /*
  31811. * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
  31812. * for the NALUs to the next stream component.
  31813. * Also, preprocess caption and sequence parameter NALUs.
  31814. *
  31815. * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
  31816. * @see NalByteStream.push
  31817. */
  31818. nalByteStream.on('data', function (data) {
  31819. var event = {
  31820. trackId: trackId,
  31821. pts: currentPts,
  31822. dts: currentDts,
  31823. data: data,
  31824. nalUnitTypeCode: data[0] & 0x1f
  31825. };
  31826. switch (event.nalUnitTypeCode) {
  31827. case 0x05:
  31828. event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
  31829. break;
  31830. case 0x06:
  31831. event.nalUnitType = 'sei_rbsp';
  31832. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  31833. break;
  31834. case 0x07:
  31835. event.nalUnitType = 'seq_parameter_set_rbsp';
  31836. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  31837. event.config = readSequenceParameterSet(event.escapedRBSP);
  31838. break;
  31839. case 0x08:
  31840. event.nalUnitType = 'pic_parameter_set_rbsp';
  31841. break;
  31842. case 0x09:
  31843. event.nalUnitType = 'access_unit_delimiter_rbsp';
  31844. break;
  31845. } // This triggers data on the H264Stream
  31846. self.trigger('data', event);
  31847. });
  31848. nalByteStream.on('done', function () {
  31849. self.trigger('done');
  31850. });
  31851. nalByteStream.on('partialdone', function () {
  31852. self.trigger('partialdone');
  31853. });
  31854. nalByteStream.on('reset', function () {
  31855. self.trigger('reset');
  31856. });
  31857. nalByteStream.on('endedtimeline', function () {
  31858. self.trigger('endedtimeline');
  31859. });
  31860. this.flush = function () {
  31861. nalByteStream.flush();
  31862. };
  31863. this.partialFlush = function () {
  31864. nalByteStream.partialFlush();
  31865. };
  31866. this.reset = function () {
  31867. nalByteStream.reset();
  31868. };
  31869. this.endTimeline = function () {
  31870. nalByteStream.endTimeline();
  31871. };
  31872. /**
  31873. * Advance the ExpGolomb decoder past a scaling list. The scaling
  31874. * list is optionally transmitted as part of a sequence parameter
  31875. * set and is not relevant to transmuxing.
  31876. * @param count {number} the number of entries in this scaling list
  31877. * @param expGolombDecoder {object} an ExpGolomb pointed to the
  31878. * start of a scaling list
  31879. * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  31880. */
  31881. skipScalingList = function skipScalingList(count, expGolombDecoder) {
  31882. var lastScale = 8,
  31883. nextScale = 8,
  31884. j,
  31885. deltaScale;
  31886. for (j = 0; j < count; j++) {
  31887. if (nextScale !== 0) {
  31888. deltaScale = expGolombDecoder.readExpGolomb();
  31889. nextScale = (lastScale + deltaScale + 256) % 256;
  31890. }
  31891. lastScale = nextScale === 0 ? lastScale : nextScale;
  31892. }
  31893. };
  31894. /**
  31895. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  31896. * Sequence Payload"
  31897. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  31898. * unit
  31899. * @return {Uint8Array} the RBSP without any Emulation
  31900. * Prevention Bytes
  31901. */
  31902. discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  31903. var length = data.byteLength,
  31904. emulationPreventionBytesPositions = [],
  31905. i = 1,
  31906. newLength,
  31907. newData; // Find all `Emulation Prevention Bytes`
  31908. while (i < length - 2) {
  31909. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  31910. emulationPreventionBytesPositions.push(i + 2);
  31911. i += 2;
  31912. } else {
  31913. i++;
  31914. }
  31915. } // If no Emulation Prevention Bytes were found just return the original
  31916. // array
  31917. if (emulationPreventionBytesPositions.length === 0) {
  31918. return data;
  31919. } // Create a new array to hold the NAL unit data
  31920. newLength = length - emulationPreventionBytesPositions.length;
  31921. newData = new Uint8Array(newLength);
  31922. var sourceIndex = 0;
  31923. for (i = 0; i < newLength; sourceIndex++, i++) {
  31924. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  31925. // Skip this byte
  31926. sourceIndex++; // Remove this position index
  31927. emulationPreventionBytesPositions.shift();
  31928. }
  31929. newData[i] = data[sourceIndex];
  31930. }
  31931. return newData;
  31932. };
  31933. /**
  31934. * Read a sequence parameter set and return some interesting video
  31935. * properties. A sequence parameter set is the H264 metadata that
  31936. * describes the properties of upcoming video frames.
  31937. * @param data {Uint8Array} the bytes of a sequence parameter set
  31938. * @return {object} an object with configuration parsed from the
  31939. * sequence parameter set, including the dimensions of the
  31940. * associated video frames.
  31941. */
  31942. readSequenceParameterSet = function readSequenceParameterSet(data) {
  31943. var frameCropLeftOffset = 0,
  31944. frameCropRightOffset = 0,
  31945. frameCropTopOffset = 0,
  31946. frameCropBottomOffset = 0,
  31947. expGolombDecoder,
  31948. profileIdc,
  31949. levelIdc,
  31950. profileCompatibility,
  31951. chromaFormatIdc,
  31952. picOrderCntType,
  31953. numRefFramesInPicOrderCntCycle,
  31954. picWidthInMbsMinus1,
  31955. picHeightInMapUnitsMinus1,
  31956. frameMbsOnlyFlag,
  31957. scalingListCount,
  31958. sarRatio = [1, 1],
  31959. aspectRatioIdc,
  31960. i;
  31961. expGolombDecoder = new expGolomb(data);
  31962. profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
  31963. profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
  31964. levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
  31965. expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
  31966. // some profiles have more optional data we don't need
  31967. if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
  31968. chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  31969. if (chromaFormatIdc === 3) {
  31970. expGolombDecoder.skipBits(1); // separate_colour_plane_flag
  31971. }
  31972. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
  31973. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
  31974. expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  31975. if (expGolombDecoder.readBoolean()) {
  31976. // seq_scaling_matrix_present_flag
  31977. scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
  31978. for (i = 0; i < scalingListCount; i++) {
  31979. if (expGolombDecoder.readBoolean()) {
  31980. // seq_scaling_list_present_flag[ i ]
  31981. if (i < 6) {
  31982. skipScalingList(16, expGolombDecoder);
  31983. } else {
  31984. skipScalingList(64, expGolombDecoder);
  31985. }
  31986. }
  31987. }
  31988. }
  31989. }
  31990. expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
  31991. picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  31992. if (picOrderCntType === 0) {
  31993. expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
  31994. } else if (picOrderCntType === 1) {
  31995. expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
  31996. expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
  31997. expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
  31998. numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
  31999. for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  32000. expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
  32001. }
  32002. }
  32003. expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
  32004. expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  32005. picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  32006. picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  32007. frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  32008. if (frameMbsOnlyFlag === 0) {
  32009. expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
  32010. }
  32011. expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
  32012. if (expGolombDecoder.readBoolean()) {
  32013. // frame_cropping_flag
  32014. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  32015. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  32016. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  32017. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  32018. }
  32019. if (expGolombDecoder.readBoolean()) {
  32020. // vui_parameters_present_flag
  32021. if (expGolombDecoder.readBoolean()) {
  32022. // aspect_ratio_info_present_flag
  32023. aspectRatioIdc = expGolombDecoder.readUnsignedByte();
  32024. switch (aspectRatioIdc) {
  32025. case 1:
  32026. sarRatio = [1, 1];
  32027. break;
  32028. case 2:
  32029. sarRatio = [12, 11];
  32030. break;
  32031. case 3:
  32032. sarRatio = [10, 11];
  32033. break;
  32034. case 4:
  32035. sarRatio = [16, 11];
  32036. break;
  32037. case 5:
  32038. sarRatio = [40, 33];
  32039. break;
  32040. case 6:
  32041. sarRatio = [24, 11];
  32042. break;
  32043. case 7:
  32044. sarRatio = [20, 11];
  32045. break;
  32046. case 8:
  32047. sarRatio = [32, 11];
  32048. break;
  32049. case 9:
  32050. sarRatio = [80, 33];
  32051. break;
  32052. case 10:
  32053. sarRatio = [18, 11];
  32054. break;
  32055. case 11:
  32056. sarRatio = [15, 11];
  32057. break;
  32058. case 12:
  32059. sarRatio = [64, 33];
  32060. break;
  32061. case 13:
  32062. sarRatio = [160, 99];
  32063. break;
  32064. case 14:
  32065. sarRatio = [4, 3];
  32066. break;
  32067. case 15:
  32068. sarRatio = [3, 2];
  32069. break;
  32070. case 16:
  32071. sarRatio = [2, 1];
  32072. break;
  32073. case 255:
  32074. {
  32075. sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
  32076. break;
  32077. }
  32078. }
  32079. if (sarRatio) {
  32080. sarRatio[0] / sarRatio[1];
  32081. }
  32082. }
  32083. }
  32084. return {
  32085. profileIdc: profileIdc,
  32086. levelIdc: levelIdc,
  32087. profileCompatibility: profileCompatibility,
  32088. width: (picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2,
  32089. height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2,
  32090. // sar is sample aspect ratio
  32091. sarRatio: sarRatio
  32092. };
  32093. };
  32094. };
  32095. _H264Stream.prototype = new stream();
  32096. var h264 = {
  32097. H264Stream: _H264Stream,
  32098. NalByteStream: _NalByteStream
  32099. };
  32100. /**
  32101. * mux.js
  32102. *
  32103. * Copyright (c) Brightcove
  32104. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  32105. *
  32106. * Utilities to detect basic properties and metadata about Aac data.
  32107. */
  32108. var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  32109. var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
  32110. var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
  32111. flags = header[byteIndex + 5],
  32112. footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0
  32113. returnSize = returnSize >= 0 ? returnSize : 0;
  32114. if (footerPresent) {
  32115. return returnSize + 20;
  32116. }
  32117. return returnSize + 10;
  32118. };
  32119. var getId3Offset = function getId3Offset(data, offset) {
  32120. if (data.length - offset < 10 || data[offset] !== 'I'.charCodeAt(0) || data[offset + 1] !== 'D'.charCodeAt(0) || data[offset + 2] !== '3'.charCodeAt(0)) {
  32121. return offset;
  32122. }
  32123. offset += parseId3TagSize(data, offset);
  32124. return getId3Offset(data, offset);
  32125. }; // TODO: use vhs-utils
  32126. var isLikelyAacData$1 = function isLikelyAacData(data) {
  32127. var offset = getId3Offset(data, 0);
  32128. return data.length >= offset + 2 && (data[offset] & 0xFF) === 0xFF && (data[offset + 1] & 0xF0) === 0xF0 && // verify that the 2 layer bits are 0, aka this
  32129. // is not mp3 data but aac data.
  32130. (data[offset + 1] & 0x16) === 0x10;
  32131. };
  32132. var parseSyncSafeInteger = function parseSyncSafeInteger(data) {
  32133. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  32134. }; // return a percent-encoded representation of the specified byte range
  32135. // @see http://en.wikipedia.org/wiki/Percent-encoding
  32136. var percentEncode = function percentEncode(bytes, start, end) {
  32137. var i,
  32138. result = '';
  32139. for (i = start; i < end; i++) {
  32140. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  32141. }
  32142. return result;
  32143. }; // return the string representation of the specified byte range,
  32144. // interpreted as ISO-8859-1.
  32145. var parseIso88591 = function parseIso88591(bytes, start, end) {
  32146. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  32147. };
  32148. var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
  32149. var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
  32150. middle = header[byteIndex + 4] << 3,
  32151. highTwo = header[byteIndex + 3] & 0x3 << 11;
  32152. return highTwo | middle | lowThree;
  32153. };
  32154. var parseType$2 = function parseType(header, byteIndex) {
  32155. if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
  32156. return 'timed-metadata';
  32157. } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
  32158. return 'audio';
  32159. }
  32160. return null;
  32161. };
  32162. var parseSampleRate = function parseSampleRate(packet) {
  32163. var i = 0;
  32164. while (i + 5 < packet.length) {
  32165. if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
  32166. // If a valid header was not found, jump one forward and attempt to
  32167. // find a valid ADTS header starting at the next byte
  32168. i++;
  32169. continue;
  32170. }
  32171. return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
  32172. }
  32173. return null;
  32174. };
  32175. var parseAacTimestamp = function parseAacTimestamp(packet) {
  32176. var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
  32177. frameStart = 10;
  32178. if (packet[5] & 0x40) {
  32179. // advance the frame start past the extended header
  32180. frameStart += 4; // header size field
  32181. frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
  32182. } // parse one or more ID3 frames
  32183. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  32184. do {
  32185. // determine the number of bytes in this frame
  32186. frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
  32187. if (frameSize < 1) {
  32188. return null;
  32189. }
  32190. frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
  32191. if (frameHeader === 'PRIV') {
  32192. frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
  32193. for (var i = 0; i < frame.byteLength; i++) {
  32194. if (frame[i] === 0) {
  32195. var owner = parseIso88591(frame, 0, i);
  32196. if (owner === 'com.apple.streaming.transportStreamTimestamp') {
  32197. var d = frame.subarray(i + 1);
  32198. var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  32199. size *= 4;
  32200. size += d[7] & 0x03;
  32201. return size;
  32202. }
  32203. break;
  32204. }
  32205. }
  32206. }
  32207. frameStart += 10; // advance past the frame header
  32208. frameStart += frameSize; // advance past the frame body
  32209. } while (frameStart < packet.byteLength);
  32210. return null;
  32211. };
  32212. var utils = {
  32213. isLikelyAacData: isLikelyAacData$1,
  32214. parseId3TagSize: parseId3TagSize,
  32215. parseAdtsSize: parseAdtsSize,
  32216. parseType: parseType$2,
  32217. parseSampleRate: parseSampleRate,
  32218. parseAacTimestamp: parseAacTimestamp
  32219. };
  32220. var _AacStream;
  32221. /**
  32222. * Splits an incoming stream of binary data into ADTS and ID3 Frames.
  32223. */
  32224. _AacStream = function AacStream() {
  32225. var everything = new Uint8Array(),
  32226. timeStamp = 0;
  32227. _AacStream.prototype.init.call(this);
  32228. this.setTimestamp = function (timestamp) {
  32229. timeStamp = timestamp;
  32230. };
  32231. this.push = function (bytes) {
  32232. var frameSize = 0,
  32233. byteIndex = 0,
  32234. bytesLeft,
  32235. chunk,
  32236. packet,
  32237. tempLength; // If there are bytes remaining from the last segment, prepend them to the
  32238. // bytes that were pushed in
  32239. if (everything.length) {
  32240. tempLength = everything.length;
  32241. everything = new Uint8Array(bytes.byteLength + tempLength);
  32242. everything.set(everything.subarray(0, tempLength));
  32243. everything.set(bytes, tempLength);
  32244. } else {
  32245. everything = bytes;
  32246. }
  32247. while (everything.length - byteIndex >= 3) {
  32248. if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
  32249. // Exit early because we don't have enough to parse
  32250. // the ID3 tag header
  32251. if (everything.length - byteIndex < 10) {
  32252. break;
  32253. } // check framesize
  32254. frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  32255. // to emit a full packet
  32256. // Add to byteIndex to support multiple ID3 tags in sequence
  32257. if (byteIndex + frameSize > everything.length) {
  32258. break;
  32259. }
  32260. chunk = {
  32261. type: 'timed-metadata',
  32262. data: everything.subarray(byteIndex, byteIndex + frameSize)
  32263. };
  32264. this.trigger('data', chunk);
  32265. byteIndex += frameSize;
  32266. continue;
  32267. } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
  32268. // Exit early because we don't have enough to parse
  32269. // the ADTS frame header
  32270. if (everything.length - byteIndex < 7) {
  32271. break;
  32272. }
  32273. frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  32274. // to emit a full packet
  32275. if (byteIndex + frameSize > everything.length) {
  32276. break;
  32277. }
  32278. packet = {
  32279. type: 'audio',
  32280. data: everything.subarray(byteIndex, byteIndex + frameSize),
  32281. pts: timeStamp,
  32282. dts: timeStamp
  32283. };
  32284. this.trigger('data', packet);
  32285. byteIndex += frameSize;
  32286. continue;
  32287. }
  32288. byteIndex++;
  32289. }
  32290. bytesLeft = everything.length - byteIndex;
  32291. if (bytesLeft > 0) {
  32292. everything = everything.subarray(byteIndex);
  32293. } else {
  32294. everything = new Uint8Array();
  32295. }
  32296. };
  32297. this.reset = function () {
  32298. everything = new Uint8Array();
  32299. this.trigger('reset');
  32300. };
  32301. this.endTimeline = function () {
  32302. everything = new Uint8Array();
  32303. this.trigger('endedtimeline');
  32304. };
  32305. };
  32306. _AacStream.prototype = new stream();
  32307. var aac = _AacStream; // constants
  32308. var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
  32309. var audioProperties = AUDIO_PROPERTIES;
  32310. var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio'];
  32311. var videoProperties = VIDEO_PROPERTIES;
  32312. var H264Stream = h264.H264Stream;
  32313. var isLikelyAacData = utils.isLikelyAacData;
  32314. var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS; // object types
  32315. var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
  32316. var retriggerForStream = function retriggerForStream(key, event) {
  32317. event.stream = key;
  32318. this.trigger('log', event);
  32319. };
  32320. var addPipelineLogRetriggers = function addPipelineLogRetriggers(transmuxer, pipeline) {
  32321. var keys = Object.keys(pipeline);
  32322. for (var i = 0; i < keys.length; i++) {
  32323. var key = keys[i]; // skip non-stream keys and headOfPipeline
  32324. // which is just a duplicate
  32325. if (key === 'headOfPipeline' || !pipeline[key].on) {
  32326. continue;
  32327. }
  32328. pipeline[key].on('log', retriggerForStream.bind(transmuxer, key));
  32329. }
  32330. };
  32331. /**
  32332. * Compare two arrays (even typed) for same-ness
  32333. */
  32334. var arrayEquals = function arrayEquals(a, b) {
  32335. var i;
  32336. if (a.length !== b.length) {
  32337. return false;
  32338. } // compare the value of each element in the array
  32339. for (i = 0; i < a.length; i++) {
  32340. if (a[i] !== b[i]) {
  32341. return false;
  32342. }
  32343. }
  32344. return true;
  32345. };
  32346. var generateSegmentTimingInfo = function generateSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
  32347. var ptsOffsetFromDts = startPts - startDts,
  32348. decodeDuration = endDts - startDts,
  32349. presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
  32350. // however, the player time values will reflect a start from the baseMediaDecodeTime.
  32351. // In order to provide relevant values for the player times, base timing info on the
  32352. // baseMediaDecodeTime and the DTS and PTS durations of the segment.
  32353. return {
  32354. start: {
  32355. dts: baseMediaDecodeTime,
  32356. pts: baseMediaDecodeTime + ptsOffsetFromDts
  32357. },
  32358. end: {
  32359. dts: baseMediaDecodeTime + decodeDuration,
  32360. pts: baseMediaDecodeTime + presentationDuration
  32361. },
  32362. prependedContentDuration: prependedContentDuration,
  32363. baseMediaDecodeTime: baseMediaDecodeTime
  32364. };
  32365. };
  32366. /**
  32367. * Constructs a single-track, ISO BMFF media segment from AAC data
  32368. * events. The output of this stream can be fed to a SourceBuffer
  32369. * configured with a suitable initialization segment.
  32370. * @param track {object} track metadata configuration
  32371. * @param options {object} transmuxer options object
  32372. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  32373. * in the source; false to adjust the first segment to start at 0.
  32374. */
  32375. _AudioSegmentStream = function AudioSegmentStream(track, options) {
  32376. var adtsFrames = [],
  32377. sequenceNumber,
  32378. earliestAllowedDts = 0,
  32379. audioAppendStartTs = 0,
  32380. videoBaseMediaDecodeTime = Infinity;
  32381. options = options || {};
  32382. sequenceNumber = options.firstSequenceNumber || 0;
  32383. _AudioSegmentStream.prototype.init.call(this);
  32384. this.push = function (data) {
  32385. trackDecodeInfo.collectDtsInfo(track, data);
  32386. if (track) {
  32387. audioProperties.forEach(function (prop) {
  32388. track[prop] = data[prop];
  32389. });
  32390. } // buffer audio data until end() is called
  32391. adtsFrames.push(data);
  32392. };
  32393. this.setEarliestDts = function (earliestDts) {
  32394. earliestAllowedDts = earliestDts;
  32395. };
  32396. this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  32397. videoBaseMediaDecodeTime = baseMediaDecodeTime;
  32398. };
  32399. this.setAudioAppendStart = function (timestamp) {
  32400. audioAppendStartTs = timestamp;
  32401. };
  32402. this.flush = function () {
  32403. var frames, moof, mdat, boxes, frameDuration, segmentDuration, videoClockCyclesOfSilencePrefixed; // return early if no audio data has been observed
  32404. if (adtsFrames.length === 0) {
  32405. this.trigger('done', 'AudioSegmentStream');
  32406. return;
  32407. }
  32408. frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
  32409. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); // amount of audio filled but the value is in video clock rather than audio clock
  32410. videoClockCyclesOfSilencePrefixed = audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
  32411. // samples (that is, adts frames) in the audio data
  32412. track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
  32413. mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
  32414. adtsFrames = [];
  32415. moof = mp4Generator.moof(sequenceNumber, [track]);
  32416. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
  32417. sequenceNumber++;
  32418. boxes.set(moof);
  32419. boxes.set(mdat, moof.byteLength);
  32420. trackDecodeInfo.clearDtsInfo(track);
  32421. frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with
  32422. // tests) on adding the timingInfo event. However, it seems unlikely that there's a
  32423. // valid use-case where an init segment/data should be triggered without associated
  32424. // frames. Leaving for now, but should be looked into.
  32425. if (frames.length) {
  32426. segmentDuration = frames.length * frameDuration;
  32427. this.trigger('segmentTimingInfo', generateSegmentTimingInfo( // The audio track's baseMediaDecodeTime is in audio clock cycles, but the
  32428. // frame info is in video clock cycles. Convert to match expectation of
  32429. // listeners (that all timestamps will be based on video clock cycles).
  32430. clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate), // frame times are already in video clock, as is segment duration
  32431. frames[0].dts, frames[0].pts, frames[0].dts + segmentDuration, frames[0].pts + segmentDuration, videoClockCyclesOfSilencePrefixed || 0));
  32432. this.trigger('timingInfo', {
  32433. start: frames[0].pts,
  32434. end: frames[0].pts + segmentDuration
  32435. });
  32436. }
  32437. this.trigger('data', {
  32438. track: track,
  32439. boxes: boxes
  32440. });
  32441. this.trigger('done', 'AudioSegmentStream');
  32442. };
  32443. this.reset = function () {
  32444. trackDecodeInfo.clearDtsInfo(track);
  32445. adtsFrames = [];
  32446. this.trigger('reset');
  32447. };
  32448. };
  32449. _AudioSegmentStream.prototype = new stream();
  32450. /**
  32451. * Constructs a single-track, ISO BMFF media segment from H264 data
  32452. * events. The output of this stream can be fed to a SourceBuffer
  32453. * configured with a suitable initialization segment.
  32454. * @param track {object} track metadata configuration
  32455. * @param options {object} transmuxer options object
  32456. * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
  32457. * gopsToAlignWith list when attempting to align gop pts
  32458. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  32459. * in the source; false to adjust the first segment to start at 0.
  32460. */
  32461. _VideoSegmentStream = function VideoSegmentStream(track, options) {
  32462. var sequenceNumber,
  32463. nalUnits = [],
  32464. gopsToAlignWith = [],
  32465. config,
  32466. pps;
  32467. options = options || {};
  32468. sequenceNumber = options.firstSequenceNumber || 0;
  32469. _VideoSegmentStream.prototype.init.call(this);
  32470. delete track.minPTS;
  32471. this.gopCache_ = [];
  32472. /**
  32473. * Constructs a ISO BMFF segment given H264 nalUnits
  32474. * @param {Object} nalUnit A data event representing a nalUnit
  32475. * @param {String} nalUnit.nalUnitType
  32476. * @param {Object} nalUnit.config Properties for a mp4 track
  32477. * @param {Uint8Array} nalUnit.data The nalUnit bytes
  32478. * @see lib/codecs/h264.js
  32479. **/
  32480. this.push = function (nalUnit) {
  32481. trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
  32482. if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
  32483. config = nalUnit.config;
  32484. track.sps = [nalUnit.data];
  32485. videoProperties.forEach(function (prop) {
  32486. track[prop] = config[prop];
  32487. }, this);
  32488. }
  32489. if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
  32490. pps = nalUnit.data;
  32491. track.pps = [nalUnit.data];
  32492. } // buffer video until flush() is called
  32493. nalUnits.push(nalUnit);
  32494. };
  32495. /**
  32496. * Pass constructed ISO BMFF track and boxes on to the
  32497. * next stream in the pipeline
  32498. **/
  32499. this.flush = function () {
  32500. var frames,
  32501. gopForFusion,
  32502. gops,
  32503. moof,
  32504. mdat,
  32505. boxes,
  32506. prependedContentDuration = 0,
  32507. firstGop,
  32508. lastGop; // Throw away nalUnits at the start of the byte stream until
  32509. // we find the first AUD
  32510. while (nalUnits.length) {
  32511. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  32512. break;
  32513. }
  32514. nalUnits.shift();
  32515. } // Return early if no video data has been observed
  32516. if (nalUnits.length === 0) {
  32517. this.resetStream_();
  32518. this.trigger('done', 'VideoSegmentStream');
  32519. return;
  32520. } // Organize the raw nal-units into arrays that represent
  32521. // higher-level constructs such as frames and gops
  32522. // (group-of-pictures)
  32523. frames = frameUtils.groupNalsIntoFrames(nalUnits);
  32524. gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
  32525. // a problem since MSE (on Chrome) requires a leading keyframe.
  32526. //
  32527. // We have two approaches to repairing this situation:
  32528. // 1) GOP-FUSION:
  32529. // This is where we keep track of the GOPS (group-of-pictures)
  32530. // from previous fragments and attempt to find one that we can
  32531. // prepend to the current fragment in order to create a valid
  32532. // fragment.
  32533. // 2) KEYFRAME-PULLING:
  32534. // Here we search for the first keyframe in the fragment and
  32535. // throw away all the frames between the start of the fragment
  32536. // and that keyframe. We then extend the duration and pull the
  32537. // PTS of the keyframe forward so that it covers the time range
  32538. // of the frames that were disposed of.
  32539. //
  32540. // #1 is far prefereable over #2 which can cause "stuttering" but
  32541. // requires more things to be just right.
  32542. if (!gops[0][0].keyFrame) {
  32543. // Search for a gop for fusion from our gopCache
  32544. gopForFusion = this.getGopForFusion_(nalUnits[0], track);
  32545. if (gopForFusion) {
  32546. // in order to provide more accurate timing information about the segment, save
  32547. // the number of seconds prepended to the original segment due to GOP fusion
  32548. prependedContentDuration = gopForFusion.duration;
  32549. gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
  32550. // new gop at the beginning
  32551. gops.byteLength += gopForFusion.byteLength;
  32552. gops.nalCount += gopForFusion.nalCount;
  32553. gops.pts = gopForFusion.pts;
  32554. gops.dts = gopForFusion.dts;
  32555. gops.duration += gopForFusion.duration;
  32556. } else {
  32557. // If we didn't find a candidate gop fall back to keyframe-pulling
  32558. gops = frameUtils.extendFirstKeyFrame(gops);
  32559. }
  32560. } // Trim gops to align with gopsToAlignWith
  32561. if (gopsToAlignWith.length) {
  32562. var alignedGops;
  32563. if (options.alignGopsAtEnd) {
  32564. alignedGops = this.alignGopsAtEnd_(gops);
  32565. } else {
  32566. alignedGops = this.alignGopsAtStart_(gops);
  32567. }
  32568. if (!alignedGops) {
  32569. // save all the nals in the last GOP into the gop cache
  32570. this.gopCache_.unshift({
  32571. gop: gops.pop(),
  32572. pps: track.pps,
  32573. sps: track.sps
  32574. }); // Keep a maximum of 6 GOPs in the cache
  32575. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  32576. nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
  32577. this.resetStream_();
  32578. this.trigger('done', 'VideoSegmentStream');
  32579. return;
  32580. } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
  32581. // when recalculated before sending off to CoalesceStream
  32582. trackDecodeInfo.clearDtsInfo(track);
  32583. gops = alignedGops;
  32584. }
  32585. trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
  32586. // samples (that is, frames) in the video data
  32587. track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
  32588. mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
  32589. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  32590. this.trigger('processedGopsInfo', gops.map(function (gop) {
  32591. return {
  32592. pts: gop.pts,
  32593. dts: gop.dts,
  32594. byteLength: gop.byteLength
  32595. };
  32596. }));
  32597. firstGop = gops[0];
  32598. lastGop = gops[gops.length - 1];
  32599. this.trigger('segmentTimingInfo', generateSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration));
  32600. this.trigger('timingInfo', {
  32601. start: gops[0].pts,
  32602. end: gops[gops.length - 1].pts + gops[gops.length - 1].duration
  32603. }); // save all the nals in the last GOP into the gop cache
  32604. this.gopCache_.unshift({
  32605. gop: gops.pop(),
  32606. pps: track.pps,
  32607. sps: track.sps
  32608. }); // Keep a maximum of 6 GOPs in the cache
  32609. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  32610. nalUnits = [];
  32611. this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
  32612. this.trigger('timelineStartInfo', track.timelineStartInfo);
  32613. moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
  32614. // throwing away hundreds of media segment fragments
  32615. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
  32616. sequenceNumber++;
  32617. boxes.set(moof);
  32618. boxes.set(mdat, moof.byteLength);
  32619. this.trigger('data', {
  32620. track: track,
  32621. boxes: boxes
  32622. });
  32623. this.resetStream_(); // Continue with the flush process now
  32624. this.trigger('done', 'VideoSegmentStream');
  32625. };
  32626. this.reset = function () {
  32627. this.resetStream_();
  32628. nalUnits = [];
  32629. this.gopCache_.length = 0;
  32630. gopsToAlignWith.length = 0;
  32631. this.trigger('reset');
  32632. };
  32633. this.resetStream_ = function () {
  32634. trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
  32635. // for instance, when we are rendition switching
  32636. config = undefined;
  32637. pps = undefined;
  32638. }; // Search for a candidate Gop for gop-fusion from the gop cache and
  32639. // return it or return null if no good candidate was found
  32640. this.getGopForFusion_ = function (nalUnit) {
  32641. var halfSecond = 45000,
  32642. // Half-a-second in a 90khz clock
  32643. allowableOverlap = 10000,
  32644. // About 3 frames @ 30fps
  32645. nearestDistance = Infinity,
  32646. dtsDistance,
  32647. nearestGopObj,
  32648. currentGop,
  32649. currentGopObj,
  32650. i; // Search for the GOP nearest to the beginning of this nal unit
  32651. for (i = 0; i < this.gopCache_.length; i++) {
  32652. currentGopObj = this.gopCache_[i];
  32653. currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
  32654. if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
  32655. continue;
  32656. } // Reject Gops that would require a negative baseMediaDecodeTime
  32657. if (currentGop.dts < track.timelineStartInfo.dts) {
  32658. continue;
  32659. } // The distance between the end of the gop and the start of the nalUnit
  32660. dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
  32661. // a half-second of the nal unit
  32662. if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
  32663. // Always use the closest GOP we found if there is more than
  32664. // one candidate
  32665. if (!nearestGopObj || nearestDistance > dtsDistance) {
  32666. nearestGopObj = currentGopObj;
  32667. nearestDistance = dtsDistance;
  32668. }
  32669. }
  32670. }
  32671. if (nearestGopObj) {
  32672. return nearestGopObj.gop;
  32673. }
  32674. return null;
  32675. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  32676. // of gopsToAlignWith starting from the START of the list
  32677. this.alignGopsAtStart_ = function (gops) {
  32678. var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
  32679. byteLength = gops.byteLength;
  32680. nalCount = gops.nalCount;
  32681. duration = gops.duration;
  32682. alignIndex = gopIndex = 0;
  32683. while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
  32684. align = gopsToAlignWith[alignIndex];
  32685. gop = gops[gopIndex];
  32686. if (align.pts === gop.pts) {
  32687. break;
  32688. }
  32689. if (gop.pts > align.pts) {
  32690. // this current gop starts after the current gop we want to align on, so increment
  32691. // align index
  32692. alignIndex++;
  32693. continue;
  32694. } // current gop starts before the current gop we want to align on. so increment gop
  32695. // index
  32696. gopIndex++;
  32697. byteLength -= gop.byteLength;
  32698. nalCount -= gop.nalCount;
  32699. duration -= gop.duration;
  32700. }
  32701. if (gopIndex === 0) {
  32702. // no gops to trim
  32703. return gops;
  32704. }
  32705. if (gopIndex === gops.length) {
  32706. // all gops trimmed, skip appending all gops
  32707. return null;
  32708. }
  32709. alignedGops = gops.slice(gopIndex);
  32710. alignedGops.byteLength = byteLength;
  32711. alignedGops.duration = duration;
  32712. alignedGops.nalCount = nalCount;
  32713. alignedGops.pts = alignedGops[0].pts;
  32714. alignedGops.dts = alignedGops[0].dts;
  32715. return alignedGops;
  32716. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  32717. // of gopsToAlignWith starting from the END of the list
  32718. this.alignGopsAtEnd_ = function (gops) {
  32719. var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
  32720. alignIndex = gopsToAlignWith.length - 1;
  32721. gopIndex = gops.length - 1;
  32722. alignEndIndex = null;
  32723. matchFound = false;
  32724. while (alignIndex >= 0 && gopIndex >= 0) {
  32725. align = gopsToAlignWith[alignIndex];
  32726. gop = gops[gopIndex];
  32727. if (align.pts === gop.pts) {
  32728. matchFound = true;
  32729. break;
  32730. }
  32731. if (align.pts > gop.pts) {
  32732. alignIndex--;
  32733. continue;
  32734. }
  32735. if (alignIndex === gopsToAlignWith.length - 1) {
  32736. // gop.pts is greater than the last alignment candidate. If no match is found
  32737. // by the end of this loop, we still want to append gops that come after this
  32738. // point
  32739. alignEndIndex = gopIndex;
  32740. }
  32741. gopIndex--;
  32742. }
  32743. if (!matchFound && alignEndIndex === null) {
  32744. return null;
  32745. }
  32746. var trimIndex;
  32747. if (matchFound) {
  32748. trimIndex = gopIndex;
  32749. } else {
  32750. trimIndex = alignEndIndex;
  32751. }
  32752. if (trimIndex === 0) {
  32753. return gops;
  32754. }
  32755. var alignedGops = gops.slice(trimIndex);
  32756. var metadata = alignedGops.reduce(function (total, gop) {
  32757. total.byteLength += gop.byteLength;
  32758. total.duration += gop.duration;
  32759. total.nalCount += gop.nalCount;
  32760. return total;
  32761. }, {
  32762. byteLength: 0,
  32763. duration: 0,
  32764. nalCount: 0
  32765. });
  32766. alignedGops.byteLength = metadata.byteLength;
  32767. alignedGops.duration = metadata.duration;
  32768. alignedGops.nalCount = metadata.nalCount;
  32769. alignedGops.pts = alignedGops[0].pts;
  32770. alignedGops.dts = alignedGops[0].dts;
  32771. return alignedGops;
  32772. };
  32773. this.alignGopsWith = function (newGopsToAlignWith) {
  32774. gopsToAlignWith = newGopsToAlignWith;
  32775. };
  32776. };
  32777. _VideoSegmentStream.prototype = new stream();
  32778. /**
  32779. * A Stream that can combine multiple streams (ie. audio & video)
  32780. * into a single output segment for MSE. Also supports audio-only
  32781. * and video-only streams.
  32782. * @param options {object} transmuxer options object
  32783. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  32784. * in the source; false to adjust the first segment to start at media timeline start.
  32785. */
  32786. _CoalesceStream = function CoalesceStream(options, metadataStream) {
  32787. // Number of Tracks per output segment
  32788. // If greater than 1, we combine multiple
  32789. // tracks into a single segment
  32790. this.numberOfTracks = 0;
  32791. this.metadataStream = metadataStream;
  32792. options = options || {};
  32793. if (typeof options.remux !== 'undefined') {
  32794. this.remuxTracks = !!options.remux;
  32795. } else {
  32796. this.remuxTracks = true;
  32797. }
  32798. if (typeof options.keepOriginalTimestamps === 'boolean') {
  32799. this.keepOriginalTimestamps = options.keepOriginalTimestamps;
  32800. } else {
  32801. this.keepOriginalTimestamps = false;
  32802. }
  32803. this.pendingTracks = [];
  32804. this.videoTrack = null;
  32805. this.pendingBoxes = [];
  32806. this.pendingCaptions = [];
  32807. this.pendingMetadata = [];
  32808. this.pendingBytes = 0;
  32809. this.emittedTracks = 0;
  32810. _CoalesceStream.prototype.init.call(this); // Take output from multiple
  32811. this.push = function (output) {
  32812. // buffer incoming captions until the associated video segment
  32813. // finishes
  32814. if (output.text) {
  32815. return this.pendingCaptions.push(output);
  32816. } // buffer incoming id3 tags until the final flush
  32817. if (output.frames) {
  32818. return this.pendingMetadata.push(output);
  32819. } // Add this track to the list of pending tracks and store
  32820. // important information required for the construction of
  32821. // the final segment
  32822. this.pendingTracks.push(output.track);
  32823. this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome?
  32824. // We unshift audio and push video because
  32825. // as of Chrome 75 when switching from
  32826. // one init segment to another if the video
  32827. // mdat does not appear after the audio mdat
  32828. // only audio will play for the duration of our transmux.
  32829. if (output.track.type === 'video') {
  32830. this.videoTrack = output.track;
  32831. this.pendingBoxes.push(output.boxes);
  32832. }
  32833. if (output.track.type === 'audio') {
  32834. this.audioTrack = output.track;
  32835. this.pendingBoxes.unshift(output.boxes);
  32836. }
  32837. };
  32838. };
  32839. _CoalesceStream.prototype = new stream();
  32840. _CoalesceStream.prototype.flush = function (flushSource) {
  32841. var offset = 0,
  32842. event = {
  32843. captions: [],
  32844. captionStreams: {},
  32845. metadata: [],
  32846. info: {}
  32847. },
  32848. caption,
  32849. id3,
  32850. initSegment,
  32851. timelineStartPts = 0,
  32852. i;
  32853. if (this.pendingTracks.length < this.numberOfTracks) {
  32854. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  32855. // Return because we haven't received a flush from a data-generating
  32856. // portion of the segment (meaning that we have only recieved meta-data
  32857. // or captions.)
  32858. return;
  32859. } else if (this.remuxTracks) {
  32860. // Return until we have enough tracks from the pipeline to remux (if we
  32861. // are remuxing audio and video into a single MP4)
  32862. return;
  32863. } else if (this.pendingTracks.length === 0) {
  32864. // In the case where we receive a flush without any data having been
  32865. // received we consider it an emitted track for the purposes of coalescing
  32866. // `done` events.
  32867. // We do this for the case where there is an audio and video track in the
  32868. // segment but no audio data. (seen in several playlists with alternate
  32869. // audio tracks and no audio present in the main TS segments.)
  32870. this.emittedTracks++;
  32871. if (this.emittedTracks >= this.numberOfTracks) {
  32872. this.trigger('done');
  32873. this.emittedTracks = 0;
  32874. }
  32875. return;
  32876. }
  32877. }
  32878. if (this.videoTrack) {
  32879. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  32880. videoProperties.forEach(function (prop) {
  32881. event.info[prop] = this.videoTrack[prop];
  32882. }, this);
  32883. } else if (this.audioTrack) {
  32884. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  32885. audioProperties.forEach(function (prop) {
  32886. event.info[prop] = this.audioTrack[prop];
  32887. }, this);
  32888. }
  32889. if (this.videoTrack || this.audioTrack) {
  32890. if (this.pendingTracks.length === 1) {
  32891. event.type = this.pendingTracks[0].type;
  32892. } else {
  32893. event.type = 'combined';
  32894. }
  32895. this.emittedTracks += this.pendingTracks.length;
  32896. initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
  32897. event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
  32898. // and track definitions
  32899. event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
  32900. event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
  32901. for (i = 0; i < this.pendingBoxes.length; i++) {
  32902. event.data.set(this.pendingBoxes[i], offset);
  32903. offset += this.pendingBoxes[i].byteLength;
  32904. } // Translate caption PTS times into second offsets to match the
  32905. // video timeline for the segment, and add track info
  32906. for (i = 0; i < this.pendingCaptions.length; i++) {
  32907. caption = this.pendingCaptions[i];
  32908. caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps);
  32909. caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps);
  32910. event.captionStreams[caption.stream] = true;
  32911. event.captions.push(caption);
  32912. } // Translate ID3 frame PTS times into second offsets to match the
  32913. // video timeline for the segment
  32914. for (i = 0; i < this.pendingMetadata.length; i++) {
  32915. id3 = this.pendingMetadata[i];
  32916. id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps);
  32917. event.metadata.push(id3);
  32918. } // We add this to every single emitted segment even though we only need
  32919. // it for the first
  32920. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  32921. this.pendingTracks.length = 0;
  32922. this.videoTrack = null;
  32923. this.pendingBoxes.length = 0;
  32924. this.pendingCaptions.length = 0;
  32925. this.pendingBytes = 0;
  32926. this.pendingMetadata.length = 0; // Emit the built segment
  32927. // We include captions and ID3 tags for backwards compatibility,
  32928. // ideally we should send only video and audio in the data event
  32929. this.trigger('data', event); // Emit each caption to the outside world
  32930. // Ideally, this would happen immediately on parsing captions,
  32931. // but we need to ensure that video data is sent back first
  32932. // so that caption timing can be adjusted to match video timing
  32933. for (i = 0; i < event.captions.length; i++) {
  32934. caption = event.captions[i];
  32935. this.trigger('caption', caption);
  32936. } // Emit each id3 tag to the outside world
  32937. // Ideally, this would happen immediately on parsing the tag,
  32938. // but we need to ensure that video data is sent back first
  32939. // so that ID3 frame timing can be adjusted to match video timing
  32940. for (i = 0; i < event.metadata.length; i++) {
  32941. id3 = event.metadata[i];
  32942. this.trigger('id3Frame', id3);
  32943. }
  32944. } // Only emit `done` if all tracks have been flushed and emitted
  32945. if (this.emittedTracks >= this.numberOfTracks) {
  32946. this.trigger('done');
  32947. this.emittedTracks = 0;
  32948. }
  32949. };
  32950. _CoalesceStream.prototype.setRemux = function (val) {
  32951. this.remuxTracks = val;
  32952. };
  32953. /**
  32954. * A Stream that expects MP2T binary data as input and produces
  32955. * corresponding media segments, suitable for use with Media Source
  32956. * Extension (MSE) implementations that support the ISO BMFF byte
  32957. * stream format, like Chrome.
  32958. */
  32959. _Transmuxer = function Transmuxer(options) {
  32960. var self = this,
  32961. hasFlushed = true,
  32962. videoTrack,
  32963. audioTrack;
  32964. _Transmuxer.prototype.init.call(this);
  32965. options = options || {};
  32966. this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
  32967. this.transmuxPipeline_ = {};
  32968. this.setupAacPipeline = function () {
  32969. var pipeline = {};
  32970. this.transmuxPipeline_ = pipeline;
  32971. pipeline.type = 'aac';
  32972. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  32973. pipeline.aacStream = new aac();
  32974. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  32975. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  32976. pipeline.adtsStream = new adts();
  32977. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  32978. pipeline.headOfPipeline = pipeline.aacStream;
  32979. pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  32980. pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
  32981. pipeline.metadataStream.on('timestamp', function (frame) {
  32982. pipeline.aacStream.setTimestamp(frame.timeStamp);
  32983. });
  32984. pipeline.aacStream.on('data', function (data) {
  32985. if (data.type !== 'timed-metadata' && data.type !== 'audio' || pipeline.audioSegmentStream) {
  32986. return;
  32987. }
  32988. audioTrack = audioTrack || {
  32989. timelineStartInfo: {
  32990. baseMediaDecodeTime: self.baseMediaDecodeTime
  32991. },
  32992. codec: 'adts',
  32993. type: 'audio'
  32994. }; // hook up the audio segment stream to the first track with aac data
  32995. pipeline.coalesceStream.numberOfTracks++;
  32996. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
  32997. pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
  32998. pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline
  32999. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); // emit pmt info
  33000. self.trigger('trackinfo', {
  33001. hasAudio: !!audioTrack,
  33002. hasVideo: !!videoTrack
  33003. });
  33004. }); // Re-emit any data coming from the coalesce stream to the outside world
  33005. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  33006. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  33007. addPipelineLogRetriggers(this, pipeline);
  33008. };
  33009. this.setupTsPipeline = function () {
  33010. var pipeline = {};
  33011. this.transmuxPipeline_ = pipeline;
  33012. pipeline.type = 'ts';
  33013. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  33014. pipeline.packetStream = new m2ts_1.TransportPacketStream();
  33015. pipeline.parseStream = new m2ts_1.TransportParseStream();
  33016. pipeline.elementaryStream = new m2ts_1.ElementaryStream();
  33017. pipeline.timestampRolloverStream = new m2ts_1.TimestampRolloverStream();
  33018. pipeline.adtsStream = new adts();
  33019. pipeline.h264Stream = new H264Stream();
  33020. pipeline.captionStream = new m2ts_1.CaptionStream(options);
  33021. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  33022. pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
  33023. pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!!
  33024. // demux the streams
  33025. pipeline.timestampRolloverStream.pipe(pipeline.h264Stream);
  33026. pipeline.timestampRolloverStream.pipe(pipeline.adtsStream);
  33027. pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
  33028. pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
  33029. pipeline.elementaryStream.on('data', function (data) {
  33030. var i;
  33031. if (data.type === 'metadata') {
  33032. i = data.tracks.length; // scan the tracks listed in the metadata
  33033. while (i--) {
  33034. if (!videoTrack && data.tracks[i].type === 'video') {
  33035. videoTrack = data.tracks[i];
  33036. videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  33037. } else if (!audioTrack && data.tracks[i].type === 'audio') {
  33038. audioTrack = data.tracks[i];
  33039. audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  33040. }
  33041. } // hook up the video segment stream to the first track with h264 data
  33042. if (videoTrack && !pipeline.videoSegmentStream) {
  33043. pipeline.coalesceStream.numberOfTracks++;
  33044. pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
  33045. pipeline.videoSegmentStream.on('log', self.getLogTrigger_('videoSegmentStream'));
  33046. pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
  33047. // When video emits timelineStartInfo data after a flush, we forward that
  33048. // info to the AudioSegmentStream, if it exists, because video timeline
  33049. // data takes precedence. Do not do this if keepOriginalTimestamps is set,
  33050. // because this is a particularly subtle form of timestamp alteration.
  33051. if (audioTrack && !options.keepOriginalTimestamps) {
  33052. audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
  33053. // very earliest DTS we have seen in video because Chrome will
  33054. // interpret any video track with a baseMediaDecodeTime that is
  33055. // non-zero as a gap.
  33056. pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - self.baseMediaDecodeTime);
  33057. }
  33058. });
  33059. pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
  33060. pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
  33061. pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
  33062. if (audioTrack) {
  33063. pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
  33064. }
  33065. });
  33066. pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline
  33067. pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
  33068. }
  33069. if (audioTrack && !pipeline.audioSegmentStream) {
  33070. // hook up the audio segment stream to the first track with aac data
  33071. pipeline.coalesceStream.numberOfTracks++;
  33072. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
  33073. pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
  33074. pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo'));
  33075. pipeline.audioSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'audioSegmentTimingInfo')); // Set up the final part of the audio pipeline
  33076. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  33077. } // emit pmt info
  33078. self.trigger('trackinfo', {
  33079. hasAudio: !!audioTrack,
  33080. hasVideo: !!videoTrack
  33081. });
  33082. }
  33083. }); // Re-emit any data coming from the coalesce stream to the outside world
  33084. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
  33085. pipeline.coalesceStream.on('id3Frame', function (id3Frame) {
  33086. id3Frame.dispatchType = pipeline.metadataStream.dispatchType;
  33087. self.trigger('id3Frame', id3Frame);
  33088. });
  33089. pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline
  33090. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  33091. addPipelineLogRetriggers(this, pipeline);
  33092. }; // hook up the segment streams once track metadata is delivered
  33093. this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  33094. var pipeline = this.transmuxPipeline_;
  33095. if (!options.keepOriginalTimestamps) {
  33096. this.baseMediaDecodeTime = baseMediaDecodeTime;
  33097. }
  33098. if (audioTrack) {
  33099. audioTrack.timelineStartInfo.dts = undefined;
  33100. audioTrack.timelineStartInfo.pts = undefined;
  33101. trackDecodeInfo.clearDtsInfo(audioTrack);
  33102. if (pipeline.audioTimestampRolloverStream) {
  33103. pipeline.audioTimestampRolloverStream.discontinuity();
  33104. }
  33105. }
  33106. if (videoTrack) {
  33107. if (pipeline.videoSegmentStream) {
  33108. pipeline.videoSegmentStream.gopCache_ = [];
  33109. }
  33110. videoTrack.timelineStartInfo.dts = undefined;
  33111. videoTrack.timelineStartInfo.pts = undefined;
  33112. trackDecodeInfo.clearDtsInfo(videoTrack);
  33113. pipeline.captionStream.reset();
  33114. }
  33115. if (pipeline.timestampRolloverStream) {
  33116. pipeline.timestampRolloverStream.discontinuity();
  33117. }
  33118. };
  33119. this.setAudioAppendStart = function (timestamp) {
  33120. if (audioTrack) {
  33121. this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
  33122. }
  33123. };
  33124. this.setRemux = function (val) {
  33125. var pipeline = this.transmuxPipeline_;
  33126. options.remux = val;
  33127. if (pipeline && pipeline.coalesceStream) {
  33128. pipeline.coalesceStream.setRemux(val);
  33129. }
  33130. };
  33131. this.alignGopsWith = function (gopsToAlignWith) {
  33132. if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
  33133. this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
  33134. }
  33135. };
  33136. this.getLogTrigger_ = function (key) {
  33137. var self = this;
  33138. return function (event) {
  33139. event.stream = key;
  33140. self.trigger('log', event);
  33141. };
  33142. }; // feed incoming data to the front of the parsing pipeline
  33143. this.push = function (data) {
  33144. if (hasFlushed) {
  33145. var isAac = isLikelyAacData(data);
  33146. if (isAac && this.transmuxPipeline_.type !== 'aac') {
  33147. this.setupAacPipeline();
  33148. } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
  33149. this.setupTsPipeline();
  33150. }
  33151. hasFlushed = false;
  33152. }
  33153. this.transmuxPipeline_.headOfPipeline.push(data);
  33154. }; // flush any buffered data
  33155. this.flush = function () {
  33156. hasFlushed = true; // Start at the top of the pipeline and flush all pending work
  33157. this.transmuxPipeline_.headOfPipeline.flush();
  33158. };
  33159. this.endTimeline = function () {
  33160. this.transmuxPipeline_.headOfPipeline.endTimeline();
  33161. };
  33162. this.reset = function () {
  33163. if (this.transmuxPipeline_.headOfPipeline) {
  33164. this.transmuxPipeline_.headOfPipeline.reset();
  33165. }
  33166. }; // Caption data has to be reset when seeking outside buffered range
  33167. this.resetCaptions = function () {
  33168. if (this.transmuxPipeline_.captionStream) {
  33169. this.transmuxPipeline_.captionStream.reset();
  33170. }
  33171. };
  33172. };
  33173. _Transmuxer.prototype = new stream();
  33174. var transmuxer = {
  33175. Transmuxer: _Transmuxer,
  33176. VideoSegmentStream: _VideoSegmentStream,
  33177. AudioSegmentStream: _AudioSegmentStream,
  33178. AUDIO_PROPERTIES: audioProperties,
  33179. VIDEO_PROPERTIES: videoProperties,
  33180. // exported for testing
  33181. generateSegmentTimingInfo: generateSegmentTimingInfo
  33182. };
  33183. /**
  33184. * mux.js
  33185. *
  33186. * Copyright (c) Brightcove
  33187. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  33188. */
  33189. var toUnsigned$3 = function toUnsigned(value) {
  33190. return value >>> 0;
  33191. };
  33192. var toHexString$1 = function toHexString(value) {
  33193. return ('00' + value.toString(16)).slice(-2);
  33194. };
  33195. var bin = {
  33196. toUnsigned: toUnsigned$3,
  33197. toHexString: toHexString$1
  33198. };
  33199. var parseType$1 = function parseType(buffer) {
  33200. var result = '';
  33201. result += String.fromCharCode(buffer[0]);
  33202. result += String.fromCharCode(buffer[1]);
  33203. result += String.fromCharCode(buffer[2]);
  33204. result += String.fromCharCode(buffer[3]);
  33205. return result;
  33206. };
  33207. var parseType_1 = parseType$1;
  33208. var toUnsigned$2 = bin.toUnsigned;
  33209. var findBox = function findBox(data, path) {
  33210. var results = [],
  33211. i,
  33212. size,
  33213. type,
  33214. end,
  33215. subresults;
  33216. if (!path.length) {
  33217. // short-circuit the search for empty paths
  33218. return null;
  33219. }
  33220. for (i = 0; i < data.byteLength;) {
  33221. size = toUnsigned$2(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
  33222. type = parseType_1(data.subarray(i + 4, i + 8));
  33223. end = size > 1 ? i + size : data.byteLength;
  33224. if (type === path[0]) {
  33225. if (path.length === 1) {
  33226. // this is the end of the path and we've found the box we were
  33227. // looking for
  33228. results.push(data.subarray(i + 8, end));
  33229. } else {
  33230. // recursively search for the next box along the path
  33231. subresults = findBox(data.subarray(i + 8, end), path.slice(1));
  33232. if (subresults.length) {
  33233. results = results.concat(subresults);
  33234. }
  33235. }
  33236. }
  33237. i = end;
  33238. } // we've finished searching all of data
  33239. return results;
  33240. };
  33241. var findBox_1 = findBox;
  33242. var toUnsigned$1 = bin.toUnsigned;
  33243. var getUint64$1 = numbers.getUint64;
  33244. var tfdt = function tfdt(data) {
  33245. var result = {
  33246. version: data[0],
  33247. flags: new Uint8Array(data.subarray(1, 4))
  33248. };
  33249. if (result.version === 1) {
  33250. result.baseMediaDecodeTime = getUint64$1(data.subarray(4));
  33251. } else {
  33252. result.baseMediaDecodeTime = toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]);
  33253. }
  33254. return result;
  33255. };
  33256. var parseTfdt = tfdt;
  33257. var parseSampleFlags = function parseSampleFlags(flags) {
  33258. return {
  33259. isLeading: (flags[0] & 0x0c) >>> 2,
  33260. dependsOn: flags[0] & 0x03,
  33261. isDependedOn: (flags[1] & 0xc0) >>> 6,
  33262. hasRedundancy: (flags[1] & 0x30) >>> 4,
  33263. paddingValue: (flags[1] & 0x0e) >>> 1,
  33264. isNonSyncSample: flags[1] & 0x01,
  33265. degradationPriority: flags[2] << 8 | flags[3]
  33266. };
  33267. };
  33268. var parseSampleFlags_1 = parseSampleFlags;
  33269. var trun = function trun(data) {
  33270. var result = {
  33271. version: data[0],
  33272. flags: new Uint8Array(data.subarray(1, 4)),
  33273. samples: []
  33274. },
  33275. view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  33276. // Flag interpretation
  33277. dataOffsetPresent = result.flags[2] & 0x01,
  33278. // compare with 2nd byte of 0x1
  33279. firstSampleFlagsPresent = result.flags[2] & 0x04,
  33280. // compare with 2nd byte of 0x4
  33281. sampleDurationPresent = result.flags[1] & 0x01,
  33282. // compare with 2nd byte of 0x100
  33283. sampleSizePresent = result.flags[1] & 0x02,
  33284. // compare with 2nd byte of 0x200
  33285. sampleFlagsPresent = result.flags[1] & 0x04,
  33286. // compare with 2nd byte of 0x400
  33287. sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
  33288. // compare with 2nd byte of 0x800
  33289. sampleCount = view.getUint32(4),
  33290. offset = 8,
  33291. sample;
  33292. if (dataOffsetPresent) {
  33293. // 32 bit signed integer
  33294. result.dataOffset = view.getInt32(offset);
  33295. offset += 4;
  33296. } // Overrides the flags for the first sample only. The order of
  33297. // optional values will be: duration, size, compositionTimeOffset
  33298. if (firstSampleFlagsPresent && sampleCount) {
  33299. sample = {
  33300. flags: parseSampleFlags_1(data.subarray(offset, offset + 4))
  33301. };
  33302. offset += 4;
  33303. if (sampleDurationPresent) {
  33304. sample.duration = view.getUint32(offset);
  33305. offset += 4;
  33306. }
  33307. if (sampleSizePresent) {
  33308. sample.size = view.getUint32(offset);
  33309. offset += 4;
  33310. }
  33311. if (sampleCompositionTimeOffsetPresent) {
  33312. if (result.version === 1) {
  33313. sample.compositionTimeOffset = view.getInt32(offset);
  33314. } else {
  33315. sample.compositionTimeOffset = view.getUint32(offset);
  33316. }
  33317. offset += 4;
  33318. }
  33319. result.samples.push(sample);
  33320. sampleCount--;
  33321. }
  33322. while (sampleCount--) {
  33323. sample = {};
  33324. if (sampleDurationPresent) {
  33325. sample.duration = view.getUint32(offset);
  33326. offset += 4;
  33327. }
  33328. if (sampleSizePresent) {
  33329. sample.size = view.getUint32(offset);
  33330. offset += 4;
  33331. }
  33332. if (sampleFlagsPresent) {
  33333. sample.flags = parseSampleFlags_1(data.subarray(offset, offset + 4));
  33334. offset += 4;
  33335. }
  33336. if (sampleCompositionTimeOffsetPresent) {
  33337. if (result.version === 1) {
  33338. sample.compositionTimeOffset = view.getInt32(offset);
  33339. } else {
  33340. sample.compositionTimeOffset = view.getUint32(offset);
  33341. }
  33342. offset += 4;
  33343. }
  33344. result.samples.push(sample);
  33345. }
  33346. return result;
  33347. };
  33348. var parseTrun = trun;
  33349. var tfhd = function tfhd(data) {
  33350. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  33351. result = {
  33352. version: data[0],
  33353. flags: new Uint8Array(data.subarray(1, 4)),
  33354. trackId: view.getUint32(4)
  33355. },
  33356. baseDataOffsetPresent = result.flags[2] & 0x01,
  33357. sampleDescriptionIndexPresent = result.flags[2] & 0x02,
  33358. defaultSampleDurationPresent = result.flags[2] & 0x08,
  33359. defaultSampleSizePresent = result.flags[2] & 0x10,
  33360. defaultSampleFlagsPresent = result.flags[2] & 0x20,
  33361. durationIsEmpty = result.flags[0] & 0x010000,
  33362. defaultBaseIsMoof = result.flags[0] & 0x020000,
  33363. i;
  33364. i = 8;
  33365. if (baseDataOffsetPresent) {
  33366. i += 4; // truncate top 4 bytes
  33367. // FIXME: should we read the full 64 bits?
  33368. result.baseDataOffset = view.getUint32(12);
  33369. i += 4;
  33370. }
  33371. if (sampleDescriptionIndexPresent) {
  33372. result.sampleDescriptionIndex = view.getUint32(i);
  33373. i += 4;
  33374. }
  33375. if (defaultSampleDurationPresent) {
  33376. result.defaultSampleDuration = view.getUint32(i);
  33377. i += 4;
  33378. }
  33379. if (defaultSampleSizePresent) {
  33380. result.defaultSampleSize = view.getUint32(i);
  33381. i += 4;
  33382. }
  33383. if (defaultSampleFlagsPresent) {
  33384. result.defaultSampleFlags = view.getUint32(i);
  33385. }
  33386. if (durationIsEmpty) {
  33387. result.durationIsEmpty = true;
  33388. }
  33389. if (!baseDataOffsetPresent && defaultBaseIsMoof) {
  33390. result.baseDataOffsetIsMoof = true;
  33391. }
  33392. return result;
  33393. };
  33394. var parseTfhd = tfhd;
  33395. var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
  33396. var win;
  33397. if (typeof window !== "undefined") {
  33398. win = window;
  33399. } else if (typeof commonjsGlobal !== "undefined") {
  33400. win = commonjsGlobal;
  33401. } else if (typeof self !== "undefined") {
  33402. win = self;
  33403. } else {
  33404. win = {};
  33405. }
  33406. var window_1 = win;
  33407. var discardEmulationPreventionBytes = captionPacketParser.discardEmulationPreventionBytes;
  33408. var CaptionStream = captionStream.CaptionStream;
  33409. /**
  33410. * Maps an offset in the mdat to a sample based on the the size of the samples.
  33411. * Assumes that `parseSamples` has been called first.
  33412. *
  33413. * @param {Number} offset - The offset into the mdat
  33414. * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
  33415. * @return {?Object} The matching sample, or null if no match was found.
  33416. *
  33417. * @see ISO-BMFF-12/2015, Section 8.8.8
  33418. **/
  33419. var mapToSample = function mapToSample(offset, samples) {
  33420. var approximateOffset = offset;
  33421. for (var i = 0; i < samples.length; i++) {
  33422. var sample = samples[i];
  33423. if (approximateOffset < sample.size) {
  33424. return sample;
  33425. }
  33426. approximateOffset -= sample.size;
  33427. }
  33428. return null;
  33429. };
  33430. /**
  33431. * Finds SEI nal units contained in a Media Data Box.
  33432. * Assumes that `parseSamples` has been called first.
  33433. *
  33434. * @param {Uint8Array} avcStream - The bytes of the mdat
  33435. * @param {Object[]} samples - The samples parsed out by `parseSamples`
  33436. * @param {Number} trackId - The trackId of this video track
  33437. * @return {Object[]} seiNals - the parsed SEI NALUs found.
  33438. * The contents of the seiNal should match what is expected by
  33439. * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
  33440. *
  33441. * @see ISO-BMFF-12/2015, Section 8.1.1
  33442. * @see Rec. ITU-T H.264, 7.3.2.3.1
  33443. **/
  33444. var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
  33445. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  33446. result = {
  33447. logs: [],
  33448. seiNals: []
  33449. },
  33450. seiNal,
  33451. i,
  33452. length,
  33453. lastMatchedSample;
  33454. for (i = 0; i + 4 < avcStream.length; i += length) {
  33455. length = avcView.getUint32(i);
  33456. i += 4; // Bail if this doesn't appear to be an H264 stream
  33457. if (length <= 0) {
  33458. continue;
  33459. }
  33460. switch (avcStream[i] & 0x1F) {
  33461. case 0x06:
  33462. var data = avcStream.subarray(i + 1, i + 1 + length);
  33463. var matchingSample = mapToSample(i, samples);
  33464. seiNal = {
  33465. nalUnitType: 'sei_rbsp',
  33466. size: length,
  33467. data: data,
  33468. escapedRBSP: discardEmulationPreventionBytes(data),
  33469. trackId: trackId
  33470. };
  33471. if (matchingSample) {
  33472. seiNal.pts = matchingSample.pts;
  33473. seiNal.dts = matchingSample.dts;
  33474. lastMatchedSample = matchingSample;
  33475. } else if (lastMatchedSample) {
  33476. // If a matching sample cannot be found, use the last
  33477. // sample's values as they should be as close as possible
  33478. seiNal.pts = lastMatchedSample.pts;
  33479. seiNal.dts = lastMatchedSample.dts;
  33480. } else {
  33481. result.logs.push({
  33482. level: 'warn',
  33483. message: 'We\'ve encountered a nal unit without data at ' + i + ' for trackId ' + trackId + '. See mux.js#223.'
  33484. });
  33485. break;
  33486. }
  33487. result.seiNals.push(seiNal);
  33488. break;
  33489. }
  33490. }
  33491. return result;
  33492. };
  33493. /**
  33494. * Parses sample information out of Track Run Boxes and calculates
  33495. * the absolute presentation and decode timestamps of each sample.
  33496. *
  33497. * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
  33498. * @param {Number|BigInt} baseMediaDecodeTime - base media decode time from tfdt
  33499. @see ISO-BMFF-12/2015, Section 8.8.12
  33500. * @param {Object} tfhd - The parsed Track Fragment Header
  33501. * @see inspect.parseTfhd
  33502. * @return {Object[]} the parsed samples
  33503. *
  33504. * @see ISO-BMFF-12/2015, Section 8.8.8
  33505. **/
  33506. var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
  33507. var currentDts = baseMediaDecodeTime;
  33508. var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
  33509. var defaultSampleSize = tfhd.defaultSampleSize || 0;
  33510. var trackId = tfhd.trackId;
  33511. var allSamples = [];
  33512. truns.forEach(function (trun) {
  33513. // Note: We currently do not parse the sample table as well
  33514. // as the trun. It's possible some sources will require this.
  33515. // moov > trak > mdia > minf > stbl
  33516. var trackRun = parseTrun(trun);
  33517. var samples = trackRun.samples;
  33518. samples.forEach(function (sample) {
  33519. if (sample.duration === undefined) {
  33520. sample.duration = defaultSampleDuration;
  33521. }
  33522. if (sample.size === undefined) {
  33523. sample.size = defaultSampleSize;
  33524. }
  33525. sample.trackId = trackId;
  33526. sample.dts = currentDts;
  33527. if (sample.compositionTimeOffset === undefined) {
  33528. sample.compositionTimeOffset = 0;
  33529. }
  33530. if (typeof currentDts === 'bigint') {
  33531. sample.pts = currentDts + window_1.BigInt(sample.compositionTimeOffset);
  33532. currentDts += window_1.BigInt(sample.duration);
  33533. } else {
  33534. sample.pts = currentDts + sample.compositionTimeOffset;
  33535. currentDts += sample.duration;
  33536. }
  33537. });
  33538. allSamples = allSamples.concat(samples);
  33539. });
  33540. return allSamples;
  33541. };
  33542. /**
  33543. * Parses out caption nals from an FMP4 segment's video tracks.
  33544. *
  33545. * @param {Uint8Array} segment - The bytes of a single segment
  33546. * @param {Number} videoTrackId - The trackId of a video track in the segment
  33547. * @return {Object.<Number, Object[]>} A mapping of video trackId to
  33548. * a list of seiNals found in that track
  33549. **/
  33550. var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
  33551. // To get the samples
  33552. var trafs = findBox_1(segment, ['moof', 'traf']); // To get SEI NAL units
  33553. var mdats = findBox_1(segment, ['mdat']);
  33554. var captionNals = {};
  33555. var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
  33556. mdats.forEach(function (mdat, index) {
  33557. var matchingTraf = trafs[index];
  33558. mdatTrafPairs.push({
  33559. mdat: mdat,
  33560. traf: matchingTraf
  33561. });
  33562. });
  33563. mdatTrafPairs.forEach(function (pair) {
  33564. var mdat = pair.mdat;
  33565. var traf = pair.traf;
  33566. var tfhd = findBox_1(traf, ['tfhd']); // Exactly 1 tfhd per traf
  33567. var headerInfo = parseTfhd(tfhd[0]);
  33568. var trackId = headerInfo.trackId;
  33569. var tfdt = findBox_1(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
  33570. var baseMediaDecodeTime = tfdt.length > 0 ? parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
  33571. var truns = findBox_1(traf, ['trun']);
  33572. var samples;
  33573. var result; // Only parse video data for the chosen video track
  33574. if (videoTrackId === trackId && truns.length > 0) {
  33575. samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
  33576. result = findSeiNals(mdat, samples, trackId);
  33577. if (!captionNals[trackId]) {
  33578. captionNals[trackId] = {
  33579. seiNals: [],
  33580. logs: []
  33581. };
  33582. }
  33583. captionNals[trackId].seiNals = captionNals[trackId].seiNals.concat(result.seiNals);
  33584. captionNals[trackId].logs = captionNals[trackId].logs.concat(result.logs);
  33585. }
  33586. });
  33587. return captionNals;
  33588. };
  33589. /**
  33590. * Parses out inband captions from an MP4 container and returns
  33591. * caption objects that can be used by WebVTT and the TextTrack API.
  33592. * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
  33593. * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
  33594. * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
  33595. *
  33596. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  33597. * @param {Number} trackId - The id of the video track to parse
  33598. * @param {Number} timescale - The timescale for the video track from the init segment
  33599. *
  33600. * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
  33601. * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
  33602. * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
  33603. * @return {String} parsedCaptions[].text - The visible content of the caption
  33604. **/
  33605. var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
  33606. var captionNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
  33607. if (trackId === null) {
  33608. return null;
  33609. }
  33610. captionNals = parseCaptionNals(segment, trackId);
  33611. var trackNals = captionNals[trackId] || {};
  33612. return {
  33613. seiNals: trackNals.seiNals,
  33614. logs: trackNals.logs,
  33615. timescale: timescale
  33616. };
  33617. };
  33618. /**
  33619. * Converts SEI NALUs into captions that can be used by video.js
  33620. **/
  33621. var CaptionParser = function CaptionParser() {
  33622. var isInitialized = false;
  33623. var captionStream; // Stores segments seen before trackId and timescale are set
  33624. var segmentCache; // Stores video track ID of the track being parsed
  33625. var trackId; // Stores the timescale of the track being parsed
  33626. var timescale; // Stores captions parsed so far
  33627. var parsedCaptions; // Stores whether we are receiving partial data or not
  33628. var parsingPartial;
  33629. /**
  33630. * A method to indicate whether a CaptionParser has been initalized
  33631. * @returns {Boolean}
  33632. **/
  33633. this.isInitialized = function () {
  33634. return isInitialized;
  33635. };
  33636. /**
  33637. * Initializes the underlying CaptionStream, SEI NAL parsing
  33638. * and management, and caption collection
  33639. **/
  33640. this.init = function (options) {
  33641. captionStream = new CaptionStream();
  33642. isInitialized = true;
  33643. parsingPartial = options ? options.isPartial : false; // Collect dispatched captions
  33644. captionStream.on('data', function (event) {
  33645. // Convert to seconds in the source's timescale
  33646. event.startTime = event.startPts / timescale;
  33647. event.endTime = event.endPts / timescale;
  33648. parsedCaptions.captions.push(event);
  33649. parsedCaptions.captionStreams[event.stream] = true;
  33650. });
  33651. captionStream.on('log', function (log) {
  33652. parsedCaptions.logs.push(log);
  33653. });
  33654. };
  33655. /**
  33656. * Determines if a new video track will be selected
  33657. * or if the timescale changed
  33658. * @return {Boolean}
  33659. **/
  33660. this.isNewInit = function (videoTrackIds, timescales) {
  33661. if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
  33662. return false;
  33663. }
  33664. return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
  33665. };
  33666. /**
  33667. * Parses out SEI captions and interacts with underlying
  33668. * CaptionStream to return dispatched captions
  33669. *
  33670. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  33671. * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
  33672. * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
  33673. * @see parseEmbeddedCaptions
  33674. * @see m2ts/caption-stream.js
  33675. **/
  33676. this.parse = function (segment, videoTrackIds, timescales) {
  33677. var parsedData;
  33678. if (!this.isInitialized()) {
  33679. return null; // This is not likely to be a video segment
  33680. } else if (!videoTrackIds || !timescales) {
  33681. return null;
  33682. } else if (this.isNewInit(videoTrackIds, timescales)) {
  33683. // Use the first video track only as there is no
  33684. // mechanism to switch to other video tracks
  33685. trackId = videoTrackIds[0];
  33686. timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
  33687. // data until we have one.
  33688. // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
  33689. } else if (trackId === null || !timescale) {
  33690. segmentCache.push(segment);
  33691. return null;
  33692. } // Now that a timescale and trackId is set, parse cached segments
  33693. while (segmentCache.length > 0) {
  33694. var cachedSegment = segmentCache.shift();
  33695. this.parse(cachedSegment, videoTrackIds, timescales);
  33696. }
  33697. parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
  33698. if (parsedData && parsedData.logs) {
  33699. parsedCaptions.logs = parsedCaptions.logs.concat(parsedData.logs);
  33700. }
  33701. if (parsedData === null || !parsedData.seiNals) {
  33702. if (parsedCaptions.logs.length) {
  33703. return {
  33704. logs: parsedCaptions.logs,
  33705. captions: [],
  33706. captionStreams: []
  33707. };
  33708. }
  33709. return null;
  33710. }
  33711. this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
  33712. this.flushStream();
  33713. return parsedCaptions;
  33714. };
  33715. /**
  33716. * Pushes SEI NALUs onto CaptionStream
  33717. * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
  33718. * Assumes that `parseCaptionNals` has been called first
  33719. * @see m2ts/caption-stream.js
  33720. **/
  33721. this.pushNals = function (nals) {
  33722. if (!this.isInitialized() || !nals || nals.length === 0) {
  33723. return null;
  33724. }
  33725. nals.forEach(function (nal) {
  33726. captionStream.push(nal);
  33727. });
  33728. };
  33729. /**
  33730. * Flushes underlying CaptionStream to dispatch processed, displayable captions
  33731. * @see m2ts/caption-stream.js
  33732. **/
  33733. this.flushStream = function () {
  33734. if (!this.isInitialized()) {
  33735. return null;
  33736. }
  33737. if (!parsingPartial) {
  33738. captionStream.flush();
  33739. } else {
  33740. captionStream.partialFlush();
  33741. }
  33742. };
  33743. /**
  33744. * Reset caption buckets for new data
  33745. **/
  33746. this.clearParsedCaptions = function () {
  33747. parsedCaptions.captions = [];
  33748. parsedCaptions.captionStreams = {};
  33749. parsedCaptions.logs = [];
  33750. };
  33751. /**
  33752. * Resets underlying CaptionStream
  33753. * @see m2ts/caption-stream.js
  33754. **/
  33755. this.resetCaptionStream = function () {
  33756. if (!this.isInitialized()) {
  33757. return null;
  33758. }
  33759. captionStream.reset();
  33760. };
  33761. /**
  33762. * Convenience method to clear all captions flushed from the
  33763. * CaptionStream and still being parsed
  33764. * @see m2ts/caption-stream.js
  33765. **/
  33766. this.clearAllCaptions = function () {
  33767. this.clearParsedCaptions();
  33768. this.resetCaptionStream();
  33769. };
  33770. /**
  33771. * Reset caption parser
  33772. **/
  33773. this.reset = function () {
  33774. segmentCache = [];
  33775. trackId = null;
  33776. timescale = null;
  33777. if (!parsedCaptions) {
  33778. parsedCaptions = {
  33779. captions: [],
  33780. // CC1, CC2, CC3, CC4
  33781. captionStreams: {},
  33782. logs: []
  33783. };
  33784. } else {
  33785. this.clearParsedCaptions();
  33786. }
  33787. this.resetCaptionStream();
  33788. };
  33789. this.reset();
  33790. };
  33791. var captionParser = CaptionParser;
  33792. var toUnsigned = bin.toUnsigned;
  33793. var toHexString = bin.toHexString;
  33794. var getUint64 = numbers.getUint64;
  33795. var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks, getTimescaleFromMediaHeader;
  33796. /**
  33797. * Parses an MP4 initialization segment and extracts the timescale
  33798. * values for any declared tracks. Timescale values indicate the
  33799. * number of clock ticks per second to assume for time-based values
  33800. * elsewhere in the MP4.
  33801. *
  33802. * To determine the start time of an MP4, you need two pieces of
  33803. * information: the timescale unit and the earliest base media decode
  33804. * time. Multiple timescales can be specified within an MP4 but the
  33805. * base media decode time is always expressed in the timescale from
  33806. * the media header box for the track:
  33807. * ```
  33808. * moov > trak > mdia > mdhd.timescale
  33809. * ```
  33810. * @param init {Uint8Array} the bytes of the init segment
  33811. * @return {object} a hash of track ids to timescale values or null if
  33812. * the init segment is malformed.
  33813. */
  33814. timescale = function timescale(init) {
  33815. var result = {},
  33816. traks = findBox_1(init, ['moov', 'trak']); // mdhd timescale
  33817. return traks.reduce(function (result, trak) {
  33818. var tkhd, version, index, id, mdhd;
  33819. tkhd = findBox_1(trak, ['tkhd'])[0];
  33820. if (!tkhd) {
  33821. return null;
  33822. }
  33823. version = tkhd[0];
  33824. index = version === 0 ? 12 : 20;
  33825. id = toUnsigned(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
  33826. mdhd = findBox_1(trak, ['mdia', 'mdhd'])[0];
  33827. if (!mdhd) {
  33828. return null;
  33829. }
  33830. version = mdhd[0];
  33831. index = version === 0 ? 12 : 20;
  33832. result[id] = toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
  33833. return result;
  33834. }, result);
  33835. };
  33836. /**
  33837. * Determine the base media decode start time, in seconds, for an MP4
  33838. * fragment. If multiple fragments are specified, the earliest time is
  33839. * returned.
  33840. *
  33841. * The base media decode time can be parsed from track fragment
  33842. * metadata:
  33843. * ```
  33844. * moof > traf > tfdt.baseMediaDecodeTime
  33845. * ```
  33846. * It requires the timescale value from the mdhd to interpret.
  33847. *
  33848. * @param timescale {object} a hash of track ids to timescale values.
  33849. * @return {number} the earliest base media decode start time for the
  33850. * fragment, in seconds
  33851. */
  33852. startTime = function startTime(timescale, fragment) {
  33853. var trafs; // we need info from two childrend of each track fragment box
  33854. trafs = findBox_1(fragment, ['moof', 'traf']); // determine the start times for each track
  33855. var lowestTime = trafs.reduce(function (acc, traf) {
  33856. var tfhd = findBox_1(traf, ['tfhd'])[0]; // get the track id from the tfhd
  33857. var id = toUnsigned(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
  33858. var scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
  33859. var tfdt = findBox_1(traf, ['tfdt'])[0];
  33860. var dv = new DataView(tfdt.buffer, tfdt.byteOffset, tfdt.byteLength);
  33861. var baseTime; // version 1 is 64 bit
  33862. if (tfdt[0] === 1) {
  33863. baseTime = getUint64(tfdt.subarray(4, 12));
  33864. } else {
  33865. baseTime = dv.getUint32(4);
  33866. } // convert base time to seconds if it is a valid number.
  33867. var seconds;
  33868. if (typeof baseTime === 'bigint') {
  33869. seconds = baseTime / window_1.BigInt(scale);
  33870. } else if (typeof baseTime === 'number' && !isNaN(baseTime)) {
  33871. seconds = baseTime / scale;
  33872. }
  33873. if (seconds < Number.MAX_SAFE_INTEGER) {
  33874. seconds = Number(seconds);
  33875. }
  33876. if (seconds < acc) {
  33877. acc = seconds;
  33878. }
  33879. return acc;
  33880. }, Infinity);
  33881. return typeof lowestTime === 'bigint' || isFinite(lowestTime) ? lowestTime : 0;
  33882. };
  33883. /**
  33884. * Determine the composition start, in seconds, for an MP4
  33885. * fragment.
  33886. *
  33887. * The composition start time of a fragment can be calculated using the base
  33888. * media decode time, composition time offset, and timescale, as follows:
  33889. *
  33890. * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale
  33891. *
  33892. * All of the aforementioned information is contained within a media fragment's
  33893. * `traf` box, except for timescale info, which comes from the initialization
  33894. * segment, so a track id (also contained within a `traf`) is also necessary to
  33895. * associate it with a timescale
  33896. *
  33897. *
  33898. * @param timescales {object} - a hash of track ids to timescale values.
  33899. * @param fragment {Unit8Array} - the bytes of a media segment
  33900. * @return {number} the composition start time for the fragment, in seconds
  33901. **/
  33902. compositionStartTime = function compositionStartTime(timescales, fragment) {
  33903. var trafBoxes = findBox_1(fragment, ['moof', 'traf']);
  33904. var baseMediaDecodeTime = 0;
  33905. var compositionTimeOffset = 0;
  33906. var trackId;
  33907. if (trafBoxes && trafBoxes.length) {
  33908. // The spec states that track run samples contained within a `traf` box are contiguous, but
  33909. // it does not explicitly state whether the `traf` boxes themselves are contiguous.
  33910. // We will assume that they are, so we only need the first to calculate start time.
  33911. var tfhd = findBox_1(trafBoxes[0], ['tfhd'])[0];
  33912. var trun = findBox_1(trafBoxes[0], ['trun'])[0];
  33913. var tfdt = findBox_1(trafBoxes[0], ['tfdt'])[0];
  33914. if (tfhd) {
  33915. var parsedTfhd = parseTfhd(tfhd);
  33916. trackId = parsedTfhd.trackId;
  33917. }
  33918. if (tfdt) {
  33919. var parsedTfdt = parseTfdt(tfdt);
  33920. baseMediaDecodeTime = parsedTfdt.baseMediaDecodeTime;
  33921. }
  33922. if (trun) {
  33923. var parsedTrun = parseTrun(trun);
  33924. if (parsedTrun.samples && parsedTrun.samples.length) {
  33925. compositionTimeOffset = parsedTrun.samples[0].compositionTimeOffset || 0;
  33926. }
  33927. }
  33928. } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was
  33929. // specified.
  33930. var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds
  33931. if (typeof baseMediaDecodeTime === 'bigint') {
  33932. compositionTimeOffset = window_1.BigInt(compositionTimeOffset);
  33933. timescale = window_1.BigInt(timescale);
  33934. }
  33935. var result = (baseMediaDecodeTime + compositionTimeOffset) / timescale;
  33936. if (typeof result === 'bigint' && result < Number.MAX_SAFE_INTEGER) {
  33937. result = Number(result);
  33938. }
  33939. return result;
  33940. };
  33941. /**
  33942. * Find the trackIds of the video tracks in this source.
  33943. * Found by parsing the Handler Reference and Track Header Boxes:
  33944. * moov > trak > mdia > hdlr
  33945. * moov > trak > tkhd
  33946. *
  33947. * @param {Uint8Array} init - The bytes of the init segment for this source
  33948. * @return {Number[]} A list of trackIds
  33949. *
  33950. * @see ISO-BMFF-12/2015, Section 8.4.3
  33951. **/
  33952. getVideoTrackIds = function getVideoTrackIds(init) {
  33953. var traks = findBox_1(init, ['moov', 'trak']);
  33954. var videoTrackIds = [];
  33955. traks.forEach(function (trak) {
  33956. var hdlrs = findBox_1(trak, ['mdia', 'hdlr']);
  33957. var tkhds = findBox_1(trak, ['tkhd']);
  33958. hdlrs.forEach(function (hdlr, index) {
  33959. var handlerType = parseType_1(hdlr.subarray(8, 12));
  33960. var tkhd = tkhds[index];
  33961. var view;
  33962. var version;
  33963. var trackId;
  33964. if (handlerType === 'vide') {
  33965. view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
  33966. version = view.getUint8(0);
  33967. trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
  33968. videoTrackIds.push(trackId);
  33969. }
  33970. });
  33971. });
  33972. return videoTrackIds;
  33973. };
  33974. getTimescaleFromMediaHeader = function getTimescaleFromMediaHeader(mdhd) {
  33975. // mdhd is a FullBox, meaning it will have its own version as the first byte
  33976. var version = mdhd[0];
  33977. var index = version === 0 ? 12 : 20;
  33978. return toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
  33979. };
  33980. /**
  33981. * Get all the video, audio, and hint tracks from a non fragmented
  33982. * mp4 segment
  33983. */
  33984. getTracks = function getTracks(init) {
  33985. var traks = findBox_1(init, ['moov', 'trak']);
  33986. var tracks = [];
  33987. traks.forEach(function (trak) {
  33988. var track = {};
  33989. var tkhd = findBox_1(trak, ['tkhd'])[0];
  33990. var view, tkhdVersion; // id
  33991. if (tkhd) {
  33992. view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
  33993. tkhdVersion = view.getUint8(0);
  33994. track.id = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20);
  33995. }
  33996. var hdlr = findBox_1(trak, ['mdia', 'hdlr'])[0]; // type
  33997. if (hdlr) {
  33998. var type = parseType_1(hdlr.subarray(8, 12));
  33999. if (type === 'vide') {
  34000. track.type = 'video';
  34001. } else if (type === 'soun') {
  34002. track.type = 'audio';
  34003. } else {
  34004. track.type = type;
  34005. }
  34006. } // codec
  34007. var stsd = findBox_1(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
  34008. if (stsd) {
  34009. var sampleDescriptions = stsd.subarray(8); // gives the codec type string
  34010. track.codec = parseType_1(sampleDescriptions.subarray(4, 8));
  34011. var codecBox = findBox_1(sampleDescriptions, [track.codec])[0];
  34012. var codecConfig, codecConfigType;
  34013. if (codecBox) {
  34014. // https://tools.ietf.org/html/rfc6381#section-3.3
  34015. if (/^[asm]vc[1-9]$/i.test(track.codec)) {
  34016. // we don't need anything but the "config" parameter of the
  34017. // avc1 codecBox
  34018. codecConfig = codecBox.subarray(78);
  34019. codecConfigType = parseType_1(codecConfig.subarray(4, 8));
  34020. if (codecConfigType === 'avcC' && codecConfig.length > 11) {
  34021. track.codec += '.'; // left padded with zeroes for single digit hex
  34022. // profile idc
  34023. track.codec += toHexString(codecConfig[9]); // the byte containing the constraint_set flags
  34024. track.codec += toHexString(codecConfig[10]); // level idc
  34025. track.codec += toHexString(codecConfig[11]);
  34026. } else {
  34027. // TODO: show a warning that we couldn't parse the codec
  34028. // and are using the default
  34029. track.codec = 'avc1.4d400d';
  34030. }
  34031. } else if (/^mp4[a,v]$/i.test(track.codec)) {
  34032. // we do not need anything but the streamDescriptor of the mp4a codecBox
  34033. codecConfig = codecBox.subarray(28);
  34034. codecConfigType = parseType_1(codecConfig.subarray(4, 8));
  34035. if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) {
  34036. track.codec += '.' + toHexString(codecConfig[19]); // this value is only a single digit
  34037. track.codec += '.' + toHexString(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, '');
  34038. } else {
  34039. // TODO: show a warning that we couldn't parse the codec
  34040. // and are using the default
  34041. track.codec = 'mp4a.40.2';
  34042. }
  34043. } else {
  34044. // flac, opus, etc
  34045. track.codec = track.codec.toLowerCase();
  34046. }
  34047. }
  34048. }
  34049. var mdhd = findBox_1(trak, ['mdia', 'mdhd'])[0];
  34050. if (mdhd) {
  34051. track.timescale = getTimescaleFromMediaHeader(mdhd);
  34052. }
  34053. tracks.push(track);
  34054. });
  34055. return tracks;
  34056. };
  34057. var probe$2 = {
  34058. // export mp4 inspector's findBox and parseType for backwards compatibility
  34059. findBox: findBox_1,
  34060. parseType: parseType_1,
  34061. timescale: timescale,
  34062. startTime: startTime,
  34063. compositionStartTime: compositionStartTime,
  34064. videoTrackIds: getVideoTrackIds,
  34065. tracks: getTracks,
  34066. getTimescaleFromMediaHeader: getTimescaleFromMediaHeader
  34067. };
  34068. var parsePid = function parsePid(packet) {
  34069. var pid = packet[1] & 0x1f;
  34070. pid <<= 8;
  34071. pid |= packet[2];
  34072. return pid;
  34073. };
  34074. var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
  34075. return !!(packet[1] & 0x40);
  34076. };
  34077. var parseAdaptionField = function parseAdaptionField(packet) {
  34078. var offset = 0; // if an adaption field is present, its length is specified by the
  34079. // fifth byte of the TS packet header. The adaptation field is
  34080. // used to add stuffing to PES packets that don't fill a complete
  34081. // TS packet, and to specify some forms of timing and control data
  34082. // that we do not currently use.
  34083. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  34084. offset += packet[4] + 1;
  34085. }
  34086. return offset;
  34087. };
  34088. var parseType = function parseType(packet, pmtPid) {
  34089. var pid = parsePid(packet);
  34090. if (pid === 0) {
  34091. return 'pat';
  34092. } else if (pid === pmtPid) {
  34093. return 'pmt';
  34094. } else if (pmtPid) {
  34095. return 'pes';
  34096. }
  34097. return null;
  34098. };
  34099. var parsePat = function parsePat(packet) {
  34100. var pusi = parsePayloadUnitStartIndicator(packet);
  34101. var offset = 4 + parseAdaptionField(packet);
  34102. if (pusi) {
  34103. offset += packet[offset] + 1;
  34104. }
  34105. return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
  34106. };
  34107. var parsePmt = function parsePmt(packet) {
  34108. var programMapTable = {};
  34109. var pusi = parsePayloadUnitStartIndicator(packet);
  34110. var payloadOffset = 4 + parseAdaptionField(packet);
  34111. if (pusi) {
  34112. payloadOffset += packet[payloadOffset] + 1;
  34113. } // PMTs can be sent ahead of the time when they should actually
  34114. // take effect. We don't believe this should ever be the case
  34115. // for HLS but we'll ignore "forward" PMT declarations if we see
  34116. // them. Future PMT declarations have the current_next_indicator
  34117. // set to zero.
  34118. if (!(packet[payloadOffset + 5] & 0x01)) {
  34119. return;
  34120. }
  34121. var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
  34122. sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
  34123. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  34124. // long the program info descriptors are
  34125. programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
  34126. var offset = 12 + programInfoLength;
  34127. while (offset < tableEnd) {
  34128. var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
  34129. programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
  34130. // skip past the elementary stream descriptors, if present
  34131. offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
  34132. }
  34133. return programMapTable;
  34134. };
  34135. var parsePesType = function parsePesType(packet, programMapTable) {
  34136. var pid = parsePid(packet);
  34137. var type = programMapTable[pid];
  34138. switch (type) {
  34139. case streamTypes.H264_STREAM_TYPE:
  34140. return 'video';
  34141. case streamTypes.ADTS_STREAM_TYPE:
  34142. return 'audio';
  34143. case streamTypes.METADATA_STREAM_TYPE:
  34144. return 'timed-metadata';
  34145. default:
  34146. return null;
  34147. }
  34148. };
  34149. var parsePesTime = function parsePesTime(packet) {
  34150. var pusi = parsePayloadUnitStartIndicator(packet);
  34151. if (!pusi) {
  34152. return null;
  34153. }
  34154. var offset = 4 + parseAdaptionField(packet);
  34155. if (offset >= packet.byteLength) {
  34156. // From the H 222.0 MPEG-TS spec
  34157. // "For transport stream packets carrying PES packets, stuffing is needed when there
  34158. // is insufficient PES packet data to completely fill the transport stream packet
  34159. // payload bytes. Stuffing is accomplished by defining an adaptation field longer than
  34160. // the sum of the lengths of the data elements in it, so that the payload bytes
  34161. // remaining after the adaptation field exactly accommodates the available PES packet
  34162. // data."
  34163. //
  34164. // If the offset is >= the length of the packet, then the packet contains no data
  34165. // and instead is just adaption field stuffing bytes
  34166. return null;
  34167. }
  34168. var pes = null;
  34169. var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
  34170. // and a DTS value. Determine what combination of values is
  34171. // available to work with.
  34172. ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  34173. // performs all bitwise operations on 32-bit integers but javascript
  34174. // supports a much greater range (52-bits) of integer using standard
  34175. // mathematical operations.
  34176. // We construct a 31-bit value using bitwise operators over the 31
  34177. // most significant bits and then multiply by 4 (equal to a left-shift
  34178. // of 2) before we add the final 2 least significant bits of the
  34179. // timestamp (equal to an OR.)
  34180. if (ptsDtsFlags & 0xC0) {
  34181. pes = {}; // the PTS and DTS are not written out directly. For information
  34182. // on how they are encoded, see
  34183. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  34184. pes.pts = (packet[offset + 9] & 0x0E) << 27 | (packet[offset + 10] & 0xFF) << 20 | (packet[offset + 11] & 0xFE) << 12 | (packet[offset + 12] & 0xFF) << 5 | (packet[offset + 13] & 0xFE) >>> 3;
  34185. pes.pts *= 4; // Left shift by 2
  34186. pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
  34187. pes.dts = pes.pts;
  34188. if (ptsDtsFlags & 0x40) {
  34189. pes.dts = (packet[offset + 14] & 0x0E) << 27 | (packet[offset + 15] & 0xFF) << 20 | (packet[offset + 16] & 0xFE) << 12 | (packet[offset + 17] & 0xFF) << 5 | (packet[offset + 18] & 0xFE) >>> 3;
  34190. pes.dts *= 4; // Left shift by 2
  34191. pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
  34192. }
  34193. }
  34194. return pes;
  34195. };
  34196. var parseNalUnitType = function parseNalUnitType(type) {
  34197. switch (type) {
  34198. case 0x05:
  34199. return 'slice_layer_without_partitioning_rbsp_idr';
  34200. case 0x06:
  34201. return 'sei_rbsp';
  34202. case 0x07:
  34203. return 'seq_parameter_set_rbsp';
  34204. case 0x08:
  34205. return 'pic_parameter_set_rbsp';
  34206. case 0x09:
  34207. return 'access_unit_delimiter_rbsp';
  34208. default:
  34209. return null;
  34210. }
  34211. };
  34212. var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
  34213. var offset = 4 + parseAdaptionField(packet);
  34214. var frameBuffer = packet.subarray(offset);
  34215. var frameI = 0;
  34216. var frameSyncPoint = 0;
  34217. var foundKeyFrame = false;
  34218. var nalType; // advance the sync point to a NAL start, if necessary
  34219. for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
  34220. if (frameBuffer[frameSyncPoint + 2] === 1) {
  34221. // the sync point is properly aligned
  34222. frameI = frameSyncPoint + 5;
  34223. break;
  34224. }
  34225. }
  34226. while (frameI < frameBuffer.byteLength) {
  34227. // look at the current byte to determine if we've hit the end of
  34228. // a NAL unit boundary
  34229. switch (frameBuffer[frameI]) {
  34230. case 0:
  34231. // skip past non-sync sequences
  34232. if (frameBuffer[frameI - 1] !== 0) {
  34233. frameI += 2;
  34234. break;
  34235. } else if (frameBuffer[frameI - 2] !== 0) {
  34236. frameI++;
  34237. break;
  34238. }
  34239. if (frameSyncPoint + 3 !== frameI - 2) {
  34240. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  34241. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  34242. foundKeyFrame = true;
  34243. }
  34244. } // drop trailing zeroes
  34245. do {
  34246. frameI++;
  34247. } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
  34248. frameSyncPoint = frameI - 2;
  34249. frameI += 3;
  34250. break;
  34251. case 1:
  34252. // skip past non-sync sequences
  34253. if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
  34254. frameI += 3;
  34255. break;
  34256. }
  34257. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  34258. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  34259. foundKeyFrame = true;
  34260. }
  34261. frameSyncPoint = frameI - 2;
  34262. frameI += 3;
  34263. break;
  34264. default:
  34265. // the current byte isn't a one or zero, so it cannot be part
  34266. // of a sync sequence
  34267. frameI += 3;
  34268. break;
  34269. }
  34270. }
  34271. frameBuffer = frameBuffer.subarray(frameSyncPoint);
  34272. frameI -= frameSyncPoint;
  34273. frameSyncPoint = 0; // parse the final nal
  34274. if (frameBuffer && frameBuffer.byteLength > 3) {
  34275. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  34276. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  34277. foundKeyFrame = true;
  34278. }
  34279. }
  34280. return foundKeyFrame;
  34281. };
  34282. var probe$1 = {
  34283. parseType: parseType,
  34284. parsePat: parsePat,
  34285. parsePmt: parsePmt,
  34286. parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
  34287. parsePesType: parsePesType,
  34288. parsePesTime: parsePesTime,
  34289. videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
  34290. };
  34291. var handleRollover = timestampRolloverStream.handleRollover;
  34292. var probe = {};
  34293. probe.ts = probe$1;
  34294. probe.aac = utils;
  34295. var ONE_SECOND_IN_TS = clock.ONE_SECOND_IN_TS;
  34296. var MP2T_PACKET_LENGTH = 188,
  34297. // bytes
  34298. SYNC_BYTE = 0x47;
  34299. /**
  34300. * walks through segment data looking for pat and pmt packets to parse out
  34301. * program map table information
  34302. */
  34303. var parsePsi_ = function parsePsi_(bytes, pmt) {
  34304. var startIndex = 0,
  34305. endIndex = MP2T_PACKET_LENGTH,
  34306. packet,
  34307. type;
  34308. while (endIndex < bytes.byteLength) {
  34309. // Look for a pair of start and end sync bytes in the data..
  34310. if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
  34311. // We found a packet
  34312. packet = bytes.subarray(startIndex, endIndex);
  34313. type = probe.ts.parseType(packet, pmt.pid);
  34314. switch (type) {
  34315. case 'pat':
  34316. pmt.pid = probe.ts.parsePat(packet);
  34317. break;
  34318. case 'pmt':
  34319. var table = probe.ts.parsePmt(packet);
  34320. pmt.table = pmt.table || {};
  34321. Object.keys(table).forEach(function (key) {
  34322. pmt.table[key] = table[key];
  34323. });
  34324. break;
  34325. }
  34326. startIndex += MP2T_PACKET_LENGTH;
  34327. endIndex += MP2T_PACKET_LENGTH;
  34328. continue;
  34329. } // If we get here, we have somehow become de-synchronized and we need to step
  34330. // forward one byte at a time until we find a pair of sync bytes that denote
  34331. // a packet
  34332. startIndex++;
  34333. endIndex++;
  34334. }
  34335. };
  34336. /**
  34337. * walks through the segment data from the start and end to get timing information
  34338. * for the first and last audio pes packets
  34339. */
  34340. var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) {
  34341. var startIndex = 0,
  34342. endIndex = MP2T_PACKET_LENGTH,
  34343. packet,
  34344. type,
  34345. pesType,
  34346. pusi,
  34347. parsed;
  34348. var endLoop = false; // Start walking from start of segment to get first audio packet
  34349. while (endIndex <= bytes.byteLength) {
  34350. // Look for a pair of start and end sync bytes in the data..
  34351. if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
  34352. // We found a packet
  34353. packet = bytes.subarray(startIndex, endIndex);
  34354. type = probe.ts.parseType(packet, pmt.pid);
  34355. switch (type) {
  34356. case 'pes':
  34357. pesType = probe.ts.parsePesType(packet, pmt.table);
  34358. pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
  34359. if (pesType === 'audio' && pusi) {
  34360. parsed = probe.ts.parsePesTime(packet);
  34361. if (parsed) {
  34362. parsed.type = 'audio';
  34363. result.audio.push(parsed);
  34364. endLoop = true;
  34365. }
  34366. }
  34367. break;
  34368. }
  34369. if (endLoop) {
  34370. break;
  34371. }
  34372. startIndex += MP2T_PACKET_LENGTH;
  34373. endIndex += MP2T_PACKET_LENGTH;
  34374. continue;
  34375. } // If we get here, we have somehow become de-synchronized and we need to step
  34376. // forward one byte at a time until we find a pair of sync bytes that denote
  34377. // a packet
  34378. startIndex++;
  34379. endIndex++;
  34380. } // Start walking from end of segment to get last audio packet
  34381. endIndex = bytes.byteLength;
  34382. startIndex = endIndex - MP2T_PACKET_LENGTH;
  34383. endLoop = false;
  34384. while (startIndex >= 0) {
  34385. // Look for a pair of start and end sync bytes in the data..
  34386. if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
  34387. // We found a packet
  34388. packet = bytes.subarray(startIndex, endIndex);
  34389. type = probe.ts.parseType(packet, pmt.pid);
  34390. switch (type) {
  34391. case 'pes':
  34392. pesType = probe.ts.parsePesType(packet, pmt.table);
  34393. pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
  34394. if (pesType === 'audio' && pusi) {
  34395. parsed = probe.ts.parsePesTime(packet);
  34396. if (parsed) {
  34397. parsed.type = 'audio';
  34398. result.audio.push(parsed);
  34399. endLoop = true;
  34400. }
  34401. }
  34402. break;
  34403. }
  34404. if (endLoop) {
  34405. break;
  34406. }
  34407. startIndex -= MP2T_PACKET_LENGTH;
  34408. endIndex -= MP2T_PACKET_LENGTH;
  34409. continue;
  34410. } // If we get here, we have somehow become de-synchronized and we need to step
  34411. // forward one byte at a time until we find a pair of sync bytes that denote
  34412. // a packet
  34413. startIndex--;
  34414. endIndex--;
  34415. }
  34416. };
  34417. /**
  34418. * walks through the segment data from the start and end to get timing information
  34419. * for the first and last video pes packets as well as timing information for the first
  34420. * key frame.
  34421. */
  34422. var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) {
  34423. var startIndex = 0,
  34424. endIndex = MP2T_PACKET_LENGTH,
  34425. packet,
  34426. type,
  34427. pesType,
  34428. pusi,
  34429. parsed,
  34430. frame,
  34431. i,
  34432. pes;
  34433. var endLoop = false;
  34434. var currentFrame = {
  34435. data: [],
  34436. size: 0
  34437. }; // Start walking from start of segment to get first video packet
  34438. while (endIndex < bytes.byteLength) {
  34439. // Look for a pair of start and end sync bytes in the data..
  34440. if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
  34441. // We found a packet
  34442. packet = bytes.subarray(startIndex, endIndex);
  34443. type = probe.ts.parseType(packet, pmt.pid);
  34444. switch (type) {
  34445. case 'pes':
  34446. pesType = probe.ts.parsePesType(packet, pmt.table);
  34447. pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
  34448. if (pesType === 'video') {
  34449. if (pusi && !endLoop) {
  34450. parsed = probe.ts.parsePesTime(packet);
  34451. if (parsed) {
  34452. parsed.type = 'video';
  34453. result.video.push(parsed);
  34454. endLoop = true;
  34455. }
  34456. }
  34457. if (!result.firstKeyFrame) {
  34458. if (pusi) {
  34459. if (currentFrame.size !== 0) {
  34460. frame = new Uint8Array(currentFrame.size);
  34461. i = 0;
  34462. while (currentFrame.data.length) {
  34463. pes = currentFrame.data.shift();
  34464. frame.set(pes, i);
  34465. i += pes.byteLength;
  34466. }
  34467. if (probe.ts.videoPacketContainsKeyFrame(frame)) {
  34468. var firstKeyFrame = probe.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting
  34469. // the keyframe seems to work fine with HLS playback
  34470. // and definitely preferable to a crash with TypeError...
  34471. if (firstKeyFrame) {
  34472. result.firstKeyFrame = firstKeyFrame;
  34473. result.firstKeyFrame.type = 'video';
  34474. } else {
  34475. // eslint-disable-next-line
  34476. console.warn('Failed to extract PTS/DTS from PES at first keyframe. ' + 'This could be an unusual TS segment, or else mux.js did not ' + 'parse your TS segment correctly. If you know your TS ' + 'segments do contain PTS/DTS on keyframes please file a bug ' + 'report! You can try ffprobe to double check for yourself.');
  34477. }
  34478. }
  34479. currentFrame.size = 0;
  34480. }
  34481. }
  34482. currentFrame.data.push(packet);
  34483. currentFrame.size += packet.byteLength;
  34484. }
  34485. }
  34486. break;
  34487. }
  34488. if (endLoop && result.firstKeyFrame) {
  34489. break;
  34490. }
  34491. startIndex += MP2T_PACKET_LENGTH;
  34492. endIndex += MP2T_PACKET_LENGTH;
  34493. continue;
  34494. } // If we get here, we have somehow become de-synchronized and we need to step
  34495. // forward one byte at a time until we find a pair of sync bytes that denote
  34496. // a packet
  34497. startIndex++;
  34498. endIndex++;
  34499. } // Start walking from end of segment to get last video packet
  34500. endIndex = bytes.byteLength;
  34501. startIndex = endIndex - MP2T_PACKET_LENGTH;
  34502. endLoop = false;
  34503. while (startIndex >= 0) {
  34504. // Look for a pair of start and end sync bytes in the data..
  34505. if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
  34506. // We found a packet
  34507. packet = bytes.subarray(startIndex, endIndex);
  34508. type = probe.ts.parseType(packet, pmt.pid);
  34509. switch (type) {
  34510. case 'pes':
  34511. pesType = probe.ts.parsePesType(packet, pmt.table);
  34512. pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
  34513. if (pesType === 'video' && pusi) {
  34514. parsed = probe.ts.parsePesTime(packet);
  34515. if (parsed) {
  34516. parsed.type = 'video';
  34517. result.video.push(parsed);
  34518. endLoop = true;
  34519. }
  34520. }
  34521. break;
  34522. }
  34523. if (endLoop) {
  34524. break;
  34525. }
  34526. startIndex -= MP2T_PACKET_LENGTH;
  34527. endIndex -= MP2T_PACKET_LENGTH;
  34528. continue;
  34529. } // If we get here, we have somehow become de-synchronized and we need to step
  34530. // forward one byte at a time until we find a pair of sync bytes that denote
  34531. // a packet
  34532. startIndex--;
  34533. endIndex--;
  34534. }
  34535. };
  34536. /**
  34537. * Adjusts the timestamp information for the segment to account for
  34538. * rollover and convert to seconds based on pes packet timescale (90khz clock)
  34539. */
  34540. var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) {
  34541. if (segmentInfo.audio && segmentInfo.audio.length) {
  34542. var audioBaseTimestamp = baseTimestamp;
  34543. if (typeof audioBaseTimestamp === 'undefined' || isNaN(audioBaseTimestamp)) {
  34544. audioBaseTimestamp = segmentInfo.audio[0].dts;
  34545. }
  34546. segmentInfo.audio.forEach(function (info) {
  34547. info.dts = handleRollover(info.dts, audioBaseTimestamp);
  34548. info.pts = handleRollover(info.pts, audioBaseTimestamp); // time in seconds
  34549. info.dtsTime = info.dts / ONE_SECOND_IN_TS;
  34550. info.ptsTime = info.pts / ONE_SECOND_IN_TS;
  34551. });
  34552. }
  34553. if (segmentInfo.video && segmentInfo.video.length) {
  34554. var videoBaseTimestamp = baseTimestamp;
  34555. if (typeof videoBaseTimestamp === 'undefined' || isNaN(videoBaseTimestamp)) {
  34556. videoBaseTimestamp = segmentInfo.video[0].dts;
  34557. }
  34558. segmentInfo.video.forEach(function (info) {
  34559. info.dts = handleRollover(info.dts, videoBaseTimestamp);
  34560. info.pts = handleRollover(info.pts, videoBaseTimestamp); // time in seconds
  34561. info.dtsTime = info.dts / ONE_SECOND_IN_TS;
  34562. info.ptsTime = info.pts / ONE_SECOND_IN_TS;
  34563. });
  34564. if (segmentInfo.firstKeyFrame) {
  34565. var frame = segmentInfo.firstKeyFrame;
  34566. frame.dts = handleRollover(frame.dts, videoBaseTimestamp);
  34567. frame.pts = handleRollover(frame.pts, videoBaseTimestamp); // time in seconds
  34568. frame.dtsTime = frame.dts / ONE_SECOND_IN_TS;
  34569. frame.ptsTime = frame.pts / ONE_SECOND_IN_TS;
  34570. }
  34571. }
  34572. };
  34573. /**
  34574. * inspects the aac data stream for start and end time information
  34575. */
  34576. var inspectAac_ = function inspectAac_(bytes) {
  34577. var endLoop = false,
  34578. audioCount = 0,
  34579. sampleRate = null,
  34580. timestamp = null,
  34581. frameSize = 0,
  34582. byteIndex = 0,
  34583. packet;
  34584. while (bytes.length - byteIndex >= 3) {
  34585. var type = probe.aac.parseType(bytes, byteIndex);
  34586. switch (type) {
  34587. case 'timed-metadata':
  34588. // Exit early because we don't have enough to parse
  34589. // the ID3 tag header
  34590. if (bytes.length - byteIndex < 10) {
  34591. endLoop = true;
  34592. break;
  34593. }
  34594. frameSize = probe.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
  34595. // to emit a full packet
  34596. if (frameSize > bytes.length) {
  34597. endLoop = true;
  34598. break;
  34599. }
  34600. if (timestamp === null) {
  34601. packet = bytes.subarray(byteIndex, byteIndex + frameSize);
  34602. timestamp = probe.aac.parseAacTimestamp(packet);
  34603. }
  34604. byteIndex += frameSize;
  34605. break;
  34606. case 'audio':
  34607. // Exit early because we don't have enough to parse
  34608. // the ADTS frame header
  34609. if (bytes.length - byteIndex < 7) {
  34610. endLoop = true;
  34611. break;
  34612. }
  34613. frameSize = probe.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
  34614. // to emit a full packet
  34615. if (frameSize > bytes.length) {
  34616. endLoop = true;
  34617. break;
  34618. }
  34619. if (sampleRate === null) {
  34620. packet = bytes.subarray(byteIndex, byteIndex + frameSize);
  34621. sampleRate = probe.aac.parseSampleRate(packet);
  34622. }
  34623. audioCount++;
  34624. byteIndex += frameSize;
  34625. break;
  34626. default:
  34627. byteIndex++;
  34628. break;
  34629. }
  34630. if (endLoop) {
  34631. return null;
  34632. }
  34633. }
  34634. if (sampleRate === null || timestamp === null) {
  34635. return null;
  34636. }
  34637. var audioTimescale = ONE_SECOND_IN_TS / sampleRate;
  34638. var result = {
  34639. audio: [{
  34640. type: 'audio',
  34641. dts: timestamp,
  34642. pts: timestamp
  34643. }, {
  34644. type: 'audio',
  34645. dts: timestamp + audioCount * 1024 * audioTimescale,
  34646. pts: timestamp + audioCount * 1024 * audioTimescale
  34647. }]
  34648. };
  34649. return result;
  34650. };
  34651. /**
  34652. * inspects the transport stream segment data for start and end time information
  34653. * of the audio and video tracks (when present) as well as the first key frame's
  34654. * start time.
  34655. */
  34656. var inspectTs_ = function inspectTs_(bytes) {
  34657. var pmt = {
  34658. pid: null,
  34659. table: null
  34660. };
  34661. var result = {};
  34662. parsePsi_(bytes, pmt);
  34663. for (var pid in pmt.table) {
  34664. if (pmt.table.hasOwnProperty(pid)) {
  34665. var type = pmt.table[pid];
  34666. switch (type) {
  34667. case streamTypes.H264_STREAM_TYPE:
  34668. result.video = [];
  34669. parseVideoPes_(bytes, pmt, result);
  34670. if (result.video.length === 0) {
  34671. delete result.video;
  34672. }
  34673. break;
  34674. case streamTypes.ADTS_STREAM_TYPE:
  34675. result.audio = [];
  34676. parseAudioPes_(bytes, pmt, result);
  34677. if (result.audio.length === 0) {
  34678. delete result.audio;
  34679. }
  34680. break;
  34681. }
  34682. }
  34683. }
  34684. return result;
  34685. };
  34686. /**
  34687. * Inspects segment byte data and returns an object with start and end timing information
  34688. *
  34689. * @param {Uint8Array} bytes The segment byte data
  34690. * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
  34691. * timestamps for rollover. This value must be in 90khz clock.
  34692. * @return {Object} Object containing start and end frame timing info of segment.
  34693. */
  34694. var inspect = function inspect(bytes, baseTimestamp) {
  34695. var isAacData = probe.aac.isLikelyAacData(bytes);
  34696. var result;
  34697. if (isAacData) {
  34698. result = inspectAac_(bytes);
  34699. } else {
  34700. result = inspectTs_(bytes);
  34701. }
  34702. if (!result || !result.audio && !result.video) {
  34703. return null;
  34704. }
  34705. adjustTimestamp_(result, baseTimestamp);
  34706. return result;
  34707. };
  34708. var tsInspector = {
  34709. inspect: inspect,
  34710. parseAudioPes_: parseAudioPes_
  34711. };
  34712. /* global self */
  34713. /**
  34714. * Re-emits transmuxer events by converting them into messages to the
  34715. * world outside the worker.
  34716. *
  34717. * @param {Object} transmuxer the transmuxer to wire events on
  34718. * @private
  34719. */
  34720. var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer) {
  34721. transmuxer.on('data', function (segment) {
  34722. // transfer ownership of the underlying ArrayBuffer
  34723. // instead of doing a copy to save memory
  34724. // ArrayBuffers are transferable but generic TypedArrays are not
  34725. // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
  34726. var initArray = segment.initSegment;
  34727. segment.initSegment = {
  34728. data: initArray.buffer,
  34729. byteOffset: initArray.byteOffset,
  34730. byteLength: initArray.byteLength
  34731. };
  34732. var typedArray = segment.data;
  34733. segment.data = typedArray.buffer;
  34734. self.postMessage({
  34735. action: 'data',
  34736. segment: segment,
  34737. byteOffset: typedArray.byteOffset,
  34738. byteLength: typedArray.byteLength
  34739. }, [segment.data]);
  34740. });
  34741. transmuxer.on('done', function (data) {
  34742. self.postMessage({
  34743. action: 'done'
  34744. });
  34745. });
  34746. transmuxer.on('gopInfo', function (gopInfo) {
  34747. self.postMessage({
  34748. action: 'gopInfo',
  34749. gopInfo: gopInfo
  34750. });
  34751. });
  34752. transmuxer.on('videoSegmentTimingInfo', function (timingInfo) {
  34753. var videoSegmentTimingInfo = {
  34754. start: {
  34755. decode: clock.videoTsToSeconds(timingInfo.start.dts),
  34756. presentation: clock.videoTsToSeconds(timingInfo.start.pts)
  34757. },
  34758. end: {
  34759. decode: clock.videoTsToSeconds(timingInfo.end.dts),
  34760. presentation: clock.videoTsToSeconds(timingInfo.end.pts)
  34761. },
  34762. baseMediaDecodeTime: clock.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
  34763. };
  34764. if (timingInfo.prependedContentDuration) {
  34765. videoSegmentTimingInfo.prependedContentDuration = clock.videoTsToSeconds(timingInfo.prependedContentDuration);
  34766. }
  34767. self.postMessage({
  34768. action: 'videoSegmentTimingInfo',
  34769. videoSegmentTimingInfo: videoSegmentTimingInfo
  34770. });
  34771. });
  34772. transmuxer.on('audioSegmentTimingInfo', function (timingInfo) {
  34773. // Note that all times for [audio/video]SegmentTimingInfo events are in video clock
  34774. var audioSegmentTimingInfo = {
  34775. start: {
  34776. decode: clock.videoTsToSeconds(timingInfo.start.dts),
  34777. presentation: clock.videoTsToSeconds(timingInfo.start.pts)
  34778. },
  34779. end: {
  34780. decode: clock.videoTsToSeconds(timingInfo.end.dts),
  34781. presentation: clock.videoTsToSeconds(timingInfo.end.pts)
  34782. },
  34783. baseMediaDecodeTime: clock.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
  34784. };
  34785. if (timingInfo.prependedContentDuration) {
  34786. audioSegmentTimingInfo.prependedContentDuration = clock.videoTsToSeconds(timingInfo.prependedContentDuration);
  34787. }
  34788. self.postMessage({
  34789. action: 'audioSegmentTimingInfo',
  34790. audioSegmentTimingInfo: audioSegmentTimingInfo
  34791. });
  34792. });
  34793. transmuxer.on('id3Frame', function (id3Frame) {
  34794. self.postMessage({
  34795. action: 'id3Frame',
  34796. id3Frame: id3Frame
  34797. });
  34798. });
  34799. transmuxer.on('caption', function (caption) {
  34800. self.postMessage({
  34801. action: 'caption',
  34802. caption: caption
  34803. });
  34804. });
  34805. transmuxer.on('trackinfo', function (trackInfo) {
  34806. self.postMessage({
  34807. action: 'trackinfo',
  34808. trackInfo: trackInfo
  34809. });
  34810. });
  34811. transmuxer.on('audioTimingInfo', function (audioTimingInfo) {
  34812. // convert to video TS since we prioritize video time over audio
  34813. self.postMessage({
  34814. action: 'audioTimingInfo',
  34815. audioTimingInfo: {
  34816. start: clock.videoTsToSeconds(audioTimingInfo.start),
  34817. end: clock.videoTsToSeconds(audioTimingInfo.end)
  34818. }
  34819. });
  34820. });
  34821. transmuxer.on('videoTimingInfo', function (videoTimingInfo) {
  34822. self.postMessage({
  34823. action: 'videoTimingInfo',
  34824. videoTimingInfo: {
  34825. start: clock.videoTsToSeconds(videoTimingInfo.start),
  34826. end: clock.videoTsToSeconds(videoTimingInfo.end)
  34827. }
  34828. });
  34829. });
  34830. transmuxer.on('log', function (log) {
  34831. self.postMessage({
  34832. action: 'log',
  34833. log: log
  34834. });
  34835. });
  34836. };
  34837. /**
  34838. * All incoming messages route through this hash. If no function exists
  34839. * to handle an incoming message, then we ignore the message.
  34840. *
  34841. * @class MessageHandlers
  34842. * @param {Object} options the options to initialize with
  34843. */
  34844. var MessageHandlers = /*#__PURE__*/function () {
  34845. function MessageHandlers(self, options) {
  34846. this.options = options || {};
  34847. this.self = self;
  34848. this.init();
  34849. }
  34850. /**
  34851. * initialize our web worker and wire all the events.
  34852. */
  34853. var _proto = MessageHandlers.prototype;
  34854. _proto.init = function init() {
  34855. if (this.transmuxer) {
  34856. this.transmuxer.dispose();
  34857. }
  34858. this.transmuxer = new transmuxer.Transmuxer(this.options);
  34859. wireTransmuxerEvents(this.self, this.transmuxer);
  34860. };
  34861. _proto.pushMp4Captions = function pushMp4Captions(data) {
  34862. if (!this.captionParser) {
  34863. this.captionParser = new captionParser();
  34864. this.captionParser.init();
  34865. }
  34866. var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
  34867. var parsed = this.captionParser.parse(segment, data.trackIds, data.timescales);
  34868. this.self.postMessage({
  34869. action: 'mp4Captions',
  34870. captions: parsed && parsed.captions || [],
  34871. logs: parsed && parsed.logs || [],
  34872. data: segment.buffer
  34873. }, [segment.buffer]);
  34874. };
  34875. _proto.probeMp4StartTime = function probeMp4StartTime(_ref) {
  34876. var timescales = _ref.timescales,
  34877. data = _ref.data;
  34878. var startTime = probe$2.startTime(timescales, data);
  34879. this.self.postMessage({
  34880. action: 'probeMp4StartTime',
  34881. startTime: startTime,
  34882. data: data
  34883. }, [data.buffer]);
  34884. };
  34885. _proto.probeMp4Tracks = function probeMp4Tracks(_ref2) {
  34886. var data = _ref2.data;
  34887. var tracks = probe$2.tracks(data);
  34888. this.self.postMessage({
  34889. action: 'probeMp4Tracks',
  34890. tracks: tracks,
  34891. data: data
  34892. }, [data.buffer]);
  34893. }
  34894. /**
  34895. * Probe an mpeg2-ts segment to determine the start time of the segment in it's
  34896. * internal "media time," as well as whether it contains video and/or audio.
  34897. *
  34898. * @private
  34899. * @param {Uint8Array} bytes - segment bytes
  34900. * @param {number} baseStartTime
  34901. * Relative reference timestamp used when adjusting frame timestamps for rollover.
  34902. * This value should be in seconds, as it's converted to a 90khz clock within the
  34903. * function body.
  34904. * @return {Object} The start time of the current segment in "media time" as well as
  34905. * whether it contains video and/or audio
  34906. */
  34907. ;
  34908. _proto.probeTs = function probeTs(_ref3) {
  34909. var data = _ref3.data,
  34910. baseStartTime = _ref3.baseStartTime;
  34911. var tsStartTime = typeof baseStartTime === 'number' && !isNaN(baseStartTime) ? baseStartTime * clock.ONE_SECOND_IN_TS : void 0;
  34912. var timeInfo = tsInspector.inspect(data, tsStartTime);
  34913. var result = null;
  34914. if (timeInfo) {
  34915. result = {
  34916. // each type's time info comes back as an array of 2 times, start and end
  34917. hasVideo: timeInfo.video && timeInfo.video.length === 2 || false,
  34918. hasAudio: timeInfo.audio && timeInfo.audio.length === 2 || false
  34919. };
  34920. if (result.hasVideo) {
  34921. result.videoStart = timeInfo.video[0].ptsTime;
  34922. }
  34923. if (result.hasAudio) {
  34924. result.audioStart = timeInfo.audio[0].ptsTime;
  34925. }
  34926. }
  34927. this.self.postMessage({
  34928. action: 'probeTs',
  34929. result: result,
  34930. data: data
  34931. }, [data.buffer]);
  34932. };
  34933. _proto.clearAllMp4Captions = function clearAllMp4Captions() {
  34934. if (this.captionParser) {
  34935. this.captionParser.clearAllCaptions();
  34936. }
  34937. };
  34938. _proto.clearParsedMp4Captions = function clearParsedMp4Captions() {
  34939. if (this.captionParser) {
  34940. this.captionParser.clearParsedCaptions();
  34941. }
  34942. }
  34943. /**
  34944. * Adds data (a ts segment) to the start of the transmuxer pipeline for
  34945. * processing.
  34946. *
  34947. * @param {ArrayBuffer} data data to push into the muxer
  34948. */
  34949. ;
  34950. _proto.push = function push(data) {
  34951. // Cast array buffer to correct type for transmuxer
  34952. var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
  34953. this.transmuxer.push(segment);
  34954. }
  34955. /**
  34956. * Recreate the transmuxer so that the next segment added via `push`
  34957. * start with a fresh transmuxer.
  34958. */
  34959. ;
  34960. _proto.reset = function reset() {
  34961. this.transmuxer.reset();
  34962. }
  34963. /**
  34964. * Set the value that will be used as the `baseMediaDecodeTime` time for the
  34965. * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
  34966. * set relative to the first based on the PTS values.
  34967. *
  34968. * @param {Object} data used to set the timestamp offset in the muxer
  34969. */
  34970. ;
  34971. _proto.setTimestampOffset = function setTimestampOffset(data) {
  34972. var timestampOffset = data.timestampOffset || 0;
  34973. this.transmuxer.setBaseMediaDecodeTime(Math.round(clock.secondsToVideoTs(timestampOffset)));
  34974. };
  34975. _proto.setAudioAppendStart = function setAudioAppendStart(data) {
  34976. this.transmuxer.setAudioAppendStart(Math.ceil(clock.secondsToVideoTs(data.appendStart)));
  34977. };
  34978. _proto.setRemux = function setRemux(data) {
  34979. this.transmuxer.setRemux(data.remux);
  34980. }
  34981. /**
  34982. * Forces the pipeline to finish processing the last segment and emit it's
  34983. * results.
  34984. *
  34985. * @param {Object} data event data, not really used
  34986. */
  34987. ;
  34988. _proto.flush = function flush(data) {
  34989. this.transmuxer.flush(); // transmuxed done action is fired after both audio/video pipelines are flushed
  34990. self.postMessage({
  34991. action: 'done',
  34992. type: 'transmuxed'
  34993. });
  34994. };
  34995. _proto.endTimeline = function endTimeline() {
  34996. this.transmuxer.endTimeline(); // transmuxed endedtimeline action is fired after both audio/video pipelines end their
  34997. // timelines
  34998. self.postMessage({
  34999. action: 'endedtimeline',
  35000. type: 'transmuxed'
  35001. });
  35002. };
  35003. _proto.alignGopsWith = function alignGopsWith(data) {
  35004. this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
  35005. };
  35006. return MessageHandlers;
  35007. }();
  35008. /**
  35009. * Our web worker interface so that things can talk to mux.js
  35010. * that will be running in a web worker. the scope is passed to this by
  35011. * webworkify.
  35012. *
  35013. * @param {Object} self the scope for the web worker
  35014. */
  35015. self.onmessage = function (event) {
  35016. if (event.data.action === 'init' && event.data.options) {
  35017. this.messageHandlers = new MessageHandlers(self, event.data.options);
  35018. return;
  35019. }
  35020. if (!this.messageHandlers) {
  35021. this.messageHandlers = new MessageHandlers(self);
  35022. }
  35023. if (event.data && event.data.action && event.data.action !== 'init') {
  35024. if (this.messageHandlers[event.data.action]) {
  35025. this.messageHandlers[event.data.action](event.data);
  35026. }
  35027. }
  35028. };
  35029. }));
  35030. var TransmuxWorker = factory(workerCode$1);
  35031. /* rollup-plugin-worker-factory end for worker!/Users/ddashkevich/projects/http-streaming/src/transmuxer-worker.js */
  35032. var handleData_ = function handleData_(event, transmuxedData, callback) {
  35033. var _event$data$segment = event.data.segment,
  35034. type = _event$data$segment.type,
  35035. initSegment = _event$data$segment.initSegment,
  35036. captions = _event$data$segment.captions,
  35037. captionStreams = _event$data$segment.captionStreams,
  35038. metadata = _event$data$segment.metadata,
  35039. videoFrameDtsTime = _event$data$segment.videoFrameDtsTime,
  35040. videoFramePtsTime = _event$data$segment.videoFramePtsTime;
  35041. transmuxedData.buffer.push({
  35042. captions: captions,
  35043. captionStreams: captionStreams,
  35044. metadata: metadata
  35045. });
  35046. var boxes = event.data.segment.boxes || {
  35047. data: event.data.segment.data
  35048. };
  35049. var result = {
  35050. type: type,
  35051. // cast ArrayBuffer to TypedArray
  35052. data: new Uint8Array(boxes.data, boxes.data.byteOffset, boxes.data.byteLength),
  35053. initSegment: new Uint8Array(initSegment.data, initSegment.byteOffset, initSegment.byteLength)
  35054. };
  35055. if (typeof videoFrameDtsTime !== 'undefined') {
  35056. result.videoFrameDtsTime = videoFrameDtsTime;
  35057. }
  35058. if (typeof videoFramePtsTime !== 'undefined') {
  35059. result.videoFramePtsTime = videoFramePtsTime;
  35060. }
  35061. callback(result);
  35062. };
  35063. var handleDone_ = function handleDone_(_ref) {
  35064. var transmuxedData = _ref.transmuxedData,
  35065. callback = _ref.callback; // Previously we only returned data on data events,
  35066. // not on done events. Clear out the buffer to keep that consistent.
  35067. transmuxedData.buffer = []; // all buffers should have been flushed from the muxer, so start processing anything we
  35068. // have received
  35069. callback(transmuxedData);
  35070. };
  35071. var handleGopInfo_ = function handleGopInfo_(event, transmuxedData) {
  35072. transmuxedData.gopInfo = event.data.gopInfo;
  35073. };
  35074. var processTransmux = function processTransmux(options) {
  35075. var transmuxer = options.transmuxer,
  35076. bytes = options.bytes,
  35077. audioAppendStart = options.audioAppendStart,
  35078. gopsToAlignWith = options.gopsToAlignWith,
  35079. remux = options.remux,
  35080. onData = options.onData,
  35081. onTrackInfo = options.onTrackInfo,
  35082. onAudioTimingInfo = options.onAudioTimingInfo,
  35083. onVideoTimingInfo = options.onVideoTimingInfo,
  35084. onVideoSegmentTimingInfo = options.onVideoSegmentTimingInfo,
  35085. onAudioSegmentTimingInfo = options.onAudioSegmentTimingInfo,
  35086. onId3 = options.onId3,
  35087. onCaptions = options.onCaptions,
  35088. onDone = options.onDone,
  35089. onEndedTimeline = options.onEndedTimeline,
  35090. onTransmuxerLog = options.onTransmuxerLog,
  35091. isEndOfTimeline = options.isEndOfTimeline;
  35092. var transmuxedData = {
  35093. buffer: []
  35094. };
  35095. var waitForEndedTimelineEvent = isEndOfTimeline;
  35096. var handleMessage = function handleMessage(event) {
  35097. if (transmuxer.currentTransmux !== options) {
  35098. // disposed
  35099. return;
  35100. }
  35101. if (event.data.action === 'data') {
  35102. handleData_(event, transmuxedData, onData);
  35103. }
  35104. if (event.data.action === 'trackinfo') {
  35105. onTrackInfo(event.data.trackInfo);
  35106. }
  35107. if (event.data.action === 'gopInfo') {
  35108. handleGopInfo_(event, transmuxedData);
  35109. }
  35110. if (event.data.action === 'audioTimingInfo') {
  35111. onAudioTimingInfo(event.data.audioTimingInfo);
  35112. }
  35113. if (event.data.action === 'videoTimingInfo') {
  35114. onVideoTimingInfo(event.data.videoTimingInfo);
  35115. }
  35116. if (event.data.action === 'videoSegmentTimingInfo') {
  35117. onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo);
  35118. }
  35119. if (event.data.action === 'audioSegmentTimingInfo') {
  35120. onAudioSegmentTimingInfo(event.data.audioSegmentTimingInfo);
  35121. }
  35122. if (event.data.action === 'id3Frame') {
  35123. onId3([event.data.id3Frame], event.data.id3Frame.dispatchType);
  35124. }
  35125. if (event.data.action === 'caption') {
  35126. onCaptions(event.data.caption);
  35127. }
  35128. if (event.data.action === 'endedtimeline') {
  35129. waitForEndedTimelineEvent = false;
  35130. onEndedTimeline();
  35131. }
  35132. if (event.data.action === 'log') {
  35133. onTransmuxerLog(event.data.log);
  35134. } // wait for the transmuxed event since we may have audio and video
  35135. if (event.data.type !== 'transmuxed') {
  35136. return;
  35137. } // If the "endedtimeline" event has not yet fired, and this segment represents the end
  35138. // of a timeline, that means there may still be data events before the segment
  35139. // processing can be considerred complete. In that case, the final event should be
  35140. // an "endedtimeline" event with the type "transmuxed."
  35141. if (waitForEndedTimelineEvent) {
  35142. return;
  35143. }
  35144. transmuxer.onmessage = null;
  35145. handleDone_({
  35146. transmuxedData: transmuxedData,
  35147. callback: onDone
  35148. });
  35149. /* eslint-disable no-use-before-define */
  35150. dequeue(transmuxer);
  35151. /* eslint-enable */
  35152. };
  35153. transmuxer.onmessage = handleMessage;
  35154. if (audioAppendStart) {
  35155. transmuxer.postMessage({
  35156. action: 'setAudioAppendStart',
  35157. appendStart: audioAppendStart
  35158. });
  35159. } // allow empty arrays to be passed to clear out GOPs
  35160. if (Array.isArray(gopsToAlignWith)) {
  35161. transmuxer.postMessage({
  35162. action: 'alignGopsWith',
  35163. gopsToAlignWith: gopsToAlignWith
  35164. });
  35165. }
  35166. if (typeof remux !== 'undefined') {
  35167. transmuxer.postMessage({
  35168. action: 'setRemux',
  35169. remux: remux
  35170. });
  35171. }
  35172. if (bytes.byteLength) {
  35173. var buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer;
  35174. var byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset;
  35175. transmuxer.postMessage({
  35176. action: 'push',
  35177. // Send the typed-array of data as an ArrayBuffer so that
  35178. // it can be sent as a "Transferable" and avoid the costly
  35179. // memory copy
  35180. data: buffer,
  35181. // To recreate the original typed-array, we need information
  35182. // about what portion of the ArrayBuffer it was a view into
  35183. byteOffset: byteOffset,
  35184. byteLength: bytes.byteLength
  35185. }, [buffer]);
  35186. }
  35187. if (isEndOfTimeline) {
  35188. transmuxer.postMessage({
  35189. action: 'endTimeline'
  35190. });
  35191. } // even if we didn't push any bytes, we have to make sure we flush in case we reached
  35192. // the end of the segment
  35193. transmuxer.postMessage({
  35194. action: 'flush'
  35195. });
  35196. };
  35197. var dequeue = function dequeue(transmuxer) {
  35198. transmuxer.currentTransmux = null;
  35199. if (transmuxer.transmuxQueue.length) {
  35200. transmuxer.currentTransmux = transmuxer.transmuxQueue.shift();
  35201. if (typeof transmuxer.currentTransmux === 'function') {
  35202. transmuxer.currentTransmux();
  35203. } else {
  35204. processTransmux(transmuxer.currentTransmux);
  35205. }
  35206. }
  35207. };
  35208. var processAction = function processAction(transmuxer, action) {
  35209. transmuxer.postMessage({
  35210. action: action
  35211. });
  35212. dequeue(transmuxer);
  35213. };
  35214. var enqueueAction = function enqueueAction(action, transmuxer) {
  35215. if (!transmuxer.currentTransmux) {
  35216. transmuxer.currentTransmux = action;
  35217. processAction(transmuxer, action);
  35218. return;
  35219. }
  35220. transmuxer.transmuxQueue.push(processAction.bind(null, transmuxer, action));
  35221. };
  35222. var reset = function reset(transmuxer) {
  35223. enqueueAction('reset', transmuxer);
  35224. };
  35225. var endTimeline = function endTimeline(transmuxer) {
  35226. enqueueAction('endTimeline', transmuxer);
  35227. };
  35228. var transmux = function transmux(options) {
  35229. if (!options.transmuxer.currentTransmux) {
  35230. options.transmuxer.currentTransmux = options;
  35231. processTransmux(options);
  35232. return;
  35233. }
  35234. options.transmuxer.transmuxQueue.push(options);
  35235. };
  35236. var createTransmuxer = function createTransmuxer(options) {
  35237. var transmuxer = new TransmuxWorker();
  35238. transmuxer.currentTransmux = null;
  35239. transmuxer.transmuxQueue = [];
  35240. var term = transmuxer.terminate;
  35241. transmuxer.terminate = function () {
  35242. transmuxer.currentTransmux = null;
  35243. transmuxer.transmuxQueue.length = 0;
  35244. return term.call(transmuxer);
  35245. };
  35246. transmuxer.postMessage({
  35247. action: 'init',
  35248. options: options
  35249. });
  35250. return transmuxer;
  35251. };
  35252. var segmentTransmuxer = {
  35253. reset: reset,
  35254. endTimeline: endTimeline,
  35255. transmux: transmux,
  35256. createTransmuxer: createTransmuxer
  35257. };
  35258. var workerCallback = function workerCallback(options) {
  35259. var transmuxer = options.transmuxer;
  35260. var endAction = options.endAction || options.action;
  35261. var callback = options.callback;
  35262. var message = _extends({}, options, {
  35263. endAction: null,
  35264. transmuxer: null,
  35265. callback: null
  35266. });
  35267. var listenForEndEvent = function listenForEndEvent(event) {
  35268. if (event.data.action !== endAction) {
  35269. return;
  35270. }
  35271. transmuxer.removeEventListener('message', listenForEndEvent); // transfer ownership of bytes back to us.
  35272. if (event.data.data) {
  35273. event.data.data = new Uint8Array(event.data.data, options.byteOffset || 0, options.byteLength || event.data.data.byteLength);
  35274. if (options.data) {
  35275. options.data = event.data.data;
  35276. }
  35277. }
  35278. callback(event.data);
  35279. };
  35280. transmuxer.addEventListener('message', listenForEndEvent);
  35281. if (options.data) {
  35282. var isArrayBuffer = options.data instanceof ArrayBuffer;
  35283. message.byteOffset = isArrayBuffer ? 0 : options.data.byteOffset;
  35284. message.byteLength = options.data.byteLength;
  35285. var transfers = [isArrayBuffer ? options.data : options.data.buffer];
  35286. transmuxer.postMessage(message, transfers);
  35287. } else {
  35288. transmuxer.postMessage(message);
  35289. }
  35290. };
  35291. var REQUEST_ERRORS = {
  35292. FAILURE: 2,
  35293. TIMEOUT: -101,
  35294. ABORTED: -102
  35295. };
  35296. /**
  35297. * Abort all requests
  35298. *
  35299. * @param {Object} activeXhrs - an object that tracks all XHR requests
  35300. */
  35301. var abortAll = function abortAll(activeXhrs) {
  35302. activeXhrs.forEach(function (xhr) {
  35303. xhr.abort();
  35304. });
  35305. };
  35306. /**
  35307. * Gather important bandwidth stats once a request has completed
  35308. *
  35309. * @param {Object} request - the XHR request from which to gather stats
  35310. */
  35311. var getRequestStats = function getRequestStats(request) {
  35312. return {
  35313. bandwidth: request.bandwidth,
  35314. bytesReceived: request.bytesReceived || 0,
  35315. roundTripTime: request.roundTripTime || 0
  35316. };
  35317. };
  35318. /**
  35319. * If possible gather bandwidth stats as a request is in
  35320. * progress
  35321. *
  35322. * @param {Event} progressEvent - an event object from an XHR's progress event
  35323. */
  35324. var getProgressStats = function getProgressStats(progressEvent) {
  35325. var request = progressEvent.target;
  35326. var roundTripTime = Date.now() - request.requestTime;
  35327. var stats = {
  35328. bandwidth: Infinity,
  35329. bytesReceived: 0,
  35330. roundTripTime: roundTripTime || 0
  35331. };
  35332. stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
  35333. // because we should only use bandwidth stats on progress to determine when
  35334. // abort a request early due to insufficient bandwidth
  35335. stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
  35336. return stats;
  35337. };
  35338. /**
  35339. * Handle all error conditions in one place and return an object
  35340. * with all the information
  35341. *
  35342. * @param {Error|null} error - if non-null signals an error occured with the XHR
  35343. * @param {Object} request - the XHR request that possibly generated the error
  35344. */
  35345. var handleErrors = function handleErrors(error, request) {
  35346. if (request.timedout) {
  35347. return {
  35348. status: request.status,
  35349. message: 'HLS request timed-out at URL: ' + request.uri,
  35350. code: REQUEST_ERRORS.TIMEOUT,
  35351. xhr: request
  35352. };
  35353. }
  35354. if (request.aborted) {
  35355. return {
  35356. status: request.status,
  35357. message: 'HLS request aborted at URL: ' + request.uri,
  35358. code: REQUEST_ERRORS.ABORTED,
  35359. xhr: request
  35360. };
  35361. }
  35362. if (error) {
  35363. return {
  35364. status: request.status,
  35365. message: 'HLS request errored at URL: ' + request.uri,
  35366. code: REQUEST_ERRORS.FAILURE,
  35367. xhr: request
  35368. };
  35369. }
  35370. if (request.responseType === 'arraybuffer' && request.response.byteLength === 0) {
  35371. return {
  35372. status: request.status,
  35373. message: 'Empty HLS response at URL: ' + request.uri,
  35374. code: REQUEST_ERRORS.FAILURE,
  35375. xhr: request
  35376. };
  35377. }
  35378. return null;
  35379. };
  35380. /**
  35381. * Handle responses for key data and convert the key data to the correct format
  35382. * for the decryption step later
  35383. *
  35384. * @param {Object} segment - a simplified copy of the segmentInfo object
  35385. * from SegmentLoader
  35386. * @param {Array} objects - objects to add the key bytes to.
  35387. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  35388. * this request
  35389. */
  35390. var handleKeyResponse = function handleKeyResponse(segment, objects, finishProcessingFn) {
  35391. return function (error, request) {
  35392. var response = request.response;
  35393. var errorObj = handleErrors(error, request);
  35394. if (errorObj) {
  35395. return finishProcessingFn(errorObj, segment);
  35396. }
  35397. if (response.byteLength !== 16) {
  35398. return finishProcessingFn({
  35399. status: request.status,
  35400. message: 'Invalid HLS key at URL: ' + request.uri,
  35401. code: REQUEST_ERRORS.FAILURE,
  35402. xhr: request
  35403. }, segment);
  35404. }
  35405. var view = new DataView(response);
  35406. var bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
  35407. for (var i = 0; i < objects.length; i++) {
  35408. objects[i].bytes = bytes;
  35409. }
  35410. return finishProcessingFn(null, segment);
  35411. };
  35412. };
  35413. var parseInitSegment = function parseInitSegment(segment, _callback) {
  35414. var type = detectContainerForBytes(segment.map.bytes); // TODO: We should also handle ts init segments here, but we
  35415. // only know how to parse mp4 init segments at the moment
  35416. if (type !== 'mp4') {
  35417. var uri = segment.map.resolvedUri || segment.map.uri;
  35418. return _callback({
  35419. internal: true,
  35420. message: "Found unsupported " + (type || 'unknown') + " container for initialization segment at URL: " + uri,
  35421. code: REQUEST_ERRORS.FAILURE
  35422. });
  35423. }
  35424. workerCallback({
  35425. action: 'probeMp4Tracks',
  35426. data: segment.map.bytes,
  35427. transmuxer: segment.transmuxer,
  35428. callback: function callback(_ref) {
  35429. var tracks = _ref.tracks,
  35430. data = _ref.data; // transfer bytes back to us
  35431. segment.map.bytes = data;
  35432. tracks.forEach(function (track) {
  35433. segment.map.tracks = segment.map.tracks || {}; // only support one track of each type for now
  35434. if (segment.map.tracks[track.type]) {
  35435. return;
  35436. }
  35437. segment.map.tracks[track.type] = track;
  35438. if (typeof track.id === 'number' && track.timescale) {
  35439. segment.map.timescales = segment.map.timescales || {};
  35440. segment.map.timescales[track.id] = track.timescale;
  35441. }
  35442. });
  35443. return _callback(null);
  35444. }
  35445. });
  35446. };
  35447. /**
  35448. * Handle init-segment responses
  35449. *
  35450. * @param {Object} segment - a simplified copy of the segmentInfo object
  35451. * from SegmentLoader
  35452. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  35453. * this request
  35454. */
  35455. var handleInitSegmentResponse = function handleInitSegmentResponse(_ref2) {
  35456. var segment = _ref2.segment,
  35457. finishProcessingFn = _ref2.finishProcessingFn;
  35458. return function (error, request) {
  35459. var errorObj = handleErrors(error, request);
  35460. if (errorObj) {
  35461. return finishProcessingFn(errorObj, segment);
  35462. }
  35463. var bytes = new Uint8Array(request.response); // init segment is encypted, we will have to wait
  35464. // until the key request is done to decrypt.
  35465. if (segment.map.key) {
  35466. segment.map.encryptedBytes = bytes;
  35467. return finishProcessingFn(null, segment);
  35468. }
  35469. segment.map.bytes = bytes;
  35470. parseInitSegment(segment, function (parseError) {
  35471. if (parseError) {
  35472. parseError.xhr = request;
  35473. parseError.status = request.status;
  35474. return finishProcessingFn(parseError, segment);
  35475. }
  35476. finishProcessingFn(null, segment);
  35477. });
  35478. };
  35479. };
  35480. /**
  35481. * Response handler for segment-requests being sure to set the correct
  35482. * property depending on whether the segment is encryped or not
  35483. * Also records and keeps track of stats that are used for ABR purposes
  35484. *
  35485. * @param {Object} segment - a simplified copy of the segmentInfo object
  35486. * from SegmentLoader
  35487. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  35488. * this request
  35489. */
  35490. var handleSegmentResponse = function handleSegmentResponse(_ref3) {
  35491. var segment = _ref3.segment,
  35492. finishProcessingFn = _ref3.finishProcessingFn,
  35493. responseType = _ref3.responseType;
  35494. return function (error, request) {
  35495. var errorObj = handleErrors(error, request);
  35496. if (errorObj) {
  35497. return finishProcessingFn(errorObj, segment);
  35498. }
  35499. var newBytes = // although responseText "should" exist, this guard serves to prevent an error being
  35500. // thrown for two primary cases:
  35501. // 1. the mime type override stops working, or is not implemented for a specific
  35502. // browser
  35503. // 2. when using mock XHR libraries like sinon that do not allow the override behavior
  35504. responseType === 'arraybuffer' || !request.responseText ? request.response : stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0));
  35505. segment.stats = getRequestStats(request);
  35506. if (segment.key) {
  35507. segment.encryptedBytes = new Uint8Array(newBytes);
  35508. } else {
  35509. segment.bytes = new Uint8Array(newBytes);
  35510. }
  35511. return finishProcessingFn(null, segment);
  35512. };
  35513. };
  35514. var transmuxAndNotify = function transmuxAndNotify(_ref4) {
  35515. var segment = _ref4.segment,
  35516. bytes = _ref4.bytes,
  35517. trackInfoFn = _ref4.trackInfoFn,
  35518. timingInfoFn = _ref4.timingInfoFn,
  35519. videoSegmentTimingInfoFn = _ref4.videoSegmentTimingInfoFn,
  35520. audioSegmentTimingInfoFn = _ref4.audioSegmentTimingInfoFn,
  35521. id3Fn = _ref4.id3Fn,
  35522. captionsFn = _ref4.captionsFn,
  35523. isEndOfTimeline = _ref4.isEndOfTimeline,
  35524. endedTimelineFn = _ref4.endedTimelineFn,
  35525. dataFn = _ref4.dataFn,
  35526. doneFn = _ref4.doneFn,
  35527. onTransmuxerLog = _ref4.onTransmuxerLog;
  35528. var fmp4Tracks = segment.map && segment.map.tracks || {};
  35529. var isMuxed = Boolean(fmp4Tracks.audio && fmp4Tracks.video); // Keep references to each function so we can null them out after we're done with them.
  35530. // One reason for this is that in the case of full segments, we want to trust start
  35531. // times from the probe, rather than the transmuxer.
  35532. var audioStartFn = timingInfoFn.bind(null, segment, 'audio', 'start');
  35533. var audioEndFn = timingInfoFn.bind(null, segment, 'audio', 'end');
  35534. var videoStartFn = timingInfoFn.bind(null, segment, 'video', 'start');
  35535. var videoEndFn = timingInfoFn.bind(null, segment, 'video', 'end');
  35536. var finish = function finish() {
  35537. return transmux({
  35538. bytes: bytes,
  35539. transmuxer: segment.transmuxer,
  35540. audioAppendStart: segment.audioAppendStart,
  35541. gopsToAlignWith: segment.gopsToAlignWith,
  35542. remux: isMuxed,
  35543. onData: function onData(result) {
  35544. result.type = result.type === 'combined' ? 'video' : result.type;
  35545. dataFn(segment, result);
  35546. },
  35547. onTrackInfo: function onTrackInfo(trackInfo) {
  35548. if (trackInfoFn) {
  35549. if (isMuxed) {
  35550. trackInfo.isMuxed = true;
  35551. }
  35552. trackInfoFn(segment, trackInfo);
  35553. }
  35554. },
  35555. onAudioTimingInfo: function onAudioTimingInfo(audioTimingInfo) {
  35556. // we only want the first start value we encounter
  35557. if (audioStartFn && typeof audioTimingInfo.start !== 'undefined') {
  35558. audioStartFn(audioTimingInfo.start);
  35559. audioStartFn = null;
  35560. } // we want to continually update the end time
  35561. if (audioEndFn && typeof audioTimingInfo.end !== 'undefined') {
  35562. audioEndFn(audioTimingInfo.end);
  35563. }
  35564. },
  35565. onVideoTimingInfo: function onVideoTimingInfo(videoTimingInfo) {
  35566. // we only want the first start value we encounter
  35567. if (videoStartFn && typeof videoTimingInfo.start !== 'undefined') {
  35568. videoStartFn(videoTimingInfo.start);
  35569. videoStartFn = null;
  35570. } // we want to continually update the end time
  35571. if (videoEndFn && typeof videoTimingInfo.end !== 'undefined') {
  35572. videoEndFn(videoTimingInfo.end);
  35573. }
  35574. },
  35575. onVideoSegmentTimingInfo: function onVideoSegmentTimingInfo(videoSegmentTimingInfo) {
  35576. videoSegmentTimingInfoFn(videoSegmentTimingInfo);
  35577. },
  35578. onAudioSegmentTimingInfo: function onAudioSegmentTimingInfo(audioSegmentTimingInfo) {
  35579. audioSegmentTimingInfoFn(audioSegmentTimingInfo);
  35580. },
  35581. onId3: function onId3(id3Frames, dispatchType) {
  35582. id3Fn(segment, id3Frames, dispatchType);
  35583. },
  35584. onCaptions: function onCaptions(captions) {
  35585. captionsFn(segment, [captions]);
  35586. },
  35587. isEndOfTimeline: isEndOfTimeline,
  35588. onEndedTimeline: function onEndedTimeline() {
  35589. endedTimelineFn();
  35590. },
  35591. onTransmuxerLog: onTransmuxerLog,
  35592. onDone: function onDone(result) {
  35593. if (!doneFn) {
  35594. return;
  35595. }
  35596. result.type = result.type === 'combined' ? 'video' : result.type;
  35597. doneFn(null, segment, result);
  35598. }
  35599. });
  35600. }; // In the transmuxer, we don't yet have the ability to extract a "proper" start time.
  35601. // Meaning cached frame data may corrupt our notion of where this segment
  35602. // really starts. To get around this, probe for the info needed.
  35603. workerCallback({
  35604. action: 'probeTs',
  35605. transmuxer: segment.transmuxer,
  35606. data: bytes,
  35607. baseStartTime: segment.baseStartTime,
  35608. callback: function callback(data) {
  35609. segment.bytes = bytes = data.data;
  35610. var probeResult = data.result;
  35611. if (probeResult) {
  35612. trackInfoFn(segment, {
  35613. hasAudio: probeResult.hasAudio,
  35614. hasVideo: probeResult.hasVideo,
  35615. isMuxed: isMuxed
  35616. });
  35617. trackInfoFn = null;
  35618. if (probeResult.hasAudio && !isMuxed) {
  35619. audioStartFn(probeResult.audioStart);
  35620. }
  35621. if (probeResult.hasVideo) {
  35622. videoStartFn(probeResult.videoStart);
  35623. }
  35624. audioStartFn = null;
  35625. videoStartFn = null;
  35626. }
  35627. finish();
  35628. }
  35629. });
  35630. };
  35631. var handleSegmentBytes = function handleSegmentBytes(_ref5) {
  35632. var segment = _ref5.segment,
  35633. bytes = _ref5.bytes,
  35634. trackInfoFn = _ref5.trackInfoFn,
  35635. timingInfoFn = _ref5.timingInfoFn,
  35636. videoSegmentTimingInfoFn = _ref5.videoSegmentTimingInfoFn,
  35637. audioSegmentTimingInfoFn = _ref5.audioSegmentTimingInfoFn,
  35638. id3Fn = _ref5.id3Fn,
  35639. captionsFn = _ref5.captionsFn,
  35640. isEndOfTimeline = _ref5.isEndOfTimeline,
  35641. endedTimelineFn = _ref5.endedTimelineFn,
  35642. dataFn = _ref5.dataFn,
  35643. doneFn = _ref5.doneFn,
  35644. onTransmuxerLog = _ref5.onTransmuxerLog;
  35645. var bytesAsUint8Array = new Uint8Array(bytes); // TODO:
  35646. // We should have a handler that fetches the number of bytes required
  35647. // to check if something is fmp4. This will allow us to save bandwidth
  35648. // because we can only blacklist a playlist and abort requests
  35649. // by codec after trackinfo triggers.
  35650. if (isLikelyFmp4MediaSegment(bytesAsUint8Array)) {
  35651. segment.isFmp4 = true;
  35652. var tracks = segment.map.tracks;
  35653. var trackInfo = {
  35654. isFmp4: true,
  35655. hasVideo: !!tracks.video,
  35656. hasAudio: !!tracks.audio
  35657. }; // if we have a audio track, with a codec that is not set to
  35658. // encrypted audio
  35659. if (tracks.audio && tracks.audio.codec && tracks.audio.codec !== 'enca') {
  35660. trackInfo.audioCodec = tracks.audio.codec;
  35661. } // if we have a video track, with a codec that is not set to
  35662. // encrypted video
  35663. if (tracks.video && tracks.video.codec && tracks.video.codec !== 'encv') {
  35664. trackInfo.videoCodec = tracks.video.codec;
  35665. }
  35666. if (tracks.video && tracks.audio) {
  35667. trackInfo.isMuxed = true;
  35668. } // since we don't support appending fmp4 data on progress, we know we have the full
  35669. // segment here
  35670. trackInfoFn(segment, trackInfo); // The probe doesn't provide the segment end time, so only callback with the start
  35671. // time. The end time can be roughly calculated by the receiver using the duration.
  35672. //
  35673. // Note that the start time returned by the probe reflects the baseMediaDecodeTime, as
  35674. // that is the true start of the segment (where the playback engine should begin
  35675. // decoding).
  35676. var finishLoading = function finishLoading(captions) {
  35677. // if the track still has audio at this point it is only possible
  35678. // for it to be audio only. See `tracks.video && tracks.audio` if statement
  35679. // above.
  35680. // we make sure to use segment.bytes here as that
  35681. dataFn(segment, {
  35682. data: bytesAsUint8Array,
  35683. type: trackInfo.hasAudio && !trackInfo.isMuxed ? 'audio' : 'video'
  35684. });
  35685. if (captions && captions.length) {
  35686. captionsFn(segment, captions);
  35687. }
  35688. doneFn(null, segment, {});
  35689. };
  35690. workerCallback({
  35691. action: 'probeMp4StartTime',
  35692. timescales: segment.map.timescales,
  35693. data: bytesAsUint8Array,
  35694. transmuxer: segment.transmuxer,
  35695. callback: function callback(_ref6) {
  35696. var data = _ref6.data,
  35697. startTime = _ref6.startTime; // transfer bytes back to us
  35698. bytes = data.buffer;
  35699. segment.bytes = bytesAsUint8Array = data;
  35700. if (trackInfo.hasAudio && !trackInfo.isMuxed) {
  35701. timingInfoFn(segment, 'audio', 'start', startTime);
  35702. }
  35703. if (trackInfo.hasVideo) {
  35704. timingInfoFn(segment, 'video', 'start', startTime);
  35705. } // Run through the CaptionParser in case there are captions.
  35706. // Initialize CaptionParser if it hasn't been yet
  35707. if (!tracks.video || !data.byteLength || !segment.transmuxer) {
  35708. finishLoading();
  35709. return;
  35710. }
  35711. workerCallback({
  35712. action: 'pushMp4Captions',
  35713. endAction: 'mp4Captions',
  35714. transmuxer: segment.transmuxer,
  35715. data: bytesAsUint8Array,
  35716. timescales: segment.map.timescales,
  35717. trackIds: [tracks.video.id],
  35718. callback: function callback(message) {
  35719. // transfer bytes back to us
  35720. bytes = message.data.buffer;
  35721. segment.bytes = bytesAsUint8Array = message.data;
  35722. message.logs.forEach(function (log) {
  35723. onTransmuxerLog(videojs.mergeOptions(log, {
  35724. stream: 'mp4CaptionParser'
  35725. }));
  35726. });
  35727. finishLoading(message.captions);
  35728. }
  35729. });
  35730. }
  35731. });
  35732. return;
  35733. } // VTT or other segments that don't need processing
  35734. if (!segment.transmuxer) {
  35735. doneFn(null, segment, {});
  35736. return;
  35737. }
  35738. if (typeof segment.container === 'undefined') {
  35739. segment.container = detectContainerForBytes(bytesAsUint8Array);
  35740. }
  35741. if (segment.container !== 'ts' && segment.container !== 'aac') {
  35742. trackInfoFn(segment, {
  35743. hasAudio: false,
  35744. hasVideo: false
  35745. });
  35746. doneFn(null, segment, {});
  35747. return;
  35748. } // ts or aac
  35749. transmuxAndNotify({
  35750. segment: segment,
  35751. bytes: bytes,
  35752. trackInfoFn: trackInfoFn,
  35753. timingInfoFn: timingInfoFn,
  35754. videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
  35755. audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
  35756. id3Fn: id3Fn,
  35757. captionsFn: captionsFn,
  35758. isEndOfTimeline: isEndOfTimeline,
  35759. endedTimelineFn: endedTimelineFn,
  35760. dataFn: dataFn,
  35761. doneFn: doneFn,
  35762. onTransmuxerLog: onTransmuxerLog
  35763. });
  35764. };
  35765. var decrypt = function decrypt(_ref7, callback) {
  35766. var id = _ref7.id,
  35767. key = _ref7.key,
  35768. encryptedBytes = _ref7.encryptedBytes,
  35769. decryptionWorker = _ref7.decryptionWorker;
  35770. var decryptionHandler = function decryptionHandler(event) {
  35771. if (event.data.source === id) {
  35772. decryptionWorker.removeEventListener('message', decryptionHandler);
  35773. var decrypted = event.data.decrypted;
  35774. callback(new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength));
  35775. }
  35776. };
  35777. decryptionWorker.addEventListener('message', decryptionHandler);
  35778. var keyBytes;
  35779. if (key.bytes.slice) {
  35780. keyBytes = key.bytes.slice();
  35781. } else {
  35782. keyBytes = new Uint32Array(Array.prototype.slice.call(key.bytes));
  35783. } // incrementally decrypt the bytes
  35784. decryptionWorker.postMessage(createTransferableMessage({
  35785. source: id,
  35786. encrypted: encryptedBytes,
  35787. key: keyBytes,
  35788. iv: key.iv
  35789. }), [encryptedBytes.buffer, keyBytes.buffer]);
  35790. };
  35791. /**
  35792. * Decrypt the segment via the decryption web worker
  35793. *
  35794. * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
  35795. * routines
  35796. * @param {Object} segment - a simplified copy of the segmentInfo object
  35797. * from SegmentLoader
  35798. * @param {Function} trackInfoFn - a callback that receives track info
  35799. * @param {Function} timingInfoFn - a callback that receives timing info
  35800. * @param {Function} videoSegmentTimingInfoFn
  35801. * a callback that receives video timing info based on media times and
  35802. * any adjustments made by the transmuxer
  35803. * @param {Function} audioSegmentTimingInfoFn
  35804. * a callback that receives audio timing info based on media times and
  35805. * any adjustments made by the transmuxer
  35806. * @param {boolean} isEndOfTimeline
  35807. * true if this segment represents the last segment in a timeline
  35808. * @param {Function} endedTimelineFn
  35809. * a callback made when a timeline is ended, will only be called if
  35810. * isEndOfTimeline is true
  35811. * @param {Function} dataFn - a callback that is executed when segment bytes are available
  35812. * and ready to use
  35813. * @param {Function} doneFn - a callback that is executed after decryption has completed
  35814. */
  35815. var decryptSegment = function decryptSegment(_ref8) {
  35816. var decryptionWorker = _ref8.decryptionWorker,
  35817. segment = _ref8.segment,
  35818. trackInfoFn = _ref8.trackInfoFn,
  35819. timingInfoFn = _ref8.timingInfoFn,
  35820. videoSegmentTimingInfoFn = _ref8.videoSegmentTimingInfoFn,
  35821. audioSegmentTimingInfoFn = _ref8.audioSegmentTimingInfoFn,
  35822. id3Fn = _ref8.id3Fn,
  35823. captionsFn = _ref8.captionsFn,
  35824. isEndOfTimeline = _ref8.isEndOfTimeline,
  35825. endedTimelineFn = _ref8.endedTimelineFn,
  35826. dataFn = _ref8.dataFn,
  35827. doneFn = _ref8.doneFn,
  35828. onTransmuxerLog = _ref8.onTransmuxerLog;
  35829. decrypt({
  35830. id: segment.requestId,
  35831. key: segment.key,
  35832. encryptedBytes: segment.encryptedBytes,
  35833. decryptionWorker: decryptionWorker
  35834. }, function (decryptedBytes) {
  35835. segment.bytes = decryptedBytes;
  35836. handleSegmentBytes({
  35837. segment: segment,
  35838. bytes: segment.bytes,
  35839. trackInfoFn: trackInfoFn,
  35840. timingInfoFn: timingInfoFn,
  35841. videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
  35842. audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
  35843. id3Fn: id3Fn,
  35844. captionsFn: captionsFn,
  35845. isEndOfTimeline: isEndOfTimeline,
  35846. endedTimelineFn: endedTimelineFn,
  35847. dataFn: dataFn,
  35848. doneFn: doneFn,
  35849. onTransmuxerLog: onTransmuxerLog
  35850. });
  35851. });
  35852. };
  35853. /**
  35854. * This function waits for all XHRs to finish (with either success or failure)
  35855. * before continueing processing via it's callback. The function gathers errors
  35856. * from each request into a single errors array so that the error status for
  35857. * each request can be examined later.
  35858. *
  35859. * @param {Object} activeXhrs - an object that tracks all XHR requests
  35860. * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
  35861. * routines
  35862. * @param {Function} trackInfoFn - a callback that receives track info
  35863. * @param {Function} timingInfoFn - a callback that receives timing info
  35864. * @param {Function} videoSegmentTimingInfoFn
  35865. * a callback that receives video timing info based on media times and
  35866. * any adjustments made by the transmuxer
  35867. * @param {Function} audioSegmentTimingInfoFn
  35868. * a callback that receives audio timing info based on media times and
  35869. * any adjustments made by the transmuxer
  35870. * @param {Function} id3Fn - a callback that receives ID3 metadata
  35871. * @param {Function} captionsFn - a callback that receives captions
  35872. * @param {boolean} isEndOfTimeline
  35873. * true if this segment represents the last segment in a timeline
  35874. * @param {Function} endedTimelineFn
  35875. * a callback made when a timeline is ended, will only be called if
  35876. * isEndOfTimeline is true
  35877. * @param {Function} dataFn - a callback that is executed when segment bytes are available
  35878. * and ready to use
  35879. * @param {Function} doneFn - a callback that is executed after all resources have been
  35880. * downloaded and any decryption completed
  35881. */
  35882. var waitForCompletion = function waitForCompletion(_ref9) {
  35883. var activeXhrs = _ref9.activeXhrs,
  35884. decryptionWorker = _ref9.decryptionWorker,
  35885. trackInfoFn = _ref9.trackInfoFn,
  35886. timingInfoFn = _ref9.timingInfoFn,
  35887. videoSegmentTimingInfoFn = _ref9.videoSegmentTimingInfoFn,
  35888. audioSegmentTimingInfoFn = _ref9.audioSegmentTimingInfoFn,
  35889. id3Fn = _ref9.id3Fn,
  35890. captionsFn = _ref9.captionsFn,
  35891. isEndOfTimeline = _ref9.isEndOfTimeline,
  35892. endedTimelineFn = _ref9.endedTimelineFn,
  35893. dataFn = _ref9.dataFn,
  35894. doneFn = _ref9.doneFn,
  35895. onTransmuxerLog = _ref9.onTransmuxerLog;
  35896. var count = 0;
  35897. var didError = false;
  35898. return function (error, segment) {
  35899. if (didError) {
  35900. return;
  35901. }
  35902. if (error) {
  35903. didError = true; // If there are errors, we have to abort any outstanding requests
  35904. abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
  35905. // handle the aborted events from those requests, there are some cases where we may
  35906. // never get an aborted event. For instance, if the network connection is lost and
  35907. // there were two requests, the first may have triggered an error immediately, while
  35908. // the second request remains unsent. In that case, the aborted algorithm will not
  35909. // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
  35910. //
  35911. // We also can't rely on the ready state of the XHR, since the request that
  35912. // triggered the connection error may also show as a ready state of 0 (unsent).
  35913. // Therefore, we have to finish this group of requests immediately after the first
  35914. // seen error.
  35915. return doneFn(error, segment);
  35916. }
  35917. count += 1;
  35918. if (count === activeXhrs.length) {
  35919. var segmentFinish = function segmentFinish() {
  35920. if (segment.encryptedBytes) {
  35921. return decryptSegment({
  35922. decryptionWorker: decryptionWorker,
  35923. segment: segment,
  35924. trackInfoFn: trackInfoFn,
  35925. timingInfoFn: timingInfoFn,
  35926. videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
  35927. audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
  35928. id3Fn: id3Fn,
  35929. captionsFn: captionsFn,
  35930. isEndOfTimeline: isEndOfTimeline,
  35931. endedTimelineFn: endedTimelineFn,
  35932. dataFn: dataFn,
  35933. doneFn: doneFn,
  35934. onTransmuxerLog: onTransmuxerLog
  35935. });
  35936. } // Otherwise, everything is ready just continue
  35937. handleSegmentBytes({
  35938. segment: segment,
  35939. bytes: segment.bytes,
  35940. trackInfoFn: trackInfoFn,
  35941. timingInfoFn: timingInfoFn,
  35942. videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
  35943. audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
  35944. id3Fn: id3Fn,
  35945. captionsFn: captionsFn,
  35946. isEndOfTimeline: isEndOfTimeline,
  35947. endedTimelineFn: endedTimelineFn,
  35948. dataFn: dataFn,
  35949. doneFn: doneFn,
  35950. onTransmuxerLog: onTransmuxerLog
  35951. });
  35952. }; // Keep track of when *all* of the requests have completed
  35953. segment.endOfAllRequests = Date.now();
  35954. if (segment.map && segment.map.encryptedBytes && !segment.map.bytes) {
  35955. return decrypt({
  35956. decryptionWorker: decryptionWorker,
  35957. // add -init to the "id" to differentiate between segment
  35958. // and init segment decryption, just in case they happen
  35959. // at the same time at some point in the future.
  35960. id: segment.requestId + '-init',
  35961. encryptedBytes: segment.map.encryptedBytes,
  35962. key: segment.map.key
  35963. }, function (decryptedBytes) {
  35964. segment.map.bytes = decryptedBytes;
  35965. parseInitSegment(segment, function (parseError) {
  35966. if (parseError) {
  35967. abortAll(activeXhrs);
  35968. return doneFn(parseError, segment);
  35969. }
  35970. segmentFinish();
  35971. });
  35972. });
  35973. }
  35974. segmentFinish();
  35975. }
  35976. };
  35977. };
  35978. /**
  35979. * Calls the abort callback if any request within the batch was aborted. Will only call
  35980. * the callback once per batch of requests, even if multiple were aborted.
  35981. *
  35982. * @param {Object} loadendState - state to check to see if the abort function was called
  35983. * @param {Function} abortFn - callback to call for abort
  35984. */
  35985. var handleLoadEnd = function handleLoadEnd(_ref10) {
  35986. var loadendState = _ref10.loadendState,
  35987. abortFn = _ref10.abortFn;
  35988. return function (event) {
  35989. var request = event.target;
  35990. if (request.aborted && abortFn && !loadendState.calledAbortFn) {
  35991. abortFn();
  35992. loadendState.calledAbortFn = true;
  35993. }
  35994. };
  35995. };
  35996. /**
  35997. * Simple progress event callback handler that gathers some stats before
  35998. * executing a provided callback with the `segment` object
  35999. *
  36000. * @param {Object} segment - a simplified copy of the segmentInfo object
  36001. * from SegmentLoader
  36002. * @param {Function} progressFn - a callback that is executed each time a progress event
  36003. * is received
  36004. * @param {Function} trackInfoFn - a callback that receives track info
  36005. * @param {Function} timingInfoFn - a callback that receives timing info
  36006. * @param {Function} videoSegmentTimingInfoFn
  36007. * a callback that receives video timing info based on media times and
  36008. * any adjustments made by the transmuxer
  36009. * @param {Function} audioSegmentTimingInfoFn
  36010. * a callback that receives audio timing info based on media times and
  36011. * any adjustments made by the transmuxer
  36012. * @param {boolean} isEndOfTimeline
  36013. * true if this segment represents the last segment in a timeline
  36014. * @param {Function} endedTimelineFn
  36015. * a callback made when a timeline is ended, will only be called if
  36016. * isEndOfTimeline is true
  36017. * @param {Function} dataFn - a callback that is executed when segment bytes are available
  36018. * and ready to use
  36019. * @param {Event} event - the progress event object from XMLHttpRequest
  36020. */
  36021. var handleProgress = function handleProgress(_ref11) {
  36022. var segment = _ref11.segment,
  36023. progressFn = _ref11.progressFn;
  36024. _ref11.trackInfoFn;
  36025. _ref11.timingInfoFn;
  36026. _ref11.videoSegmentTimingInfoFn;
  36027. _ref11.audioSegmentTimingInfoFn;
  36028. _ref11.id3Fn;
  36029. _ref11.captionsFn;
  36030. _ref11.isEndOfTimeline;
  36031. _ref11.endedTimelineFn;
  36032. _ref11.dataFn;
  36033. return function (event) {
  36034. var request = event.target;
  36035. if (request.aborted) {
  36036. return;
  36037. }
  36038. segment.stats = videojs.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
  36039. if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
  36040. segment.stats.firstBytesReceivedAt = Date.now();
  36041. }
  36042. return progressFn(event, segment);
  36043. };
  36044. };
  36045. /**
  36046. * Load all resources and does any processing necessary for a media-segment
  36047. *
  36048. * Features:
  36049. * decrypts the media-segment if it has a key uri and an iv
  36050. * aborts *all* requests if *any* one request fails
  36051. *
  36052. * The segment object, at minimum, has the following format:
  36053. * {
  36054. * resolvedUri: String,
  36055. * [transmuxer]: Object,
  36056. * [byterange]: {
  36057. * offset: Number,
  36058. * length: Number
  36059. * },
  36060. * [key]: {
  36061. * resolvedUri: String
  36062. * [byterange]: {
  36063. * offset: Number,
  36064. * length: Number
  36065. * },
  36066. * iv: {
  36067. * bytes: Uint32Array
  36068. * }
  36069. * },
  36070. * [map]: {
  36071. * resolvedUri: String,
  36072. * [byterange]: {
  36073. * offset: Number,
  36074. * length: Number
  36075. * },
  36076. * [bytes]: Uint8Array
  36077. * }
  36078. * }
  36079. * ...where [name] denotes optional properties
  36080. *
  36081. * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
  36082. * @param {Object} xhrOptions - the base options to provide to all xhr requests
  36083. * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
  36084. * decryption routines
  36085. * @param {Object} segment - a simplified copy of the segmentInfo object
  36086. * from SegmentLoader
  36087. * @param {Function} abortFn - a callback called (only once) if any piece of a request was
  36088. * aborted
  36089. * @param {Function} progressFn - a callback that receives progress events from the main
  36090. * segment's xhr request
  36091. * @param {Function} trackInfoFn - a callback that receives track info
  36092. * @param {Function} timingInfoFn - a callback that receives timing info
  36093. * @param {Function} videoSegmentTimingInfoFn
  36094. * a callback that receives video timing info based on media times and
  36095. * any adjustments made by the transmuxer
  36096. * @param {Function} audioSegmentTimingInfoFn
  36097. * a callback that receives audio timing info based on media times and
  36098. * any adjustments made by the transmuxer
  36099. * @param {Function} id3Fn - a callback that receives ID3 metadata
  36100. * @param {Function} captionsFn - a callback that receives captions
  36101. * @param {boolean} isEndOfTimeline
  36102. * true if this segment represents the last segment in a timeline
  36103. * @param {Function} endedTimelineFn
  36104. * a callback made when a timeline is ended, will only be called if
  36105. * isEndOfTimeline is true
  36106. * @param {Function} dataFn - a callback that receives data from the main segment's xhr
  36107. * request, transmuxed if needed
  36108. * @param {Function} doneFn - a callback that is executed only once all requests have
  36109. * succeeded or failed
  36110. * @return {Function} a function that, when invoked, immediately aborts all
  36111. * outstanding requests
  36112. */
  36113. var mediaSegmentRequest = function mediaSegmentRequest(_ref12) {
  36114. var xhr = _ref12.xhr,
  36115. xhrOptions = _ref12.xhrOptions,
  36116. decryptionWorker = _ref12.decryptionWorker,
  36117. segment = _ref12.segment,
  36118. abortFn = _ref12.abortFn,
  36119. progressFn = _ref12.progressFn,
  36120. trackInfoFn = _ref12.trackInfoFn,
  36121. timingInfoFn = _ref12.timingInfoFn,
  36122. videoSegmentTimingInfoFn = _ref12.videoSegmentTimingInfoFn,
  36123. audioSegmentTimingInfoFn = _ref12.audioSegmentTimingInfoFn,
  36124. id3Fn = _ref12.id3Fn,
  36125. captionsFn = _ref12.captionsFn,
  36126. isEndOfTimeline = _ref12.isEndOfTimeline,
  36127. endedTimelineFn = _ref12.endedTimelineFn,
  36128. dataFn = _ref12.dataFn,
  36129. doneFn = _ref12.doneFn,
  36130. onTransmuxerLog = _ref12.onTransmuxerLog;
  36131. var activeXhrs = [];
  36132. var finishProcessingFn = waitForCompletion({
  36133. activeXhrs: activeXhrs,
  36134. decryptionWorker: decryptionWorker,
  36135. trackInfoFn: trackInfoFn,
  36136. timingInfoFn: timingInfoFn,
  36137. videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
  36138. audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
  36139. id3Fn: id3Fn,
  36140. captionsFn: captionsFn,
  36141. isEndOfTimeline: isEndOfTimeline,
  36142. endedTimelineFn: endedTimelineFn,
  36143. dataFn: dataFn,
  36144. doneFn: doneFn,
  36145. onTransmuxerLog: onTransmuxerLog
  36146. }); // optionally, request the decryption key
  36147. if (segment.key && !segment.key.bytes) {
  36148. var objects = [segment.key];
  36149. if (segment.map && !segment.map.bytes && segment.map.key && segment.map.key.resolvedUri === segment.key.resolvedUri) {
  36150. objects.push(segment.map.key);
  36151. }
  36152. var keyRequestOptions = videojs.mergeOptions(xhrOptions, {
  36153. uri: segment.key.resolvedUri,
  36154. responseType: 'arraybuffer'
  36155. });
  36156. var keyRequestCallback = handleKeyResponse(segment, objects, finishProcessingFn);
  36157. var keyXhr = xhr(keyRequestOptions, keyRequestCallback);
  36158. activeXhrs.push(keyXhr);
  36159. } // optionally, request the associated media init segment
  36160. if (segment.map && !segment.map.bytes) {
  36161. var differentMapKey = segment.map.key && (!segment.key || segment.key.resolvedUri !== segment.map.key.resolvedUri);
  36162. if (differentMapKey) {
  36163. var mapKeyRequestOptions = videojs.mergeOptions(xhrOptions, {
  36164. uri: segment.map.key.resolvedUri,
  36165. responseType: 'arraybuffer'
  36166. });
  36167. var mapKeyRequestCallback = handleKeyResponse(segment, [segment.map.key], finishProcessingFn);
  36168. var mapKeyXhr = xhr(mapKeyRequestOptions, mapKeyRequestCallback);
  36169. activeXhrs.push(mapKeyXhr);
  36170. }
  36171. var initSegmentOptions = videojs.mergeOptions(xhrOptions, {
  36172. uri: segment.map.resolvedUri,
  36173. responseType: 'arraybuffer',
  36174. headers: segmentXhrHeaders(segment.map)
  36175. });
  36176. var initSegmentRequestCallback = handleInitSegmentResponse({
  36177. segment: segment,
  36178. finishProcessingFn: finishProcessingFn
  36179. });
  36180. var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
  36181. activeXhrs.push(initSegmentXhr);
  36182. }
  36183. var segmentRequestOptions = videojs.mergeOptions(xhrOptions, {
  36184. uri: segment.part && segment.part.resolvedUri || segment.resolvedUri,
  36185. responseType: 'arraybuffer',
  36186. headers: segmentXhrHeaders(segment)
  36187. });
  36188. var segmentRequestCallback = handleSegmentResponse({
  36189. segment: segment,
  36190. finishProcessingFn: finishProcessingFn,
  36191. responseType: segmentRequestOptions.responseType
  36192. });
  36193. var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
  36194. segmentXhr.addEventListener('progress', handleProgress({
  36195. segment: segment,
  36196. progressFn: progressFn,
  36197. trackInfoFn: trackInfoFn,
  36198. timingInfoFn: timingInfoFn,
  36199. videoSegmentTimingInfoFn: videoSegmentTimingInfoFn,
  36200. audioSegmentTimingInfoFn: audioSegmentTimingInfoFn,
  36201. id3Fn: id3Fn,
  36202. captionsFn: captionsFn,
  36203. isEndOfTimeline: isEndOfTimeline,
  36204. endedTimelineFn: endedTimelineFn,
  36205. dataFn: dataFn
  36206. }));
  36207. activeXhrs.push(segmentXhr); // since all parts of the request must be considered, but should not make callbacks
  36208. // multiple times, provide a shared state object
  36209. var loadendState = {};
  36210. activeXhrs.forEach(function (activeXhr) {
  36211. activeXhr.addEventListener('loadend', handleLoadEnd({
  36212. loadendState: loadendState,
  36213. abortFn: abortFn
  36214. }));
  36215. });
  36216. return function () {
  36217. return abortAll(activeXhrs);
  36218. };
  36219. };
  36220. /**
  36221. * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
  36222. * codec strings, or translating codec strings into objects that can be examined.
  36223. */
  36224. var logFn$1 = logger('CodecUtils');
  36225. /**
  36226. * Returns a set of codec strings parsed from the playlist or the default
  36227. * codec strings if no codecs were specified in the playlist
  36228. *
  36229. * @param {Playlist} media the current media playlist
  36230. * @return {Object} an object with the video and audio codecs
  36231. */
  36232. var getCodecs = function getCodecs(media) {
  36233. // if the codecs were explicitly specified, use them instead of the
  36234. // defaults
  36235. var mediaAttributes = media.attributes || {};
  36236. if (mediaAttributes.CODECS) {
  36237. return parseCodecs(mediaAttributes.CODECS);
  36238. }
  36239. };
  36240. var isMaat = function isMaat(master, media) {
  36241. var mediaAttributes = media.attributes || {};
  36242. return master && master.mediaGroups && master.mediaGroups.AUDIO && mediaAttributes.AUDIO && master.mediaGroups.AUDIO[mediaAttributes.AUDIO];
  36243. };
  36244. var isMuxed = function isMuxed(master, media) {
  36245. if (!isMaat(master, media)) {
  36246. return true;
  36247. }
  36248. var mediaAttributes = media.attributes || {};
  36249. var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO];
  36250. for (var groupId in audioGroup) {
  36251. // If an audio group has a URI (the case for HLS, as HLS will use external playlists),
  36252. // or there are listed playlists (the case for DASH, as the manifest will have already
  36253. // provided all of the details necessary to generate the audio playlist, as opposed to
  36254. // HLS' externally requested playlists), then the content is demuxed.
  36255. if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
  36256. return true;
  36257. }
  36258. }
  36259. return false;
  36260. };
  36261. var unwrapCodecList = function unwrapCodecList(codecList) {
  36262. var codecs = {};
  36263. codecList.forEach(function (_ref) {
  36264. var mediaType = _ref.mediaType,
  36265. type = _ref.type,
  36266. details = _ref.details;
  36267. codecs[mediaType] = codecs[mediaType] || [];
  36268. codecs[mediaType].push(translateLegacyCodec("" + type + details));
  36269. });
  36270. Object.keys(codecs).forEach(function (mediaType) {
  36271. if (codecs[mediaType].length > 1) {
  36272. logFn$1("multiple " + mediaType + " codecs found as attributes: " + codecs[mediaType].join(', ') + ". Setting playlist codecs to null so that we wait for mux.js to probe segments for real codecs.");
  36273. codecs[mediaType] = null;
  36274. return;
  36275. }
  36276. codecs[mediaType] = codecs[mediaType][0];
  36277. });
  36278. return codecs;
  36279. };
  36280. var codecCount = function codecCount(codecObj) {
  36281. var count = 0;
  36282. if (codecObj.audio) {
  36283. count++;
  36284. }
  36285. if (codecObj.video) {
  36286. count++;
  36287. }
  36288. return count;
  36289. };
  36290. /**
  36291. * Calculates the codec strings for a working configuration of
  36292. * SourceBuffers to play variant streams in a master playlist. If
  36293. * there is no possible working configuration, an empty object will be
  36294. * returned.
  36295. *
  36296. * @param master {Object} the m3u8 object for the master playlist
  36297. * @param media {Object} the m3u8 object for the variant playlist
  36298. * @return {Object} the codec strings.
  36299. *
  36300. * @private
  36301. */
  36302. var codecsForPlaylist = function codecsForPlaylist(master, media) {
  36303. var mediaAttributes = media.attributes || {};
  36304. var codecInfo = unwrapCodecList(getCodecs(media) || []); // HLS with multiple-audio tracks must always get an audio codec.
  36305. // Put another way, there is no way to have a video-only multiple-audio HLS!
  36306. if (isMaat(master, media) && !codecInfo.audio) {
  36307. if (!isMuxed(master, media)) {
  36308. // It is possible for codecs to be specified on the audio media group playlist but
  36309. // not on the rendition playlist. This is mostly the case for DASH, where audio and
  36310. // video are always separate (and separately specified).
  36311. var defaultCodecs = unwrapCodecList(codecsFromDefault(master, mediaAttributes.AUDIO) || []);
  36312. if (defaultCodecs.audio) {
  36313. codecInfo.audio = defaultCodecs.audio;
  36314. }
  36315. }
  36316. }
  36317. return codecInfo;
  36318. };
  36319. var logFn = logger('PlaylistSelector');
  36320. var representationToString = function representationToString(representation) {
  36321. if (!representation || !representation.playlist) {
  36322. return;
  36323. }
  36324. var playlist = representation.playlist;
  36325. return JSON.stringify({
  36326. id: playlist.id,
  36327. bandwidth: representation.bandwidth,
  36328. width: representation.width,
  36329. height: representation.height,
  36330. codecs: playlist.attributes && playlist.attributes.CODECS || ''
  36331. });
  36332. }; // Utilities
  36333. /**
  36334. * Returns the CSS value for the specified property on an element
  36335. * using `getComputedStyle`. Firefox has a long-standing issue where
  36336. * getComputedStyle() may return null when running in an iframe with
  36337. * `display: none`.
  36338. *
  36339. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
  36340. * @param {HTMLElement} el the htmlelement to work on
  36341. * @param {string} the proprety to get the style for
  36342. */
  36343. var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
  36344. if (!el) {
  36345. return '';
  36346. }
  36347. var result = window$1.getComputedStyle(el);
  36348. if (!result) {
  36349. return '';
  36350. }
  36351. return result[property];
  36352. };
  36353. /**
  36354. * Resuable stable sort function
  36355. *
  36356. * @param {Playlists} array
  36357. * @param {Function} sortFn Different comparators
  36358. * @function stableSort
  36359. */
  36360. var stableSort = function stableSort(array, sortFn) {
  36361. var newArray = array.slice();
  36362. array.sort(function (left, right) {
  36363. var cmp = sortFn(left, right);
  36364. if (cmp === 0) {
  36365. return newArray.indexOf(left) - newArray.indexOf(right);
  36366. }
  36367. return cmp;
  36368. });
  36369. };
  36370. /**
  36371. * A comparator function to sort two playlist object by bandwidth.
  36372. *
  36373. * @param {Object} left a media playlist object
  36374. * @param {Object} right a media playlist object
  36375. * @return {number} Greater than zero if the bandwidth attribute of
  36376. * left is greater than the corresponding attribute of right. Less
  36377. * than zero if the bandwidth of right is greater than left and
  36378. * exactly zero if the two are equal.
  36379. */
  36380. var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
  36381. var leftBandwidth;
  36382. var rightBandwidth;
  36383. if (left.attributes.BANDWIDTH) {
  36384. leftBandwidth = left.attributes.BANDWIDTH;
  36385. }
  36386. leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE;
  36387. if (right.attributes.BANDWIDTH) {
  36388. rightBandwidth = right.attributes.BANDWIDTH;
  36389. }
  36390. rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE;
  36391. return leftBandwidth - rightBandwidth;
  36392. };
  36393. /**
  36394. * A comparator function to sort two playlist object by resolution (width).
  36395. *
  36396. * @param {Object} left a media playlist object
  36397. * @param {Object} right a media playlist object
  36398. * @return {number} Greater than zero if the resolution.width attribute of
  36399. * left is greater than the corresponding attribute of right. Less
  36400. * than zero if the resolution.width of right is greater than left and
  36401. * exactly zero if the two are equal.
  36402. */
  36403. var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
  36404. var leftWidth;
  36405. var rightWidth;
  36406. if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
  36407. leftWidth = left.attributes.RESOLUTION.width;
  36408. }
  36409. leftWidth = leftWidth || window$1.Number.MAX_VALUE;
  36410. if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
  36411. rightWidth = right.attributes.RESOLUTION.width;
  36412. }
  36413. rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
  36414. // have the same media dimensions/ resolution
  36415. if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
  36416. return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
  36417. }
  36418. return leftWidth - rightWidth;
  36419. };
  36420. /**
  36421. * Chooses the appropriate media playlist based on bandwidth and player size
  36422. *
  36423. * @param {Object} master
  36424. * Object representation of the master manifest
  36425. * @param {number} playerBandwidth
  36426. * Current calculated bandwidth of the player
  36427. * @param {number} playerWidth
  36428. * Current width of the player element (should account for the device pixel ratio)
  36429. * @param {number} playerHeight
  36430. * Current height of the player element (should account for the device pixel ratio)
  36431. * @param {boolean} limitRenditionByPlayerDimensions
  36432. * True if the player width and height should be used during the selection, false otherwise
  36433. * @param {Object} masterPlaylistController
  36434. * the current masterPlaylistController object
  36435. * @return {Playlist} the highest bitrate playlist less than the
  36436. * currently detected bandwidth, accounting for some amount of
  36437. * bandwidth variance
  36438. */
  36439. var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions, masterPlaylistController) {
  36440. // If we end up getting called before `master` is available, exit early
  36441. if (!master) {
  36442. return;
  36443. }
  36444. var options = {
  36445. bandwidth: playerBandwidth,
  36446. width: playerWidth,
  36447. height: playerHeight,
  36448. limitRenditionByPlayerDimensions: limitRenditionByPlayerDimensions
  36449. };
  36450. var playlists = master.playlists; // if playlist is audio only, select between currently active audio group playlists.
  36451. if (Playlist.isAudioOnly(master)) {
  36452. playlists = masterPlaylistController.getAudioTrackPlaylists_(); // add audioOnly to options so that we log audioOnly: true
  36453. // at the buttom of this function for debugging.
  36454. options.audioOnly = true;
  36455. } // convert the playlists to an intermediary representation to make comparisons easier
  36456. var sortedPlaylistReps = playlists.map(function (playlist) {
  36457. var bandwidth;
  36458. var width = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
  36459. var height = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
  36460. bandwidth = playlist.attributes && playlist.attributes.BANDWIDTH;
  36461. bandwidth = bandwidth || window$1.Number.MAX_VALUE;
  36462. return {
  36463. bandwidth: bandwidth,
  36464. width: width,
  36465. height: height,
  36466. playlist: playlist
  36467. };
  36468. });
  36469. stableSort(sortedPlaylistReps, function (left, right) {
  36470. return left.bandwidth - right.bandwidth;
  36471. }); // filter out any playlists that have been excluded due to
  36472. // incompatible configurations
  36473. sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  36474. return !Playlist.isIncompatible(rep.playlist);
  36475. }); // filter out any playlists that have been disabled manually through the representations
  36476. // api or blacklisted temporarily due to playback errors.
  36477. var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  36478. return Playlist.isEnabled(rep.playlist);
  36479. });
  36480. if (!enabledPlaylistReps.length) {
  36481. // if there are no enabled playlists, then they have all been blacklisted or disabled
  36482. // by the user through the representations api. In this case, ignore blacklisting and
  36483. // fallback to what the user wants by using playlists the user has not disabled.
  36484. enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  36485. return !Playlist.isDisabled(rep.playlist);
  36486. });
  36487. } // filter out any variant that has greater effective bitrate
  36488. // than the current estimated bandwidth
  36489. var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
  36490. return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
  36491. });
  36492. var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
  36493. // and then taking the very first element
  36494. var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
  36495. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  36496. })[0]; // if we're not going to limit renditions by player size, make an early decision.
  36497. if (limitRenditionByPlayerDimensions === false) {
  36498. var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  36499. if (_chosenRep && _chosenRep.playlist) {
  36500. var type = 'sortedPlaylistReps';
  36501. if (bandwidthBestRep) {
  36502. type = 'bandwidthBestRep';
  36503. }
  36504. if (enabledPlaylistReps[0]) {
  36505. type = 'enabledPlaylistReps';
  36506. }
  36507. logFn("choosing " + representationToString(_chosenRep) + " using " + type + " with options", options);
  36508. return _chosenRep.playlist;
  36509. }
  36510. logFn('could not choose a playlist with options', options);
  36511. return null;
  36512. } // filter out playlists without resolution information
  36513. var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
  36514. return rep.width && rep.height;
  36515. }); // sort variants by resolution
  36516. stableSort(haveResolution, function (left, right) {
  36517. return left.width - right.width;
  36518. }); // if we have the exact resolution as the player use it
  36519. var resolutionBestRepList = haveResolution.filter(function (rep) {
  36520. return rep.width === playerWidth && rep.height === playerHeight;
  36521. });
  36522. highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
  36523. var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
  36524. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  36525. })[0];
  36526. var resolutionPlusOneList;
  36527. var resolutionPlusOneSmallest;
  36528. var resolutionPlusOneRep; // find the smallest variant that is larger than the player
  36529. // if there is no match of exact resolution
  36530. if (!resolutionBestRep) {
  36531. resolutionPlusOneList = haveResolution.filter(function (rep) {
  36532. return rep.width > playerWidth || rep.height > playerHeight;
  36533. }); // find all the variants have the same smallest resolution
  36534. resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
  36535. return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
  36536. }); // ensure that we also pick the highest bandwidth variant that
  36537. // is just-larger-than the video player
  36538. highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
  36539. resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
  36540. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  36541. })[0];
  36542. }
  36543. var leastPixelDiffRep; // If this selector proves to be better than others,
  36544. // resolutionPlusOneRep and resolutionBestRep and all
  36545. // the code involving them should be removed.
  36546. if (masterPlaylistController.experimentalLeastPixelDiffSelector) {
  36547. // find the variant that is closest to the player's pixel size
  36548. var leastPixelDiffList = haveResolution.map(function (rep) {
  36549. rep.pixelDiff = Math.abs(rep.width - playerWidth) + Math.abs(rep.height - playerHeight);
  36550. return rep;
  36551. }); // get the highest bandwidth, closest resolution playlist
  36552. stableSort(leastPixelDiffList, function (left, right) {
  36553. // sort by highest bandwidth if pixelDiff is the same
  36554. if (left.pixelDiff === right.pixelDiff) {
  36555. return right.bandwidth - left.bandwidth;
  36556. }
  36557. return left.pixelDiff - right.pixelDiff;
  36558. });
  36559. leastPixelDiffRep = leastPixelDiffList[0];
  36560. } // fallback chain of variants
  36561. var chosenRep = leastPixelDiffRep || resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  36562. if (chosenRep && chosenRep.playlist) {
  36563. var _type = 'sortedPlaylistReps';
  36564. if (leastPixelDiffRep) {
  36565. _type = 'leastPixelDiffRep';
  36566. } else if (resolutionPlusOneRep) {
  36567. _type = 'resolutionPlusOneRep';
  36568. } else if (resolutionBestRep) {
  36569. _type = 'resolutionBestRep';
  36570. } else if (bandwidthBestRep) {
  36571. _type = 'bandwidthBestRep';
  36572. } else if (enabledPlaylistReps[0]) {
  36573. _type = 'enabledPlaylistReps';
  36574. }
  36575. logFn("choosing " + representationToString(chosenRep) + " using " + _type + " with options", options);
  36576. return chosenRep.playlist;
  36577. }
  36578. logFn('could not choose a playlist with options', options);
  36579. return null;
  36580. };
  36581. /**
  36582. * Chooses the appropriate media playlist based on the most recent
  36583. * bandwidth estimate and the player size.
  36584. *
  36585. * Expects to be called within the context of an instance of VhsHandler
  36586. *
  36587. * @return {Playlist} the highest bitrate playlist less than the
  36588. * currently detected bandwidth, accounting for some amount of
  36589. * bandwidth variance
  36590. */
  36591. var lastBandwidthSelector = function lastBandwidthSelector() {
  36592. var pixelRatio = this.useDevicePixelRatio ? window$1.devicePixelRatio || 1 : 1;
  36593. return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions, this.masterPlaylistController_);
  36594. };
  36595. /**
  36596. * Chooses the appropriate media playlist based on an
  36597. * exponential-weighted moving average of the bandwidth after
  36598. * filtering for player size.
  36599. *
  36600. * Expects to be called within the context of an instance of VhsHandler
  36601. *
  36602. * @param {number} decay - a number between 0 and 1. Higher values of
  36603. * this parameter will cause previous bandwidth estimates to lose
  36604. * significance more quickly.
  36605. * @return {Function} a function which can be invoked to create a new
  36606. * playlist selector function.
  36607. * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
  36608. */
  36609. var movingAverageBandwidthSelector = function movingAverageBandwidthSelector(decay) {
  36610. var average = -1;
  36611. var lastSystemBandwidth = -1;
  36612. if (decay < 0 || decay > 1) {
  36613. throw new Error('Moving average bandwidth decay must be between 0 and 1.');
  36614. }
  36615. return function () {
  36616. var pixelRatio = this.useDevicePixelRatio ? window$1.devicePixelRatio || 1 : 1;
  36617. if (average < 0) {
  36618. average = this.systemBandwidth;
  36619. lastSystemBandwidth = this.systemBandwidth;
  36620. } // stop the average value from decaying for every 250ms
  36621. // when the systemBandwidth is constant
  36622. // and
  36623. // stop average from setting to a very low value when the
  36624. // systemBandwidth becomes 0 in case of chunk cancellation
  36625. if (this.systemBandwidth > 0 && this.systemBandwidth !== lastSystemBandwidth) {
  36626. average = decay * this.systemBandwidth + (1 - decay) * average;
  36627. lastSystemBandwidth = this.systemBandwidth;
  36628. }
  36629. return simpleSelector(this.playlists.master, average, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions, this.masterPlaylistController_);
  36630. };
  36631. };
  36632. /**
  36633. * Chooses the appropriate media playlist based on the potential to rebuffer
  36634. *
  36635. * @param {Object} settings
  36636. * Object of information required to use this selector
  36637. * @param {Object} settings.master
  36638. * Object representation of the master manifest
  36639. * @param {number} settings.currentTime
  36640. * The current time of the player
  36641. * @param {number} settings.bandwidth
  36642. * Current measured bandwidth
  36643. * @param {number} settings.duration
  36644. * Duration of the media
  36645. * @param {number} settings.segmentDuration
  36646. * Segment duration to be used in round trip time calculations
  36647. * @param {number} settings.timeUntilRebuffer
  36648. * Time left in seconds until the player has to rebuffer
  36649. * @param {number} settings.currentTimeline
  36650. * The current timeline segments are being loaded from
  36651. * @param {SyncController} settings.syncController
  36652. * SyncController for determining if we have a sync point for a given playlist
  36653. * @return {Object|null}
  36654. * {Object} return.playlist
  36655. * The highest bandwidth playlist with the least amount of rebuffering
  36656. * {Number} return.rebufferingImpact
  36657. * The amount of time in seconds switching to this playlist will rebuffer. A
  36658. * negative value means that switching will cause zero rebuffering.
  36659. */
  36660. var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
  36661. var master = settings.master,
  36662. currentTime = settings.currentTime,
  36663. bandwidth = settings.bandwidth,
  36664. duration = settings.duration,
  36665. segmentDuration = settings.segmentDuration,
  36666. timeUntilRebuffer = settings.timeUntilRebuffer,
  36667. currentTimeline = settings.currentTimeline,
  36668. syncController = settings.syncController; // filter out any playlists that have been excluded due to
  36669. // incompatible configurations
  36670. var compatiblePlaylists = master.playlists.filter(function (playlist) {
  36671. return !Playlist.isIncompatible(playlist);
  36672. }); // filter out any playlists that have been disabled manually through the representations
  36673. // api or blacklisted temporarily due to playback errors.
  36674. var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
  36675. if (!enabledPlaylists.length) {
  36676. // if there are no enabled playlists, then they have all been blacklisted or disabled
  36677. // by the user through the representations api. In this case, ignore blacklisting and
  36678. // fallback to what the user wants by using playlists the user has not disabled.
  36679. enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
  36680. return !Playlist.isDisabled(playlist);
  36681. });
  36682. }
  36683. var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
  36684. var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
  36685. var syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
  36686. // sync request first. This will double the request time
  36687. var numRequests = syncPoint ? 1 : 2;
  36688. var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
  36689. var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
  36690. return {
  36691. playlist: playlist,
  36692. rebufferingImpact: rebufferingImpact
  36693. };
  36694. });
  36695. var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
  36696. return estimate.rebufferingImpact <= 0;
  36697. }); // Sort by bandwidth DESC
  36698. stableSort(noRebufferingPlaylists, function (a, b) {
  36699. return comparePlaylistBandwidth(b.playlist, a.playlist);
  36700. });
  36701. if (noRebufferingPlaylists.length) {
  36702. return noRebufferingPlaylists[0];
  36703. }
  36704. stableSort(rebufferingEstimates, function (a, b) {
  36705. return a.rebufferingImpact - b.rebufferingImpact;
  36706. });
  36707. return rebufferingEstimates[0] || null;
  36708. };
  36709. /**
  36710. * Chooses the appropriate media playlist, which in this case is the lowest bitrate
  36711. * one with video. If no renditions with video exist, return the lowest audio rendition.
  36712. *
  36713. * Expects to be called within the context of an instance of VhsHandler
  36714. *
  36715. * @return {Object|null}
  36716. * {Object} return.playlist
  36717. * The lowest bitrate playlist that contains a video codec. If no such rendition
  36718. * exists pick the lowest audio rendition.
  36719. */
  36720. var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
  36721. var _this = this; // filter out any playlists that have been excluded due to
  36722. // incompatible configurations or playback errors
  36723. var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
  36724. stableSort(playlists, function (a, b) {
  36725. return comparePlaylistBandwidth(a, b);
  36726. }); // Parse and assume that playlists with no video codec have no video
  36727. // (this is not necessarily true, although it is generally true).
  36728. //
  36729. // If an entire manifest has no valid videos everything will get filtered
  36730. // out.
  36731. var playlistsWithVideo = playlists.filter(function (playlist) {
  36732. return !!codecsForPlaylist(_this.playlists.master, playlist).video;
  36733. });
  36734. return playlistsWithVideo[0] || null;
  36735. };
  36736. /**
  36737. * Combine all segments into a single Uint8Array
  36738. *
  36739. * @param {Object} segmentObj
  36740. * @return {Uint8Array} concatenated bytes
  36741. * @private
  36742. */
  36743. var concatSegments = function concatSegments(segmentObj) {
  36744. var offset = 0;
  36745. var tempBuffer;
  36746. if (segmentObj.bytes) {
  36747. tempBuffer = new Uint8Array(segmentObj.bytes); // combine the individual segments into one large typed-array
  36748. segmentObj.segments.forEach(function (segment) {
  36749. tempBuffer.set(segment, offset);
  36750. offset += segment.byteLength;
  36751. });
  36752. }
  36753. return tempBuffer;
  36754. };
  36755. /**
  36756. * @file text-tracks.js
  36757. */
  36758. /**
  36759. * Create captions text tracks on video.js if they do not exist
  36760. *
  36761. * @param {Object} inbandTextTracks a reference to current inbandTextTracks
  36762. * @param {Object} tech the video.js tech
  36763. * @param {Object} captionStream the caption stream to create
  36764. * @private
  36765. */
  36766. var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStream) {
  36767. if (!inbandTextTracks[captionStream]) {
  36768. tech.trigger({
  36769. type: 'usage',
  36770. name: 'vhs-608'
  36771. });
  36772. tech.trigger({
  36773. type: 'usage',
  36774. name: 'hls-608'
  36775. });
  36776. var instreamId = captionStream; // we need to translate SERVICEn for 708 to how mux.js currently labels them
  36777. if (/^cc708_/.test(captionStream)) {
  36778. instreamId = 'SERVICE' + captionStream.split('_')[1];
  36779. }
  36780. var track = tech.textTracks().getTrackById(instreamId);
  36781. if (track) {
  36782. // Resuse an existing track with a CC# id because this was
  36783. // very likely created by videojs-contrib-hls from information
  36784. // in the m3u8 for us to use
  36785. inbandTextTracks[captionStream] = track;
  36786. } else {
  36787. // This section gets called when we have caption services that aren't specified in the manifest.
  36788. // Manifest level caption services are handled in media-groups.js under CLOSED-CAPTIONS.
  36789. var captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
  36790. var label = captionStream;
  36791. var language = captionStream;
  36792. var def = false;
  36793. var captionService = captionServices[instreamId];
  36794. if (captionService) {
  36795. label = captionService.label;
  36796. language = captionService.language;
  36797. def = captionService["default"];
  36798. } // Otherwise, create a track with the default `CC#` label and
  36799. // without a language
  36800. inbandTextTracks[captionStream] = tech.addRemoteTextTrack({
  36801. kind: 'captions',
  36802. id: instreamId,
  36803. // TODO: investigate why this doesn't seem to turn the caption on by default
  36804. "default": def,
  36805. label: label,
  36806. language: language
  36807. }, false).track;
  36808. }
  36809. }
  36810. };
  36811. /**
  36812. * Add caption text track data to a source handler given an array of captions
  36813. *
  36814. * @param {Object}
  36815. * @param {Object} inbandTextTracks the inband text tracks
  36816. * @param {number} timestampOffset the timestamp offset of the source buffer
  36817. * @param {Array} captionArray an array of caption data
  36818. * @private
  36819. */
  36820. var addCaptionData = function addCaptionData(_ref) {
  36821. var inbandTextTracks = _ref.inbandTextTracks,
  36822. captionArray = _ref.captionArray,
  36823. timestampOffset = _ref.timestampOffset;
  36824. if (!captionArray) {
  36825. return;
  36826. }
  36827. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  36828. captionArray.forEach(function (caption) {
  36829. var track = caption.stream;
  36830. inbandTextTracks[track].addCue(new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, caption.text));
  36831. });
  36832. };
  36833. /**
  36834. * Define properties on a cue for backwards compatability,
  36835. * but warn the user that the way that they are using it
  36836. * is depricated and will be removed at a later date.
  36837. *
  36838. * @param {Cue} cue the cue to add the properties on
  36839. * @private
  36840. */
  36841. var deprecateOldCue = function deprecateOldCue(cue) {
  36842. Object.defineProperties(cue.frame, {
  36843. id: {
  36844. get: function get() {
  36845. videojs.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
  36846. return cue.value.key;
  36847. }
  36848. },
  36849. value: {
  36850. get: function get() {
  36851. videojs.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
  36852. return cue.value.data;
  36853. }
  36854. },
  36855. privateData: {
  36856. get: function get() {
  36857. videojs.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
  36858. return cue.value.data;
  36859. }
  36860. }
  36861. });
  36862. };
  36863. /**
  36864. * Add metadata text track data to a source handler given an array of metadata
  36865. *
  36866. * @param {Object}
  36867. * @param {Object} inbandTextTracks the inband text tracks
  36868. * @param {Array} metadataArray an array of meta data
  36869. * @param {number} timestampOffset the timestamp offset of the source buffer
  36870. * @param {number} videoDuration the duration of the video
  36871. * @private
  36872. */
  36873. var addMetadata = function addMetadata(_ref2) {
  36874. var inbandTextTracks = _ref2.inbandTextTracks,
  36875. metadataArray = _ref2.metadataArray,
  36876. timestampOffset = _ref2.timestampOffset,
  36877. videoDuration = _ref2.videoDuration;
  36878. if (!metadataArray) {
  36879. return;
  36880. }
  36881. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  36882. var metadataTrack = inbandTextTracks.metadataTrack_;
  36883. if (!metadataTrack) {
  36884. return;
  36885. }
  36886. metadataArray.forEach(function (metadata) {
  36887. var time = metadata.cueTime + timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
  36888. // ignore this bit of metadata.
  36889. // This likely occurs when you have an non-timed ID3 tag like TIT2,
  36890. // which is the "Title/Songname/Content description" frame
  36891. if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) {
  36892. return;
  36893. }
  36894. metadata.frames.forEach(function (frame) {
  36895. var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
  36896. cue.frame = frame;
  36897. cue.value = frame;
  36898. deprecateOldCue(cue);
  36899. metadataTrack.addCue(cue);
  36900. });
  36901. });
  36902. if (!metadataTrack.cues || !metadataTrack.cues.length) {
  36903. return;
  36904. } // Updating the metadeta cues so that
  36905. // the endTime of each cue is the startTime of the next cue
  36906. // the endTime of last cue is the duration of the video
  36907. var cues = metadataTrack.cues;
  36908. var cuesArray = []; // Create a copy of the TextTrackCueList...
  36909. // ...disregarding cues with a falsey value
  36910. for (var i = 0; i < cues.length; i++) {
  36911. if (cues[i]) {
  36912. cuesArray.push(cues[i]);
  36913. }
  36914. } // Group cues by their startTime value
  36915. var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
  36916. var timeSlot = obj[cue.startTime] || [];
  36917. timeSlot.push(cue);
  36918. obj[cue.startTime] = timeSlot;
  36919. return obj;
  36920. }, {}); // Sort startTimes by ascending order
  36921. var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
  36922. return Number(a) - Number(b);
  36923. }); // Map each cue group's endTime to the next group's startTime
  36924. sortedStartTimes.forEach(function (startTime, idx) {
  36925. var cueGroup = cuesGroupedByStartTime[startTime];
  36926. var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime
  36927. cueGroup.forEach(function (cue) {
  36928. cue.endTime = nextTime;
  36929. });
  36930. });
  36931. };
  36932. /**
  36933. * Create metadata text track on video.js if it does not exist
  36934. *
  36935. * @param {Object} inbandTextTracks a reference to current inbandTextTracks
  36936. * @param {string} dispatchType the inband metadata track dispatch type
  36937. * @param {Object} tech the video.js tech
  36938. * @private
  36939. */
  36940. var createMetadataTrackIfNotExists = function createMetadataTrackIfNotExists(inbandTextTracks, dispatchType, tech) {
  36941. if (inbandTextTracks.metadataTrack_) {
  36942. return;
  36943. }
  36944. inbandTextTracks.metadataTrack_ = tech.addRemoteTextTrack({
  36945. kind: 'metadata',
  36946. label: 'Timed Metadata'
  36947. }, false).track;
  36948. inbandTextTracks.metadataTrack_.inBandMetadataTrackDispatchType = dispatchType;
  36949. };
  36950. /**
  36951. * Remove cues from a track on video.js.
  36952. *
  36953. * @param {Double} start start of where we should remove the cue
  36954. * @param {Double} end end of where the we should remove the cue
  36955. * @param {Object} track the text track to remove the cues from
  36956. * @private
  36957. */
  36958. var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
  36959. var i;
  36960. var cue;
  36961. if (!track) {
  36962. return;
  36963. }
  36964. if (!track.cues) {
  36965. return;
  36966. }
  36967. i = track.cues.length;
  36968. while (i--) {
  36969. cue = track.cues[i]; // Remove any cue within the provided start and end time
  36970. if (cue.startTime >= start && cue.endTime <= end) {
  36971. track.removeCue(cue);
  36972. }
  36973. }
  36974. };
  36975. /**
  36976. * Remove duplicate cues from a track on video.js (a cue is considered a
  36977. * duplicate if it has the same time interval and text as another)
  36978. *
  36979. * @param {Object} track the text track to remove the duplicate cues from
  36980. * @private
  36981. */
  36982. var removeDuplicateCuesFromTrack = function removeDuplicateCuesFromTrack(track) {
  36983. var cues = track.cues;
  36984. if (!cues) {
  36985. return;
  36986. }
  36987. for (var i = 0; i < cues.length; i++) {
  36988. var duplicates = [];
  36989. var occurrences = 0;
  36990. for (var j = 0; j < cues.length; j++) {
  36991. if (cues[i].startTime === cues[j].startTime && cues[i].endTime === cues[j].endTime && cues[i].text === cues[j].text) {
  36992. occurrences++;
  36993. if (occurrences > 1) {
  36994. duplicates.push(cues[j]);
  36995. }
  36996. }
  36997. }
  36998. if (duplicates.length) {
  36999. duplicates.forEach(function (dupe) {
  37000. return track.removeCue(dupe);
  37001. });
  37002. }
  37003. }
  37004. };
  37005. /**
  37006. * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
  37007. * front of current time.
  37008. *
  37009. * @param {Array} buffer
  37010. * The current buffer of gop information
  37011. * @param {number} currentTime
  37012. * The current time
  37013. * @param {Double} mapping
  37014. * Offset to map display time to stream presentation time
  37015. * @return {Array}
  37016. * List of gops considered safe to append over
  37017. */
  37018. var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) {
  37019. if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
  37020. return [];
  37021. } // pts value for current time + 3 seconds to give a bit more wiggle room
  37022. var currentTimePts = Math.ceil((currentTime - mapping + 3) * ONE_SECOND_IN_TS);
  37023. var i;
  37024. for (i = 0; i < buffer.length; i++) {
  37025. if (buffer[i].pts > currentTimePts) {
  37026. break;
  37027. }
  37028. }
  37029. return buffer.slice(i);
  37030. };
  37031. /**
  37032. * Appends gop information (timing and byteLength) received by the transmuxer for the
  37033. * gops appended in the last call to appendBuffer
  37034. *
  37035. * @param {Array} buffer
  37036. * The current buffer of gop information
  37037. * @param {Array} gops
  37038. * List of new gop information
  37039. * @param {boolean} replace
  37040. * If true, replace the buffer with the new gop information. If false, append the
  37041. * new gop information to the buffer in the right location of time.
  37042. * @return {Array}
  37043. * Updated list of gop information
  37044. */
  37045. var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
  37046. if (!gops.length) {
  37047. return buffer;
  37048. }
  37049. if (replace) {
  37050. // If we are in safe append mode, then completely overwrite the gop buffer
  37051. // with the most recent appeneded data. This will make sure that when appending
  37052. // future segments, we only try to align with gops that are both ahead of current
  37053. // time and in the last segment appended.
  37054. return gops.slice();
  37055. }
  37056. var start = gops[0].pts;
  37057. var i = 0;
  37058. for (i; i < buffer.length; i++) {
  37059. if (buffer[i].pts >= start) {
  37060. break;
  37061. }
  37062. }
  37063. return buffer.slice(0, i).concat(gops);
  37064. };
  37065. /**
  37066. * Removes gop information in buffer that overlaps with provided start and end
  37067. *
  37068. * @param {Array} buffer
  37069. * The current buffer of gop information
  37070. * @param {Double} start
  37071. * position to start the remove at
  37072. * @param {Double} end
  37073. * position to end the remove at
  37074. * @param {Double} mapping
  37075. * Offset to map display time to stream presentation time
  37076. */
  37077. var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
  37078. var startPts = Math.ceil((start - mapping) * ONE_SECOND_IN_TS);
  37079. var endPts = Math.ceil((end - mapping) * ONE_SECOND_IN_TS);
  37080. var updatedBuffer = buffer.slice();
  37081. var i = buffer.length;
  37082. while (i--) {
  37083. if (buffer[i].pts <= endPts) {
  37084. break;
  37085. }
  37086. }
  37087. if (i === -1) {
  37088. // no removal because end of remove range is before start of buffer
  37089. return updatedBuffer;
  37090. }
  37091. var j = i + 1;
  37092. while (j--) {
  37093. if (buffer[j].pts <= startPts) {
  37094. break;
  37095. }
  37096. } // clamp remove range start to 0 index
  37097. j = Math.max(j, 0);
  37098. updatedBuffer.splice(j, i - j + 1);
  37099. return updatedBuffer;
  37100. };
  37101. var shallowEqual = function shallowEqual(a, b) {
  37102. // if both are undefined
  37103. // or one or the other is undefined
  37104. // they are not equal
  37105. if (!a && !b || !a && b || a && !b) {
  37106. return false;
  37107. } // they are the same object and thus, equal
  37108. if (a === b) {
  37109. return true;
  37110. } // sort keys so we can make sure they have
  37111. // all the same keys later.
  37112. var akeys = Object.keys(a).sort();
  37113. var bkeys = Object.keys(b).sort(); // different number of keys, not equal
  37114. if (akeys.length !== bkeys.length) {
  37115. return false;
  37116. }
  37117. for (var i = 0; i < akeys.length; i++) {
  37118. var key = akeys[i]; // different sorted keys, not equal
  37119. if (key !== bkeys[i]) {
  37120. return false;
  37121. } // different values, not equal
  37122. if (a[key] !== b[key]) {
  37123. return false;
  37124. }
  37125. }
  37126. return true;
  37127. }; // https://www.w3.org/TR/WebIDL-1/#quotaexceedederror
  37128. var QUOTA_EXCEEDED_ERR = 22;
  37129. /**
  37130. * The segment loader has no recourse except to fetch a segment in the
  37131. * current playlist and use the internal timestamps in that segment to
  37132. * generate a syncPoint. This function returns a good candidate index
  37133. * for that process.
  37134. *
  37135. * @param {Array} segments - the segments array from a playlist.
  37136. * @return {number} An index of a segment from the playlist to load
  37137. */
  37138. var getSyncSegmentCandidate = function getSyncSegmentCandidate(currentTimeline, segments, targetTime) {
  37139. segments = segments || [];
  37140. var timelineSegments = [];
  37141. var time = 0;
  37142. for (var i = 0; i < segments.length; i++) {
  37143. var segment = segments[i];
  37144. if (currentTimeline === segment.timeline) {
  37145. timelineSegments.push(i);
  37146. time += segment.duration;
  37147. if (time > targetTime) {
  37148. return i;
  37149. }
  37150. }
  37151. }
  37152. if (timelineSegments.length === 0) {
  37153. return 0;
  37154. } // default to the last timeline segment
  37155. return timelineSegments[timelineSegments.length - 1];
  37156. }; // In the event of a quota exceeded error, keep at least one second of back buffer. This
  37157. // number was arbitrarily chosen and may be updated in the future, but seemed reasonable
  37158. // as a start to prevent any potential issues with removing content too close to the
  37159. // playhead.
  37160. var MIN_BACK_BUFFER = 1; // in ms
  37161. var CHECK_BUFFER_DELAY = 500;
  37162. var finite = function finite(num) {
  37163. return typeof num === 'number' && isFinite(num);
  37164. }; // With most content hovering around 30fps, if a segment has a duration less than a half
  37165. // frame at 30fps or one frame at 60fps, the bandwidth and throughput calculations will
  37166. // not accurately reflect the rest of the content.
  37167. var MIN_SEGMENT_DURATION_TO_SAVE_STATS = 1 / 60;
  37168. var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, trackInfo) {
  37169. // Although these checks should most likely cover non 'main' types, for now it narrows
  37170. // the scope of our checks.
  37171. if (loaderType !== 'main' || !startingMedia || !trackInfo) {
  37172. return null;
  37173. }
  37174. if (!trackInfo.hasAudio && !trackInfo.hasVideo) {
  37175. return 'Neither audio nor video found in segment.';
  37176. }
  37177. if (startingMedia.hasVideo && !trackInfo.hasVideo) {
  37178. return 'Only audio found in segment when we expected video.' + ' We can\'t switch to audio only from a stream that had video.' + ' To get rid of this message, please add codec information to the manifest.';
  37179. }
  37180. if (!startingMedia.hasVideo && trackInfo.hasVideo) {
  37181. return 'Video found in segment when we expected only audio.' + ' We can\'t switch to a stream with video from an audio only stream.' + ' To get rid of this message, please add codec information to the manifest.';
  37182. }
  37183. return null;
  37184. };
  37185. /**
  37186. * Calculates a time value that is safe to remove from the back buffer without interrupting
  37187. * playback.
  37188. *
  37189. * @param {TimeRange} seekable
  37190. * The current seekable range
  37191. * @param {number} currentTime
  37192. * The current time of the player
  37193. * @param {number} targetDuration
  37194. * The target duration of the current playlist
  37195. * @return {number}
  37196. * Time that is safe to remove from the back buffer without interrupting playback
  37197. */
  37198. var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable, currentTime, targetDuration) {
  37199. // 30 seconds before the playhead provides a safe default for trimming.
  37200. //
  37201. // Choosing a reasonable default is particularly important for high bitrate content and
  37202. // VOD videos/live streams with large windows, as the buffer may end up overfilled and
  37203. // throw an APPEND_BUFFER_ERR.
  37204. var trimTime = currentTime - Config.BACK_BUFFER_LENGTH;
  37205. if (seekable.length) {
  37206. // Some live playlists may have a shorter window of content than the full allowed back
  37207. // buffer. For these playlists, don't save content that's no longer within the window.
  37208. trimTime = Math.max(trimTime, seekable.start(0));
  37209. } // Don't remove within target duration of the current time to avoid the possibility of
  37210. // removing the GOP currently being played, as removing it can cause playback stalls.
  37211. var maxTrimTime = currentTime - targetDuration;
  37212. return Math.min(maxTrimTime, trimTime);
  37213. };
  37214. var segmentInfoString = function segmentInfoString(segmentInfo) {
  37215. var startOfSegment = segmentInfo.startOfSegment,
  37216. duration = segmentInfo.duration,
  37217. segment = segmentInfo.segment,
  37218. part = segmentInfo.part,
  37219. _segmentInfo$playlist = segmentInfo.playlist,
  37220. seq = _segmentInfo$playlist.mediaSequence,
  37221. id = _segmentInfo$playlist.id,
  37222. _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
  37223. segments = _segmentInfo$playlist2 === void 0 ? [] : _segmentInfo$playlist2,
  37224. index = segmentInfo.mediaIndex,
  37225. partIndex = segmentInfo.partIndex,
  37226. timeline = segmentInfo.timeline;
  37227. var segmentLen = segments.length - 1;
  37228. var selection = 'mediaIndex/partIndex increment';
  37229. if (segmentInfo.getMediaInfoForTime) {
  37230. selection = "getMediaInfoForTime (" + segmentInfo.getMediaInfoForTime + ")";
  37231. } else if (segmentInfo.isSyncRequest) {
  37232. selection = 'getSyncSegmentCandidate (isSyncRequest)';
  37233. }
  37234. if (segmentInfo.independent) {
  37235. selection += " with independent " + segmentInfo.independent;
  37236. }
  37237. var hasPartIndex = typeof partIndex === 'number';
  37238. var name = segmentInfo.segment.uri ? 'segment' : 'pre-segment';
  37239. var zeroBasedPartCount = hasPartIndex ? getKnownPartCount({
  37240. preloadSegment: segment
  37241. }) - 1 : 0;
  37242. return name + " [" + (seq + index) + "/" + (seq + segmentLen) + "]" + (hasPartIndex ? " part [" + partIndex + "/" + zeroBasedPartCount + "]" : '') + (" segment start/end [" + segment.start + " => " + segment.end + "]") + (hasPartIndex ? " part start/end [" + part.start + " => " + part.end + "]" : '') + (" startOfSegment [" + startOfSegment + "]") + (" duration [" + duration + "]") + (" timeline [" + timeline + "]") + (" selected by [" + selection + "]") + (" playlist [" + id + "]");
  37243. };
  37244. var timingInfoPropertyForMedia = function timingInfoPropertyForMedia(mediaType) {
  37245. return mediaType + "TimingInfo";
  37246. };
  37247. /**
  37248. * Returns the timestamp offset to use for the segment.
  37249. *
  37250. * @param {number} segmentTimeline
  37251. * The timeline of the segment
  37252. * @param {number} currentTimeline
  37253. * The timeline currently being followed by the loader
  37254. * @param {number} startOfSegment
  37255. * The estimated segment start
  37256. * @param {TimeRange[]} buffered
  37257. * The loader's buffer
  37258. * @param {boolean} overrideCheck
  37259. * If true, no checks are made to see if the timestamp offset value should be set,
  37260. * but sets it directly to a value.
  37261. *
  37262. * @return {number|null}
  37263. * Either a number representing a new timestamp offset, or null if the segment is
  37264. * part of the same timeline
  37265. */
  37266. var timestampOffsetForSegment = function timestampOffsetForSegment(_ref) {
  37267. var segmentTimeline = _ref.segmentTimeline,
  37268. currentTimeline = _ref.currentTimeline,
  37269. startOfSegment = _ref.startOfSegment,
  37270. buffered = _ref.buffered,
  37271. overrideCheck = _ref.overrideCheck; // Check to see if we are crossing a discontinuity to see if we need to set the
  37272. // timestamp offset on the transmuxer and source buffer.
  37273. //
  37274. // Previously, we changed the timestampOffset if the start of this segment was less than
  37275. // the currently set timestampOffset, but this isn't desirable as it can produce bad
  37276. // behavior, especially around long running live streams.
  37277. if (!overrideCheck && segmentTimeline === currentTimeline) {
  37278. return null;
  37279. } // When changing renditions, it's possible to request a segment on an older timeline. For
  37280. // instance, given two renditions with the following:
  37281. //
  37282. // #EXTINF:10
  37283. // segment1
  37284. // #EXT-X-DISCONTINUITY
  37285. // #EXTINF:10
  37286. // segment2
  37287. // #EXTINF:10
  37288. // segment3
  37289. //
  37290. // And the current player state:
  37291. //
  37292. // current time: 8
  37293. // buffer: 0 => 20
  37294. //
  37295. // The next segment on the current rendition would be segment3, filling the buffer from
  37296. // 20s onwards. However, if a rendition switch happens after segment2 was requested,
  37297. // then the next segment to be requested will be segment1 from the new rendition in
  37298. // order to fill time 8 and onwards. Using the buffered end would result in repeated
  37299. // content (since it would position segment1 of the new rendition starting at 20s). This
  37300. // case can be identified when the new segment's timeline is a prior value. Instead of
  37301. // using the buffered end, the startOfSegment can be used, which, hopefully, will be
  37302. // more accurate to the actual start time of the segment.
  37303. if (segmentTimeline < currentTimeline) {
  37304. return startOfSegment;
  37305. } // segmentInfo.startOfSegment used to be used as the timestamp offset, however, that
  37306. // value uses the end of the last segment if it is available. While this value
  37307. // should often be correct, it's better to rely on the buffered end, as the new
  37308. // content post discontinuity should line up with the buffered end as if it were
  37309. // time 0 for the new content.
  37310. return buffered.length ? buffered.end(buffered.length - 1) : startOfSegment;
  37311. };
  37312. /**
  37313. * Returns whether or not the loader should wait for a timeline change from the timeline
  37314. * change controller before processing the segment.
  37315. *
  37316. * Primary timing in VHS goes by video. This is different from most media players, as
  37317. * audio is more often used as the primary timing source. For the foreseeable future, VHS
  37318. * will continue to use video as the primary timing source, due to the current logic and
  37319. * expectations built around it.
  37320. * Since the timing follows video, in order to maintain sync, the video loader is
  37321. * responsible for setting both audio and video source buffer timestamp offsets.
  37322. *
  37323. * Setting different values for audio and video source buffers could lead to
  37324. * desyncing. The following examples demonstrate some of the situations where this
  37325. * distinction is important. Note that all of these cases involve demuxed content. When
  37326. * content is muxed, the audio and video are packaged together, therefore syncing
  37327. * separate media playlists is not an issue.
  37328. *
  37329. * CASE 1: Audio prepares to load a new timeline before video:
  37330. *
  37331. * Timeline: 0 1
  37332. * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
  37333. * Audio Loader: ^
  37334. * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
  37335. * Video Loader ^
  37336. *
  37337. * In the above example, the audio loader is preparing to load the 6th segment, the first
  37338. * after a discontinuity, while the video loader is still loading the 5th segment, before
  37339. * the discontinuity.
  37340. *
  37341. * If the audio loader goes ahead and loads and appends the 6th segment before the video
  37342. * loader crosses the discontinuity, then when appended, the 6th audio segment will use
  37343. * the timestamp offset from timeline 0. This will likely lead to desyncing. In addition,
  37344. * the audio loader must provide the audioAppendStart value to trim the content in the
  37345. * transmuxer, and that value relies on the audio timestamp offset. Since the audio
  37346. * timestamp offset is set by the video (main) loader, the audio loader shouldn't load the
  37347. * segment until that value is provided.
  37348. *
  37349. * CASE 2: Video prepares to load a new timeline before audio:
  37350. *
  37351. * Timeline: 0 1
  37352. * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
  37353. * Audio Loader: ^
  37354. * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
  37355. * Video Loader ^
  37356. *
  37357. * In the above example, the video loader is preparing to load the 6th segment, the first
  37358. * after a discontinuity, while the audio loader is still loading the 5th segment, before
  37359. * the discontinuity.
  37360. *
  37361. * If the video loader goes ahead and loads and appends the 6th segment, then once the
  37362. * segment is loaded and processed, both the video and audio timestamp offsets will be
  37363. * set, since video is used as the primary timing source. This is to ensure content lines
  37364. * up appropriately, as any modifications to the video timing are reflected by audio when
  37365. * the video loader sets the audio and video timestamp offsets to the same value. However,
  37366. * setting the timestamp offset for audio before audio has had a chance to change
  37367. * timelines will likely lead to desyncing, as the audio loader will append segment 5 with
  37368. * a timestamp intended to apply to segments from timeline 1 rather than timeline 0.
  37369. *
  37370. * CASE 3: When seeking, audio prepares to load a new timeline before video
  37371. *
  37372. * Timeline: 0 1
  37373. * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
  37374. * Audio Loader: ^
  37375. * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
  37376. * Video Loader ^
  37377. *
  37378. * In the above example, both audio and video loaders are loading segments from timeline
  37379. * 0, but imagine that the seek originated from timeline 1.
  37380. *
  37381. * When seeking to a new timeline, the timestamp offset will be set based on the expected
  37382. * segment start of the loaded video segment. In order to maintain sync, the audio loader
  37383. * must wait for the video loader to load its segment and update both the audio and video
  37384. * timestamp offsets before it may load and append its own segment. This is the case
  37385. * whether the seek results in a mismatched segment request (e.g., the audio loader
  37386. * chooses to load segment 3 and the video loader chooses to load segment 4) or the
  37387. * loaders choose to load the same segment index from each playlist, as the segments may
  37388. * not be aligned perfectly, even for matching segment indexes.
  37389. *
  37390. * @param {Object} timelinechangeController
  37391. * @param {number} currentTimeline
  37392. * The timeline currently being followed by the loader
  37393. * @param {number} segmentTimeline
  37394. * The timeline of the segment being loaded
  37395. * @param {('main'|'audio')} loaderType
  37396. * The loader type
  37397. * @param {boolean} audioDisabled
  37398. * Whether the audio is disabled for the loader. This should only be true when the
  37399. * loader may have muxed audio in its segment, but should not append it, e.g., for
  37400. * the main loader when an alternate audio playlist is active.
  37401. *
  37402. * @return {boolean}
  37403. * Whether the loader should wait for a timeline change from the timeline change
  37404. * controller before processing the segment
  37405. */
  37406. var shouldWaitForTimelineChange = function shouldWaitForTimelineChange(_ref2) {
  37407. var timelineChangeController = _ref2.timelineChangeController,
  37408. currentTimeline = _ref2.currentTimeline,
  37409. segmentTimeline = _ref2.segmentTimeline,
  37410. loaderType = _ref2.loaderType,
  37411. audioDisabled = _ref2.audioDisabled;
  37412. if (currentTimeline === segmentTimeline) {
  37413. return false;
  37414. }
  37415. if (loaderType === 'audio') {
  37416. var lastMainTimelineChange = timelineChangeController.lastTimelineChange({
  37417. type: 'main'
  37418. }); // Audio loader should wait if:
  37419. //
  37420. // * main hasn't had a timeline change yet (thus has not loaded its first segment)
  37421. // * main hasn't yet changed to the timeline audio is looking to load
  37422. return !lastMainTimelineChange || lastMainTimelineChange.to !== segmentTimeline;
  37423. } // The main loader only needs to wait for timeline changes if there's demuxed audio.
  37424. // Otherwise, there's nothing to wait for, since audio would be muxed into the main
  37425. // loader's segments (or the content is audio/video only and handled by the main
  37426. // loader).
  37427. if (loaderType === 'main' && audioDisabled) {
  37428. var pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({
  37429. type: 'audio'
  37430. }); // Main loader should wait for the audio loader if audio is not pending a timeline
  37431. // change to the current timeline.
  37432. //
  37433. // Since the main loader is responsible for setting the timestamp offset for both
  37434. // audio and video, the main loader must wait for audio to be about to change to its
  37435. // timeline before setting the offset, otherwise, if audio is behind in loading,
  37436. // segments from the previous timeline would be adjusted by the new timestamp offset.
  37437. //
  37438. // This requirement means that video will not cross a timeline until the audio is
  37439. // about to cross to it, so that way audio and video will always cross the timeline
  37440. // together.
  37441. //
  37442. // In addition to normal timeline changes, these rules also apply to the start of a
  37443. // stream (going from a non-existent timeline, -1, to timeline 0). It's important
  37444. // that these rules apply to the first timeline change because if they did not, it's
  37445. // possible that the main loader will cross two timelines before the audio loader has
  37446. // crossed one. Logic may be implemented to handle the startup as a special case, but
  37447. // it's easier to simply treat all timeline changes the same.
  37448. if (pendingAudioTimelineChange && pendingAudioTimelineChange.to === segmentTimeline) {
  37449. return false;
  37450. }
  37451. return true;
  37452. }
  37453. return false;
  37454. };
  37455. var mediaDuration = function mediaDuration(timingInfos) {
  37456. var maxDuration = 0;
  37457. ['video', 'audio'].forEach(function (type) {
  37458. var typeTimingInfo = timingInfos[type + "TimingInfo"];
  37459. if (!typeTimingInfo) {
  37460. return;
  37461. }
  37462. var start = typeTimingInfo.start,
  37463. end = typeTimingInfo.end;
  37464. var duration;
  37465. if (typeof start === 'bigint' || typeof end === 'bigint') {
  37466. duration = window$1.BigInt(end) - window$1.BigInt(start);
  37467. } else if (typeof start === 'number' && typeof end === 'number') {
  37468. duration = end - start;
  37469. }
  37470. if (typeof duration !== 'undefined' && duration > maxDuration) {
  37471. maxDuration = duration;
  37472. }
  37473. }); // convert back to a number if it is lower than MAX_SAFE_INTEGER
  37474. // as we only need BigInt when we are above that.
  37475. if (typeof maxDuration === 'bigint' && maxDuration < Number.MAX_SAFE_INTEGER) {
  37476. maxDuration = Number(maxDuration);
  37477. }
  37478. return maxDuration;
  37479. };
  37480. var segmentTooLong = function segmentTooLong(_ref3) {
  37481. var segmentDuration = _ref3.segmentDuration,
  37482. maxDuration = _ref3.maxDuration; // 0 duration segments are most likely due to metadata only segments or a lack of
  37483. // information.
  37484. if (!segmentDuration) {
  37485. return false;
  37486. } // For HLS:
  37487. //
  37488. // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1
  37489. // The EXTINF duration of each Media Segment in the Playlist
  37490. // file, when rounded to the nearest integer, MUST be less than or equal
  37491. // to the target duration; longer segments can trigger playback stalls
  37492. // or other errors.
  37493. //
  37494. // For DASH, the mpd-parser uses the largest reported segment duration as the target
  37495. // duration. Although that reported duration is occasionally approximate (i.e., not
  37496. // exact), a strict check may report that a segment is too long more often in DASH.
  37497. return Math.round(segmentDuration) > maxDuration + TIME_FUDGE_FACTOR;
  37498. };
  37499. var getTroublesomeSegmentDurationMessage = function getTroublesomeSegmentDurationMessage(segmentInfo, sourceType) {
  37500. // Right now we aren't following DASH's timing model exactly, so only perform
  37501. // this check for HLS content.
  37502. if (sourceType !== 'hls') {
  37503. return null;
  37504. }
  37505. var segmentDuration = mediaDuration({
  37506. audioTimingInfo: segmentInfo.audioTimingInfo,
  37507. videoTimingInfo: segmentInfo.videoTimingInfo
  37508. }); // Don't report if we lack information.
  37509. //
  37510. // If the segment has a duration of 0 it is either a lack of information or a
  37511. // metadata only segment and shouldn't be reported here.
  37512. if (!segmentDuration) {
  37513. return null;
  37514. }
  37515. var targetDuration = segmentInfo.playlist.targetDuration;
  37516. var isSegmentWayTooLong = segmentTooLong({
  37517. segmentDuration: segmentDuration,
  37518. maxDuration: targetDuration * 2
  37519. });
  37520. var isSegmentSlightlyTooLong = segmentTooLong({
  37521. segmentDuration: segmentDuration,
  37522. maxDuration: targetDuration
  37523. });
  37524. var segmentTooLongMessage = "Segment with index " + segmentInfo.mediaIndex + " " + ("from playlist " + segmentInfo.playlist.id + " ") + ("has a duration of " + segmentDuration + " ") + ("when the reported duration is " + segmentInfo.duration + " ") + ("and the target duration is " + targetDuration + ". ") + 'For HLS content, a duration in excess of the target duration may result in ' + 'playback issues. See the HLS specification section on EXT-X-TARGETDURATION for ' + 'more details: ' + 'https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1';
  37525. if (isSegmentWayTooLong || isSegmentSlightlyTooLong) {
  37526. return {
  37527. severity: isSegmentWayTooLong ? 'warn' : 'info',
  37528. message: segmentTooLongMessage
  37529. };
  37530. }
  37531. return null;
  37532. };
  37533. /**
  37534. * An object that manages segment loading and appending.
  37535. *
  37536. * @class SegmentLoader
  37537. * @param {Object} options required and optional options
  37538. * @extends videojs.EventTarget
  37539. */
  37540. var SegmentLoader = /*#__PURE__*/function (_videojs$EventTarget) {
  37541. _inheritsLoose(SegmentLoader, _videojs$EventTarget);
  37542. function SegmentLoader(settings, options) {
  37543. var _this;
  37544. _this = _videojs$EventTarget.call(this) || this; // check pre-conditions
  37545. if (!settings) {
  37546. throw new TypeError('Initialization settings are required');
  37547. }
  37548. if (typeof settings.currentTime !== 'function') {
  37549. throw new TypeError('No currentTime getter specified');
  37550. }
  37551. if (!settings.mediaSource) {
  37552. throw new TypeError('No MediaSource specified');
  37553. } // public properties
  37554. _this.bandwidth = settings.bandwidth;
  37555. _this.throughput = {
  37556. rate: 0,
  37557. count: 0
  37558. };
  37559. _this.roundTrip = NaN;
  37560. _this.resetStats_();
  37561. _this.mediaIndex = null;
  37562. _this.partIndex = null; // private settings
  37563. _this.hasPlayed_ = settings.hasPlayed;
  37564. _this.currentTime_ = settings.currentTime;
  37565. _this.seekable_ = settings.seekable;
  37566. _this.seeking_ = settings.seeking;
  37567. _this.duration_ = settings.duration;
  37568. _this.mediaSource_ = settings.mediaSource;
  37569. _this.vhs_ = settings.vhs;
  37570. _this.loaderType_ = settings.loaderType;
  37571. _this.currentMediaInfo_ = void 0;
  37572. _this.startingMediaInfo_ = void 0;
  37573. _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
  37574. _this.goalBufferLength_ = settings.goalBufferLength;
  37575. _this.sourceType_ = settings.sourceType;
  37576. _this.sourceUpdater_ = settings.sourceUpdater;
  37577. _this.inbandTextTracks_ = settings.inbandTextTracks;
  37578. _this.state_ = 'INIT';
  37579. _this.timelineChangeController_ = settings.timelineChangeController;
  37580. _this.shouldSaveSegmentTimingInfo_ = true;
  37581. _this.parse708captions_ = settings.parse708captions;
  37582. _this.useDtsForTimestampOffset_ = settings.useDtsForTimestampOffset;
  37583. _this.captionServices_ = settings.captionServices;
  37584. _this.experimentalExactManifestTimings = settings.experimentalExactManifestTimings; // private instance variables
  37585. _this.checkBufferTimeout_ = null;
  37586. _this.error_ = void 0;
  37587. _this.currentTimeline_ = -1;
  37588. _this.pendingSegment_ = null;
  37589. _this.xhrOptions_ = null;
  37590. _this.pendingSegments_ = [];
  37591. _this.audioDisabled_ = false;
  37592. _this.isPendingTimestampOffset_ = false; // TODO possibly move gopBuffer and timeMapping info to a separate controller
  37593. _this.gopBuffer_ = [];
  37594. _this.timeMapping_ = 0;
  37595. _this.safeAppend_ = videojs.browser.IE_VERSION >= 11;
  37596. _this.appendInitSegment_ = {
  37597. audio: true,
  37598. video: true
  37599. };
  37600. _this.playlistOfLastInitSegment_ = {
  37601. audio: null,
  37602. video: null
  37603. };
  37604. _this.callQueue_ = []; // If the segment loader prepares to load a segment, but does not have enough
  37605. // information yet to start the loading process (e.g., if the audio loader wants to
  37606. // load a segment from the next timeline but the main loader hasn't yet crossed that
  37607. // timeline), then the load call will be added to the queue until it is ready to be
  37608. // processed.
  37609. _this.loadQueue_ = [];
  37610. _this.metadataQueue_ = {
  37611. id3: [],
  37612. caption: []
  37613. };
  37614. _this.waitingOnRemove_ = false;
  37615. _this.quotaExceededErrorRetryTimeout_ = null; // Fragmented mp4 playback
  37616. _this.activeInitSegmentId_ = null;
  37617. _this.initSegments_ = {}; // HLSe playback
  37618. _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
  37619. _this.keyCache_ = {};
  37620. _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
  37621. // between a time in the display time and a segment index within
  37622. // a playlist
  37623. _this.syncController_ = settings.syncController;
  37624. _this.syncPoint_ = {
  37625. segmentIndex: 0,
  37626. time: 0
  37627. };
  37628. _this.transmuxer_ = _this.createTransmuxer_();
  37629. _this.triggerSyncInfoUpdate_ = function () {
  37630. return _this.trigger('syncinfoupdate');
  37631. };
  37632. _this.syncController_.on('syncinfoupdate', _this.triggerSyncInfoUpdate_);
  37633. _this.mediaSource_.addEventListener('sourceopen', function () {
  37634. if (!_this.isEndOfStream_()) {
  37635. _this.ended_ = false;
  37636. }
  37637. }); // ...for determining the fetch location
  37638. _this.fetchAtBuffer_ = false;
  37639. _this.logger_ = logger("SegmentLoader[" + _this.loaderType_ + "]");
  37640. Object.defineProperty(_assertThisInitialized(_this), 'state', {
  37641. get: function get() {
  37642. return this.state_;
  37643. },
  37644. set: function set(newState) {
  37645. if (newState !== this.state_) {
  37646. this.logger_(this.state_ + " -> " + newState);
  37647. this.state_ = newState;
  37648. this.trigger('statechange');
  37649. }
  37650. }
  37651. });
  37652. _this.sourceUpdater_.on('ready', function () {
  37653. if (_this.hasEnoughInfoToAppend_()) {
  37654. _this.processCallQueue_();
  37655. }
  37656. }); // Only the main loader needs to listen for pending timeline changes, as the main
  37657. // loader should wait for audio to be ready to change its timeline so that both main
  37658. // and audio timelines change together. For more details, see the
  37659. // shouldWaitForTimelineChange function.
  37660. if (_this.loaderType_ === 'main') {
  37661. _this.timelineChangeController_.on('pendingtimelinechange', function () {
  37662. if (_this.hasEnoughInfoToAppend_()) {
  37663. _this.processCallQueue_();
  37664. }
  37665. });
  37666. } // The main loader only listens on pending timeline changes, but the audio loader,
  37667. // since its loads follow main, needs to listen on timeline changes. For more details,
  37668. // see the shouldWaitForTimelineChange function.
  37669. if (_this.loaderType_ === 'audio') {
  37670. _this.timelineChangeController_.on('timelinechange', function () {
  37671. if (_this.hasEnoughInfoToLoad_()) {
  37672. _this.processLoadQueue_();
  37673. }
  37674. if (_this.hasEnoughInfoToAppend_()) {
  37675. _this.processCallQueue_();
  37676. }
  37677. });
  37678. }
  37679. return _this;
  37680. }
  37681. var _proto = SegmentLoader.prototype;
  37682. _proto.createTransmuxer_ = function createTransmuxer_() {
  37683. return segmentTransmuxer.createTransmuxer({
  37684. remux: false,
  37685. alignGopsAtEnd: this.safeAppend_,
  37686. keepOriginalTimestamps: true,
  37687. parse708captions: this.parse708captions_,
  37688. captionServices: this.captionServices_
  37689. });
  37690. }
  37691. /**
  37692. * reset all of our media stats
  37693. *
  37694. * @private
  37695. */
  37696. ;
  37697. _proto.resetStats_ = function resetStats_() {
  37698. this.mediaBytesTransferred = 0;
  37699. this.mediaRequests = 0;
  37700. this.mediaRequestsAborted = 0;
  37701. this.mediaRequestsTimedout = 0;
  37702. this.mediaRequestsErrored = 0;
  37703. this.mediaTransferDuration = 0;
  37704. this.mediaSecondsLoaded = 0;
  37705. this.mediaAppends = 0;
  37706. }
  37707. /**
  37708. * dispose of the SegmentLoader and reset to the default state
  37709. */
  37710. ;
  37711. _proto.dispose = function dispose() {
  37712. this.trigger('dispose');
  37713. this.state = 'DISPOSED';
  37714. this.pause();
  37715. this.abort_();
  37716. if (this.transmuxer_) {
  37717. this.transmuxer_.terminate();
  37718. }
  37719. this.resetStats_();
  37720. if (this.checkBufferTimeout_) {
  37721. window$1.clearTimeout(this.checkBufferTimeout_);
  37722. }
  37723. if (this.syncController_ && this.triggerSyncInfoUpdate_) {
  37724. this.syncController_.off('syncinfoupdate', this.triggerSyncInfoUpdate_);
  37725. }
  37726. this.off();
  37727. };
  37728. _proto.setAudio = function setAudio(enable) {
  37729. this.audioDisabled_ = !enable;
  37730. if (enable) {
  37731. this.appendInitSegment_.audio = true;
  37732. } else {
  37733. // remove current track audio if it gets disabled
  37734. this.sourceUpdater_.removeAudio(0, this.duration_());
  37735. }
  37736. }
  37737. /**
  37738. * abort anything that is currently doing on with the SegmentLoader
  37739. * and reset to a default state
  37740. */
  37741. ;
  37742. _proto.abort = function abort() {
  37743. if (this.state !== 'WAITING') {
  37744. if (this.pendingSegment_) {
  37745. this.pendingSegment_ = null;
  37746. }
  37747. return;
  37748. }
  37749. this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
  37750. // since we are no longer "waiting" on any requests. XHR callback is not always run
  37751. // when the request is aborted. This will prevent the loader from being stuck in the
  37752. // WAITING state indefinitely.
  37753. this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
  37754. // next segment
  37755. if (!this.paused()) {
  37756. this.monitorBuffer_();
  37757. }
  37758. }
  37759. /**
  37760. * abort all pending xhr requests and null any pending segements
  37761. *
  37762. * @private
  37763. */
  37764. ;
  37765. _proto.abort_ = function abort_() {
  37766. if (this.pendingSegment_ && this.pendingSegment_.abortRequests) {
  37767. this.pendingSegment_.abortRequests();
  37768. } // clear out the segment being processed
  37769. this.pendingSegment_ = null;
  37770. this.callQueue_ = [];
  37771. this.loadQueue_ = [];
  37772. this.metadataQueue_.id3 = [];
  37773. this.metadataQueue_.caption = [];
  37774. this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_);
  37775. this.waitingOnRemove_ = false;
  37776. window$1.clearTimeout(this.quotaExceededErrorRetryTimeout_);
  37777. this.quotaExceededErrorRetryTimeout_ = null;
  37778. };
  37779. _proto.checkForAbort_ = function checkForAbort_(requestId) {
  37780. // If the state is APPENDING, then aborts will not modify the state, meaning the first
  37781. // callback that happens should reset the state to READY so that loading can continue.
  37782. if (this.state === 'APPENDING' && !this.pendingSegment_) {
  37783. this.state = 'READY';
  37784. return true;
  37785. }
  37786. if (!this.pendingSegment_ || this.pendingSegment_.requestId !== requestId) {
  37787. return true;
  37788. }
  37789. return false;
  37790. }
  37791. /**
  37792. * set an error on the segment loader and null out any pending segements
  37793. *
  37794. * @param {Error} error the error to set on the SegmentLoader
  37795. * @return {Error} the error that was set or that is currently set
  37796. */
  37797. ;
  37798. _proto.error = function error(_error) {
  37799. if (typeof _error !== 'undefined') {
  37800. this.logger_('error occurred:', _error);
  37801. this.error_ = _error;
  37802. }
  37803. this.pendingSegment_ = null;
  37804. return this.error_;
  37805. };
  37806. _proto.endOfStream = function endOfStream() {
  37807. this.ended_ = true;
  37808. if (this.transmuxer_) {
  37809. // need to clear out any cached data to prepare for the new segment
  37810. segmentTransmuxer.reset(this.transmuxer_);
  37811. }
  37812. this.gopBuffer_.length = 0;
  37813. this.pause();
  37814. this.trigger('ended');
  37815. }
  37816. /**
  37817. * Indicates which time ranges are buffered
  37818. *
  37819. * @return {TimeRange}
  37820. * TimeRange object representing the current buffered ranges
  37821. */
  37822. ;
  37823. _proto.buffered_ = function buffered_() {
  37824. var trackInfo = this.getMediaInfo_();
  37825. if (!this.sourceUpdater_ || !trackInfo) {
  37826. return videojs.createTimeRanges();
  37827. }
  37828. if (this.loaderType_ === 'main') {
  37829. var hasAudio = trackInfo.hasAudio,
  37830. hasVideo = trackInfo.hasVideo,
  37831. isMuxed = trackInfo.isMuxed;
  37832. if (hasVideo && hasAudio && !this.audioDisabled_ && !isMuxed) {
  37833. return this.sourceUpdater_.buffered();
  37834. }
  37835. if (hasVideo) {
  37836. return this.sourceUpdater_.videoBuffered();
  37837. }
  37838. } // One case that can be ignored for now is audio only with alt audio,
  37839. // as we don't yet have proper support for that.
  37840. return this.sourceUpdater_.audioBuffered();
  37841. }
  37842. /**
  37843. * Gets and sets init segment for the provided map
  37844. *
  37845. * @param {Object} map
  37846. * The map object representing the init segment to get or set
  37847. * @param {boolean=} set
  37848. * If true, the init segment for the provided map should be saved
  37849. * @return {Object}
  37850. * map object for desired init segment
  37851. */
  37852. ;
  37853. _proto.initSegmentForMap = function initSegmentForMap(map, set) {
  37854. if (set === void 0) {
  37855. set = false;
  37856. }
  37857. if (!map) {
  37858. return null;
  37859. }
  37860. var id = initSegmentId(map);
  37861. var storedMap = this.initSegments_[id];
  37862. if (set && !storedMap && map.bytes) {
  37863. this.initSegments_[id] = storedMap = {
  37864. resolvedUri: map.resolvedUri,
  37865. byterange: map.byterange,
  37866. bytes: map.bytes,
  37867. tracks: map.tracks,
  37868. timescales: map.timescales
  37869. };
  37870. }
  37871. return storedMap || map;
  37872. }
  37873. /**
  37874. * Gets and sets key for the provided key
  37875. *
  37876. * @param {Object} key
  37877. * The key object representing the key to get or set
  37878. * @param {boolean=} set
  37879. * If true, the key for the provided key should be saved
  37880. * @return {Object}
  37881. * Key object for desired key
  37882. */
  37883. ;
  37884. _proto.segmentKey = function segmentKey(key, set) {
  37885. if (set === void 0) {
  37886. set = false;
  37887. }
  37888. if (!key) {
  37889. return null;
  37890. }
  37891. var id = segmentKeyId(key);
  37892. var storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
  37893. // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
  37894. if (this.cacheEncryptionKeys_ && set && !storedKey && key.bytes) {
  37895. this.keyCache_[id] = storedKey = {
  37896. resolvedUri: key.resolvedUri,
  37897. bytes: key.bytes
  37898. };
  37899. }
  37900. var result = {
  37901. resolvedUri: (storedKey || key).resolvedUri
  37902. };
  37903. if (storedKey) {
  37904. result.bytes = storedKey.bytes;
  37905. }
  37906. return result;
  37907. }
  37908. /**
  37909. * Returns true if all configuration required for loading is present, otherwise false.
  37910. *
  37911. * @return {boolean} True if the all configuration is ready for loading
  37912. * @private
  37913. */
  37914. ;
  37915. _proto.couldBeginLoading_ = function couldBeginLoading_() {
  37916. return this.playlist_ && !this.paused();
  37917. }
  37918. /**
  37919. * load a playlist and start to fill the buffer
  37920. */
  37921. ;
  37922. _proto.load = function load() {
  37923. // un-pause
  37924. this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
  37925. // specified
  37926. if (!this.playlist_) {
  37927. return;
  37928. } // if all the configuration is ready, initialize and begin loading
  37929. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  37930. return this.init_();
  37931. } // if we're in the middle of processing a segment already, don't
  37932. // kick off an additional segment request
  37933. if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
  37934. return;
  37935. }
  37936. this.state = 'READY';
  37937. }
  37938. /**
  37939. * Once all the starting parameters have been specified, begin
  37940. * operation. This method should only be invoked from the INIT
  37941. * state.
  37942. *
  37943. * @private
  37944. */
  37945. ;
  37946. _proto.init_ = function init_() {
  37947. this.state = 'READY'; // if this is the audio segment loader, and it hasn't been inited before, then any old
  37948. // audio data from the muxed content should be removed
  37949. this.resetEverything();
  37950. return this.monitorBuffer_();
  37951. }
  37952. /**
  37953. * set a playlist on the segment loader
  37954. *
  37955. * @param {PlaylistLoader} media the playlist to set on the segment loader
  37956. */
  37957. ;
  37958. _proto.playlist = function playlist(newPlaylist, options) {
  37959. if (options === void 0) {
  37960. options = {};
  37961. }
  37962. if (!newPlaylist) {
  37963. return;
  37964. }
  37965. var oldPlaylist = this.playlist_;
  37966. var segmentInfo = this.pendingSegment_;
  37967. this.playlist_ = newPlaylist;
  37968. this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
  37969. // is always our zero-time so force a sync update each time the playlist
  37970. // is refreshed from the server
  37971. //
  37972. // Use the INIT state to determine if playback has started, as the playlist sync info
  37973. // should be fixed once requests begin (as sync points are generated based on sync
  37974. // info), but not before then.
  37975. if (this.state === 'INIT') {
  37976. newPlaylist.syncInfo = {
  37977. mediaSequence: newPlaylist.mediaSequence,
  37978. time: 0
  37979. }; // Setting the date time mapping means mapping the program date time (if available)
  37980. // to time 0 on the player's timeline. The playlist's syncInfo serves a similar
  37981. // purpose, mapping the initial mediaSequence to time zero. Since the syncInfo can
  37982. // be updated as the playlist is refreshed before the loader starts loading, the
  37983. // program date time mapping needs to be updated as well.
  37984. //
  37985. // This mapping is only done for the main loader because a program date time should
  37986. // map equivalently between playlists.
  37987. if (this.loaderType_ === 'main') {
  37988. this.syncController_.setDateTimeMappingForStart(newPlaylist);
  37989. }
  37990. }
  37991. var oldId = null;
  37992. if (oldPlaylist) {
  37993. if (oldPlaylist.id) {
  37994. oldId = oldPlaylist.id;
  37995. } else if (oldPlaylist.uri) {
  37996. oldId = oldPlaylist.uri;
  37997. }
  37998. }
  37999. this.logger_("playlist update [" + oldId + " => " + (newPlaylist.id || newPlaylist.uri) + "]"); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
  38000. // in LIVE, we always want to update with new playlists (including refreshes)
  38001. this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
  38002. // buffering now
  38003. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  38004. return this.init_();
  38005. }
  38006. if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
  38007. if (this.mediaIndex !== null) {
  38008. // we must reset/resync the segment loader when we switch renditions and
  38009. // the segment loader is already synced to the previous rendition
  38010. // on playlist changes we want it to be possible to fetch
  38011. // at the buffer for vod but not for live. So we use resetLoader
  38012. // for live and resyncLoader for vod. We want this because
  38013. // if a playlist uses independent and non-independent segments/parts the
  38014. // buffer may not accurately reflect the next segment that we should try
  38015. // downloading.
  38016. if (!newPlaylist.endList) {
  38017. this.resetLoader();
  38018. } else {
  38019. this.resyncLoader();
  38020. }
  38021. }
  38022. this.currentMediaInfo_ = void 0;
  38023. this.trigger('playlistupdate'); // the rest of this function depends on `oldPlaylist` being defined
  38024. return;
  38025. } // we reloaded the same playlist so we are in a live scenario
  38026. // and we will likely need to adjust the mediaIndex
  38027. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
  38028. this.logger_("live window shift [" + mediaSequenceDiff + "]"); // update the mediaIndex on the SegmentLoader
  38029. // this is important because we can abort a request and this value must be
  38030. // equal to the last appended mediaIndex
  38031. if (this.mediaIndex !== null) {
  38032. this.mediaIndex -= mediaSequenceDiff; // this can happen if we are going to load the first segment, but get a playlist
  38033. // update during that. mediaIndex would go from 0 to -1 if mediaSequence in the
  38034. // new playlist was incremented by 1.
  38035. if (this.mediaIndex < 0) {
  38036. this.mediaIndex = null;
  38037. this.partIndex = null;
  38038. } else {
  38039. var segment = this.playlist_.segments[this.mediaIndex]; // partIndex should remain the same for the same segment
  38040. // unless parts fell off of the playlist for this segment.
  38041. // In that case we need to reset partIndex and resync
  38042. if (this.partIndex && (!segment.parts || !segment.parts.length || !segment.parts[this.partIndex])) {
  38043. var mediaIndex = this.mediaIndex;
  38044. this.logger_("currently processing part (index " + this.partIndex + ") no longer exists.");
  38045. this.resetLoader(); // We want to throw away the partIndex and the data associated with it,
  38046. // as the part was dropped from our current playlists segment.
  38047. // The mediaIndex will still be valid so keep that around.
  38048. this.mediaIndex = mediaIndex;
  38049. }
  38050. }
  38051. } // update the mediaIndex on the SegmentInfo object
  38052. // this is important because we will update this.mediaIndex with this value
  38053. // in `handleAppendsDone_` after the segment has been successfully appended
  38054. if (segmentInfo) {
  38055. segmentInfo.mediaIndex -= mediaSequenceDiff;
  38056. if (segmentInfo.mediaIndex < 0) {
  38057. segmentInfo.mediaIndex = null;
  38058. segmentInfo.partIndex = null;
  38059. } else {
  38060. // we need to update the referenced segment so that timing information is
  38061. // saved for the new playlist's segment, however, if the segment fell off the
  38062. // playlist, we can leave the old reference and just lose the timing info
  38063. if (segmentInfo.mediaIndex >= 0) {
  38064. segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
  38065. }
  38066. if (segmentInfo.partIndex >= 0 && segmentInfo.segment.parts) {
  38067. segmentInfo.part = segmentInfo.segment.parts[segmentInfo.partIndex];
  38068. }
  38069. }
  38070. }
  38071. this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
  38072. }
  38073. /**
  38074. * Prevent the loader from fetching additional segments. If there
  38075. * is a segment request outstanding, it will finish processing
  38076. * before the loader halts. A segment loader can be unpaused by
  38077. * calling load().
  38078. */
  38079. ;
  38080. _proto.pause = function pause() {
  38081. if (this.checkBufferTimeout_) {
  38082. window$1.clearTimeout(this.checkBufferTimeout_);
  38083. this.checkBufferTimeout_ = null;
  38084. }
  38085. }
  38086. /**
  38087. * Returns whether the segment loader is fetching additional
  38088. * segments when given the opportunity. This property can be
  38089. * modified through calls to pause() and load().
  38090. */
  38091. ;
  38092. _proto.paused = function paused() {
  38093. return this.checkBufferTimeout_ === null;
  38094. }
  38095. /**
  38096. * Delete all the buffered data and reset the SegmentLoader
  38097. *
  38098. * @param {Function} [done] an optional callback to be executed when the remove
  38099. * operation is complete
  38100. */
  38101. ;
  38102. _proto.resetEverything = function resetEverything(done) {
  38103. this.ended_ = false;
  38104. this.activeInitSegmentId_ = null;
  38105. this.appendInitSegment_ = {
  38106. audio: true,
  38107. video: true
  38108. };
  38109. this.resetLoader(); // remove from 0, the earliest point, to Infinity, to signify removal of everything.
  38110. // VTT Segment Loader doesn't need to do anything but in the regular SegmentLoader,
  38111. // we then clamp the value to duration if necessary.
  38112. this.remove(0, Infinity, done); // clears fmp4 captions
  38113. if (this.transmuxer_) {
  38114. this.transmuxer_.postMessage({
  38115. action: 'clearAllMp4Captions'
  38116. }); // reset the cache in the transmuxer
  38117. this.transmuxer_.postMessage({
  38118. action: 'reset'
  38119. });
  38120. }
  38121. }
  38122. /**
  38123. * Force the SegmentLoader to resync and start loading around the currentTime instead
  38124. * of starting at the end of the buffer
  38125. *
  38126. * Useful for fast quality changes
  38127. */
  38128. ;
  38129. _proto.resetLoader = function resetLoader() {
  38130. this.fetchAtBuffer_ = false;
  38131. this.resyncLoader();
  38132. }
  38133. /**
  38134. * Force the SegmentLoader to restart synchronization and make a conservative guess
  38135. * before returning to the simple walk-forward method
  38136. */
  38137. ;
  38138. _proto.resyncLoader = function resyncLoader() {
  38139. if (this.transmuxer_) {
  38140. // need to clear out any cached data to prepare for the new segment
  38141. segmentTransmuxer.reset(this.transmuxer_);
  38142. }
  38143. this.mediaIndex = null;
  38144. this.partIndex = null;
  38145. this.syncPoint_ = null;
  38146. this.isPendingTimestampOffset_ = false;
  38147. this.callQueue_ = [];
  38148. this.loadQueue_ = [];
  38149. this.metadataQueue_.id3 = [];
  38150. this.metadataQueue_.caption = [];
  38151. this.abort();
  38152. if (this.transmuxer_) {
  38153. this.transmuxer_.postMessage({
  38154. action: 'clearParsedMp4Captions'
  38155. });
  38156. }
  38157. }
  38158. /**
  38159. * Remove any data in the source buffer between start and end times
  38160. *
  38161. * @param {number} start - the start time of the region to remove from the buffer
  38162. * @param {number} end - the end time of the region to remove from the buffer
  38163. * @param {Function} [done] - an optional callback to be executed when the remove
  38164. * @param {boolean} force - force all remove operations to happen
  38165. * operation is complete
  38166. */
  38167. ;
  38168. _proto.remove = function remove(start, end, done, force) {
  38169. if (done === void 0) {
  38170. done = function done() {};
  38171. }
  38172. if (force === void 0) {
  38173. force = false;
  38174. } // clamp end to duration if we need to remove everything.
  38175. // This is due to a browser bug that causes issues if we remove to Infinity.
  38176. // videojs/videojs-contrib-hls#1225
  38177. if (end === Infinity) {
  38178. end = this.duration_();
  38179. } // skip removes that would throw an error
  38180. // commonly happens during a rendition switch at the start of a video
  38181. // from start 0 to end 0
  38182. if (end <= start) {
  38183. this.logger_('skipping remove because end ${end} is <= start ${start}');
  38184. return;
  38185. }
  38186. if (!this.sourceUpdater_ || !this.getMediaInfo_()) {
  38187. this.logger_('skipping remove because no source updater or starting media info'); // nothing to remove if we haven't processed any media
  38188. return;
  38189. } // set it to one to complete this function's removes
  38190. var removesRemaining = 1;
  38191. var removeFinished = function removeFinished() {
  38192. removesRemaining--;
  38193. if (removesRemaining === 0) {
  38194. done();
  38195. }
  38196. };
  38197. if (force || !this.audioDisabled_) {
  38198. removesRemaining++;
  38199. this.sourceUpdater_.removeAudio(start, end, removeFinished);
  38200. } // While it would be better to only remove video if the main loader has video, this
  38201. // should be safe with audio only as removeVideo will call back even if there's no
  38202. // video buffer.
  38203. //
  38204. // In theory we can check to see if there's video before calling the remove, but in
  38205. // the event that we're switching between renditions and from video to audio only
  38206. // (when we add support for that), we may need to clear the video contents despite
  38207. // what the new media will contain.
  38208. if (force || this.loaderType_ === 'main') {
  38209. this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
  38210. removesRemaining++;
  38211. this.sourceUpdater_.removeVideo(start, end, removeFinished);
  38212. } // remove any captions and ID3 tags
  38213. for (var track in this.inbandTextTracks_) {
  38214. removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
  38215. }
  38216. removeCuesFromTrack(start, end, this.segmentMetadataTrack_); // finished this function's removes
  38217. removeFinished();
  38218. }
  38219. /**
  38220. * (re-)schedule monitorBufferTick_ to run as soon as possible
  38221. *
  38222. * @private
  38223. */
  38224. ;
  38225. _proto.monitorBuffer_ = function monitorBuffer_() {
  38226. if (this.checkBufferTimeout_) {
  38227. window$1.clearTimeout(this.checkBufferTimeout_);
  38228. }
  38229. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1);
  38230. }
  38231. /**
  38232. * As long as the SegmentLoader is in the READY state, periodically
  38233. * invoke fillBuffer_().
  38234. *
  38235. * @private
  38236. */
  38237. ;
  38238. _proto.monitorBufferTick_ = function monitorBufferTick_() {
  38239. if (this.state === 'READY') {
  38240. this.fillBuffer_();
  38241. }
  38242. if (this.checkBufferTimeout_) {
  38243. window$1.clearTimeout(this.checkBufferTimeout_);
  38244. }
  38245. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
  38246. }
  38247. /**
  38248. * fill the buffer with segements unless the sourceBuffers are
  38249. * currently updating
  38250. *
  38251. * Note: this function should only ever be called by monitorBuffer_
  38252. * and never directly
  38253. *
  38254. * @private
  38255. */
  38256. ;
  38257. _proto.fillBuffer_ = function fillBuffer_() {
  38258. // TODO since the source buffer maintains a queue, and we shouldn't call this function
  38259. // except when we're ready for the next segment, this check can most likely be removed
  38260. if (this.sourceUpdater_.updating()) {
  38261. return;
  38262. } // see if we need to begin loading immediately
  38263. var segmentInfo = this.chooseNextRequest_();
  38264. if (!segmentInfo) {
  38265. return;
  38266. }
  38267. if (typeof segmentInfo.timestampOffset === 'number') {
  38268. this.isPendingTimestampOffset_ = false;
  38269. this.timelineChangeController_.pendingTimelineChange({
  38270. type: this.loaderType_,
  38271. from: this.currentTimeline_,
  38272. to: segmentInfo.timeline
  38273. });
  38274. }
  38275. this.loadSegment_(segmentInfo);
  38276. }
  38277. /**
  38278. * Determines if we should call endOfStream on the media source based
  38279. * on the state of the buffer or if appened segment was the final
  38280. * segment in the playlist.
  38281. *
  38282. * @param {number} [mediaIndex] the media index of segment we last appended
  38283. * @param {Object} [playlist] a media playlist object
  38284. * @return {boolean} do we need to call endOfStream on the MediaSource
  38285. */
  38286. ;
  38287. _proto.isEndOfStream_ = function isEndOfStream_(mediaIndex, playlist, partIndex) {
  38288. if (mediaIndex === void 0) {
  38289. mediaIndex = this.mediaIndex;
  38290. }
  38291. if (playlist === void 0) {
  38292. playlist = this.playlist_;
  38293. }
  38294. if (partIndex === void 0) {
  38295. partIndex = this.partIndex;
  38296. }
  38297. if (!playlist || !this.mediaSource_) {
  38298. return false;
  38299. }
  38300. var segment = typeof mediaIndex === 'number' && playlist.segments[mediaIndex]; // mediaIndex is zero based but length is 1 based
  38301. var appendedLastSegment = mediaIndex + 1 === playlist.segments.length; // true if there are no parts, or this is the last part.
  38302. var appendedLastPart = !segment || !segment.parts || partIndex + 1 === segment.parts.length; // if we've buffered to the end of the video, we need to call endOfStream
  38303. // so that MediaSources can trigger the `ended` event when it runs out of
  38304. // buffered data instead of waiting for me
  38305. return playlist.endList && this.mediaSource_.readyState === 'open' && appendedLastSegment && appendedLastPart;
  38306. }
  38307. /**
  38308. * Determines what request should be made given current segment loader state.
  38309. *
  38310. * @return {Object} a request object that describes the segment/part to load
  38311. */
  38312. ;
  38313. _proto.chooseNextRequest_ = function chooseNextRequest_() {
  38314. var buffered = this.buffered_();
  38315. var bufferedEnd = lastBufferedEnd(buffered) || 0;
  38316. var bufferedTime = timeAheadOf(buffered, this.currentTime_());
  38317. var preloaded = !this.hasPlayed_() && bufferedTime >= 1;
  38318. var haveEnoughBuffer = bufferedTime >= this.goalBufferLength_();
  38319. var segments = this.playlist_.segments; // return no segment if:
  38320. // 1. we don't have segments
  38321. // 2. The video has not yet played and we already downloaded a segment
  38322. // 3. we already have enough buffered time
  38323. if (!segments.length || preloaded || haveEnoughBuffer) {
  38324. return null;
  38325. }
  38326. this.syncPoint_ = this.syncPoint_ || this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  38327. var next = {
  38328. partIndex: null,
  38329. mediaIndex: null,
  38330. startOfSegment: null,
  38331. playlist: this.playlist_,
  38332. isSyncRequest: Boolean(!this.syncPoint_)
  38333. };
  38334. if (next.isSyncRequest) {
  38335. next.mediaIndex = getSyncSegmentCandidate(this.currentTimeline_, segments, bufferedEnd);
  38336. } else if (this.mediaIndex !== null) {
  38337. var segment = segments[this.mediaIndex];
  38338. var partIndex = typeof this.partIndex === 'number' ? this.partIndex : -1;
  38339. next.startOfSegment = segment.end ? segment.end : bufferedEnd;
  38340. if (segment.parts && segment.parts[partIndex + 1]) {
  38341. next.mediaIndex = this.mediaIndex;
  38342. next.partIndex = partIndex + 1;
  38343. } else {
  38344. next.mediaIndex = this.mediaIndex + 1;
  38345. }
  38346. } else {
  38347. // Find the segment containing the end of the buffer or current time.
  38348. var _Playlist$getMediaInf = Playlist.getMediaInfoForTime({
  38349. experimentalExactManifestTimings: this.experimentalExactManifestTimings,
  38350. playlist: this.playlist_,
  38351. currentTime: this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_(),
  38352. startingPartIndex: this.syncPoint_.partIndex,
  38353. startingSegmentIndex: this.syncPoint_.segmentIndex,
  38354. startTime: this.syncPoint_.time
  38355. }),
  38356. segmentIndex = _Playlist$getMediaInf.segmentIndex,
  38357. startTime = _Playlist$getMediaInf.startTime,
  38358. _partIndex = _Playlist$getMediaInf.partIndex;
  38359. next.getMediaInfoForTime = this.fetchAtBuffer_ ? "bufferedEnd " + bufferedEnd : "currentTime " + this.currentTime_();
  38360. next.mediaIndex = segmentIndex;
  38361. next.startOfSegment = startTime;
  38362. next.partIndex = _partIndex;
  38363. }
  38364. var nextSegment = segments[next.mediaIndex];
  38365. var nextPart = nextSegment && typeof next.partIndex === 'number' && nextSegment.parts && nextSegment.parts[next.partIndex]; // if the next segment index is invalid or
  38366. // the next partIndex is invalid do not choose a next segment.
  38367. if (!nextSegment || typeof next.partIndex === 'number' && !nextPart) {
  38368. return null;
  38369. } // if the next segment has parts, and we don't have a partIndex.
  38370. // Set partIndex to 0
  38371. if (typeof next.partIndex !== 'number' && nextSegment.parts) {
  38372. next.partIndex = 0;
  38373. nextPart = nextSegment.parts[0];
  38374. } // if we have no buffered data then we need to make sure
  38375. // that the next part we append is "independent" if possible.
  38376. // So we check if the previous part is independent, and request
  38377. // it if it is.
  38378. if (!bufferedTime && nextPart && !nextPart.independent) {
  38379. if (next.partIndex === 0) {
  38380. var lastSegment = segments[next.mediaIndex - 1];
  38381. var lastSegmentLastPart = lastSegment.parts && lastSegment.parts.length && lastSegment.parts[lastSegment.parts.length - 1];
  38382. if (lastSegmentLastPart && lastSegmentLastPart.independent) {
  38383. next.mediaIndex -= 1;
  38384. next.partIndex = lastSegment.parts.length - 1;
  38385. next.independent = 'previous segment';
  38386. }
  38387. } else if (nextSegment.parts[next.partIndex - 1].independent) {
  38388. next.partIndex -= 1;
  38389. next.independent = 'previous part';
  38390. }
  38391. }
  38392. var ended = this.mediaSource_ && this.mediaSource_.readyState === 'ended'; // do not choose a next segment if all of the following:
  38393. // 1. this is the last segment in the playlist
  38394. // 2. end of stream has been called on the media source already
  38395. // 3. the player is not seeking
  38396. if (next.mediaIndex >= segments.length - 1 && ended && !this.seeking_()) {
  38397. return null;
  38398. }
  38399. return this.generateSegmentInfo_(next);
  38400. };
  38401. _proto.generateSegmentInfo_ = function generateSegmentInfo_(options) {
  38402. var independent = options.independent,
  38403. playlist = options.playlist,
  38404. mediaIndex = options.mediaIndex,
  38405. startOfSegment = options.startOfSegment,
  38406. isSyncRequest = options.isSyncRequest,
  38407. partIndex = options.partIndex,
  38408. forceTimestampOffset = options.forceTimestampOffset,
  38409. getMediaInfoForTime = options.getMediaInfoForTime;
  38410. var segment = playlist.segments[mediaIndex];
  38411. var part = typeof partIndex === 'number' && segment.parts[partIndex];
  38412. var segmentInfo = {
  38413. requestId: 'segment-loader-' + Math.random(),
  38414. // resolve the segment URL relative to the playlist
  38415. uri: part && part.resolvedUri || segment.resolvedUri,
  38416. // the segment's mediaIndex at the time it was requested
  38417. mediaIndex: mediaIndex,
  38418. partIndex: part ? partIndex : null,
  38419. // whether or not to update the SegmentLoader's state with this
  38420. // segment's mediaIndex
  38421. isSyncRequest: isSyncRequest,
  38422. startOfSegment: startOfSegment,
  38423. // the segment's playlist
  38424. playlist: playlist,
  38425. // unencrypted bytes of the segment
  38426. bytes: null,
  38427. // when a key is defined for this segment, the encrypted bytes
  38428. encryptedBytes: null,
  38429. // The target timestampOffset for this segment when we append it
  38430. // to the source buffer
  38431. timestampOffset: null,
  38432. // The timeline that the segment is in
  38433. timeline: segment.timeline,
  38434. // The expected duration of the segment in seconds
  38435. duration: part && part.duration || segment.duration,
  38436. // retain the segment in case the playlist updates while doing an async process
  38437. segment: segment,
  38438. part: part,
  38439. byteLength: 0,
  38440. transmuxer: this.transmuxer_,
  38441. // type of getMediaInfoForTime that was used to get this segment
  38442. getMediaInfoForTime: getMediaInfoForTime,
  38443. independent: independent
  38444. };
  38445. var overrideCheck = typeof forceTimestampOffset !== 'undefined' ? forceTimestampOffset : this.isPendingTimestampOffset_;
  38446. segmentInfo.timestampOffset = this.timestampOffsetForSegment_({
  38447. segmentTimeline: segment.timeline,
  38448. currentTimeline: this.currentTimeline_,
  38449. startOfSegment: startOfSegment,
  38450. buffered: this.buffered_(),
  38451. overrideCheck: overrideCheck
  38452. });
  38453. var audioBufferedEnd = lastBufferedEnd(this.sourceUpdater_.audioBuffered());
  38454. if (typeof audioBufferedEnd === 'number') {
  38455. // since the transmuxer is using the actual timing values, but the buffer is
  38456. // adjusted by the timestamp offset, we must adjust the value here
  38457. segmentInfo.audioAppendStart = audioBufferedEnd - this.sourceUpdater_.audioTimestampOffset();
  38458. }
  38459. if (this.sourceUpdater_.videoBuffered().length) {
  38460. segmentInfo.gopsToAlignWith = gopsSafeToAlignWith(this.gopBuffer_, // since the transmuxer is using the actual timing values, but the time is
  38461. // adjusted by the timestmap offset, we must adjust the value here
  38462. this.currentTime_() - this.sourceUpdater_.videoTimestampOffset(), this.timeMapping_);
  38463. }
  38464. return segmentInfo;
  38465. } // get the timestampoffset for a segment,
  38466. // added so that vtt segment loader can override and prevent
  38467. // adding timestamp offsets.
  38468. ;
  38469. _proto.timestampOffsetForSegment_ = function timestampOffsetForSegment_(options) {
  38470. return timestampOffsetForSegment(options);
  38471. }
  38472. /**
  38473. * Determines if the network has enough bandwidth to complete the current segment
  38474. * request in a timely manner. If not, the request will be aborted early and bandwidth
  38475. * updated to trigger a playlist switch.
  38476. *
  38477. * @param {Object} stats
  38478. * Object containing stats about the request timing and size
  38479. * @private
  38480. */
  38481. ;
  38482. _proto.earlyAbortWhenNeeded_ = function earlyAbortWhenNeeded_(stats) {
  38483. if (this.vhs_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition
  38484. // TODO: Replace using timeout with a boolean indicating whether this playlist is
  38485. // the lowestEnabledRendition.
  38486. !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes
  38487. !this.playlist_.attributes.BANDWIDTH) {
  38488. return;
  38489. } // Wait at least 1 second since the first byte of data has been received before
  38490. // using the calculated bandwidth from the progress event to allow the bitrate
  38491. // to stabilize
  38492. if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
  38493. return;
  38494. }
  38495. var currentTime = this.currentTime_();
  38496. var measuredBandwidth = stats.bandwidth;
  38497. var segmentDuration = this.pendingSegment_.duration;
  38498. var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
  38499. // if we are only left with less than 1 second when the request completes.
  38500. // A negative timeUntilRebuffering indicates we are already rebuffering
  38501. var timeUntilRebuffer$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.vhs_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
  38502. // is larger than the estimated time until the player runs out of forward buffer
  38503. if (requestTimeRemaining <= timeUntilRebuffer$1) {
  38504. return;
  38505. }
  38506. var switchCandidate = minRebufferMaxBandwidthSelector({
  38507. master: this.vhs_.playlists.master,
  38508. currentTime: currentTime,
  38509. bandwidth: measuredBandwidth,
  38510. duration: this.duration_(),
  38511. segmentDuration: segmentDuration,
  38512. timeUntilRebuffer: timeUntilRebuffer$1,
  38513. currentTimeline: this.currentTimeline_,
  38514. syncController: this.syncController_
  38515. });
  38516. if (!switchCandidate) {
  38517. return;
  38518. }
  38519. var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$1;
  38520. var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
  38521. var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
  38522. // potential round trip time of the new request so that we are not too aggressive
  38523. // with switching to a playlist that might save us a fraction of a second.
  38524. if (timeUntilRebuffer$1 <= TIME_FUDGE_FACTOR) {
  38525. minimumTimeSaving = 1;
  38526. }
  38527. if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
  38528. return;
  38529. } // set the bandwidth to that of the desired playlist being sure to scale by
  38530. // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
  38531. // don't trigger a bandwidthupdate as the bandwidth is artifial
  38532. this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
  38533. this.trigger('earlyabort');
  38534. };
  38535. _proto.handleAbort_ = function handleAbort_(segmentInfo) {
  38536. this.logger_("Aborting " + segmentInfoString(segmentInfo));
  38537. this.mediaRequestsAborted += 1;
  38538. }
  38539. /**
  38540. * XHR `progress` event handler
  38541. *
  38542. * @param {Event}
  38543. * The XHR `progress` event
  38544. * @param {Object} simpleSegment
  38545. * A simplified segment object copy
  38546. * @private
  38547. */
  38548. ;
  38549. _proto.handleProgress_ = function handleProgress_(event, simpleSegment) {
  38550. this.earlyAbortWhenNeeded_(simpleSegment.stats);
  38551. if (this.checkForAbort_(simpleSegment.requestId)) {
  38552. return;
  38553. }
  38554. this.trigger('progress');
  38555. };
  38556. _proto.handleTrackInfo_ = function handleTrackInfo_(simpleSegment, trackInfo) {
  38557. this.earlyAbortWhenNeeded_(simpleSegment.stats);
  38558. if (this.checkForAbort_(simpleSegment.requestId)) {
  38559. return;
  38560. }
  38561. if (this.checkForIllegalMediaSwitch(trackInfo)) {
  38562. return;
  38563. }
  38564. trackInfo = trackInfo || {}; // When we have track info, determine what media types this loader is dealing with.
  38565. // Guard against cases where we're not getting track info at all until we are
  38566. // certain that all streams will provide it.
  38567. if (!shallowEqual(this.currentMediaInfo_, trackInfo)) {
  38568. this.appendInitSegment_ = {
  38569. audio: true,
  38570. video: true
  38571. };
  38572. this.startingMediaInfo_ = trackInfo;
  38573. this.currentMediaInfo_ = trackInfo;
  38574. this.logger_('trackinfo update', trackInfo);
  38575. this.trigger('trackinfo');
  38576. } // trackinfo may cause an abort if the trackinfo
  38577. // causes a codec change to an unsupported codec.
  38578. if (this.checkForAbort_(simpleSegment.requestId)) {
  38579. return;
  38580. } // set trackinfo on the pending segment so that
  38581. // it can append.
  38582. this.pendingSegment_.trackInfo = trackInfo; // check if any calls were waiting on the track info
  38583. if (this.hasEnoughInfoToAppend_()) {
  38584. this.processCallQueue_();
  38585. }
  38586. };
  38587. _proto.handleTimingInfo_ = function handleTimingInfo_(simpleSegment, mediaType, timeType, time) {
  38588. this.earlyAbortWhenNeeded_(simpleSegment.stats);
  38589. if (this.checkForAbort_(simpleSegment.requestId)) {
  38590. return;
  38591. }
  38592. var segmentInfo = this.pendingSegment_;
  38593. var timingInfoProperty = timingInfoPropertyForMedia(mediaType);
  38594. segmentInfo[timingInfoProperty] = segmentInfo[timingInfoProperty] || {};
  38595. segmentInfo[timingInfoProperty][timeType] = time;
  38596. this.logger_("timinginfo: " + mediaType + " - " + timeType + " - " + time); // check if any calls were waiting on the timing info
  38597. if (this.hasEnoughInfoToAppend_()) {
  38598. this.processCallQueue_();
  38599. }
  38600. };
  38601. _proto.handleCaptions_ = function handleCaptions_(simpleSegment, captionData) {
  38602. var _this2 = this;
  38603. this.earlyAbortWhenNeeded_(simpleSegment.stats);
  38604. if (this.checkForAbort_(simpleSegment.requestId)) {
  38605. return;
  38606. } // This could only happen with fmp4 segments, but
  38607. // should still not happen in general
  38608. if (captionData.length === 0) {
  38609. this.logger_('SegmentLoader received no captions from a caption event');
  38610. return;
  38611. }
  38612. var segmentInfo = this.pendingSegment_; // Wait until we have some video data so that caption timing
  38613. // can be adjusted by the timestamp offset
  38614. if (!segmentInfo.hasAppendedData_) {
  38615. this.metadataQueue_.caption.push(this.handleCaptions_.bind(this, simpleSegment, captionData));
  38616. return;
  38617. }
  38618. var timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset();
  38619. var captionTracks = {}; // get total start/end and captions for each track/stream
  38620. captionData.forEach(function (caption) {
  38621. // caption.stream is actually a track name...
  38622. // set to the existing values in tracks or default values
  38623. captionTracks[caption.stream] = captionTracks[caption.stream] || {
  38624. // Infinity, as any other value will be less than this
  38625. startTime: Infinity,
  38626. captions: [],
  38627. // 0 as an other value will be more than this
  38628. endTime: 0
  38629. };
  38630. var captionTrack = captionTracks[caption.stream];
  38631. captionTrack.startTime = Math.min(captionTrack.startTime, caption.startTime + timestampOffset);
  38632. captionTrack.endTime = Math.max(captionTrack.endTime, caption.endTime + timestampOffset);
  38633. captionTrack.captions.push(caption);
  38634. });
  38635. Object.keys(captionTracks).forEach(function (trackName) {
  38636. var _captionTracks$trackN = captionTracks[trackName],
  38637. startTime = _captionTracks$trackN.startTime,
  38638. endTime = _captionTracks$trackN.endTime,
  38639. captions = _captionTracks$trackN.captions;
  38640. var inbandTextTracks = _this2.inbandTextTracks_;
  38641. _this2.logger_("adding cues from " + startTime + " -> " + endTime + " for " + trackName);
  38642. createCaptionsTrackIfNotExists(inbandTextTracks, _this2.vhs_.tech_, trackName); // clear out any cues that start and end at the same time period for the same track.
  38643. // We do this because a rendition change that also changes the timescale for captions
  38644. // will result in captions being re-parsed for certain segments. If we add them again
  38645. // without clearing we will have two of the same captions visible.
  38646. removeCuesFromTrack(startTime, endTime, inbandTextTracks[trackName]);
  38647. addCaptionData({
  38648. captionArray: captions,
  38649. inbandTextTracks: inbandTextTracks,
  38650. timestampOffset: timestampOffset
  38651. });
  38652. }); // Reset stored captions since we added parsed
  38653. // captions to a text track at this point
  38654. if (this.transmuxer_) {
  38655. this.transmuxer_.postMessage({
  38656. action: 'clearParsedMp4Captions'
  38657. });
  38658. }
  38659. };
  38660. _proto.handleId3_ = function handleId3_(simpleSegment, id3Frames, dispatchType) {
  38661. this.earlyAbortWhenNeeded_(simpleSegment.stats);
  38662. if (this.checkForAbort_(simpleSegment.requestId)) {
  38663. return;
  38664. }
  38665. var segmentInfo = this.pendingSegment_; // we need to have appended data in order for the timestamp offset to be set
  38666. if (!segmentInfo.hasAppendedData_) {
  38667. this.metadataQueue_.id3.push(this.handleId3_.bind(this, simpleSegment, id3Frames, dispatchType));
  38668. return;
  38669. }
  38670. var timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset(); // There's potentially an issue where we could double add metadata if there's a muxed
  38671. // audio/video source with a metadata track, and an alt audio with a metadata track.
  38672. // However, this probably won't happen, and if it does it can be handled then.
  38673. createMetadataTrackIfNotExists(this.inbandTextTracks_, dispatchType, this.vhs_.tech_);
  38674. addMetadata({
  38675. inbandTextTracks: this.inbandTextTracks_,
  38676. metadataArray: id3Frames,
  38677. timestampOffset: timestampOffset,
  38678. videoDuration: this.duration_()
  38679. });
  38680. };
  38681. _proto.processMetadataQueue_ = function processMetadataQueue_() {
  38682. this.metadataQueue_.id3.forEach(function (fn) {
  38683. return fn();
  38684. });
  38685. this.metadataQueue_.caption.forEach(function (fn) {
  38686. return fn();
  38687. });
  38688. this.metadataQueue_.id3 = [];
  38689. this.metadataQueue_.caption = [];
  38690. };
  38691. _proto.processCallQueue_ = function processCallQueue_() {
  38692. var callQueue = this.callQueue_; // Clear out the queue before the queued functions are run, since some of the
  38693. // functions may check the length of the load queue and default to pushing themselves
  38694. // back onto the queue.
  38695. this.callQueue_ = [];
  38696. callQueue.forEach(function (fun) {
  38697. return fun();
  38698. });
  38699. };
  38700. _proto.processLoadQueue_ = function processLoadQueue_() {
  38701. var loadQueue = this.loadQueue_; // Clear out the queue before the queued functions are run, since some of the
  38702. // functions may check the length of the load queue and default to pushing themselves
  38703. // back onto the queue.
  38704. this.loadQueue_ = [];
  38705. loadQueue.forEach(function (fun) {
  38706. return fun();
  38707. });
  38708. }
  38709. /**
  38710. * Determines whether the loader has enough info to load the next segment.
  38711. *
  38712. * @return {boolean}
  38713. * Whether or not the loader has enough info to load the next segment
  38714. */
  38715. ;
  38716. _proto.hasEnoughInfoToLoad_ = function hasEnoughInfoToLoad_() {
  38717. // Since primary timing goes by video, only the audio loader potentially needs to wait
  38718. // to load.
  38719. if (this.loaderType_ !== 'audio') {
  38720. return true;
  38721. }
  38722. var segmentInfo = this.pendingSegment_; // A fill buffer must have already run to establish a pending segment before there's
  38723. // enough info to load.
  38724. if (!segmentInfo) {
  38725. return false;
  38726. } // The first segment can and should be loaded immediately so that source buffers are
  38727. // created together (before appending). Source buffer creation uses the presence of
  38728. // audio and video data to determine whether to create audio/video source buffers, and
  38729. // uses processed (transmuxed or parsed) media to determine the types required.
  38730. if (!this.getCurrentMediaInfo_()) {
  38731. return true;
  38732. }
  38733. if ( // Technically, instead of waiting to load a segment on timeline changes, a segment
  38734. // can be requested and downloaded and only wait before it is transmuxed or parsed.
  38735. // But in practice, there are a few reasons why it is better to wait until a loader
  38736. // is ready to append that segment before requesting and downloading:
  38737. //
  38738. // 1. Because audio and main loaders cross discontinuities together, if this loader
  38739. // is waiting for the other to catch up, then instead of requesting another
  38740. // segment and using up more bandwidth, by not yet loading, more bandwidth is
  38741. // allotted to the loader currently behind.
  38742. // 2. media-segment-request doesn't have to have logic to consider whether a segment
  38743. // is ready to be processed or not, isolating the queueing behavior to the loader.
  38744. // 3. The audio loader bases some of its segment properties on timing information
  38745. // provided by the main loader, meaning that, if the logic for waiting on
  38746. // processing was in media-segment-request, then it would also need to know how
  38747. // to re-generate the segment information after the main loader caught up.
  38748. shouldWaitForTimelineChange({
  38749. timelineChangeController: this.timelineChangeController_,
  38750. currentTimeline: this.currentTimeline_,
  38751. segmentTimeline: segmentInfo.timeline,
  38752. loaderType: this.loaderType_,
  38753. audioDisabled: this.audioDisabled_
  38754. })) {
  38755. return false;
  38756. }
  38757. return true;
  38758. };
  38759. _proto.getCurrentMediaInfo_ = function getCurrentMediaInfo_(segmentInfo) {
  38760. if (segmentInfo === void 0) {
  38761. segmentInfo = this.pendingSegment_;
  38762. }
  38763. return segmentInfo && segmentInfo.trackInfo || this.currentMediaInfo_;
  38764. };
  38765. _proto.getMediaInfo_ = function getMediaInfo_(segmentInfo) {
  38766. if (segmentInfo === void 0) {
  38767. segmentInfo = this.pendingSegment_;
  38768. }
  38769. return this.getCurrentMediaInfo_(segmentInfo) || this.startingMediaInfo_;
  38770. };
  38771. _proto.getPendingSegmentPlaylist = function getPendingSegmentPlaylist() {
  38772. return this.pendingSegment_ ? this.pendingSegment_.playlist : null;
  38773. };
  38774. _proto.hasEnoughInfoToAppend_ = function hasEnoughInfoToAppend_() {
  38775. if (!this.sourceUpdater_.ready()) {
  38776. return false;
  38777. } // If content needs to be removed or the loader is waiting on an append reattempt,
  38778. // then no additional content should be appended until the prior append is resolved.
  38779. if (this.waitingOnRemove_ || this.quotaExceededErrorRetryTimeout_) {
  38780. return false;
  38781. }
  38782. var segmentInfo = this.pendingSegment_;
  38783. var trackInfo = this.getCurrentMediaInfo_(); // no segment to append any data for or
  38784. // we do not have information on this specific
  38785. // segment yet
  38786. if (!segmentInfo || !trackInfo) {
  38787. return false;
  38788. }
  38789. var hasAudio = trackInfo.hasAudio,
  38790. hasVideo = trackInfo.hasVideo,
  38791. isMuxed = trackInfo.isMuxed;
  38792. if (hasVideo && !segmentInfo.videoTimingInfo) {
  38793. return false;
  38794. } // muxed content only relies on video timing information for now.
  38795. if (hasAudio && !this.audioDisabled_ && !isMuxed && !segmentInfo.audioTimingInfo) {
  38796. return false;
  38797. }
  38798. if (shouldWaitForTimelineChange({
  38799. timelineChangeController: this.timelineChangeController_,
  38800. currentTimeline: this.currentTimeline_,
  38801. segmentTimeline: segmentInfo.timeline,
  38802. loaderType: this.loaderType_,
  38803. audioDisabled: this.audioDisabled_
  38804. })) {
  38805. return false;
  38806. }
  38807. return true;
  38808. };
  38809. _proto.handleData_ = function handleData_(simpleSegment, result) {
  38810. this.earlyAbortWhenNeeded_(simpleSegment.stats);
  38811. if (this.checkForAbort_(simpleSegment.requestId)) {
  38812. return;
  38813. } // If there's anything in the call queue, then this data came later and should be
  38814. // executed after the calls currently queued.
  38815. if (this.callQueue_.length || !this.hasEnoughInfoToAppend_()) {
  38816. this.callQueue_.push(this.handleData_.bind(this, simpleSegment, result));
  38817. return;
  38818. }
  38819. var segmentInfo = this.pendingSegment_; // update the time mapping so we can translate from display time to media time
  38820. this.setTimeMapping_(segmentInfo.timeline); // for tracking overall stats
  38821. this.updateMediaSecondsLoaded_(segmentInfo.part || segmentInfo.segment); // Note that the state isn't changed from loading to appending. This is because abort
  38822. // logic may change behavior depending on the state, and changing state too early may
  38823. // inflate our estimates of bandwidth. In the future this should be re-examined to
  38824. // note more granular states.
  38825. // don't process and append data if the mediaSource is closed
  38826. if (this.mediaSource_.readyState === 'closed') {
  38827. return;
  38828. } // if this request included an initialization segment, save that data
  38829. // to the initSegment cache
  38830. if (simpleSegment.map) {
  38831. simpleSegment.map = this.initSegmentForMap(simpleSegment.map, true); // move over init segment properties to media request
  38832. segmentInfo.segment.map = simpleSegment.map;
  38833. } // if this request included a segment key, save that data in the cache
  38834. if (simpleSegment.key) {
  38835. this.segmentKey(simpleSegment.key, true);
  38836. }
  38837. segmentInfo.isFmp4 = simpleSegment.isFmp4;
  38838. segmentInfo.timingInfo = segmentInfo.timingInfo || {};
  38839. if (segmentInfo.isFmp4) {
  38840. this.trigger('fmp4');
  38841. segmentInfo.timingInfo.start = segmentInfo[timingInfoPropertyForMedia(result.type)].start;
  38842. } else {
  38843. var trackInfo = this.getCurrentMediaInfo_();
  38844. var useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
  38845. var firstVideoFrameTimeForData;
  38846. if (useVideoTimingInfo) {
  38847. firstVideoFrameTimeForData = segmentInfo.videoTimingInfo.start;
  38848. } // Segment loader knows more about segment timing than the transmuxer (in certain
  38849. // aspects), so make any changes required for a more accurate start time.
  38850. // Don't set the end time yet, as the segment may not be finished processing.
  38851. segmentInfo.timingInfo.start = this.trueSegmentStart_({
  38852. currentStart: segmentInfo.timingInfo.start,
  38853. playlist: segmentInfo.playlist,
  38854. mediaIndex: segmentInfo.mediaIndex,
  38855. currentVideoTimestampOffset: this.sourceUpdater_.videoTimestampOffset(),
  38856. useVideoTimingInfo: useVideoTimingInfo,
  38857. firstVideoFrameTimeForData: firstVideoFrameTimeForData,
  38858. videoTimingInfo: segmentInfo.videoTimingInfo,
  38859. audioTimingInfo: segmentInfo.audioTimingInfo
  38860. });
  38861. } // Init segments for audio and video only need to be appended in certain cases. Now
  38862. // that data is about to be appended, we can check the final cases to determine
  38863. // whether we should append an init segment.
  38864. this.updateAppendInitSegmentStatus(segmentInfo, result.type); // Timestamp offset should be updated once we get new data and have its timing info,
  38865. // as we use the start of the segment to offset the best guess (playlist provided)
  38866. // timestamp offset.
  38867. this.updateSourceBufferTimestampOffset_(segmentInfo); // if this is a sync request we need to determine whether it should
  38868. // be appended or not.
  38869. if (segmentInfo.isSyncRequest) {
  38870. // first save/update our timing info for this segment.
  38871. // this is what allows us to choose an accurate segment
  38872. // and the main reason we make a sync request.
  38873. this.updateTimingInfoEnd_(segmentInfo);
  38874. this.syncController_.saveSegmentTimingInfo({
  38875. segmentInfo: segmentInfo,
  38876. shouldSaveTimelineMapping: this.loaderType_ === 'main'
  38877. });
  38878. var next = this.chooseNextRequest_(); // If the sync request isn't the segment that would be requested next
  38879. // after taking into account its timing info, do not append it.
  38880. if (next.mediaIndex !== segmentInfo.mediaIndex || next.partIndex !== segmentInfo.partIndex) {
  38881. this.logger_('sync segment was incorrect, not appending');
  38882. return;
  38883. } // otherwise append it like any other segment as our guess was correct.
  38884. this.logger_('sync segment was correct, appending');
  38885. } // Save some state so that in the future anything waiting on first append (and/or
  38886. // timestamp offset(s)) can process immediately. While the extra state isn't optimal,
  38887. // we need some notion of whether the timestamp offset or other relevant information
  38888. // has had a chance to be set.
  38889. segmentInfo.hasAppendedData_ = true; // Now that the timestamp offset should be set, we can append any waiting ID3 tags.
  38890. this.processMetadataQueue_();
  38891. this.appendData_(segmentInfo, result);
  38892. };
  38893. _proto.updateAppendInitSegmentStatus = function updateAppendInitSegmentStatus(segmentInfo, type) {
  38894. // alt audio doesn't manage timestamp offset
  38895. if (this.loaderType_ === 'main' && typeof segmentInfo.timestampOffset === 'number' && // in the case that we're handling partial data, we don't want to append an init
  38896. // segment for each chunk
  38897. !segmentInfo.changedTimestampOffset) {
  38898. // if the timestamp offset changed, the timeline may have changed, so we have to re-
  38899. // append init segments
  38900. this.appendInitSegment_ = {
  38901. audio: true,
  38902. video: true
  38903. };
  38904. }
  38905. if (this.playlistOfLastInitSegment_[type] !== segmentInfo.playlist) {
  38906. // make sure we append init segment on playlist changes, in case the media config
  38907. // changed
  38908. this.appendInitSegment_[type] = true;
  38909. }
  38910. };
  38911. _proto.getInitSegmentAndUpdateState_ = function getInitSegmentAndUpdateState_(_ref4) {
  38912. var type = _ref4.type,
  38913. initSegment = _ref4.initSegment,
  38914. map = _ref4.map,
  38915. playlist = _ref4.playlist; // "The EXT-X-MAP tag specifies how to obtain the Media Initialization Section
  38916. // (Section 3) required to parse the applicable Media Segments. It applies to every
  38917. // Media Segment that appears after it in the Playlist until the next EXT-X-MAP tag
  38918. // or until the end of the playlist."
  38919. // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5
  38920. if (map) {
  38921. var id = initSegmentId(map);
  38922. if (this.activeInitSegmentId_ === id) {
  38923. // don't need to re-append the init segment if the ID matches
  38924. return null;
  38925. } // a map-specified init segment takes priority over any transmuxed (or otherwise
  38926. // obtained) init segment
  38927. //
  38928. // this also caches the init segment for later use
  38929. initSegment = this.initSegmentForMap(map, true).bytes;
  38930. this.activeInitSegmentId_ = id;
  38931. } // We used to always prepend init segments for video, however, that shouldn't be
  38932. // necessary. Instead, we should only append on changes, similar to what we've always
  38933. // done for audio. This is more important (though may not be that important) for
  38934. // frame-by-frame appending for LHLS, simply because of the increased quantity of
  38935. // appends.
  38936. if (initSegment && this.appendInitSegment_[type]) {
  38937. // Make sure we track the playlist that we last used for the init segment, so that
  38938. // we can re-append the init segment in the event that we get data from a new
  38939. // playlist. Discontinuities and track changes are handled in other sections.
  38940. this.playlistOfLastInitSegment_[type] = playlist; // Disable future init segment appends for this type. Until a change is necessary.
  38941. this.appendInitSegment_[type] = false; // we need to clear out the fmp4 active init segment id, since
  38942. // we are appending the muxer init segment
  38943. this.activeInitSegmentId_ = null;
  38944. return initSegment;
  38945. }
  38946. return null;
  38947. };
  38948. _proto.handleQuotaExceededError_ = function handleQuotaExceededError_(_ref5, error) {
  38949. var _this3 = this;
  38950. var segmentInfo = _ref5.segmentInfo,
  38951. type = _ref5.type,
  38952. bytes = _ref5.bytes;
  38953. var audioBuffered = this.sourceUpdater_.audioBuffered();
  38954. var videoBuffered = this.sourceUpdater_.videoBuffered(); // For now we're ignoring any notion of gaps in the buffer, but they, in theory,
  38955. // should be cleared out during the buffer removals. However, log in case it helps
  38956. // debug.
  38957. if (audioBuffered.length > 1) {
  38958. this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the audio buffer: ' + timeRangesToArray(audioBuffered).join(', '));
  38959. }
  38960. if (videoBuffered.length > 1) {
  38961. this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the video buffer: ' + timeRangesToArray(videoBuffered).join(', '));
  38962. }
  38963. var audioBufferStart = audioBuffered.length ? audioBuffered.start(0) : 0;
  38964. var audioBufferEnd = audioBuffered.length ? audioBuffered.end(audioBuffered.length - 1) : 0;
  38965. var videoBufferStart = videoBuffered.length ? videoBuffered.start(0) : 0;
  38966. var videoBufferEnd = videoBuffered.length ? videoBuffered.end(videoBuffered.length - 1) : 0;
  38967. if (audioBufferEnd - audioBufferStart <= MIN_BACK_BUFFER && videoBufferEnd - videoBufferStart <= MIN_BACK_BUFFER) {
  38968. // Can't remove enough buffer to make room for new segment (or the browser doesn't
  38969. // allow for appends of segments this size). In the future, it may be possible to
  38970. // split up the segment and append in pieces, but for now, error out this playlist
  38971. // in an attempt to switch to a more manageable rendition.
  38972. this.logger_('On QUOTA_EXCEEDED_ERR, single segment too large to append to ' + 'buffer, triggering an error. ' + ("Appended byte length: " + bytes.byteLength + ", ") + ("audio buffer: " + timeRangesToArray(audioBuffered).join(', ') + ", ") + ("video buffer: " + timeRangesToArray(videoBuffered).join(', ') + ", "));
  38973. this.error({
  38974. message: 'Quota exceeded error with append of a single segment of content',
  38975. excludeUntil: Infinity
  38976. });
  38977. this.trigger('error');
  38978. return;
  38979. } // To try to resolve the quota exceeded error, clear back buffer and retry. This means
  38980. // that the segment-loader should block on future events until this one is handled, so
  38981. // that it doesn't keep moving onto further segments. Adding the call to the call
  38982. // queue will prevent further appends until waitingOnRemove_ and
  38983. // quotaExceededErrorRetryTimeout_ are cleared.
  38984. //
  38985. // Note that this will only block the current loader. In the case of demuxed content,
  38986. // the other load may keep filling as fast as possible. In practice, this should be
  38987. // OK, as it is a rare case when either audio has a high enough bitrate to fill up a
  38988. // source buffer, or video fills without enough room for audio to append (and without
  38989. // the availability of clearing out seconds of back buffer to make room for audio).
  38990. // But it might still be good to handle this case in the future as a TODO.
  38991. this.waitingOnRemove_ = true;
  38992. this.callQueue_.push(this.appendToSourceBuffer_.bind(this, {
  38993. segmentInfo: segmentInfo,
  38994. type: type,
  38995. bytes: bytes
  38996. }));
  38997. var currentTime = this.currentTime_(); // Try to remove as much audio and video as possible to make room for new content
  38998. // before retrying.
  38999. var timeToRemoveUntil = currentTime - MIN_BACK_BUFFER;
  39000. this.logger_("On QUOTA_EXCEEDED_ERR, removing audio/video from 0 to " + timeToRemoveUntil);
  39001. this.remove(0, timeToRemoveUntil, function () {
  39002. _this3.logger_("On QUOTA_EXCEEDED_ERR, retrying append in " + MIN_BACK_BUFFER + "s");
  39003. _this3.waitingOnRemove_ = false; // wait the length of time alotted in the back buffer to prevent wasted
  39004. // attempts (since we can't clear less than the minimum)
  39005. _this3.quotaExceededErrorRetryTimeout_ = window$1.setTimeout(function () {
  39006. _this3.logger_('On QUOTA_EXCEEDED_ERR, re-processing call queue');
  39007. _this3.quotaExceededErrorRetryTimeout_ = null;
  39008. _this3.processCallQueue_();
  39009. }, MIN_BACK_BUFFER * 1000);
  39010. }, true);
  39011. };
  39012. _proto.handleAppendError_ = function handleAppendError_(_ref6, error) {
  39013. var segmentInfo = _ref6.segmentInfo,
  39014. type = _ref6.type,
  39015. bytes = _ref6.bytes; // if there's no error, nothing to do
  39016. if (!error) {
  39017. return;
  39018. }
  39019. if (error.code === QUOTA_EXCEEDED_ERR) {
  39020. this.handleQuotaExceededError_({
  39021. segmentInfo: segmentInfo,
  39022. type: type,
  39023. bytes: bytes
  39024. }); // A quota exceeded error should be recoverable with a future re-append, so no need
  39025. // to trigger an append error.
  39026. return;
  39027. }
  39028. this.logger_('Received non QUOTA_EXCEEDED_ERR on append', error);
  39029. this.error(type + " append of " + bytes.length + "b failed for segment " + ("#" + segmentInfo.mediaIndex + " in playlist " + segmentInfo.playlist.id)); // If an append errors, we often can't recover.
  39030. // (see https://w3c.github.io/media-source/#sourcebuffer-append-error).
  39031. //
  39032. // Trigger a special error so that it can be handled separately from normal,
  39033. // recoverable errors.
  39034. this.trigger('appenderror');
  39035. };
  39036. _proto.appendToSourceBuffer_ = function appendToSourceBuffer_(_ref7) {
  39037. var segmentInfo = _ref7.segmentInfo,
  39038. type = _ref7.type,
  39039. initSegment = _ref7.initSegment,
  39040. data = _ref7.data,
  39041. bytes = _ref7.bytes; // If this is a re-append, bytes were already created and don't need to be recreated
  39042. if (!bytes) {
  39043. var segments = [data];
  39044. var byteLength = data.byteLength;
  39045. if (initSegment) {
  39046. // if the media initialization segment is changing, append it before the content
  39047. // segment
  39048. segments.unshift(initSegment);
  39049. byteLength += initSegment.byteLength;
  39050. } // Technically we should be OK appending the init segment separately, however, we
  39051. // haven't yet tested that, and prepending is how we have always done things.
  39052. bytes = concatSegments({
  39053. bytes: byteLength,
  39054. segments: segments
  39055. });
  39056. }
  39057. this.sourceUpdater_.appendBuffer({
  39058. segmentInfo: segmentInfo,
  39059. type: type,
  39060. bytes: bytes
  39061. }, this.handleAppendError_.bind(this, {
  39062. segmentInfo: segmentInfo,
  39063. type: type,
  39064. bytes: bytes
  39065. }));
  39066. };
  39067. _proto.handleSegmentTimingInfo_ = function handleSegmentTimingInfo_(type, requestId, segmentTimingInfo) {
  39068. if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
  39069. return;
  39070. }
  39071. var segment = this.pendingSegment_.segment;
  39072. var timingInfoProperty = type + "TimingInfo";
  39073. if (!segment[timingInfoProperty]) {
  39074. segment[timingInfoProperty] = {};
  39075. }
  39076. segment[timingInfoProperty].transmuxerPrependedSeconds = segmentTimingInfo.prependedContentDuration || 0;
  39077. segment[timingInfoProperty].transmuxedPresentationStart = segmentTimingInfo.start.presentation;
  39078. segment[timingInfoProperty].transmuxedDecodeStart = segmentTimingInfo.start.decode;
  39079. segment[timingInfoProperty].transmuxedPresentationEnd = segmentTimingInfo.end.presentation;
  39080. segment[timingInfoProperty].transmuxedDecodeEnd = segmentTimingInfo.end.decode; // mainly used as a reference for debugging
  39081. segment[timingInfoProperty].baseMediaDecodeTime = segmentTimingInfo.baseMediaDecodeTime;
  39082. };
  39083. _proto.appendData_ = function appendData_(segmentInfo, result) {
  39084. var type = result.type,
  39085. data = result.data;
  39086. if (!data || !data.byteLength) {
  39087. return;
  39088. }
  39089. if (type === 'audio' && this.audioDisabled_) {
  39090. return;
  39091. }
  39092. var initSegment = this.getInitSegmentAndUpdateState_({
  39093. type: type,
  39094. initSegment: result.initSegment,
  39095. playlist: segmentInfo.playlist,
  39096. map: segmentInfo.isFmp4 ? segmentInfo.segment.map : null
  39097. });
  39098. this.appendToSourceBuffer_({
  39099. segmentInfo: segmentInfo,
  39100. type: type,
  39101. initSegment: initSegment,
  39102. data: data
  39103. });
  39104. }
  39105. /**
  39106. * load a specific segment from a request into the buffer
  39107. *
  39108. * @private
  39109. */
  39110. ;
  39111. _proto.loadSegment_ = function loadSegment_(segmentInfo) {
  39112. var _this4 = this;
  39113. this.state = 'WAITING';
  39114. this.pendingSegment_ = segmentInfo;
  39115. this.trimBackBuffer_(segmentInfo);
  39116. if (typeof segmentInfo.timestampOffset === 'number') {
  39117. if (this.transmuxer_) {
  39118. this.transmuxer_.postMessage({
  39119. action: 'clearAllMp4Captions'
  39120. });
  39121. }
  39122. }
  39123. if (!this.hasEnoughInfoToLoad_()) {
  39124. this.loadQueue_.push(function () {
  39125. // regenerate the audioAppendStart, timestampOffset, etc as they
  39126. // may have changed since this function was added to the queue.
  39127. var options = _extends({}, segmentInfo, {
  39128. forceTimestampOffset: true
  39129. });
  39130. _extends(segmentInfo, _this4.generateSegmentInfo_(options));
  39131. _this4.isPendingTimestampOffset_ = false;
  39132. _this4.updateTransmuxerAndRequestSegment_(segmentInfo);
  39133. });
  39134. return;
  39135. }
  39136. this.updateTransmuxerAndRequestSegment_(segmentInfo);
  39137. };
  39138. _proto.updateTransmuxerAndRequestSegment_ = function updateTransmuxerAndRequestSegment_(segmentInfo) {
  39139. var _this5 = this; // We'll update the source buffer's timestamp offset once we have transmuxed data, but
  39140. // the transmuxer still needs to be updated before then.
  39141. //
  39142. // Even though keepOriginalTimestamps is set to true for the transmuxer, timestamp
  39143. // offset must be passed to the transmuxer for stream correcting adjustments.
  39144. if (this.shouldUpdateTransmuxerTimestampOffset_(segmentInfo.timestampOffset)) {
  39145. this.gopBuffer_.length = 0; // gopsToAlignWith was set before the GOP buffer was cleared
  39146. segmentInfo.gopsToAlignWith = [];
  39147. this.timeMapping_ = 0; // reset values in the transmuxer since a discontinuity should start fresh
  39148. this.transmuxer_.postMessage({
  39149. action: 'reset'
  39150. });
  39151. this.transmuxer_.postMessage({
  39152. action: 'setTimestampOffset',
  39153. timestampOffset: segmentInfo.timestampOffset
  39154. });
  39155. }
  39156. var simpleSegment = this.createSimplifiedSegmentObj_(segmentInfo);
  39157. var isEndOfStream = this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex);
  39158. var isWalkingForward = this.mediaIndex !== null;
  39159. var isDiscontinuity = segmentInfo.timeline !== this.currentTimeline_ && // currentTimeline starts at -1, so we shouldn't end the timeline switching to 0,
  39160. // the first timeline
  39161. segmentInfo.timeline > 0;
  39162. var isEndOfTimeline = isEndOfStream || isWalkingForward && isDiscontinuity;
  39163. this.logger_("Requesting " + segmentInfoString(segmentInfo)); // If there's an init segment associated with this segment, but it is not cached (identified by a lack of bytes),
  39164. // then this init segment has never been seen before and should be appended.
  39165. //
  39166. // At this point the content type (audio/video or both) is not yet known, but it should be safe to set
  39167. // both to true and leave the decision of whether to append the init segment to append time.
  39168. if (simpleSegment.map && !simpleSegment.map.bytes) {
  39169. this.logger_('going to request init segment.');
  39170. this.appendInitSegment_ = {
  39171. video: true,
  39172. audio: true
  39173. };
  39174. }
  39175. segmentInfo.abortRequests = mediaSegmentRequest({
  39176. xhr: this.vhs_.xhr,
  39177. xhrOptions: this.xhrOptions_,
  39178. decryptionWorker: this.decrypter_,
  39179. segment: simpleSegment,
  39180. abortFn: this.handleAbort_.bind(this, segmentInfo),
  39181. progressFn: this.handleProgress_.bind(this),
  39182. trackInfoFn: this.handleTrackInfo_.bind(this),
  39183. timingInfoFn: this.handleTimingInfo_.bind(this),
  39184. videoSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'video', segmentInfo.requestId),
  39185. audioSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'audio', segmentInfo.requestId),
  39186. captionsFn: this.handleCaptions_.bind(this),
  39187. isEndOfTimeline: isEndOfTimeline,
  39188. endedTimelineFn: function endedTimelineFn() {
  39189. _this5.logger_('received endedtimeline callback');
  39190. },
  39191. id3Fn: this.handleId3_.bind(this),
  39192. dataFn: this.handleData_.bind(this),
  39193. doneFn: this.segmentRequestFinished_.bind(this),
  39194. onTransmuxerLog: function onTransmuxerLog(_ref8) {
  39195. var message = _ref8.message,
  39196. level = _ref8.level,
  39197. stream = _ref8.stream;
  39198. _this5.logger_(segmentInfoString(segmentInfo) + " logged from transmuxer stream " + stream + " as a " + level + ": " + message);
  39199. }
  39200. });
  39201. }
  39202. /**
  39203. * trim the back buffer so that we don't have too much data
  39204. * in the source buffer
  39205. *
  39206. * @private
  39207. *
  39208. * @param {Object} segmentInfo - the current segment
  39209. */
  39210. ;
  39211. _proto.trimBackBuffer_ = function trimBackBuffer_(segmentInfo) {
  39212. var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
  39213. // buffer and a very conservative "garbage collector"
  39214. // We manually clear out the old buffer to ensure
  39215. // we don't trigger the QuotaExceeded error
  39216. // on the source buffer during subsequent appends
  39217. if (removeToTime > 0) {
  39218. this.remove(0, removeToTime);
  39219. }
  39220. }
  39221. /**
  39222. * created a simplified copy of the segment object with just the
  39223. * information necessary to perform the XHR and decryption
  39224. *
  39225. * @private
  39226. *
  39227. * @param {Object} segmentInfo - the current segment
  39228. * @return {Object} a simplified segment object copy
  39229. */
  39230. ;
  39231. _proto.createSimplifiedSegmentObj_ = function createSimplifiedSegmentObj_(segmentInfo) {
  39232. var segment = segmentInfo.segment;
  39233. var part = segmentInfo.part;
  39234. var simpleSegment = {
  39235. resolvedUri: part ? part.resolvedUri : segment.resolvedUri,
  39236. byterange: part ? part.byterange : segment.byterange,
  39237. requestId: segmentInfo.requestId,
  39238. transmuxer: segmentInfo.transmuxer,
  39239. audioAppendStart: segmentInfo.audioAppendStart,
  39240. gopsToAlignWith: segmentInfo.gopsToAlignWith,
  39241. part: segmentInfo.part
  39242. };
  39243. var previousSegment = segmentInfo.playlist.segments[segmentInfo.mediaIndex - 1];
  39244. if (previousSegment && previousSegment.timeline === segment.timeline) {
  39245. // The baseStartTime of a segment is used to handle rollover when probing the TS
  39246. // segment to retrieve timing information. Since the probe only looks at the media's
  39247. // times (e.g., PTS and DTS values of the segment), and doesn't consider the
  39248. // player's time (e.g., player.currentTime()), baseStartTime should reflect the
  39249. // media time as well. transmuxedDecodeEnd represents the end time of a segment, in
  39250. // seconds of media time, so should be used here. The previous segment is used since
  39251. // the end of the previous segment should represent the beginning of the current
  39252. // segment, so long as they are on the same timeline.
  39253. if (previousSegment.videoTimingInfo) {
  39254. simpleSegment.baseStartTime = previousSegment.videoTimingInfo.transmuxedDecodeEnd;
  39255. } else if (previousSegment.audioTimingInfo) {
  39256. simpleSegment.baseStartTime = previousSegment.audioTimingInfo.transmuxedDecodeEnd;
  39257. }
  39258. }
  39259. if (segment.key) {
  39260. // if the media sequence is greater than 2^32, the IV will be incorrect
  39261. // assuming 10s segments, that would be about 1300 years
  39262. var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
  39263. simpleSegment.key = this.segmentKey(segment.key);
  39264. simpleSegment.key.iv = iv;
  39265. }
  39266. if (segment.map) {
  39267. simpleSegment.map = this.initSegmentForMap(segment.map);
  39268. }
  39269. return simpleSegment;
  39270. };
  39271. _proto.saveTransferStats_ = function saveTransferStats_(stats) {
  39272. // every request counts as a media request even if it has been aborted
  39273. // or canceled due to a timeout
  39274. this.mediaRequests += 1;
  39275. if (stats) {
  39276. this.mediaBytesTransferred += stats.bytesReceived;
  39277. this.mediaTransferDuration += stats.roundTripTime;
  39278. }
  39279. };
  39280. _proto.saveBandwidthRelatedStats_ = function saveBandwidthRelatedStats_(duration, stats) {
  39281. // byteLength will be used for throughput, and should be based on bytes receieved,
  39282. // which we only know at the end of the request and should reflect total bytes
  39283. // downloaded rather than just bytes processed from components of the segment
  39284. this.pendingSegment_.byteLength = stats.bytesReceived;
  39285. if (duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
  39286. this.logger_("Ignoring segment's bandwidth because its duration of " + duration + (" is less than the min to record " + MIN_SEGMENT_DURATION_TO_SAVE_STATS));
  39287. return;
  39288. }
  39289. this.bandwidth = stats.bandwidth;
  39290. this.roundTrip = stats.roundTripTime;
  39291. };
  39292. _proto.handleTimeout_ = function handleTimeout_() {
  39293. // although the VTT segment loader bandwidth isn't really used, it's good to
  39294. // maintain functinality between segment loaders
  39295. this.mediaRequestsTimedout += 1;
  39296. this.bandwidth = 1;
  39297. this.roundTrip = NaN;
  39298. this.trigger('bandwidthupdate');
  39299. this.trigger('timeout');
  39300. }
  39301. /**
  39302. * Handle the callback from the segmentRequest function and set the
  39303. * associated SegmentLoader state and errors if necessary
  39304. *
  39305. * @private
  39306. */
  39307. ;
  39308. _proto.segmentRequestFinished_ = function segmentRequestFinished_(error, simpleSegment, result) {
  39309. // TODO handle special cases, e.g., muxed audio/video but only audio in the segment
  39310. // check the call queue directly since this function doesn't need to deal with any
  39311. // data, and can continue even if the source buffers are not set up and we didn't get
  39312. // any data from the segment
  39313. if (this.callQueue_.length) {
  39314. this.callQueue_.push(this.segmentRequestFinished_.bind(this, error, simpleSegment, result));
  39315. return;
  39316. }
  39317. this.saveTransferStats_(simpleSegment.stats); // The request was aborted and the SegmentLoader has already been reset
  39318. if (!this.pendingSegment_) {
  39319. return;
  39320. } // the request was aborted and the SegmentLoader has already started
  39321. // another request. this can happen when the timeout for an aborted
  39322. // request triggers due to a limitation in the XHR library
  39323. // do not count this as any sort of request or we risk double-counting
  39324. if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
  39325. return;
  39326. } // an error occurred from the active pendingSegment_ so reset everything
  39327. if (error) {
  39328. this.pendingSegment_ = null;
  39329. this.state = 'READY'; // aborts are not a true error condition and nothing corrective needs to be done
  39330. if (error.code === REQUEST_ERRORS.ABORTED) {
  39331. return;
  39332. }
  39333. this.pause(); // the error is really just that at least one of the requests timed-out
  39334. // set the bandwidth to a very low value and trigger an ABR switch to
  39335. // take emergency action
  39336. if (error.code === REQUEST_ERRORS.TIMEOUT) {
  39337. this.handleTimeout_();
  39338. return;
  39339. } // if control-flow has arrived here, then the error is real
  39340. // emit an error event to blacklist the current playlist
  39341. this.mediaRequestsErrored += 1;
  39342. this.error(error);
  39343. this.trigger('error');
  39344. return;
  39345. }
  39346. var segmentInfo = this.pendingSegment_; // the response was a success so set any bandwidth stats the request
  39347. // generated for ABR purposes
  39348. this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats);
  39349. segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests;
  39350. if (result.gopInfo) {
  39351. this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, result.gopInfo, this.safeAppend_);
  39352. } // Although we may have already started appending on progress, we shouldn't switch the
  39353. // state away from loading until we are officially done loading the segment data.
  39354. this.state = 'APPENDING'; // used for testing
  39355. this.trigger('appending');
  39356. this.waitForAppendsToComplete_(segmentInfo);
  39357. };
  39358. _proto.setTimeMapping_ = function setTimeMapping_(timeline) {
  39359. var timelineMapping = this.syncController_.mappingForTimeline(timeline);
  39360. if (timelineMapping !== null) {
  39361. this.timeMapping_ = timelineMapping;
  39362. }
  39363. };
  39364. _proto.updateMediaSecondsLoaded_ = function updateMediaSecondsLoaded_(segment) {
  39365. if (typeof segment.start === 'number' && typeof segment.end === 'number') {
  39366. this.mediaSecondsLoaded += segment.end - segment.start;
  39367. } else {
  39368. this.mediaSecondsLoaded += segment.duration;
  39369. }
  39370. };
  39371. _proto.shouldUpdateTransmuxerTimestampOffset_ = function shouldUpdateTransmuxerTimestampOffset_(timestampOffset) {
  39372. if (timestampOffset === null) {
  39373. return false;
  39374. } // note that we're potentially using the same timestamp offset for both video and
  39375. // audio
  39376. if (this.loaderType_ === 'main' && timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
  39377. return true;
  39378. }
  39379. if (!this.audioDisabled_ && timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
  39380. return true;
  39381. }
  39382. return false;
  39383. };
  39384. _proto.trueSegmentStart_ = function trueSegmentStart_(_ref9) {
  39385. var currentStart = _ref9.currentStart,
  39386. playlist = _ref9.playlist,
  39387. mediaIndex = _ref9.mediaIndex,
  39388. firstVideoFrameTimeForData = _ref9.firstVideoFrameTimeForData,
  39389. currentVideoTimestampOffset = _ref9.currentVideoTimestampOffset,
  39390. useVideoTimingInfo = _ref9.useVideoTimingInfo,
  39391. videoTimingInfo = _ref9.videoTimingInfo,
  39392. audioTimingInfo = _ref9.audioTimingInfo;
  39393. if (typeof currentStart !== 'undefined') {
  39394. // if start was set once, keep using it
  39395. return currentStart;
  39396. }
  39397. if (!useVideoTimingInfo) {
  39398. return audioTimingInfo.start;
  39399. }
  39400. var previousSegment = playlist.segments[mediaIndex - 1]; // The start of a segment should be the start of the first full frame contained
  39401. // within that segment. Since the transmuxer maintains a cache of incomplete data
  39402. // from and/or the last frame seen, the start time may reflect a frame that starts
  39403. // in the previous segment. Check for that case and ensure the start time is
  39404. // accurate for the segment.
  39405. if (mediaIndex === 0 || !previousSegment || typeof previousSegment.start === 'undefined' || previousSegment.end !== firstVideoFrameTimeForData + currentVideoTimestampOffset) {
  39406. return firstVideoFrameTimeForData;
  39407. }
  39408. return videoTimingInfo.start;
  39409. };
  39410. _proto.waitForAppendsToComplete_ = function waitForAppendsToComplete_(segmentInfo) {
  39411. var trackInfo = this.getCurrentMediaInfo_(segmentInfo);
  39412. if (!trackInfo) {
  39413. this.error({
  39414. message: 'No starting media returned, likely due to an unsupported media format.',
  39415. blacklistDuration: Infinity
  39416. });
  39417. this.trigger('error');
  39418. return;
  39419. } // Although transmuxing is done, appends may not yet be finished. Throw a marker
  39420. // on each queue this loader is responsible for to ensure that the appends are
  39421. // complete.
  39422. var hasAudio = trackInfo.hasAudio,
  39423. hasVideo = trackInfo.hasVideo,
  39424. isMuxed = trackInfo.isMuxed;
  39425. var waitForVideo = this.loaderType_ === 'main' && hasVideo;
  39426. var waitForAudio = !this.audioDisabled_ && hasAudio && !isMuxed;
  39427. segmentInfo.waitingOnAppends = 0; // segments with no data
  39428. if (!segmentInfo.hasAppendedData_) {
  39429. if (!segmentInfo.timingInfo && typeof segmentInfo.timestampOffset === 'number') {
  39430. // When there's no audio or video data in the segment, there's no audio or video
  39431. // timing information.
  39432. //
  39433. // If there's no audio or video timing information, then the timestamp offset
  39434. // can't be adjusted to the appropriate value for the transmuxer and source
  39435. // buffers.
  39436. //
  39437. // Therefore, the next segment should be used to set the timestamp offset.
  39438. this.isPendingTimestampOffset_ = true;
  39439. } // override settings for metadata only segments
  39440. segmentInfo.timingInfo = {
  39441. start: 0
  39442. };
  39443. segmentInfo.waitingOnAppends++;
  39444. if (!this.isPendingTimestampOffset_) {
  39445. // update the timestampoffset
  39446. this.updateSourceBufferTimestampOffset_(segmentInfo); // make sure the metadata queue is processed even though we have
  39447. // no video/audio data.
  39448. this.processMetadataQueue_();
  39449. } // append is "done" instantly with no data.
  39450. this.checkAppendsDone_(segmentInfo);
  39451. return;
  39452. } // Since source updater could call back synchronously, do the increments first.
  39453. if (waitForVideo) {
  39454. segmentInfo.waitingOnAppends++;
  39455. }
  39456. if (waitForAudio) {
  39457. segmentInfo.waitingOnAppends++;
  39458. }
  39459. if (waitForVideo) {
  39460. this.sourceUpdater_.videoQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
  39461. }
  39462. if (waitForAudio) {
  39463. this.sourceUpdater_.audioQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
  39464. }
  39465. };
  39466. _proto.checkAppendsDone_ = function checkAppendsDone_(segmentInfo) {
  39467. if (this.checkForAbort_(segmentInfo.requestId)) {
  39468. return;
  39469. }
  39470. segmentInfo.waitingOnAppends--;
  39471. if (segmentInfo.waitingOnAppends === 0) {
  39472. this.handleAppendsDone_();
  39473. }
  39474. };
  39475. _proto.checkForIllegalMediaSwitch = function checkForIllegalMediaSwitch(trackInfo) {
  39476. var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.getCurrentMediaInfo_(), trackInfo);
  39477. if (illegalMediaSwitchError) {
  39478. this.error({
  39479. message: illegalMediaSwitchError,
  39480. blacklistDuration: Infinity
  39481. });
  39482. this.trigger('error');
  39483. return true;
  39484. }
  39485. return false;
  39486. };
  39487. _proto.updateSourceBufferTimestampOffset_ = function updateSourceBufferTimestampOffset_(segmentInfo) {
  39488. if (segmentInfo.timestampOffset === null || // we don't yet have the start for whatever media type (video or audio) has
  39489. // priority, timing-wise, so we must wait
  39490. typeof segmentInfo.timingInfo.start !== 'number' || // already updated the timestamp offset for this segment
  39491. segmentInfo.changedTimestampOffset || // the alt audio loader should not be responsible for setting the timestamp offset
  39492. this.loaderType_ !== 'main') {
  39493. return;
  39494. }
  39495. var didChange = false; // Primary timing goes by video, and audio is trimmed in the transmuxer, meaning that
  39496. // the timing info here comes from video. In the event that the audio is longer than
  39497. // the video, this will trim the start of the audio.
  39498. // This also trims any offset from 0 at the beginning of the media
  39499. segmentInfo.timestampOffset -= this.getSegmentStartTimeForTimestampOffsetCalculation_({
  39500. videoTimingInfo: segmentInfo.segment.videoTimingInfo,
  39501. audioTimingInfo: segmentInfo.segment.audioTimingInfo,
  39502. timingInfo: segmentInfo.timingInfo
  39503. }); // In the event that there are part segment downloads, each will try to update the
  39504. // timestamp offset. Retaining this bit of state prevents us from updating in the
  39505. // future (within the same segment), however, there may be a better way to handle it.
  39506. segmentInfo.changedTimestampOffset = true;
  39507. if (segmentInfo.timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
  39508. this.sourceUpdater_.videoTimestampOffset(segmentInfo.timestampOffset);
  39509. didChange = true;
  39510. }
  39511. if (segmentInfo.timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
  39512. this.sourceUpdater_.audioTimestampOffset(segmentInfo.timestampOffset);
  39513. didChange = true;
  39514. }
  39515. if (didChange) {
  39516. this.trigger('timestampoffset');
  39517. }
  39518. };
  39519. _proto.getSegmentStartTimeForTimestampOffsetCalculation_ = function getSegmentStartTimeForTimestampOffsetCalculation_(_ref10) {
  39520. var videoTimingInfo = _ref10.videoTimingInfo,
  39521. audioTimingInfo = _ref10.audioTimingInfo,
  39522. timingInfo = _ref10.timingInfo;
  39523. if (!this.useDtsForTimestampOffset_) {
  39524. return timingInfo.start;
  39525. }
  39526. if (videoTimingInfo && typeof videoTimingInfo.transmuxedDecodeStart === 'number') {
  39527. return videoTimingInfo.transmuxedDecodeStart;
  39528. } // handle audio only
  39529. if (audioTimingInfo && typeof audioTimingInfo.transmuxedDecodeStart === 'number') {
  39530. return audioTimingInfo.transmuxedDecodeStart;
  39531. } // handle content not transmuxed (e.g., MP4)
  39532. return timingInfo.start;
  39533. };
  39534. _proto.updateTimingInfoEnd_ = function updateTimingInfoEnd_(segmentInfo) {
  39535. segmentInfo.timingInfo = segmentInfo.timingInfo || {};
  39536. var trackInfo = this.getMediaInfo_();
  39537. var useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
  39538. var prioritizedTimingInfo = useVideoTimingInfo && segmentInfo.videoTimingInfo ? segmentInfo.videoTimingInfo : segmentInfo.audioTimingInfo;
  39539. if (!prioritizedTimingInfo) {
  39540. return;
  39541. }
  39542. segmentInfo.timingInfo.end = typeof prioritizedTimingInfo.end === 'number' ? // End time may not exist in a case where we aren't parsing the full segment (one
  39543. // current example is the case of fmp4), so use the rough duration to calculate an
  39544. // end time.
  39545. prioritizedTimingInfo.end : prioritizedTimingInfo.start + segmentInfo.duration;
  39546. }
  39547. /**
  39548. * callback to run when appendBuffer is finished. detects if we are
  39549. * in a good state to do things with the data we got, or if we need
  39550. * to wait for more
  39551. *
  39552. * @private
  39553. */
  39554. ;
  39555. _proto.handleAppendsDone_ = function handleAppendsDone_() {
  39556. // appendsdone can cause an abort
  39557. if (this.pendingSegment_) {
  39558. this.trigger('appendsdone');
  39559. }
  39560. if (!this.pendingSegment_) {
  39561. this.state = 'READY'; // TODO should this move into this.checkForAbort to speed up requests post abort in
  39562. // all appending cases?
  39563. if (!this.paused()) {
  39564. this.monitorBuffer_();
  39565. }
  39566. return;
  39567. }
  39568. var segmentInfo = this.pendingSegment_; // Now that the end of the segment has been reached, we can set the end time. It's
  39569. // best to wait until all appends are done so we're sure that the primary media is
  39570. // finished (and we have its end time).
  39571. this.updateTimingInfoEnd_(segmentInfo);
  39572. if (this.shouldSaveSegmentTimingInfo_) {
  39573. // Timeline mappings should only be saved for the main loader. This is for multiple
  39574. // reasons:
  39575. //
  39576. // 1) Only one mapping is saved per timeline, meaning that if both the audio loader
  39577. // and the main loader try to save the timeline mapping, whichever comes later
  39578. // will overwrite the first. In theory this is OK, as the mappings should be the
  39579. // same, however, it breaks for (2)
  39580. // 2) In the event of a live stream, the initial live point will make for a somewhat
  39581. // arbitrary mapping. If audio and video streams are not perfectly in-sync, then
  39582. // the mapping will be off for one of the streams, dependent on which one was
  39583. // first saved (see (1)).
  39584. // 3) Primary timing goes by video in VHS, so the mapping should be video.
  39585. //
  39586. // Since the audio loader will wait for the main loader to load the first segment,
  39587. // the main loader will save the first timeline mapping, and ensure that there won't
  39588. // be a case where audio loads two segments without saving a mapping (thus leading
  39589. // to missing segment timing info).
  39590. this.syncController_.saveSegmentTimingInfo({
  39591. segmentInfo: segmentInfo,
  39592. shouldSaveTimelineMapping: this.loaderType_ === 'main'
  39593. });
  39594. }
  39595. var segmentDurationMessage = getTroublesomeSegmentDurationMessage(segmentInfo, this.sourceType_);
  39596. if (segmentDurationMessage) {
  39597. if (segmentDurationMessage.severity === 'warn') {
  39598. videojs.log.warn(segmentDurationMessage.message);
  39599. } else {
  39600. this.logger_(segmentDurationMessage.message);
  39601. }
  39602. }
  39603. this.recordThroughput_(segmentInfo);
  39604. this.pendingSegment_ = null;
  39605. this.state = 'READY';
  39606. if (segmentInfo.isSyncRequest) {
  39607. this.trigger('syncinfoupdate'); // if the sync request was not appended
  39608. // then it was not the correct segment.
  39609. // throw it away and use the data it gave us
  39610. // to get the correct one.
  39611. if (!segmentInfo.hasAppendedData_) {
  39612. this.logger_("Throwing away un-appended sync request " + segmentInfoString(segmentInfo));
  39613. return;
  39614. }
  39615. }
  39616. this.logger_("Appended " + segmentInfoString(segmentInfo));
  39617. this.addSegmentMetadataCue_(segmentInfo);
  39618. this.fetchAtBuffer_ = true;
  39619. if (this.currentTimeline_ !== segmentInfo.timeline) {
  39620. this.timelineChangeController_.lastTimelineChange({
  39621. type: this.loaderType_,
  39622. from: this.currentTimeline_,
  39623. to: segmentInfo.timeline
  39624. }); // If audio is not disabled, the main segment loader is responsible for updating
  39625. // the audio timeline as well. If the content is video only, this won't have any
  39626. // impact.
  39627. if (this.loaderType_ === 'main' && !this.audioDisabled_) {
  39628. this.timelineChangeController_.lastTimelineChange({
  39629. type: 'audio',
  39630. from: this.currentTimeline_,
  39631. to: segmentInfo.timeline
  39632. });
  39633. }
  39634. }
  39635. this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
  39636. // the following conditional otherwise it may consider this a bad "guess"
  39637. // and attempt to resync when the post-update seekable window and live
  39638. // point would mean that this was the perfect segment to fetch
  39639. this.trigger('syncinfoupdate');
  39640. var segment = segmentInfo.segment;
  39641. var part = segmentInfo.part;
  39642. var badSegmentGuess = segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3;
  39643. var badPartGuess = part && part.end && this.currentTime_() - part.end > segmentInfo.playlist.partTargetDuration * 3; // If we previously appended a segment/part that ends more than 3 part/targetDurations before
  39644. // the currentTime_ that means that our conservative guess was too conservative.
  39645. // In that case, reset the loader state so that we try to use any information gained
  39646. // from the previous request to create a new, more accurate, sync-point.
  39647. if (badSegmentGuess || badPartGuess) {
  39648. this.logger_("bad " + (badSegmentGuess ? 'segment' : 'part') + " " + segmentInfoString(segmentInfo));
  39649. this.resetEverything();
  39650. return;
  39651. }
  39652. var isWalkingForward = this.mediaIndex !== null; // Don't do a rendition switch unless we have enough time to get a sync segment
  39653. // and conservatively guess
  39654. if (isWalkingForward) {
  39655. this.trigger('bandwidthupdate');
  39656. }
  39657. this.trigger('progress');
  39658. this.mediaIndex = segmentInfo.mediaIndex;
  39659. this.partIndex = segmentInfo.partIndex; // any time an update finishes and the last segment is in the
  39660. // buffer, end the stream. this ensures the "ended" event will
  39661. // fire if playback reaches that point.
  39662. if (this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex)) {
  39663. this.endOfStream();
  39664. } // used for testing
  39665. this.trigger('appended');
  39666. if (segmentInfo.hasAppendedData_) {
  39667. this.mediaAppends++;
  39668. }
  39669. if (!this.paused()) {
  39670. this.monitorBuffer_();
  39671. }
  39672. }
  39673. /**
  39674. * Records the current throughput of the decrypt, transmux, and append
  39675. * portion of the semgment pipeline. `throughput.rate` is a the cumulative
  39676. * moving average of the throughput. `throughput.count` is the number of
  39677. * data points in the average.
  39678. *
  39679. * @private
  39680. * @param {Object} segmentInfo the object returned by loadSegment
  39681. */
  39682. ;
  39683. _proto.recordThroughput_ = function recordThroughput_(segmentInfo) {
  39684. if (segmentInfo.duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
  39685. this.logger_("Ignoring segment's throughput because its duration of " + segmentInfo.duration + (" is less than the min to record " + MIN_SEGMENT_DURATION_TO_SAVE_STATS));
  39686. return;
  39687. }
  39688. var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
  39689. // by zero in the case where the throughput is ridiculously high
  39690. var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
  39691. var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
  39692. // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
  39693. this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
  39694. }
  39695. /**
  39696. * Adds a cue to the segment-metadata track with some metadata information about the
  39697. * segment
  39698. *
  39699. * @private
  39700. * @param {Object} segmentInfo
  39701. * the object returned by loadSegment
  39702. * @method addSegmentMetadataCue_
  39703. */
  39704. ;
  39705. _proto.addSegmentMetadataCue_ = function addSegmentMetadataCue_(segmentInfo) {
  39706. if (!this.segmentMetadataTrack_) {
  39707. return;
  39708. }
  39709. var segment = segmentInfo.segment;
  39710. var start = segment.start;
  39711. var end = segment.end; // Do not try adding the cue if the start and end times are invalid.
  39712. if (!finite(start) || !finite(end)) {
  39713. return;
  39714. }
  39715. removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
  39716. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  39717. var value = {
  39718. custom: segment.custom,
  39719. dateTimeObject: segment.dateTimeObject,
  39720. dateTimeString: segment.dateTimeString,
  39721. bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
  39722. resolution: segmentInfo.playlist.attributes.RESOLUTION,
  39723. codecs: segmentInfo.playlist.attributes.CODECS,
  39724. byteLength: segmentInfo.byteLength,
  39725. uri: segmentInfo.uri,
  39726. timeline: segmentInfo.timeline,
  39727. playlist: segmentInfo.playlist.id,
  39728. start: start,
  39729. end: end
  39730. };
  39731. var data = JSON.stringify(value);
  39732. var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
  39733. // the differences of WebKitDataCue in safari and VTTCue in other browsers
  39734. cue.value = value;
  39735. this.segmentMetadataTrack_.addCue(cue);
  39736. };
  39737. return SegmentLoader;
  39738. }(videojs.EventTarget);
  39739. function noop() {}
  39740. var toTitleCase = function toTitleCase(string) {
  39741. if (typeof string !== 'string') {
  39742. return string;
  39743. }
  39744. return string.replace(/./, function (w) {
  39745. return w.toUpperCase();
  39746. });
  39747. };
  39748. var bufferTypes = ['video', 'audio'];
  39749. var _updating = function updating(type, sourceUpdater) {
  39750. var sourceBuffer = sourceUpdater[type + "Buffer"];
  39751. return sourceBuffer && sourceBuffer.updating || sourceUpdater.queuePending[type];
  39752. };
  39753. var nextQueueIndexOfType = function nextQueueIndexOfType(type, queue) {
  39754. for (var i = 0; i < queue.length; i++) {
  39755. var queueEntry = queue[i];
  39756. if (queueEntry.type === 'mediaSource') {
  39757. // If the next entry is a media source entry (uses multiple source buffers), block
  39758. // processing to allow it to go through first.
  39759. return null;
  39760. }
  39761. if (queueEntry.type === type) {
  39762. return i;
  39763. }
  39764. }
  39765. return null;
  39766. };
  39767. var shiftQueue = function shiftQueue(type, sourceUpdater) {
  39768. if (sourceUpdater.queue.length === 0) {
  39769. return;
  39770. }
  39771. var queueIndex = 0;
  39772. var queueEntry = sourceUpdater.queue[queueIndex];
  39773. if (queueEntry.type === 'mediaSource') {
  39774. if (!sourceUpdater.updating() && sourceUpdater.mediaSource.readyState !== 'closed') {
  39775. sourceUpdater.queue.shift();
  39776. queueEntry.action(sourceUpdater);
  39777. if (queueEntry.doneFn) {
  39778. queueEntry.doneFn();
  39779. } // Only specific source buffer actions must wait for async updateend events. Media
  39780. // Source actions process synchronously. Therefore, both audio and video source
  39781. // buffers are now clear to process the next queue entries.
  39782. shiftQueue('audio', sourceUpdater);
  39783. shiftQueue('video', sourceUpdater);
  39784. } // Media Source actions require both source buffers, so if the media source action
  39785. // couldn't process yet (because one or both source buffers are busy), block other
  39786. // queue actions until both are available and the media source action can process.
  39787. return;
  39788. }
  39789. if (type === 'mediaSource') {
  39790. // If the queue was shifted by a media source action (this happens when pushing a
  39791. // media source action onto the queue), then it wasn't from an updateend event from an
  39792. // audio or video source buffer, so there's no change from previous state, and no
  39793. // processing should be done.
  39794. return;
  39795. } // Media source queue entries don't need to consider whether the source updater is
  39796. // started (i.e., source buffers are created) as they don't need the source buffers, but
  39797. // source buffer queue entries do.
  39798. if (!sourceUpdater.ready() || sourceUpdater.mediaSource.readyState === 'closed' || _updating(type, sourceUpdater)) {
  39799. return;
  39800. }
  39801. if (queueEntry.type !== type) {
  39802. queueIndex = nextQueueIndexOfType(type, sourceUpdater.queue);
  39803. if (queueIndex === null) {
  39804. // Either there's no queue entry that uses this source buffer type in the queue, or
  39805. // there's a media source queue entry before the next entry of this type, in which
  39806. // case wait for that action to process first.
  39807. return;
  39808. }
  39809. queueEntry = sourceUpdater.queue[queueIndex];
  39810. }
  39811. sourceUpdater.queue.splice(queueIndex, 1); // Keep a record that this source buffer type is in use.
  39812. //
  39813. // The queue pending operation must be set before the action is performed in the event
  39814. // that the action results in a synchronous event that is acted upon. For instance, if
  39815. // an exception is thrown that can be handled, it's possible that new actions will be
  39816. // appended to an empty queue and immediately executed, but would not have the correct
  39817. // pending information if this property was set after the action was performed.
  39818. sourceUpdater.queuePending[type] = queueEntry;
  39819. queueEntry.action(type, sourceUpdater);
  39820. if (!queueEntry.doneFn) {
  39821. // synchronous operation, process next entry
  39822. sourceUpdater.queuePending[type] = null;
  39823. shiftQueue(type, sourceUpdater);
  39824. return;
  39825. }
  39826. };
  39827. var cleanupBuffer = function cleanupBuffer(type, sourceUpdater) {
  39828. var buffer = sourceUpdater[type + "Buffer"];
  39829. var titleType = toTitleCase(type);
  39830. if (!buffer) {
  39831. return;
  39832. }
  39833. buffer.removeEventListener('updateend', sourceUpdater["on" + titleType + "UpdateEnd_"]);
  39834. buffer.removeEventListener('error', sourceUpdater["on" + titleType + "Error_"]);
  39835. sourceUpdater.codecs[type] = null;
  39836. sourceUpdater[type + "Buffer"] = null;
  39837. };
  39838. var inSourceBuffers = function inSourceBuffers(mediaSource, sourceBuffer) {
  39839. return mediaSource && sourceBuffer && Array.prototype.indexOf.call(mediaSource.sourceBuffers, sourceBuffer) !== -1;
  39840. };
  39841. var actions = {
  39842. appendBuffer: function appendBuffer(bytes, segmentInfo, onError) {
  39843. return function (type, sourceUpdater) {
  39844. var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
  39845. // or the media source does not contain this source buffer.
  39846. if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
  39847. return;
  39848. }
  39849. sourceUpdater.logger_("Appending segment " + segmentInfo.mediaIndex + "'s " + bytes.length + " bytes to " + type + "Buffer");
  39850. try {
  39851. sourceBuffer.appendBuffer(bytes);
  39852. } catch (e) {
  39853. sourceUpdater.logger_("Error with code " + e.code + " " + (e.code === QUOTA_EXCEEDED_ERR ? '(QUOTA_EXCEEDED_ERR) ' : '') + ("when appending segment " + segmentInfo.mediaIndex + " to " + type + "Buffer"));
  39854. sourceUpdater.queuePending[type] = null;
  39855. onError(e);
  39856. }
  39857. };
  39858. },
  39859. remove: function remove(start, end) {
  39860. return function (type, sourceUpdater) {
  39861. var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
  39862. // or the media source does not contain this source buffer.
  39863. if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
  39864. return;
  39865. }
  39866. sourceUpdater.logger_("Removing " + start + " to " + end + " from " + type + "Buffer");
  39867. try {
  39868. sourceBuffer.remove(start, end);
  39869. } catch (e) {
  39870. sourceUpdater.logger_("Remove " + start + " to " + end + " from " + type + "Buffer failed");
  39871. }
  39872. };
  39873. },
  39874. timestampOffset: function timestampOffset(offset) {
  39875. return function (type, sourceUpdater) {
  39876. var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
  39877. // or the media source does not contain this source buffer.
  39878. if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
  39879. return;
  39880. }
  39881. sourceUpdater.logger_("Setting " + type + "timestampOffset to " + offset);
  39882. sourceBuffer.timestampOffset = offset;
  39883. };
  39884. },
  39885. callback: function callback(_callback) {
  39886. return function (type, sourceUpdater) {
  39887. _callback();
  39888. };
  39889. },
  39890. endOfStream: function endOfStream(error) {
  39891. return function (sourceUpdater) {
  39892. if (sourceUpdater.mediaSource.readyState !== 'open') {
  39893. return;
  39894. }
  39895. sourceUpdater.logger_("Calling mediaSource endOfStream(" + (error || '') + ")");
  39896. try {
  39897. sourceUpdater.mediaSource.endOfStream(error);
  39898. } catch (e) {
  39899. videojs.log.warn('Failed to call media source endOfStream', e);
  39900. }
  39901. };
  39902. },
  39903. duration: function duration(_duration) {
  39904. return function (sourceUpdater) {
  39905. sourceUpdater.logger_("Setting mediaSource duration to " + _duration);
  39906. try {
  39907. sourceUpdater.mediaSource.duration = _duration;
  39908. } catch (e) {
  39909. videojs.log.warn('Failed to set media source duration', e);
  39910. }
  39911. };
  39912. },
  39913. abort: function abort() {
  39914. return function (type, sourceUpdater) {
  39915. if (sourceUpdater.mediaSource.readyState !== 'open') {
  39916. return;
  39917. }
  39918. var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null
  39919. // or the media source does not contain this source buffer.
  39920. if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
  39921. return;
  39922. }
  39923. sourceUpdater.logger_("calling abort on " + type + "Buffer");
  39924. try {
  39925. sourceBuffer.abort();
  39926. } catch (e) {
  39927. videojs.log.warn("Failed to abort on " + type + "Buffer", e);
  39928. }
  39929. };
  39930. },
  39931. addSourceBuffer: function addSourceBuffer(type, codec) {
  39932. return function (sourceUpdater) {
  39933. var titleType = toTitleCase(type);
  39934. var mime = getMimeForCodec(codec);
  39935. sourceUpdater.logger_("Adding " + type + "Buffer with codec " + codec + " to mediaSource");
  39936. var sourceBuffer = sourceUpdater.mediaSource.addSourceBuffer(mime);
  39937. sourceBuffer.addEventListener('updateend', sourceUpdater["on" + titleType + "UpdateEnd_"]);
  39938. sourceBuffer.addEventListener('error', sourceUpdater["on" + titleType + "Error_"]);
  39939. sourceUpdater.codecs[type] = codec;
  39940. sourceUpdater[type + "Buffer"] = sourceBuffer;
  39941. };
  39942. },
  39943. removeSourceBuffer: function removeSourceBuffer(type) {
  39944. return function (sourceUpdater) {
  39945. var sourceBuffer = sourceUpdater[type + "Buffer"];
  39946. cleanupBuffer(type, sourceUpdater); // can't do anything if the media source / source buffer is null
  39947. // or the media source does not contain this source buffer.
  39948. if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
  39949. return;
  39950. }
  39951. sourceUpdater.logger_("Removing " + type + "Buffer with codec " + sourceUpdater.codecs[type] + " from mediaSource");
  39952. try {
  39953. sourceUpdater.mediaSource.removeSourceBuffer(sourceBuffer);
  39954. } catch (e) {
  39955. videojs.log.warn("Failed to removeSourceBuffer " + type + "Buffer", e);
  39956. }
  39957. };
  39958. },
  39959. changeType: function changeType(codec) {
  39960. return function (type, sourceUpdater) {
  39961. var sourceBuffer = sourceUpdater[type + "Buffer"];
  39962. var mime = getMimeForCodec(codec); // can't do anything if the media source / source buffer is null
  39963. // or the media source does not contain this source buffer.
  39964. if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
  39965. return;
  39966. } // do not update codec if we don't need to.
  39967. if (sourceUpdater.codecs[type] === codec) {
  39968. return;
  39969. }
  39970. sourceUpdater.logger_("changing " + type + "Buffer codec from " + sourceUpdater.codecs[type] + " to " + codec);
  39971. sourceBuffer.changeType(mime);
  39972. sourceUpdater.codecs[type] = codec;
  39973. };
  39974. }
  39975. };
  39976. var pushQueue = function pushQueue(_ref) {
  39977. var type = _ref.type,
  39978. sourceUpdater = _ref.sourceUpdater,
  39979. action = _ref.action,
  39980. doneFn = _ref.doneFn,
  39981. name = _ref.name;
  39982. sourceUpdater.queue.push({
  39983. type: type,
  39984. action: action,
  39985. doneFn: doneFn,
  39986. name: name
  39987. });
  39988. shiftQueue(type, sourceUpdater);
  39989. };
  39990. var onUpdateend = function onUpdateend(type, sourceUpdater) {
  39991. return function (e) {
  39992. // Although there should, in theory, be a pending action for any updateend receieved,
  39993. // there are some actions that may trigger updateend events without set definitions in
  39994. // the w3c spec. For instance, setting the duration on the media source may trigger
  39995. // updateend events on source buffers. This does not appear to be in the spec. As such,
  39996. // if we encounter an updateend without a corresponding pending action from our queue
  39997. // for that source buffer type, process the next action.
  39998. if (sourceUpdater.queuePending[type]) {
  39999. var doneFn = sourceUpdater.queuePending[type].doneFn;
  40000. sourceUpdater.queuePending[type] = null;
  40001. if (doneFn) {
  40002. // if there's an error, report it
  40003. doneFn(sourceUpdater[type + "Error_"]);
  40004. }
  40005. }
  40006. shiftQueue(type, sourceUpdater);
  40007. };
  40008. };
  40009. /**
  40010. * A queue of callbacks to be serialized and applied when a
  40011. * MediaSource and its associated SourceBuffers are not in the
  40012. * updating state. It is used by the segment loader to update the
  40013. * underlying SourceBuffers when new data is loaded, for instance.
  40014. *
  40015. * @class SourceUpdater
  40016. * @param {MediaSource} mediaSource the MediaSource to create the SourceBuffer from
  40017. * @param {string} mimeType the desired MIME type of the underlying SourceBuffer
  40018. */
  40019. var SourceUpdater = /*#__PURE__*/function (_videojs$EventTarget) {
  40020. _inheritsLoose(SourceUpdater, _videojs$EventTarget);
  40021. function SourceUpdater(mediaSource) {
  40022. var _this;
  40023. _this = _videojs$EventTarget.call(this) || this;
  40024. _this.mediaSource = mediaSource;
  40025. _this.sourceopenListener_ = function () {
  40026. return shiftQueue('mediaSource', _assertThisInitialized(_this));
  40027. };
  40028. _this.mediaSource.addEventListener('sourceopen', _this.sourceopenListener_);
  40029. _this.logger_ = logger('SourceUpdater'); // initial timestamp offset is 0
  40030. _this.audioTimestampOffset_ = 0;
  40031. _this.videoTimestampOffset_ = 0;
  40032. _this.queue = [];
  40033. _this.queuePending = {
  40034. audio: null,
  40035. video: null
  40036. };
  40037. _this.delayedAudioAppendQueue_ = [];
  40038. _this.videoAppendQueued_ = false;
  40039. _this.codecs = {};
  40040. _this.onVideoUpdateEnd_ = onUpdateend('video', _assertThisInitialized(_this));
  40041. _this.onAudioUpdateEnd_ = onUpdateend('audio', _assertThisInitialized(_this));
  40042. _this.onVideoError_ = function (e) {
  40043. // used for debugging
  40044. _this.videoError_ = e;
  40045. };
  40046. _this.onAudioError_ = function (e) {
  40047. // used for debugging
  40048. _this.audioError_ = e;
  40049. };
  40050. _this.createdSourceBuffers_ = false;
  40051. _this.initializedEme_ = false;
  40052. _this.triggeredReady_ = false;
  40053. return _this;
  40054. }
  40055. var _proto = SourceUpdater.prototype;
  40056. _proto.initializedEme = function initializedEme() {
  40057. this.initializedEme_ = true;
  40058. this.triggerReady();
  40059. };
  40060. _proto.hasCreatedSourceBuffers = function hasCreatedSourceBuffers() {
  40061. // if false, likely waiting on one of the segment loaders to get enough data to create
  40062. // source buffers
  40063. return this.createdSourceBuffers_;
  40064. };
  40065. _proto.hasInitializedAnyEme = function hasInitializedAnyEme() {
  40066. return this.initializedEme_;
  40067. };
  40068. _proto.ready = function ready() {
  40069. return this.hasCreatedSourceBuffers() && this.hasInitializedAnyEme();
  40070. };
  40071. _proto.createSourceBuffers = function createSourceBuffers(codecs) {
  40072. if (this.hasCreatedSourceBuffers()) {
  40073. // already created them before
  40074. return;
  40075. } // the intial addOrChangeSourceBuffers will always be
  40076. // two add buffers.
  40077. this.addOrChangeSourceBuffers(codecs);
  40078. this.createdSourceBuffers_ = true;
  40079. this.trigger('createdsourcebuffers');
  40080. this.triggerReady();
  40081. };
  40082. _proto.triggerReady = function triggerReady() {
  40083. // only allow ready to be triggered once, this prevents the case
  40084. // where:
  40085. // 1. we trigger createdsourcebuffers
  40086. // 2. ie 11 synchronously initializates eme
  40087. // 3. the synchronous initialization causes us to trigger ready
  40088. // 4. We go back to the ready check in createSourceBuffers and ready is triggered again.
  40089. if (this.ready() && !this.triggeredReady_) {
  40090. this.triggeredReady_ = true;
  40091. this.trigger('ready');
  40092. }
  40093. }
  40094. /**
  40095. * Add a type of source buffer to the media source.
  40096. *
  40097. * @param {string} type
  40098. * The type of source buffer to add.
  40099. *
  40100. * @param {string} codec
  40101. * The codec to add the source buffer with.
  40102. */
  40103. ;
  40104. _proto.addSourceBuffer = function addSourceBuffer(type, codec) {
  40105. pushQueue({
  40106. type: 'mediaSource',
  40107. sourceUpdater: this,
  40108. action: actions.addSourceBuffer(type, codec),
  40109. name: 'addSourceBuffer'
  40110. });
  40111. }
  40112. /**
  40113. * call abort on a source buffer.
  40114. *
  40115. * @param {string} type
  40116. * The type of source buffer to call abort on.
  40117. */
  40118. ;
  40119. _proto.abort = function abort(type) {
  40120. pushQueue({
  40121. type: type,
  40122. sourceUpdater: this,
  40123. action: actions.abort(type),
  40124. name: 'abort'
  40125. });
  40126. }
  40127. /**
  40128. * Call removeSourceBuffer and remove a specific type
  40129. * of source buffer on the mediaSource.
  40130. *
  40131. * @param {string} type
  40132. * The type of source buffer to remove.
  40133. */
  40134. ;
  40135. _proto.removeSourceBuffer = function removeSourceBuffer(type) {
  40136. if (!this.canRemoveSourceBuffer()) {
  40137. videojs.log.error('removeSourceBuffer is not supported!');
  40138. return;
  40139. }
  40140. pushQueue({
  40141. type: 'mediaSource',
  40142. sourceUpdater: this,
  40143. action: actions.removeSourceBuffer(type),
  40144. name: 'removeSourceBuffer'
  40145. });
  40146. }
  40147. /**
  40148. * Whether or not the removeSourceBuffer function is supported
  40149. * on the mediaSource.
  40150. *
  40151. * @return {boolean}
  40152. * if removeSourceBuffer can be called.
  40153. */
  40154. ;
  40155. _proto.canRemoveSourceBuffer = function canRemoveSourceBuffer() {
  40156. // IE reports that it supports removeSourceBuffer, but often throws
  40157. // errors when attempting to use the function. So we report that it
  40158. // does not support removeSourceBuffer. As of Firefox 83 removeSourceBuffer
  40159. // throws errors, so we report that it does not support this as well.
  40160. return !videojs.browser.IE_VERSION && !videojs.browser.IS_FIREFOX && window$1.MediaSource && window$1.MediaSource.prototype && typeof window$1.MediaSource.prototype.removeSourceBuffer === 'function';
  40161. }
  40162. /**
  40163. * Whether or not the changeType function is supported
  40164. * on our SourceBuffers.
  40165. *
  40166. * @return {boolean}
  40167. * if changeType can be called.
  40168. */
  40169. ;
  40170. SourceUpdater.canChangeType = function canChangeType() {
  40171. return window$1.SourceBuffer && window$1.SourceBuffer.prototype && typeof window$1.SourceBuffer.prototype.changeType === 'function';
  40172. }
  40173. /**
  40174. * Whether or not the changeType function is supported
  40175. * on our SourceBuffers.
  40176. *
  40177. * @return {boolean}
  40178. * if changeType can be called.
  40179. */
  40180. ;
  40181. _proto.canChangeType = function canChangeType() {
  40182. return this.constructor.canChangeType();
  40183. }
  40184. /**
  40185. * Call the changeType function on a source buffer, given the code and type.
  40186. *
  40187. * @param {string} type
  40188. * The type of source buffer to call changeType on.
  40189. *
  40190. * @param {string} codec
  40191. * The codec string to change type with on the source buffer.
  40192. */
  40193. ;
  40194. _proto.changeType = function changeType(type, codec) {
  40195. if (!this.canChangeType()) {
  40196. videojs.log.error('changeType is not supported!');
  40197. return;
  40198. }
  40199. pushQueue({
  40200. type: type,
  40201. sourceUpdater: this,
  40202. action: actions.changeType(codec),
  40203. name: 'changeType'
  40204. });
  40205. }
  40206. /**
  40207. * Add source buffers with a codec or, if they are already created,
  40208. * call changeType on source buffers using changeType.
  40209. *
  40210. * @param {Object} codecs
  40211. * Codecs to switch to
  40212. */
  40213. ;
  40214. _proto.addOrChangeSourceBuffers = function addOrChangeSourceBuffers(codecs) {
  40215. var _this2 = this;
  40216. if (!codecs || typeof codecs !== 'object' || Object.keys(codecs).length === 0) {
  40217. throw new Error('Cannot addOrChangeSourceBuffers to undefined codecs');
  40218. }
  40219. Object.keys(codecs).forEach(function (type) {
  40220. var codec = codecs[type];
  40221. if (!_this2.hasCreatedSourceBuffers()) {
  40222. return _this2.addSourceBuffer(type, codec);
  40223. }
  40224. if (_this2.canChangeType()) {
  40225. _this2.changeType(type, codec);
  40226. }
  40227. });
  40228. }
  40229. /**
  40230. * Queue an update to append an ArrayBuffer.
  40231. *
  40232. * @param {MediaObject} object containing audioBytes and/or videoBytes
  40233. * @param {Function} done the function to call when done
  40234. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
  40235. */
  40236. ;
  40237. _proto.appendBuffer = function appendBuffer(options, doneFn) {
  40238. var _this3 = this;
  40239. var segmentInfo = options.segmentInfo,
  40240. type = options.type,
  40241. bytes = options.bytes;
  40242. this.processedAppend_ = true;
  40243. if (type === 'audio' && this.videoBuffer && !this.videoAppendQueued_) {
  40244. this.delayedAudioAppendQueue_.push([options, doneFn]);
  40245. this.logger_("delayed audio append of " + bytes.length + " until video append");
  40246. return;
  40247. } // In the case of certain errors, for instance, QUOTA_EXCEEDED_ERR, updateend will
  40248. // not be fired. This means that the queue will be blocked until the next action
  40249. // taken by the segment-loader. Provide a mechanism for segment-loader to handle
  40250. // these errors by calling the doneFn with the specific error.
  40251. var onError = doneFn;
  40252. pushQueue({
  40253. type: type,
  40254. sourceUpdater: this,
  40255. action: actions.appendBuffer(bytes, segmentInfo || {
  40256. mediaIndex: -1
  40257. }, onError),
  40258. doneFn: doneFn,
  40259. name: 'appendBuffer'
  40260. });
  40261. if (type === 'video') {
  40262. this.videoAppendQueued_ = true;
  40263. if (!this.delayedAudioAppendQueue_.length) {
  40264. return;
  40265. }
  40266. var queue = this.delayedAudioAppendQueue_.slice();
  40267. this.logger_("queuing delayed audio " + queue.length + " appendBuffers");
  40268. this.delayedAudioAppendQueue_.length = 0;
  40269. queue.forEach(function (que) {
  40270. _this3.appendBuffer.apply(_this3, que);
  40271. });
  40272. }
  40273. }
  40274. /**
  40275. * Get the audio buffer's buffered timerange.
  40276. *
  40277. * @return {TimeRange}
  40278. * The audio buffer's buffered time range
  40279. */
  40280. ;
  40281. _proto.audioBuffered = function audioBuffered() {
  40282. // no media source/source buffer or it isn't in the media sources
  40283. // source buffer list
  40284. if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) {
  40285. return videojs.createTimeRange();
  40286. }
  40287. return this.audioBuffer.buffered ? this.audioBuffer.buffered : videojs.createTimeRange();
  40288. }
  40289. /**
  40290. * Get the video buffer's buffered timerange.
  40291. *
  40292. * @return {TimeRange}
  40293. * The video buffer's buffered time range
  40294. */
  40295. ;
  40296. _proto.videoBuffered = function videoBuffered() {
  40297. // no media source/source buffer or it isn't in the media sources
  40298. // source buffer list
  40299. if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) {
  40300. return videojs.createTimeRange();
  40301. }
  40302. return this.videoBuffer.buffered ? this.videoBuffer.buffered : videojs.createTimeRange();
  40303. }
  40304. /**
  40305. * Get a combined video/audio buffer's buffered timerange.
  40306. *
  40307. * @return {TimeRange}
  40308. * the combined time range
  40309. */
  40310. ;
  40311. _proto.buffered = function buffered() {
  40312. var video = inSourceBuffers(this.mediaSource, this.videoBuffer) ? this.videoBuffer : null;
  40313. var audio = inSourceBuffers(this.mediaSource, this.audioBuffer) ? this.audioBuffer : null;
  40314. if (audio && !video) {
  40315. return this.audioBuffered();
  40316. }
  40317. if (video && !audio) {
  40318. return this.videoBuffered();
  40319. }
  40320. return bufferIntersection(this.audioBuffered(), this.videoBuffered());
  40321. }
  40322. /**
  40323. * Add a callback to the queue that will set duration on the mediaSource.
  40324. *
  40325. * @param {number} duration
  40326. * The duration to set
  40327. *
  40328. * @param {Function} [doneFn]
  40329. * function to run after duration has been set.
  40330. */
  40331. ;
  40332. _proto.setDuration = function setDuration(duration, doneFn) {
  40333. if (doneFn === void 0) {
  40334. doneFn = noop;
  40335. } // In order to set the duration on the media source, it's necessary to wait for all
  40336. // source buffers to no longer be updating. "If the updating attribute equals true on
  40337. // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
  40338. // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
  40339. pushQueue({
  40340. type: 'mediaSource',
  40341. sourceUpdater: this,
  40342. action: actions.duration(duration),
  40343. name: 'duration',
  40344. doneFn: doneFn
  40345. });
  40346. }
  40347. /**
  40348. * Add a mediaSource endOfStream call to the queue
  40349. *
  40350. * @param {Error} [error]
  40351. * Call endOfStream with an error
  40352. *
  40353. * @param {Function} [doneFn]
  40354. * A function that should be called when the
  40355. * endOfStream call has finished.
  40356. */
  40357. ;
  40358. _proto.endOfStream = function endOfStream(error, doneFn) {
  40359. if (error === void 0) {
  40360. error = null;
  40361. }
  40362. if (doneFn === void 0) {
  40363. doneFn = noop;
  40364. }
  40365. if (typeof error !== 'string') {
  40366. error = undefined;
  40367. } // In order to set the duration on the media source, it's necessary to wait for all
  40368. // source buffers to no longer be updating. "If the updating attribute equals true on
  40369. // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
  40370. // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
  40371. pushQueue({
  40372. type: 'mediaSource',
  40373. sourceUpdater: this,
  40374. action: actions.endOfStream(error),
  40375. name: 'endOfStream',
  40376. doneFn: doneFn
  40377. });
  40378. }
  40379. /**
  40380. * Queue an update to remove a time range from the buffer.
  40381. *
  40382. * @param {number} start where to start the removal
  40383. * @param {number} end where to end the removal
  40384. * @param {Function} [done=noop] optional callback to be executed when the remove
  40385. * operation is complete
  40386. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
  40387. */
  40388. ;
  40389. _proto.removeAudio = function removeAudio(start, end, done) {
  40390. if (done === void 0) {
  40391. done = noop;
  40392. }
  40393. if (!this.audioBuffered().length || this.audioBuffered().end(0) === 0) {
  40394. done();
  40395. return;
  40396. }
  40397. pushQueue({
  40398. type: 'audio',
  40399. sourceUpdater: this,
  40400. action: actions.remove(start, end),
  40401. doneFn: done,
  40402. name: 'remove'
  40403. });
  40404. }
  40405. /**
  40406. * Queue an update to remove a time range from the buffer.
  40407. *
  40408. * @param {number} start where to start the removal
  40409. * @param {number} end where to end the removal
  40410. * @param {Function} [done=noop] optional callback to be executed when the remove
  40411. * operation is complete
  40412. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
  40413. */
  40414. ;
  40415. _proto.removeVideo = function removeVideo(start, end, done) {
  40416. if (done === void 0) {
  40417. done = noop;
  40418. }
  40419. if (!this.videoBuffered().length || this.videoBuffered().end(0) === 0) {
  40420. done();
  40421. return;
  40422. }
  40423. pushQueue({
  40424. type: 'video',
  40425. sourceUpdater: this,
  40426. action: actions.remove(start, end),
  40427. doneFn: done,
  40428. name: 'remove'
  40429. });
  40430. }
  40431. /**
  40432. * Whether the underlying sourceBuffer is updating or not
  40433. *
  40434. * @return {boolean} the updating status of the SourceBuffer
  40435. */
  40436. ;
  40437. _proto.updating = function updating() {
  40438. // the audio/video source buffer is updating
  40439. if (_updating('audio', this) || _updating('video', this)) {
  40440. return true;
  40441. }
  40442. return false;
  40443. }
  40444. /**
  40445. * Set/get the timestampoffset on the audio SourceBuffer
  40446. *
  40447. * @return {number} the timestamp offset
  40448. */
  40449. ;
  40450. _proto.audioTimestampOffset = function audioTimestampOffset(offset) {
  40451. if (typeof offset !== 'undefined' && this.audioBuffer && // no point in updating if it's the same
  40452. this.audioTimestampOffset_ !== offset) {
  40453. pushQueue({
  40454. type: 'audio',
  40455. sourceUpdater: this,
  40456. action: actions.timestampOffset(offset),
  40457. name: 'timestampOffset'
  40458. });
  40459. this.audioTimestampOffset_ = offset;
  40460. }
  40461. return this.audioTimestampOffset_;
  40462. }
  40463. /**
  40464. * Set/get the timestampoffset on the video SourceBuffer
  40465. *
  40466. * @return {number} the timestamp offset
  40467. */
  40468. ;
  40469. _proto.videoTimestampOffset = function videoTimestampOffset(offset) {
  40470. if (typeof offset !== 'undefined' && this.videoBuffer && // no point in updating if it's the same
  40471. this.videoTimestampOffset !== offset) {
  40472. pushQueue({
  40473. type: 'video',
  40474. sourceUpdater: this,
  40475. action: actions.timestampOffset(offset),
  40476. name: 'timestampOffset'
  40477. });
  40478. this.videoTimestampOffset_ = offset;
  40479. }
  40480. return this.videoTimestampOffset_;
  40481. }
  40482. /**
  40483. * Add a function to the queue that will be called
  40484. * when it is its turn to run in the audio queue.
  40485. *
  40486. * @param {Function} callback
  40487. * The callback to queue.
  40488. */
  40489. ;
  40490. _proto.audioQueueCallback = function audioQueueCallback(callback) {
  40491. if (!this.audioBuffer) {
  40492. return;
  40493. }
  40494. pushQueue({
  40495. type: 'audio',
  40496. sourceUpdater: this,
  40497. action: actions.callback(callback),
  40498. name: 'callback'
  40499. });
  40500. }
  40501. /**
  40502. * Add a function to the queue that will be called
  40503. * when it is its turn to run in the video queue.
  40504. *
  40505. * @param {Function} callback
  40506. * The callback to queue.
  40507. */
  40508. ;
  40509. _proto.videoQueueCallback = function videoQueueCallback(callback) {
  40510. if (!this.videoBuffer) {
  40511. return;
  40512. }
  40513. pushQueue({
  40514. type: 'video',
  40515. sourceUpdater: this,
  40516. action: actions.callback(callback),
  40517. name: 'callback'
  40518. });
  40519. }
  40520. /**
  40521. * dispose of the source updater and the underlying sourceBuffer
  40522. */
  40523. ;
  40524. _proto.dispose = function dispose() {
  40525. var _this4 = this;
  40526. this.trigger('dispose');
  40527. bufferTypes.forEach(function (type) {
  40528. _this4.abort(type);
  40529. if (_this4.canRemoveSourceBuffer()) {
  40530. _this4.removeSourceBuffer(type);
  40531. } else {
  40532. _this4[type + "QueueCallback"](function () {
  40533. return cleanupBuffer(type, _this4);
  40534. });
  40535. }
  40536. });
  40537. this.videoAppendQueued_ = false;
  40538. this.delayedAudioAppendQueue_.length = 0;
  40539. if (this.sourceopenListener_) {
  40540. this.mediaSource.removeEventListener('sourceopen', this.sourceopenListener_);
  40541. }
  40542. this.off();
  40543. };
  40544. return SourceUpdater;
  40545. }(videojs.EventTarget);
  40546. var uint8ToUtf8 = function uint8ToUtf8(uintArray) {
  40547. return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
  40548. };
  40549. var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (_char3) {
  40550. return _char3.charCodeAt(0);
  40551. }));
  40552. var NoVttJsError = /*#__PURE__*/function (_Error) {
  40553. _inheritsLoose(NoVttJsError, _Error);
  40554. function NoVttJsError() {
  40555. return _Error.call(this, 'Trying to parse received VTT cues, but there is no WebVTT. Make sure vtt.js is loaded.') || this;
  40556. }
  40557. return NoVttJsError;
  40558. }( /*#__PURE__*/_wrapNativeSuper(Error));
  40559. /**
  40560. * An object that manages segment loading and appending.
  40561. *
  40562. * @class VTTSegmentLoader
  40563. * @param {Object} options required and optional options
  40564. * @extends videojs.EventTarget
  40565. */
  40566. var VTTSegmentLoader = /*#__PURE__*/function (_SegmentLoader) {
  40567. _inheritsLoose(VTTSegmentLoader, _SegmentLoader);
  40568. function VTTSegmentLoader(settings, options) {
  40569. var _this;
  40570. if (options === void 0) {
  40571. options = {};
  40572. }
  40573. _this = _SegmentLoader.call(this, settings, options) || this; // SegmentLoader requires a MediaSource be specified or it will throw an error;
  40574. // however, VTTSegmentLoader has no need of a media source, so delete the reference
  40575. _this.mediaSource_ = null;
  40576. _this.subtitlesTrack_ = null;
  40577. _this.loaderType_ = 'subtitle';
  40578. _this.featuresNativeTextTracks_ = settings.featuresNativeTextTracks;
  40579. _this.loadVttJs = settings.loadVttJs; // The VTT segment will have its own time mappings. Saving VTT segment timing info in
  40580. // the sync controller leads to improper behavior.
  40581. _this.shouldSaveSegmentTimingInfo_ = false;
  40582. return _this;
  40583. }
  40584. var _proto = VTTSegmentLoader.prototype;
  40585. _proto.createTransmuxer_ = function createTransmuxer_() {
  40586. // don't need to transmux any subtitles
  40587. return null;
  40588. }
  40589. /**
  40590. * Indicates which time ranges are buffered
  40591. *
  40592. * @return {TimeRange}
  40593. * TimeRange object representing the current buffered ranges
  40594. */
  40595. ;
  40596. _proto.buffered_ = function buffered_() {
  40597. if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues || !this.subtitlesTrack_.cues.length) {
  40598. return videojs.createTimeRanges();
  40599. }
  40600. var cues = this.subtitlesTrack_.cues;
  40601. var start = cues[0].startTime;
  40602. var end = cues[cues.length - 1].startTime;
  40603. return videojs.createTimeRanges([[start, end]]);
  40604. }
  40605. /**
  40606. * Gets and sets init segment for the provided map
  40607. *
  40608. * @param {Object} map
  40609. * The map object representing the init segment to get or set
  40610. * @param {boolean=} set
  40611. * If true, the init segment for the provided map should be saved
  40612. * @return {Object}
  40613. * map object for desired init segment
  40614. */
  40615. ;
  40616. _proto.initSegmentForMap = function initSegmentForMap(map, set) {
  40617. if (set === void 0) {
  40618. set = false;
  40619. }
  40620. if (!map) {
  40621. return null;
  40622. }
  40623. var id = initSegmentId(map);
  40624. var storedMap = this.initSegments_[id];
  40625. if (set && !storedMap && map.bytes) {
  40626. // append WebVTT line terminators to the media initialization segment if it exists
  40627. // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
  40628. // requires two or more WebVTT line terminators between the WebVTT header and the
  40629. // rest of the file
  40630. var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
  40631. var combinedSegment = new Uint8Array(combinedByteLength);
  40632. combinedSegment.set(map.bytes);
  40633. combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
  40634. this.initSegments_[id] = storedMap = {
  40635. resolvedUri: map.resolvedUri,
  40636. byterange: map.byterange,
  40637. bytes: combinedSegment
  40638. };
  40639. }
  40640. return storedMap || map;
  40641. }
  40642. /**
  40643. * Returns true if all configuration required for loading is present, otherwise false.
  40644. *
  40645. * @return {boolean} True if the all configuration is ready for loading
  40646. * @private
  40647. */
  40648. ;
  40649. _proto.couldBeginLoading_ = function couldBeginLoading_() {
  40650. return this.playlist_ && this.subtitlesTrack_ && !this.paused();
  40651. }
  40652. /**
  40653. * Once all the starting parameters have been specified, begin
  40654. * operation. This method should only be invoked from the INIT
  40655. * state.
  40656. *
  40657. * @private
  40658. */
  40659. ;
  40660. _proto.init_ = function init_() {
  40661. this.state = 'READY';
  40662. this.resetEverything();
  40663. return this.monitorBuffer_();
  40664. }
  40665. /**
  40666. * Set a subtitle track on the segment loader to add subtitles to
  40667. *
  40668. * @param {TextTrack=} track
  40669. * The text track to add loaded subtitles to
  40670. * @return {TextTrack}
  40671. * Returns the subtitles track
  40672. */
  40673. ;
  40674. _proto.track = function track(_track) {
  40675. if (typeof _track === 'undefined') {
  40676. return this.subtitlesTrack_;
  40677. }
  40678. this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start
  40679. // buffering now
  40680. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  40681. this.init_();
  40682. }
  40683. return this.subtitlesTrack_;
  40684. }
  40685. /**
  40686. * Remove any data in the source buffer between start and end times
  40687. *
  40688. * @param {number} start - the start time of the region to remove from the buffer
  40689. * @param {number} end - the end time of the region to remove from the buffer
  40690. */
  40691. ;
  40692. _proto.remove = function remove(start, end) {
  40693. removeCuesFromTrack(start, end, this.subtitlesTrack_);
  40694. }
  40695. /**
  40696. * fill the buffer with segements unless the sourceBuffers are
  40697. * currently updating
  40698. *
  40699. * Note: this function should only ever be called by monitorBuffer_
  40700. * and never directly
  40701. *
  40702. * @private
  40703. */
  40704. ;
  40705. _proto.fillBuffer_ = function fillBuffer_() {
  40706. var _this2 = this; // see if we need to begin loading immediately
  40707. var segmentInfo = this.chooseNextRequest_();
  40708. if (!segmentInfo) {
  40709. return;
  40710. }
  40711. if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
  40712. // We don't have the timestamp offset that we need to sync subtitles.
  40713. // Rerun on a timestamp offset or user interaction.
  40714. var checkTimestampOffset = function checkTimestampOffset() {
  40715. _this2.state = 'READY';
  40716. if (!_this2.paused()) {
  40717. // if not paused, queue a buffer check as soon as possible
  40718. _this2.monitorBuffer_();
  40719. }
  40720. };
  40721. this.syncController_.one('timestampoffset', checkTimestampOffset);
  40722. this.state = 'WAITING_ON_TIMELINE';
  40723. return;
  40724. }
  40725. this.loadSegment_(segmentInfo);
  40726. } // never set a timestamp offset for vtt segments.
  40727. ;
  40728. _proto.timestampOffsetForSegment_ = function timestampOffsetForSegment_() {
  40729. return null;
  40730. };
  40731. _proto.chooseNextRequest_ = function chooseNextRequest_() {
  40732. return this.skipEmptySegments_(_SegmentLoader.prototype.chooseNextRequest_.call(this));
  40733. }
  40734. /**
  40735. * Prevents the segment loader from requesting segments we know contain no subtitles
  40736. * by walking forward until we find the next segment that we don't know whether it is
  40737. * empty or not.
  40738. *
  40739. * @param {Object} segmentInfo
  40740. * a segment info object that describes the current segment
  40741. * @return {Object}
  40742. * a segment info object that describes the current segment
  40743. */
  40744. ;
  40745. _proto.skipEmptySegments_ = function skipEmptySegments_(segmentInfo) {
  40746. while (segmentInfo && segmentInfo.segment.empty) {
  40747. // stop at the last possible segmentInfo
  40748. if (segmentInfo.mediaIndex + 1 >= segmentInfo.playlist.segments.length) {
  40749. segmentInfo = null;
  40750. break;
  40751. }
  40752. segmentInfo = this.generateSegmentInfo_({
  40753. playlist: segmentInfo.playlist,
  40754. mediaIndex: segmentInfo.mediaIndex + 1,
  40755. startOfSegment: segmentInfo.startOfSegment + segmentInfo.duration,
  40756. isSyncRequest: segmentInfo.isSyncRequest
  40757. });
  40758. }
  40759. return segmentInfo;
  40760. };
  40761. _proto.stopForError = function stopForError(error) {
  40762. this.error(error);
  40763. this.state = 'READY';
  40764. this.pause();
  40765. this.trigger('error');
  40766. }
  40767. /**
  40768. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  40769. *
  40770. * @private
  40771. */
  40772. ;
  40773. _proto.segmentRequestFinished_ = function segmentRequestFinished_(error, simpleSegment, result) {
  40774. var _this3 = this;
  40775. if (!this.subtitlesTrack_) {
  40776. this.state = 'READY';
  40777. return;
  40778. }
  40779. this.saveTransferStats_(simpleSegment.stats); // the request was aborted
  40780. if (!this.pendingSegment_) {
  40781. this.state = 'READY';
  40782. this.mediaRequestsAborted += 1;
  40783. return;
  40784. }
  40785. if (error) {
  40786. if (error.code === REQUEST_ERRORS.TIMEOUT) {
  40787. this.handleTimeout_();
  40788. }
  40789. if (error.code === REQUEST_ERRORS.ABORTED) {
  40790. this.mediaRequestsAborted += 1;
  40791. } else {
  40792. this.mediaRequestsErrored += 1;
  40793. }
  40794. this.stopForError(error);
  40795. return;
  40796. }
  40797. var segmentInfo = this.pendingSegment_; // although the VTT segment loader bandwidth isn't really used, it's good to
  40798. // maintain functionality between segment loaders
  40799. this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats); // if this request included a segment key, save that data in the cache
  40800. if (simpleSegment.key) {
  40801. this.segmentKey(simpleSegment.key, true);
  40802. }
  40803. this.state = 'APPENDING'; // used for tests
  40804. this.trigger('appending');
  40805. var segment = segmentInfo.segment;
  40806. if (segment.map) {
  40807. segment.map.bytes = simpleSegment.map.bytes;
  40808. }
  40809. segmentInfo.bytes = simpleSegment.bytes; // Make sure that vttjs has loaded, otherwise, load it and wait till it finished loading
  40810. if (typeof window$1.WebVTT !== 'function' && typeof this.loadVttJs === 'function') {
  40811. this.state = 'WAITING_ON_VTTJS'; // should be fine to call multiple times
  40812. // script will be loaded once but multiple listeners will be added to the queue, which is expected.
  40813. this.loadVttJs().then(function () {
  40814. return _this3.segmentRequestFinished_(error, simpleSegment, result);
  40815. }, function () {
  40816. return _this3.stopForError({
  40817. message: 'Error loading vtt.js'
  40818. });
  40819. });
  40820. return;
  40821. }
  40822. segment.requested = true;
  40823. try {
  40824. this.parseVTTCues_(segmentInfo);
  40825. } catch (e) {
  40826. this.stopForError({
  40827. message: e.message
  40828. });
  40829. return;
  40830. }
  40831. this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
  40832. if (segmentInfo.cues.length) {
  40833. segmentInfo.timingInfo = {
  40834. start: segmentInfo.cues[0].startTime,
  40835. end: segmentInfo.cues[segmentInfo.cues.length - 1].endTime
  40836. };
  40837. } else {
  40838. segmentInfo.timingInfo = {
  40839. start: segmentInfo.startOfSegment,
  40840. end: segmentInfo.startOfSegment + segmentInfo.duration
  40841. };
  40842. }
  40843. if (segmentInfo.isSyncRequest) {
  40844. this.trigger('syncinfoupdate');
  40845. this.pendingSegment_ = null;
  40846. this.state = 'READY';
  40847. return;
  40848. }
  40849. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  40850. this.mediaSecondsLoaded += segment.duration; // Create VTTCue instances for each cue in the new segment and add them to
  40851. // the subtitle track
  40852. segmentInfo.cues.forEach(function (cue) {
  40853. _this3.subtitlesTrack_.addCue(_this3.featuresNativeTextTracks_ ? new window$1.VTTCue(cue.startTime, cue.endTime, cue.text) : cue);
  40854. }); // Remove any duplicate cues from the subtitle track. The WebVTT spec allows
  40855. // cues to have identical time-intervals, but if the text is also identical
  40856. // we can safely assume it is a duplicate that can be removed (ex. when a cue
  40857. // "overlaps" VTT segments)
  40858. removeDuplicateCuesFromTrack(this.subtitlesTrack_);
  40859. this.handleAppendsDone_();
  40860. };
  40861. _proto.handleData_ = function handleData_() {// noop as we shouldn't be getting video/audio data captions
  40862. // that we do not support here.
  40863. };
  40864. _proto.updateTimingInfoEnd_ = function updateTimingInfoEnd_() {// noop
  40865. }
  40866. /**
  40867. * Uses the WebVTT parser to parse the segment response
  40868. *
  40869. * @throws NoVttJsError
  40870. *
  40871. * @param {Object} segmentInfo
  40872. * a segment info object that describes the current segment
  40873. * @private
  40874. */
  40875. ;
  40876. _proto.parseVTTCues_ = function parseVTTCues_(segmentInfo) {
  40877. var decoder;
  40878. var decodeBytesToString = false;
  40879. if (typeof window$1.WebVTT !== 'function') {
  40880. // caller is responsible for exception handling.
  40881. throw new NoVttJsError();
  40882. }
  40883. if (typeof window$1.TextDecoder === 'function') {
  40884. decoder = new window$1.TextDecoder('utf8');
  40885. } else {
  40886. decoder = window$1.WebVTT.StringDecoder();
  40887. decodeBytesToString = true;
  40888. }
  40889. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder);
  40890. segmentInfo.cues = [];
  40891. segmentInfo.timestampmap = {
  40892. MPEGTS: 0,
  40893. LOCAL: 0
  40894. };
  40895. parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
  40896. parser.ontimestampmap = function (map) {
  40897. segmentInfo.timestampmap = map;
  40898. };
  40899. parser.onparsingerror = function (error) {
  40900. videojs.log.warn('Error encountered when parsing cues: ' + error.message);
  40901. };
  40902. if (segmentInfo.segment.map) {
  40903. var mapData = segmentInfo.segment.map.bytes;
  40904. if (decodeBytesToString) {
  40905. mapData = uint8ToUtf8(mapData);
  40906. }
  40907. parser.parse(mapData);
  40908. }
  40909. var segmentData = segmentInfo.bytes;
  40910. if (decodeBytesToString) {
  40911. segmentData = uint8ToUtf8(segmentData);
  40912. }
  40913. parser.parse(segmentData);
  40914. parser.flush();
  40915. }
  40916. /**
  40917. * Updates the start and end times of any cues parsed by the WebVTT parser using
  40918. * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
  40919. * from the SyncController
  40920. *
  40921. * @param {Object} segmentInfo
  40922. * a segment info object that describes the current segment
  40923. * @param {Object} mappingObj
  40924. * object containing a mapping from TS to media time
  40925. * @param {Object} playlist
  40926. * the playlist object containing the segment
  40927. * @private
  40928. */
  40929. ;
  40930. _proto.updateTimeMapping_ = function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
  40931. var segment = segmentInfo.segment;
  40932. if (!mappingObj) {
  40933. // If the sync controller does not have a mapping of TS to Media Time for the
  40934. // timeline, then we don't have enough information to update the cue
  40935. // start/end times
  40936. return;
  40937. }
  40938. if (!segmentInfo.cues.length) {
  40939. // If there are no cues, we also do not have enough information to figure out
  40940. // segment timing. Mark that the segment contains no cues so we don't re-request
  40941. // an empty segment.
  40942. segment.empty = true;
  40943. return;
  40944. }
  40945. var timestampmap = segmentInfo.timestampmap;
  40946. var diff = timestampmap.MPEGTS / ONE_SECOND_IN_TS - timestampmap.LOCAL + mappingObj.mapping;
  40947. segmentInfo.cues.forEach(function (cue) {
  40948. // First convert cue time to TS time using the timestamp-map provided within the vtt
  40949. cue.startTime += diff;
  40950. cue.endTime += diff;
  40951. });
  40952. if (!playlist.syncInfo) {
  40953. var firstStart = segmentInfo.cues[0].startTime;
  40954. var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
  40955. playlist.syncInfo = {
  40956. mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
  40957. time: Math.min(firstStart, lastStart - segment.duration)
  40958. };
  40959. }
  40960. };
  40961. return VTTSegmentLoader;
  40962. }(SegmentLoader);
  40963. /**
  40964. * @file ad-cue-tags.js
  40965. */
  40966. /**
  40967. * Searches for an ad cue that overlaps with the given mediaTime
  40968. *
  40969. * @param {Object} track
  40970. * the track to find the cue for
  40971. *
  40972. * @param {number} mediaTime
  40973. * the time to find the cue at
  40974. *
  40975. * @return {Object|null}
  40976. * the found cue or null
  40977. */
  40978. var findAdCue = function findAdCue(track, mediaTime) {
  40979. var cues = track.cues;
  40980. for (var i = 0; i < cues.length; i++) {
  40981. var cue = cues[i];
  40982. if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
  40983. return cue;
  40984. }
  40985. }
  40986. return null;
  40987. };
  40988. var updateAdCues = function updateAdCues(media, track, offset) {
  40989. if (offset === void 0) {
  40990. offset = 0;
  40991. }
  40992. if (!media.segments) {
  40993. return;
  40994. }
  40995. var mediaTime = offset;
  40996. var cue;
  40997. for (var i = 0; i < media.segments.length; i++) {
  40998. var segment = media.segments[i];
  40999. if (!cue) {
  41000. // Since the cues will span for at least the segment duration, adding a fudge
  41001. // factor of half segment duration will prevent duplicate cues from being
  41002. // created when timing info is not exact (e.g. cue start time initialized
  41003. // at 10.006677, but next call mediaTime is 10.003332 )
  41004. cue = findAdCue(track, mediaTime + segment.duration / 2);
  41005. }
  41006. if (cue) {
  41007. if ('cueIn' in segment) {
  41008. // Found a CUE-IN so end the cue
  41009. cue.endTime = mediaTime;
  41010. cue.adEndTime = mediaTime;
  41011. mediaTime += segment.duration;
  41012. cue = null;
  41013. continue;
  41014. }
  41015. if (mediaTime < cue.endTime) {
  41016. // Already processed this mediaTime for this cue
  41017. mediaTime += segment.duration;
  41018. continue;
  41019. } // otherwise extend cue until a CUE-IN is found
  41020. cue.endTime += segment.duration;
  41021. } else {
  41022. if ('cueOut' in segment) {
  41023. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
  41024. cue.adStartTime = mediaTime; // Assumes tag format to be
  41025. // #EXT-X-CUE-OUT:30
  41026. cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
  41027. track.addCue(cue);
  41028. }
  41029. if ('cueOutCont' in segment) {
  41030. // Entered into the middle of an ad cue
  41031. // Assumes tag formate to be
  41032. // #EXT-X-CUE-OUT-CONT:10/30
  41033. var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat),
  41034. adOffset = _segment$cueOutCont$s[0],
  41035. adTotal = _segment$cueOutCont$s[1];
  41036. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, '');
  41037. cue.adStartTime = mediaTime - adOffset;
  41038. cue.adEndTime = cue.adStartTime + adTotal;
  41039. track.addCue(cue);
  41040. }
  41041. }
  41042. mediaTime += segment.duration;
  41043. }
  41044. }; // synchronize expired playlist segments.
  41045. // the max media sequence diff is 48 hours of live stream
  41046. // content with two second segments. Anything larger than that
  41047. // will likely be invalid.
  41048. var MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC = 86400;
  41049. var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
  41050. // the equivalence display-time 0 === segment-index 0
  41051. {
  41052. name: 'VOD',
  41053. run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
  41054. if (duration !== Infinity) {
  41055. var syncPoint = {
  41056. time: 0,
  41057. segmentIndex: 0,
  41058. partIndex: null
  41059. };
  41060. return syncPoint;
  41061. }
  41062. return null;
  41063. }
  41064. }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
  41065. {
  41066. name: 'ProgramDateTime',
  41067. run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
  41068. if (!Object.keys(syncController.timelineToDatetimeMappings).length) {
  41069. return null;
  41070. }
  41071. var syncPoint = null;
  41072. var lastDistance = null;
  41073. var partsAndSegments = getPartsAndSegments(playlist);
  41074. currentTime = currentTime || 0;
  41075. for (var i = 0; i < partsAndSegments.length; i++) {
  41076. // start from the end and loop backwards for live
  41077. // or start from the front and loop forwards for non-live
  41078. var index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
  41079. var partAndSegment = partsAndSegments[index];
  41080. var segment = partAndSegment.segment;
  41081. var datetimeMapping = syncController.timelineToDatetimeMappings[segment.timeline];
  41082. if (!datetimeMapping || !segment.dateTimeObject) {
  41083. continue;
  41084. }
  41085. var segmentTime = segment.dateTimeObject.getTime() / 1000;
  41086. var start = segmentTime + datetimeMapping; // take part duration into account.
  41087. if (segment.parts && typeof partAndSegment.partIndex === 'number') {
  41088. for (var z = 0; z < partAndSegment.partIndex; z++) {
  41089. start += segment.parts[z].duration;
  41090. }
  41091. }
  41092. var distance = Math.abs(currentTime - start); // Once the distance begins to increase, or if distance is 0, we have passed
  41093. // currentTime and can stop looking for better candidates
  41094. if (lastDistance !== null && (distance === 0 || lastDistance < distance)) {
  41095. break;
  41096. }
  41097. lastDistance = distance;
  41098. syncPoint = {
  41099. time: start,
  41100. segmentIndex: partAndSegment.segmentIndex,
  41101. partIndex: partAndSegment.partIndex
  41102. };
  41103. }
  41104. return syncPoint;
  41105. }
  41106. }, // Stategy "Segment": We have a known time mapping for a timeline and a
  41107. // segment in the current timeline with timing data
  41108. {
  41109. name: 'Segment',
  41110. run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
  41111. var syncPoint = null;
  41112. var lastDistance = null;
  41113. currentTime = currentTime || 0;
  41114. var partsAndSegments = getPartsAndSegments(playlist);
  41115. for (var i = 0; i < partsAndSegments.length; i++) {
  41116. // start from the end and loop backwards for live
  41117. // or start from the front and loop forwards for non-live
  41118. var index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
  41119. var partAndSegment = partsAndSegments[index];
  41120. var segment = partAndSegment.segment;
  41121. var start = partAndSegment.part && partAndSegment.part.start || segment && segment.start;
  41122. if (segment.timeline === currentTimeline && typeof start !== 'undefined') {
  41123. var distance = Math.abs(currentTime - start); // Once the distance begins to increase, we have passed
  41124. // currentTime and can stop looking for better candidates
  41125. if (lastDistance !== null && lastDistance < distance) {
  41126. break;
  41127. }
  41128. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  41129. lastDistance = distance;
  41130. syncPoint = {
  41131. time: start,
  41132. segmentIndex: partAndSegment.segmentIndex,
  41133. partIndex: partAndSegment.partIndex
  41134. };
  41135. }
  41136. }
  41137. }
  41138. return syncPoint;
  41139. }
  41140. }, // Stategy "Discontinuity": We have a discontinuity with a known
  41141. // display-time
  41142. {
  41143. name: 'Discontinuity',
  41144. run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
  41145. var syncPoint = null;
  41146. currentTime = currentTime || 0;
  41147. if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  41148. var lastDistance = null;
  41149. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  41150. var segmentIndex = playlist.discontinuityStarts[i];
  41151. var discontinuity = playlist.discontinuitySequence + i + 1;
  41152. var discontinuitySync = syncController.discontinuities[discontinuity];
  41153. if (discontinuitySync) {
  41154. var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
  41155. // currentTime and can stop looking for better candidates
  41156. if (lastDistance !== null && lastDistance < distance) {
  41157. break;
  41158. }
  41159. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  41160. lastDistance = distance;
  41161. syncPoint = {
  41162. time: discontinuitySync.time,
  41163. segmentIndex: segmentIndex,
  41164. partIndex: null
  41165. };
  41166. }
  41167. }
  41168. }
  41169. }
  41170. return syncPoint;
  41171. }
  41172. }, // Stategy "Playlist": We have a playlist with a known mapping of
  41173. // segment index to display time
  41174. {
  41175. name: 'Playlist',
  41176. run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
  41177. if (playlist.syncInfo) {
  41178. var syncPoint = {
  41179. time: playlist.syncInfo.time,
  41180. segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence,
  41181. partIndex: null
  41182. };
  41183. return syncPoint;
  41184. }
  41185. return null;
  41186. }
  41187. }];
  41188. var SyncController = /*#__PURE__*/function (_videojs$EventTarget) {
  41189. _inheritsLoose(SyncController, _videojs$EventTarget);
  41190. function SyncController(options) {
  41191. var _this;
  41192. _this = _videojs$EventTarget.call(this) || this; // ...for synching across variants
  41193. _this.timelines = [];
  41194. _this.discontinuities = [];
  41195. _this.timelineToDatetimeMappings = {};
  41196. _this.logger_ = logger('SyncController');
  41197. return _this;
  41198. }
  41199. /**
  41200. * Find a sync-point for the playlist specified
  41201. *
  41202. * A sync-point is defined as a known mapping from display-time to
  41203. * a segment-index in the current playlist.
  41204. *
  41205. * @param {Playlist} playlist
  41206. * The playlist that needs a sync-point
  41207. * @param {number} duration
  41208. * Duration of the MediaSource (Infinite if playing a live source)
  41209. * @param {number} currentTimeline
  41210. * The last timeline from which a segment was loaded
  41211. * @return {Object}
  41212. * A sync-point object
  41213. */
  41214. var _proto = SyncController.prototype;
  41215. _proto.getSyncPoint = function getSyncPoint(playlist, duration, currentTimeline, currentTime) {
  41216. var syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime);
  41217. if (!syncPoints.length) {
  41218. // Signal that we need to attempt to get a sync-point manually
  41219. // by fetching a segment in the playlist and constructing
  41220. // a sync-point from that information
  41221. return null;
  41222. } // Now find the sync-point that is closest to the currentTime because
  41223. // that should result in the most accurate guess about which segment
  41224. // to fetch
  41225. return this.selectSyncPoint_(syncPoints, {
  41226. key: 'time',
  41227. value: currentTime
  41228. });
  41229. }
  41230. /**
  41231. * Calculate the amount of time that has expired off the playlist during playback
  41232. *
  41233. * @param {Playlist} playlist
  41234. * Playlist object to calculate expired from
  41235. * @param {number} duration
  41236. * Duration of the MediaSource (Infinity if playling a live source)
  41237. * @return {number|null}
  41238. * The amount of time that has expired off the playlist during playback. Null
  41239. * if no sync-points for the playlist can be found.
  41240. */
  41241. ;
  41242. _proto.getExpiredTime = function getExpiredTime(playlist, duration) {
  41243. if (!playlist || !playlist.segments) {
  41244. return null;
  41245. }
  41246. var syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
  41247. if (!syncPoints.length) {
  41248. return null;
  41249. }
  41250. var syncPoint = this.selectSyncPoint_(syncPoints, {
  41251. key: 'segmentIndex',
  41252. value: 0
  41253. }); // If the sync-point is beyond the start of the playlist, we want to subtract the
  41254. // duration from index 0 to syncPoint.segmentIndex instead of adding.
  41255. if (syncPoint.segmentIndex > 0) {
  41256. syncPoint.time *= -1;
  41257. }
  41258. return Math.abs(syncPoint.time + sumDurations({
  41259. defaultDuration: playlist.targetDuration,
  41260. durationList: playlist.segments,
  41261. startIndex: syncPoint.segmentIndex,
  41262. endIndex: 0
  41263. }));
  41264. }
  41265. /**
  41266. * Runs each sync-point strategy and returns a list of sync-points returned by the
  41267. * strategies
  41268. *
  41269. * @private
  41270. * @param {Playlist} playlist
  41271. * The playlist that needs a sync-point
  41272. * @param {number} duration
  41273. * Duration of the MediaSource (Infinity if playing a live source)
  41274. * @param {number} currentTimeline
  41275. * The last timeline from which a segment was loaded
  41276. * @return {Array}
  41277. * A list of sync-point objects
  41278. */
  41279. ;
  41280. _proto.runStrategies_ = function runStrategies_(playlist, duration, currentTimeline, currentTime) {
  41281. var syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
  41282. for (var i = 0; i < syncPointStrategies.length; i++) {
  41283. var strategy = syncPointStrategies[i];
  41284. var syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime);
  41285. if (syncPoint) {
  41286. syncPoint.strategy = strategy.name;
  41287. syncPoints.push({
  41288. strategy: strategy.name,
  41289. syncPoint: syncPoint
  41290. });
  41291. }
  41292. }
  41293. return syncPoints;
  41294. }
  41295. /**
  41296. * Selects the sync-point nearest the specified target
  41297. *
  41298. * @private
  41299. * @param {Array} syncPoints
  41300. * List of sync-points to select from
  41301. * @param {Object} target
  41302. * Object specifying the property and value we are targeting
  41303. * @param {string} target.key
  41304. * Specifies the property to target. Must be either 'time' or 'segmentIndex'
  41305. * @param {number} target.value
  41306. * The value to target for the specified key.
  41307. * @return {Object}
  41308. * The sync-point nearest the target
  41309. */
  41310. ;
  41311. _proto.selectSyncPoint_ = function selectSyncPoint_(syncPoints, target) {
  41312. var bestSyncPoint = syncPoints[0].syncPoint;
  41313. var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
  41314. var bestStrategy = syncPoints[0].strategy;
  41315. for (var i = 1; i < syncPoints.length; i++) {
  41316. var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
  41317. if (newDistance < bestDistance) {
  41318. bestDistance = newDistance;
  41319. bestSyncPoint = syncPoints[i].syncPoint;
  41320. bestStrategy = syncPoints[i].strategy;
  41321. }
  41322. }
  41323. this.logger_("syncPoint for [" + target.key + ": " + target.value + "] chosen with strategy" + (" [" + bestStrategy + "]: [time:" + bestSyncPoint.time + ",") + (" segmentIndex:" + bestSyncPoint.segmentIndex) + (typeof bestSyncPoint.partIndex === 'number' ? ",partIndex:" + bestSyncPoint.partIndex : '') + ']');
  41324. return bestSyncPoint;
  41325. }
  41326. /**
  41327. * Save any meta-data present on the segments when segments leave
  41328. * the live window to the playlist to allow for synchronization at the
  41329. * playlist level later.
  41330. *
  41331. * @param {Playlist} oldPlaylist - The previous active playlist
  41332. * @param {Playlist} newPlaylist - The updated and most current playlist
  41333. */
  41334. ;
  41335. _proto.saveExpiredSegmentInfo = function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
  41336. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // Ignore large media sequence gaps
  41337. if (mediaSequenceDiff > MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC) {
  41338. videojs.log.warn("Not saving expired segment info. Media sequence gap " + mediaSequenceDiff + " is too large.");
  41339. return;
  41340. } // When a segment expires from the playlist and it has a start time
  41341. // save that information as a possible sync-point reference in future
  41342. for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
  41343. var lastRemovedSegment = oldPlaylist.segments[i];
  41344. if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
  41345. newPlaylist.syncInfo = {
  41346. mediaSequence: oldPlaylist.mediaSequence + i,
  41347. time: lastRemovedSegment.start
  41348. };
  41349. this.logger_("playlist refresh sync: [time:" + newPlaylist.syncInfo.time + "," + (" mediaSequence: " + newPlaylist.syncInfo.mediaSequence + "]"));
  41350. this.trigger('syncinfoupdate');
  41351. break;
  41352. }
  41353. }
  41354. }
  41355. /**
  41356. * Save the mapping from playlist's ProgramDateTime to display. This should only happen
  41357. * before segments start to load.
  41358. *
  41359. * @param {Playlist} playlist - The currently active playlist
  41360. */
  41361. ;
  41362. _proto.setDateTimeMappingForStart = function setDateTimeMappingForStart(playlist) {
  41363. // It's possible for the playlist to be updated before playback starts, meaning time
  41364. // zero is not yet set. If, during these playlist refreshes, a discontinuity is
  41365. // crossed, then the old time zero mapping (for the prior timeline) would be retained
  41366. // unless the mappings are cleared.
  41367. this.timelineToDatetimeMappings = {};
  41368. if (playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
  41369. var firstSegment = playlist.segments[0];
  41370. var playlistTimestamp = firstSegment.dateTimeObject.getTime() / 1000;
  41371. this.timelineToDatetimeMappings[firstSegment.timeline] = -playlistTimestamp;
  41372. }
  41373. }
  41374. /**
  41375. * Calculates and saves timeline mappings, playlist sync info, and segment timing values
  41376. * based on the latest timing information.
  41377. *
  41378. * @param {Object} options
  41379. * Options object
  41380. * @param {SegmentInfo} options.segmentInfo
  41381. * The current active request information
  41382. * @param {boolean} options.shouldSaveTimelineMapping
  41383. * If there's a timeline change, determines if the timeline mapping should be
  41384. * saved for timeline mapping and program date time mappings.
  41385. */
  41386. ;
  41387. _proto.saveSegmentTimingInfo = function saveSegmentTimingInfo(_ref) {
  41388. var segmentInfo = _ref.segmentInfo,
  41389. shouldSaveTimelineMapping = _ref.shouldSaveTimelineMapping;
  41390. var didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(segmentInfo, segmentInfo.timingInfo, shouldSaveTimelineMapping);
  41391. var segment = segmentInfo.segment;
  41392. if (didCalculateSegmentTimeMapping) {
  41393. this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
  41394. // now with segment timing information
  41395. if (!segmentInfo.playlist.syncInfo) {
  41396. segmentInfo.playlist.syncInfo = {
  41397. mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex,
  41398. time: segment.start
  41399. };
  41400. }
  41401. }
  41402. var dateTime = segment.dateTimeObject;
  41403. if (segment.discontinuity && shouldSaveTimelineMapping && dateTime) {
  41404. this.timelineToDatetimeMappings[segment.timeline] = -(dateTime.getTime() / 1000);
  41405. }
  41406. };
  41407. _proto.timestampOffsetForTimeline = function timestampOffsetForTimeline(timeline) {
  41408. if (typeof this.timelines[timeline] === 'undefined') {
  41409. return null;
  41410. }
  41411. return this.timelines[timeline].time;
  41412. };
  41413. _proto.mappingForTimeline = function mappingForTimeline(timeline) {
  41414. if (typeof this.timelines[timeline] === 'undefined') {
  41415. return null;
  41416. }
  41417. return this.timelines[timeline].mapping;
  41418. }
  41419. /**
  41420. * Use the "media time" for a segment to generate a mapping to "display time" and
  41421. * save that display time to the segment.
  41422. *
  41423. * @private
  41424. * @param {SegmentInfo} segmentInfo
  41425. * The current active request information
  41426. * @param {Object} timingInfo
  41427. * The start and end time of the current segment in "media time"
  41428. * @param {boolean} shouldSaveTimelineMapping
  41429. * If there's a timeline change, determines if the timeline mapping should be
  41430. * saved in timelines.
  41431. * @return {boolean}
  41432. * Returns false if segment time mapping could not be calculated
  41433. */
  41434. ;
  41435. _proto.calculateSegmentTimeMapping_ = function calculateSegmentTimeMapping_(segmentInfo, timingInfo, shouldSaveTimelineMapping) {
  41436. // TODO: remove side effects
  41437. var segment = segmentInfo.segment;
  41438. var part = segmentInfo.part;
  41439. var mappingObj = this.timelines[segmentInfo.timeline];
  41440. var start;
  41441. var end;
  41442. if (typeof segmentInfo.timestampOffset === 'number') {
  41443. mappingObj = {
  41444. time: segmentInfo.startOfSegment,
  41445. mapping: segmentInfo.startOfSegment - timingInfo.start
  41446. };
  41447. if (shouldSaveTimelineMapping) {
  41448. this.timelines[segmentInfo.timeline] = mappingObj;
  41449. this.trigger('timestampoffset');
  41450. this.logger_("time mapping for timeline " + segmentInfo.timeline + ": " + ("[time: " + mappingObj.time + "] [mapping: " + mappingObj.mapping + "]"));
  41451. }
  41452. start = segmentInfo.startOfSegment;
  41453. end = timingInfo.end + mappingObj.mapping;
  41454. } else if (mappingObj) {
  41455. start = timingInfo.start + mappingObj.mapping;
  41456. end = timingInfo.end + mappingObj.mapping;
  41457. } else {
  41458. return false;
  41459. }
  41460. if (part) {
  41461. part.start = start;
  41462. part.end = end;
  41463. } // If we don't have a segment start yet or the start value we got
  41464. // is less than our current segment.start value, save a new start value.
  41465. // We have to do this because parts will have segment timing info saved
  41466. // multiple times and we want segment start to be the earliest part start
  41467. // value for that segment.
  41468. if (!segment.start || start < segment.start) {
  41469. segment.start = start;
  41470. }
  41471. segment.end = end;
  41472. return true;
  41473. }
  41474. /**
  41475. * Each time we have discontinuity in the playlist, attempt to calculate the location
  41476. * in display of the start of the discontinuity and save that. We also save an accuracy
  41477. * value so that we save values with the most accuracy (closest to 0.)
  41478. *
  41479. * @private
  41480. * @param {SegmentInfo} segmentInfo - The current active request information
  41481. */
  41482. ;
  41483. _proto.saveDiscontinuitySyncInfo_ = function saveDiscontinuitySyncInfo_(segmentInfo) {
  41484. var playlist = segmentInfo.playlist;
  41485. var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
  41486. // the start of the range and it's accuracy is 0 (greater accuracy values
  41487. // mean more approximation)
  41488. if (segment.discontinuity) {
  41489. this.discontinuities[segment.timeline] = {
  41490. time: segment.start,
  41491. accuracy: 0
  41492. };
  41493. } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  41494. // Search for future discontinuities that we can provide better timing
  41495. // information for and save that information for sync purposes
  41496. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  41497. var segmentIndex = playlist.discontinuityStarts[i];
  41498. var discontinuity = playlist.discontinuitySequence + i + 1;
  41499. var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
  41500. var accuracy = Math.abs(mediaIndexDiff);
  41501. if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
  41502. var time = void 0;
  41503. if (mediaIndexDiff < 0) {
  41504. time = segment.start - sumDurations({
  41505. defaultDuration: playlist.targetDuration,
  41506. durationList: playlist.segments,
  41507. startIndex: segmentInfo.mediaIndex,
  41508. endIndex: segmentIndex
  41509. });
  41510. } else {
  41511. time = segment.end + sumDurations({
  41512. defaultDuration: playlist.targetDuration,
  41513. durationList: playlist.segments,
  41514. startIndex: segmentInfo.mediaIndex + 1,
  41515. endIndex: segmentIndex
  41516. });
  41517. }
  41518. this.discontinuities[discontinuity] = {
  41519. time: time,
  41520. accuracy: accuracy
  41521. };
  41522. }
  41523. }
  41524. }
  41525. };
  41526. _proto.dispose = function dispose() {
  41527. this.trigger('dispose');
  41528. this.off();
  41529. };
  41530. return SyncController;
  41531. }(videojs.EventTarget);
  41532. /**
  41533. * The TimelineChangeController acts as a source for segment loaders to listen for and
  41534. * keep track of latest and pending timeline changes. This is useful to ensure proper
  41535. * sync, as each loader may need to make a consideration for what timeline the other
  41536. * loader is on before making changes which could impact the other loader's media.
  41537. *
  41538. * @class TimelineChangeController
  41539. * @extends videojs.EventTarget
  41540. */
  41541. var TimelineChangeController = /*#__PURE__*/function (_videojs$EventTarget) {
  41542. _inheritsLoose(TimelineChangeController, _videojs$EventTarget);
  41543. function TimelineChangeController() {
  41544. var _this;
  41545. _this = _videojs$EventTarget.call(this) || this;
  41546. _this.pendingTimelineChanges_ = {};
  41547. _this.lastTimelineChanges_ = {};
  41548. return _this;
  41549. }
  41550. var _proto = TimelineChangeController.prototype;
  41551. _proto.clearPendingTimelineChange = function clearPendingTimelineChange(type) {
  41552. this.pendingTimelineChanges_[type] = null;
  41553. this.trigger('pendingtimelinechange');
  41554. };
  41555. _proto.pendingTimelineChange = function pendingTimelineChange(_ref) {
  41556. var type = _ref.type,
  41557. from = _ref.from,
  41558. to = _ref.to;
  41559. if (typeof from === 'number' && typeof to === 'number') {
  41560. this.pendingTimelineChanges_[type] = {
  41561. type: type,
  41562. from: from,
  41563. to: to
  41564. };
  41565. this.trigger('pendingtimelinechange');
  41566. }
  41567. return this.pendingTimelineChanges_[type];
  41568. };
  41569. _proto.lastTimelineChange = function lastTimelineChange(_ref2) {
  41570. var type = _ref2.type,
  41571. from = _ref2.from,
  41572. to = _ref2.to;
  41573. if (typeof from === 'number' && typeof to === 'number') {
  41574. this.lastTimelineChanges_[type] = {
  41575. type: type,
  41576. from: from,
  41577. to: to
  41578. };
  41579. delete this.pendingTimelineChanges_[type];
  41580. this.trigger('timelinechange');
  41581. }
  41582. return this.lastTimelineChanges_[type];
  41583. };
  41584. _proto.dispose = function dispose() {
  41585. this.trigger('dispose');
  41586. this.pendingTimelineChanges_ = {};
  41587. this.lastTimelineChanges_ = {};
  41588. this.off();
  41589. };
  41590. return TimelineChangeController;
  41591. }(videojs.EventTarget);
  41592. /* rollup-plugin-worker-factory start for worker!/Users/ddashkevich/projects/http-streaming/src/decrypter-worker.js */
  41593. var workerCode = transform(getWorkerString(function () {
  41594. var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
  41595. function createCommonjsModule(fn, basedir, module) {
  41596. return module = {
  41597. path: basedir,
  41598. exports: {},
  41599. require: function require(path, base) {
  41600. return commonjsRequire(path, base === undefined || base === null ? module.path : base);
  41601. }
  41602. }, fn(module, module.exports), module.exports;
  41603. }
  41604. function commonjsRequire() {
  41605. throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
  41606. }
  41607. var createClass = createCommonjsModule(function (module) {
  41608. function _defineProperties(target, props) {
  41609. for (var i = 0; i < props.length; i++) {
  41610. var descriptor = props[i];
  41611. descriptor.enumerable = descriptor.enumerable || false;
  41612. descriptor.configurable = true;
  41613. if ("value" in descriptor) descriptor.writable = true;
  41614. Object.defineProperty(target, descriptor.key, descriptor);
  41615. }
  41616. }
  41617. function _createClass(Constructor, protoProps, staticProps) {
  41618. if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  41619. if (staticProps) _defineProperties(Constructor, staticProps);
  41620. return Constructor;
  41621. }
  41622. module.exports = _createClass;
  41623. module.exports["default"] = module.exports, module.exports.__esModule = true;
  41624. });
  41625. var setPrototypeOf = createCommonjsModule(function (module) {
  41626. function _setPrototypeOf(o, p) {
  41627. module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
  41628. o.__proto__ = p;
  41629. return o;
  41630. };
  41631. module.exports["default"] = module.exports, module.exports.__esModule = true;
  41632. return _setPrototypeOf(o, p);
  41633. }
  41634. module.exports = _setPrototypeOf;
  41635. module.exports["default"] = module.exports, module.exports.__esModule = true;
  41636. });
  41637. var inheritsLoose = createCommonjsModule(function (module) {
  41638. function _inheritsLoose(subClass, superClass) {
  41639. subClass.prototype = Object.create(superClass.prototype);
  41640. subClass.prototype.constructor = subClass;
  41641. setPrototypeOf(subClass, superClass);
  41642. }
  41643. module.exports = _inheritsLoose;
  41644. module.exports["default"] = module.exports, module.exports.__esModule = true;
  41645. });
  41646. /**
  41647. * @file stream.js
  41648. */
  41649. /**
  41650. * A lightweight readable stream implemention that handles event dispatching.
  41651. *
  41652. * @class Stream
  41653. */
  41654. var Stream = /*#__PURE__*/function () {
  41655. function Stream() {
  41656. this.listeners = {};
  41657. }
  41658. /**
  41659. * Add a listener for a specified event type.
  41660. *
  41661. * @param {string} type the event name
  41662. * @param {Function} listener the callback to be invoked when an event of
  41663. * the specified type occurs
  41664. */
  41665. var _proto = Stream.prototype;
  41666. _proto.on = function on(type, listener) {
  41667. if (!this.listeners[type]) {
  41668. this.listeners[type] = [];
  41669. }
  41670. this.listeners[type].push(listener);
  41671. }
  41672. /**
  41673. * Remove a listener for a specified event type.
  41674. *
  41675. * @param {string} type the event name
  41676. * @param {Function} listener a function previously registered for this
  41677. * type of event through `on`
  41678. * @return {boolean} if we could turn it off or not
  41679. */
  41680. ;
  41681. _proto.off = function off(type, listener) {
  41682. if (!this.listeners[type]) {
  41683. return false;
  41684. }
  41685. var index = this.listeners[type].indexOf(listener); // TODO: which is better?
  41686. // In Video.js we slice listener functions
  41687. // on trigger so that it does not mess up the order
  41688. // while we loop through.
  41689. //
  41690. // Here we slice on off so that the loop in trigger
  41691. // can continue using it's old reference to loop without
  41692. // messing up the order.
  41693. this.listeners[type] = this.listeners[type].slice(0);
  41694. this.listeners[type].splice(index, 1);
  41695. return index > -1;
  41696. }
  41697. /**
  41698. * Trigger an event of the specified type on this stream. Any additional
  41699. * arguments to this function are passed as parameters to event listeners.
  41700. *
  41701. * @param {string} type the event name
  41702. */
  41703. ;
  41704. _proto.trigger = function trigger(type) {
  41705. var callbacks = this.listeners[type];
  41706. if (!callbacks) {
  41707. return;
  41708. } // Slicing the arguments on every invocation of this method
  41709. // can add a significant amount of overhead. Avoid the
  41710. // intermediate object creation for the common case of a
  41711. // single callback argument
  41712. if (arguments.length === 2) {
  41713. var length = callbacks.length;
  41714. for (var i = 0; i < length; ++i) {
  41715. callbacks[i].call(this, arguments[1]);
  41716. }
  41717. } else {
  41718. var args = Array.prototype.slice.call(arguments, 1);
  41719. var _length = callbacks.length;
  41720. for (var _i = 0; _i < _length; ++_i) {
  41721. callbacks[_i].apply(this, args);
  41722. }
  41723. }
  41724. }
  41725. /**
  41726. * Destroys the stream and cleans up.
  41727. */
  41728. ;
  41729. _proto.dispose = function dispose() {
  41730. this.listeners = {};
  41731. }
  41732. /**
  41733. * Forwards all `data` events on this stream to the destination stream. The
  41734. * destination stream should provide a method `push` to receive the data
  41735. * events as they arrive.
  41736. *
  41737. * @param {Stream} destination the stream that will receive all `data` events
  41738. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  41739. */
  41740. ;
  41741. _proto.pipe = function pipe(destination) {
  41742. this.on('data', function (data) {
  41743. destination.push(data);
  41744. });
  41745. };
  41746. return Stream;
  41747. }();
  41748. /*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
  41749. /**
  41750. * Returns the subarray of a Uint8Array without PKCS#7 padding.
  41751. *
  41752. * @param padded {Uint8Array} unencrypted bytes that have been padded
  41753. * @return {Uint8Array} the unpadded bytes
  41754. * @see http://tools.ietf.org/html/rfc5652
  41755. */
  41756. function unpad(padded) {
  41757. return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
  41758. }
  41759. /*! @name aes-decrypter @version 3.1.3 @license Apache-2.0 */
  41760. /**
  41761. * @file aes.js
  41762. *
  41763. * This file contains an adaptation of the AES decryption algorithm
  41764. * from the Standford Javascript Cryptography Library. That work is
  41765. * covered by the following copyright and permissions notice:
  41766. *
  41767. * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
  41768. * All rights reserved.
  41769. *
  41770. * Redistribution and use in source and binary forms, with or without
  41771. * modification, are permitted provided that the following conditions are
  41772. * met:
  41773. *
  41774. * 1. Redistributions of source code must retain the above copyright
  41775. * notice, this list of conditions and the following disclaimer.
  41776. *
  41777. * 2. Redistributions in binary form must reproduce the above
  41778. * copyright notice, this list of conditions and the following
  41779. * disclaimer in the documentation and/or other materials provided
  41780. * with the distribution.
  41781. *
  41782. * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
  41783. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  41784. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  41785. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
  41786. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  41787. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  41788. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  41789. * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  41790. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  41791. * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
  41792. * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  41793. *
  41794. * The views and conclusions contained in the software and documentation
  41795. * are those of the authors and should not be interpreted as representing
  41796. * official policies, either expressed or implied, of the authors.
  41797. */
  41798. /**
  41799. * Expand the S-box tables.
  41800. *
  41801. * @private
  41802. */
  41803. var precompute = function precompute() {
  41804. var tables = [[[], [], [], [], []], [[], [], [], [], []]];
  41805. var encTable = tables[0];
  41806. var decTable = tables[1];
  41807. var sbox = encTable[4];
  41808. var sboxInv = decTable[4];
  41809. var i;
  41810. var x;
  41811. var xInv;
  41812. var d = [];
  41813. var th = [];
  41814. var x2;
  41815. var x4;
  41816. var x8;
  41817. var s;
  41818. var tEnc;
  41819. var tDec; // Compute double and third tables
  41820. for (i = 0; i < 256; i++) {
  41821. th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
  41822. }
  41823. for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
  41824. // Compute sbox
  41825. s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
  41826. s = s >> 8 ^ s & 255 ^ 99;
  41827. sbox[x] = s;
  41828. sboxInv[s] = x; // Compute MixColumns
  41829. x8 = d[x4 = d[x2 = d[x]]];
  41830. tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
  41831. tEnc = d[s] * 0x101 ^ s * 0x1010100;
  41832. for (i = 0; i < 4; i++) {
  41833. encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
  41834. decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
  41835. }
  41836. } // Compactify. Considerable speedup on Firefox.
  41837. for (i = 0; i < 5; i++) {
  41838. encTable[i] = encTable[i].slice(0);
  41839. decTable[i] = decTable[i].slice(0);
  41840. }
  41841. return tables;
  41842. };
  41843. var aesTables = null;
  41844. /**
  41845. * Schedule out an AES key for both encryption and decryption. This
  41846. * is a low-level class. Use a cipher mode to do bulk encryption.
  41847. *
  41848. * @class AES
  41849. * @param key {Array} The key as an array of 4, 6 or 8 words.
  41850. */
  41851. var AES = /*#__PURE__*/function () {
  41852. function AES(key) {
  41853. /**
  41854. * The expanded S-box and inverse S-box tables. These will be computed
  41855. * on the client so that we don't have to send them down the wire.
  41856. *
  41857. * There are two tables, _tables[0] is for encryption and
  41858. * _tables[1] is for decryption.
  41859. *
  41860. * The first 4 sub-tables are the expanded S-box with MixColumns. The
  41861. * last (_tables[01][4]) is the S-box itself.
  41862. *
  41863. * @private
  41864. */
  41865. // if we have yet to precompute the S-box tables
  41866. // do so now
  41867. if (!aesTables) {
  41868. aesTables = precompute();
  41869. } // then make a copy of that object for use
  41870. this._tables = [[aesTables[0][0].slice(), aesTables[0][1].slice(), aesTables[0][2].slice(), aesTables[0][3].slice(), aesTables[0][4].slice()], [aesTables[1][0].slice(), aesTables[1][1].slice(), aesTables[1][2].slice(), aesTables[1][3].slice(), aesTables[1][4].slice()]];
  41871. var i;
  41872. var j;
  41873. var tmp;
  41874. var sbox = this._tables[0][4];
  41875. var decTable = this._tables[1];
  41876. var keyLen = key.length;
  41877. var rcon = 1;
  41878. if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
  41879. throw new Error('Invalid aes key size');
  41880. }
  41881. var encKey = key.slice(0);
  41882. var decKey = [];
  41883. this._key = [encKey, decKey]; // schedule encryption keys
  41884. for (i = keyLen; i < 4 * keyLen + 28; i++) {
  41885. tmp = encKey[i - 1]; // apply sbox
  41886. if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
  41887. tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
  41888. if (i % keyLen === 0) {
  41889. tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
  41890. rcon = rcon << 1 ^ (rcon >> 7) * 283;
  41891. }
  41892. }
  41893. encKey[i] = encKey[i - keyLen] ^ tmp;
  41894. } // schedule decryption keys
  41895. for (j = 0; i; j++, i--) {
  41896. tmp = encKey[j & 3 ? i : i - 4];
  41897. if (i <= 4 || j < 4) {
  41898. decKey[j] = tmp;
  41899. } else {
  41900. decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
  41901. }
  41902. }
  41903. }
  41904. /**
  41905. * Decrypt 16 bytes, specified as four 32-bit words.
  41906. *
  41907. * @param {number} encrypted0 the first word to decrypt
  41908. * @param {number} encrypted1 the second word to decrypt
  41909. * @param {number} encrypted2 the third word to decrypt
  41910. * @param {number} encrypted3 the fourth word to decrypt
  41911. * @param {Int32Array} out the array to write the decrypted words
  41912. * into
  41913. * @param {number} offset the offset into the output array to start
  41914. * writing results
  41915. * @return {Array} The plaintext.
  41916. */
  41917. var _proto = AES.prototype;
  41918. _proto.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
  41919. var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
  41920. var a = encrypted0 ^ key[0];
  41921. var b = encrypted3 ^ key[1];
  41922. var c = encrypted2 ^ key[2];
  41923. var d = encrypted1 ^ key[3];
  41924. var a2;
  41925. var b2;
  41926. var c2; // key.length === 2 ?
  41927. var nInnerRounds = key.length / 4 - 2;
  41928. var i;
  41929. var kIndex = 4;
  41930. var table = this._tables[1]; // load up the tables
  41931. var table0 = table[0];
  41932. var table1 = table[1];
  41933. var table2 = table[2];
  41934. var table3 = table[3];
  41935. var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
  41936. for (i = 0; i < nInnerRounds; i++) {
  41937. a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
  41938. b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
  41939. c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
  41940. d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
  41941. kIndex += 4;
  41942. a = a2;
  41943. b = b2;
  41944. c = c2;
  41945. } // Last round.
  41946. for (i = 0; i < 4; i++) {
  41947. out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
  41948. a2 = a;
  41949. a = b;
  41950. b = c;
  41951. c = d;
  41952. d = a2;
  41953. }
  41954. };
  41955. return AES;
  41956. }();
  41957. /**
  41958. * A wrapper around the Stream class to use setTimeout
  41959. * and run stream "jobs" Asynchronously
  41960. *
  41961. * @class AsyncStream
  41962. * @extends Stream
  41963. */
  41964. var AsyncStream = /*#__PURE__*/function (_Stream) {
  41965. inheritsLoose(AsyncStream, _Stream);
  41966. function AsyncStream() {
  41967. var _this;
  41968. _this = _Stream.call(this, Stream) || this;
  41969. _this.jobs = [];
  41970. _this.delay = 1;
  41971. _this.timeout_ = null;
  41972. return _this;
  41973. }
  41974. /**
  41975. * process an async job
  41976. *
  41977. * @private
  41978. */
  41979. var _proto = AsyncStream.prototype;
  41980. _proto.processJob_ = function processJob_() {
  41981. this.jobs.shift()();
  41982. if (this.jobs.length) {
  41983. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  41984. } else {
  41985. this.timeout_ = null;
  41986. }
  41987. }
  41988. /**
  41989. * push a job into the stream
  41990. *
  41991. * @param {Function} job the job to push into the stream
  41992. */
  41993. ;
  41994. _proto.push = function push(job) {
  41995. this.jobs.push(job);
  41996. if (!this.timeout_) {
  41997. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  41998. }
  41999. };
  42000. return AsyncStream;
  42001. }(Stream);
  42002. /**
  42003. * Convert network-order (big-endian) bytes into their little-endian
  42004. * representation.
  42005. */
  42006. var ntoh = function ntoh(word) {
  42007. return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
  42008. };
  42009. /**
  42010. * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
  42011. *
  42012. * @param {Uint8Array} encrypted the encrypted bytes
  42013. * @param {Uint32Array} key the bytes of the decryption key
  42014. * @param {Uint32Array} initVector the initialization vector (IV) to
  42015. * use for the first round of CBC.
  42016. * @return {Uint8Array} the decrypted bytes
  42017. *
  42018. * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
  42019. * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
  42020. * @see https://tools.ietf.org/html/rfc2315
  42021. */
  42022. var decrypt = function decrypt(encrypted, key, initVector) {
  42023. // word-level access to the encrypted bytes
  42024. var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
  42025. var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
  42026. var decrypted = new Uint8Array(encrypted.byteLength);
  42027. var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
  42028. // decrypted data
  42029. var init0;
  42030. var init1;
  42031. var init2;
  42032. var init3;
  42033. var encrypted0;
  42034. var encrypted1;
  42035. var encrypted2;
  42036. var encrypted3; // iteration variable
  42037. var wordIx; // pull out the words of the IV to ensure we don't modify the
  42038. // passed-in reference and easier access
  42039. init0 = initVector[0];
  42040. init1 = initVector[1];
  42041. init2 = initVector[2];
  42042. init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
  42043. // to each decrypted block
  42044. for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
  42045. // convert big-endian (network order) words into little-endian
  42046. // (javascript order)
  42047. encrypted0 = ntoh(encrypted32[wordIx]);
  42048. encrypted1 = ntoh(encrypted32[wordIx + 1]);
  42049. encrypted2 = ntoh(encrypted32[wordIx + 2]);
  42050. encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
  42051. decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
  42052. // plaintext
  42053. decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
  42054. decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
  42055. decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
  42056. decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
  42057. init0 = encrypted0;
  42058. init1 = encrypted1;
  42059. init2 = encrypted2;
  42060. init3 = encrypted3;
  42061. }
  42062. return decrypted;
  42063. };
  42064. /**
  42065. * The `Decrypter` class that manages decryption of AES
  42066. * data through `AsyncStream` objects and the `decrypt`
  42067. * function
  42068. *
  42069. * @param {Uint8Array} encrypted the encrypted bytes
  42070. * @param {Uint32Array} key the bytes of the decryption key
  42071. * @param {Uint32Array} initVector the initialization vector (IV) to
  42072. * @param {Function} done the function to run when done
  42073. * @class Decrypter
  42074. */
  42075. var Decrypter = /*#__PURE__*/function () {
  42076. function Decrypter(encrypted, key, initVector, done) {
  42077. var step = Decrypter.STEP;
  42078. var encrypted32 = new Int32Array(encrypted.buffer);
  42079. var decrypted = new Uint8Array(encrypted.byteLength);
  42080. var i = 0;
  42081. this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
  42082. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  42083. for (i = step; i < encrypted32.length; i += step) {
  42084. initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
  42085. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  42086. } // invoke the done() callback when everything is finished
  42087. this.asyncStream_.push(function () {
  42088. // remove pkcs#7 padding from the decrypted bytes
  42089. done(null, unpad(decrypted));
  42090. });
  42091. }
  42092. /**
  42093. * a getter for step the maximum number of bytes to process at one time
  42094. *
  42095. * @return {number} the value of step 32000
  42096. */
  42097. var _proto = Decrypter.prototype;
  42098. /**
  42099. * @private
  42100. */
  42101. _proto.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
  42102. return function () {
  42103. var bytes = decrypt(encrypted, key, initVector);
  42104. decrypted.set(bytes, encrypted.byteOffset);
  42105. };
  42106. };
  42107. createClass(Decrypter, null, [{
  42108. key: "STEP",
  42109. get: function get() {
  42110. // 4 * 8000;
  42111. return 32000;
  42112. }
  42113. }]);
  42114. return Decrypter;
  42115. }();
  42116. var win;
  42117. if (typeof window !== "undefined") {
  42118. win = window;
  42119. } else if (typeof commonjsGlobal !== "undefined") {
  42120. win = commonjsGlobal;
  42121. } else if (typeof self !== "undefined") {
  42122. win = self;
  42123. } else {
  42124. win = {};
  42125. }
  42126. var window_1 = win;
  42127. var isArrayBufferView = function isArrayBufferView(obj) {
  42128. if (ArrayBuffer.isView === 'function') {
  42129. return ArrayBuffer.isView(obj);
  42130. }
  42131. return obj && obj.buffer instanceof ArrayBuffer;
  42132. };
  42133. var BigInt = window_1.BigInt || Number;
  42134. [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
  42135. /**
  42136. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  42137. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  42138. *
  42139. * @param {Object} message
  42140. * Object of properties and values to send to the web worker
  42141. * @return {Object}
  42142. * Modified message with TypedArray values expanded
  42143. * @function createTransferableMessage
  42144. */
  42145. var createTransferableMessage = function createTransferableMessage(message) {
  42146. var transferable = {};
  42147. Object.keys(message).forEach(function (key) {
  42148. var value = message[key];
  42149. if (isArrayBufferView(value)) {
  42150. transferable[key] = {
  42151. bytes: value.buffer,
  42152. byteOffset: value.byteOffset,
  42153. byteLength: value.byteLength
  42154. };
  42155. } else {
  42156. transferable[key] = value;
  42157. }
  42158. });
  42159. return transferable;
  42160. };
  42161. /* global self */
  42162. /**
  42163. * Our web worker interface so that things can talk to aes-decrypter
  42164. * that will be running in a web worker. the scope is passed to this by
  42165. * webworkify.
  42166. */
  42167. self.onmessage = function (event) {
  42168. var data = event.data;
  42169. var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
  42170. var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
  42171. var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
  42172. /* eslint-disable no-new, handle-callback-err */
  42173. new Decrypter(encrypted, key, iv, function (err, bytes) {
  42174. self.postMessage(createTransferableMessage({
  42175. source: data.source,
  42176. decrypted: bytes
  42177. }), [bytes.buffer]);
  42178. });
  42179. /* eslint-enable */
  42180. };
  42181. }));
  42182. var Decrypter = factory(workerCode);
  42183. /* rollup-plugin-worker-factory end for worker!/Users/ddashkevich/projects/http-streaming/src/decrypter-worker.js */
  42184. /**
  42185. * Convert the properties of an HLS track into an audioTrackKind.
  42186. *
  42187. * @private
  42188. */
  42189. var audioTrackKind_ = function audioTrackKind_(properties) {
  42190. var kind = properties["default"] ? 'main' : 'alternative';
  42191. if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
  42192. kind = 'main-desc';
  42193. }
  42194. return kind;
  42195. };
  42196. /**
  42197. * Pause provided segment loader and playlist loader if active
  42198. *
  42199. * @param {SegmentLoader} segmentLoader
  42200. * SegmentLoader to pause
  42201. * @param {Object} mediaType
  42202. * Active media type
  42203. * @function stopLoaders
  42204. */
  42205. var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
  42206. segmentLoader.abort();
  42207. segmentLoader.pause();
  42208. if (mediaType && mediaType.activePlaylistLoader) {
  42209. mediaType.activePlaylistLoader.pause();
  42210. mediaType.activePlaylistLoader = null;
  42211. }
  42212. };
  42213. /**
  42214. * Start loading provided segment loader and playlist loader
  42215. *
  42216. * @param {PlaylistLoader} playlistLoader
  42217. * PlaylistLoader to start loading
  42218. * @param {Object} mediaType
  42219. * Active media type
  42220. * @function startLoaders
  42221. */
  42222. var startLoaders = function startLoaders(playlistLoader, mediaType) {
  42223. // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
  42224. // playlist loader
  42225. mediaType.activePlaylistLoader = playlistLoader;
  42226. playlistLoader.load();
  42227. };
  42228. /**
  42229. * Returns a function to be called when the media group changes. It performs a
  42230. * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
  42231. * change of group is merely a rendition switch of the same content at another encoding,
  42232. * rather than a change of content, such as switching audio from English to Spanish.
  42233. *
  42234. * @param {string} type
  42235. * MediaGroup type
  42236. * @param {Object} settings
  42237. * Object containing required information for media groups
  42238. * @return {Function}
  42239. * Handler for a non-destructive resync of SegmentLoader when the active media
  42240. * group changes.
  42241. * @function onGroupChanged
  42242. */
  42243. var onGroupChanged = function onGroupChanged(type, settings) {
  42244. return function () {
  42245. var _settings$segmentLoad = settings.segmentLoaders,
  42246. segmentLoader = _settings$segmentLoad[type],
  42247. mainSegmentLoader = _settings$segmentLoad.main,
  42248. mediaType = settings.mediaTypes[type];
  42249. var activeTrack = mediaType.activeTrack();
  42250. var activeGroup = mediaType.getActiveGroup();
  42251. var previousActiveLoader = mediaType.activePlaylistLoader;
  42252. var lastGroup = mediaType.lastGroup_; // the group did not change do nothing
  42253. if (activeGroup && lastGroup && activeGroup.id === lastGroup.id) {
  42254. return;
  42255. }
  42256. mediaType.lastGroup_ = activeGroup;
  42257. mediaType.lastTrack_ = activeTrack;
  42258. stopLoaders(segmentLoader, mediaType);
  42259. if (!activeGroup || activeGroup.isMasterPlaylist) {
  42260. // there is no group active or active group is a main playlist and won't change
  42261. return;
  42262. }
  42263. if (!activeGroup.playlistLoader) {
  42264. if (previousActiveLoader) {
  42265. // The previous group had a playlist loader but the new active group does not
  42266. // this means we are switching from demuxed to muxed audio. In this case we want to
  42267. // do a destructive reset of the main segment loader and not restart the audio
  42268. // loaders.
  42269. mainSegmentLoader.resetEverything();
  42270. }
  42271. return;
  42272. } // Non-destructive resync
  42273. segmentLoader.resyncLoader();
  42274. startLoaders(activeGroup.playlistLoader, mediaType);
  42275. };
  42276. };
  42277. var onGroupChanging = function onGroupChanging(type, settings) {
  42278. return function () {
  42279. var segmentLoader = settings.segmentLoaders[type],
  42280. mediaType = settings.mediaTypes[type];
  42281. mediaType.lastGroup_ = null;
  42282. segmentLoader.abort();
  42283. segmentLoader.pause();
  42284. };
  42285. };
  42286. /**
  42287. * Returns a function to be called when the media track changes. It performs a
  42288. * destructive reset of the SegmentLoader to ensure we start loading as close to
  42289. * currentTime as possible.
  42290. *
  42291. * @param {string} type
  42292. * MediaGroup type
  42293. * @param {Object} settings
  42294. * Object containing required information for media groups
  42295. * @return {Function}
  42296. * Handler for a destructive reset of SegmentLoader when the active media
  42297. * track changes.
  42298. * @function onTrackChanged
  42299. */
  42300. var onTrackChanged = function onTrackChanged(type, settings) {
  42301. return function () {
  42302. var masterPlaylistLoader = settings.masterPlaylistLoader,
  42303. _settings$segmentLoad2 = settings.segmentLoaders,
  42304. segmentLoader = _settings$segmentLoad2[type],
  42305. mainSegmentLoader = _settings$segmentLoad2.main,
  42306. mediaType = settings.mediaTypes[type];
  42307. var activeTrack = mediaType.activeTrack();
  42308. var activeGroup = mediaType.getActiveGroup();
  42309. var previousActiveLoader = mediaType.activePlaylistLoader;
  42310. var lastTrack = mediaType.lastTrack_; // track did not change, do nothing
  42311. if (lastTrack && activeTrack && lastTrack.id === activeTrack.id) {
  42312. return;
  42313. }
  42314. mediaType.lastGroup_ = activeGroup;
  42315. mediaType.lastTrack_ = activeTrack;
  42316. stopLoaders(segmentLoader, mediaType);
  42317. if (!activeGroup) {
  42318. // there is no group active so we do not want to restart loaders
  42319. return;
  42320. }
  42321. if (activeGroup.isMasterPlaylist) {
  42322. // track did not change, do nothing
  42323. if (!activeTrack || !lastTrack || activeTrack.id === lastTrack.id) {
  42324. return;
  42325. }
  42326. var mpc = settings.vhs.masterPlaylistController_;
  42327. var newPlaylist = mpc.selectPlaylist(); // media will not change do nothing
  42328. if (mpc.media() === newPlaylist) {
  42329. return;
  42330. }
  42331. mediaType.logger_("track change. Switching master audio from " + lastTrack.id + " to " + activeTrack.id);
  42332. masterPlaylistLoader.pause();
  42333. mainSegmentLoader.resetEverything();
  42334. mpc.fastQualityChange_(newPlaylist);
  42335. return;
  42336. }
  42337. if (type === 'AUDIO') {
  42338. if (!activeGroup.playlistLoader) {
  42339. // when switching from demuxed audio/video to muxed audio/video (noted by no
  42340. // playlist loader for the audio group), we want to do a destructive reset of the
  42341. // main segment loader and not restart the audio loaders
  42342. mainSegmentLoader.setAudio(true); // don't have to worry about disabling the audio of the audio segment loader since
  42343. // it should be stopped
  42344. mainSegmentLoader.resetEverything();
  42345. return;
  42346. } // although the segment loader is an audio segment loader, call the setAudio
  42347. // function to ensure it is prepared to re-append the init segment (or handle other
  42348. // config changes)
  42349. segmentLoader.setAudio(true);
  42350. mainSegmentLoader.setAudio(false);
  42351. }
  42352. if (previousActiveLoader === activeGroup.playlistLoader) {
  42353. // Nothing has actually changed. This can happen because track change events can fire
  42354. // multiple times for a "single" change. One for enabling the new active track, and
  42355. // one for disabling the track that was active
  42356. startLoaders(activeGroup.playlistLoader, mediaType);
  42357. return;
  42358. }
  42359. if (segmentLoader.track) {
  42360. // For WebVTT, set the new text track in the segmentloader
  42361. segmentLoader.track(activeTrack);
  42362. } // destructive reset
  42363. segmentLoader.resetEverything();
  42364. startLoaders(activeGroup.playlistLoader, mediaType);
  42365. };
  42366. };
  42367. var onError = {
  42368. /**
  42369. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  42370. * an error.
  42371. *
  42372. * @param {string} type
  42373. * MediaGroup type
  42374. * @param {Object} settings
  42375. * Object containing required information for media groups
  42376. * @return {Function}
  42377. * Error handler. Logs warning (or error if the playlist is blacklisted) to
  42378. * console and switches back to default audio track.
  42379. * @function onError.AUDIO
  42380. */
  42381. AUDIO: function AUDIO(type, settings) {
  42382. return function () {
  42383. var segmentLoader = settings.segmentLoaders[type],
  42384. mediaType = settings.mediaTypes[type],
  42385. blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
  42386. stopLoaders(segmentLoader, mediaType); // switch back to default audio track
  42387. var activeTrack = mediaType.activeTrack();
  42388. var activeGroup = mediaType.activeGroup();
  42389. var id = (activeGroup.filter(function (group) {
  42390. return group["default"];
  42391. })[0] || activeGroup[0]).id;
  42392. var defaultTrack = mediaType.tracks[id];
  42393. if (activeTrack === defaultTrack) {
  42394. // Default track encountered an error. All we can do now is blacklist the current
  42395. // rendition and hope another will switch audio groups
  42396. blacklistCurrentPlaylist({
  42397. message: 'Problem encountered loading the default audio track.'
  42398. });
  42399. return;
  42400. }
  42401. videojs.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
  42402. for (var trackId in mediaType.tracks) {
  42403. mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
  42404. }
  42405. mediaType.onTrackChanged();
  42406. };
  42407. },
  42408. /**
  42409. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  42410. * an error.
  42411. *
  42412. * @param {string} type
  42413. * MediaGroup type
  42414. * @param {Object} settings
  42415. * Object containing required information for media groups
  42416. * @return {Function}
  42417. * Error handler. Logs warning to console and disables the active subtitle track
  42418. * @function onError.SUBTITLES
  42419. */
  42420. SUBTITLES: function SUBTITLES(type, settings) {
  42421. return function () {
  42422. var segmentLoader = settings.segmentLoaders[type],
  42423. mediaType = settings.mediaTypes[type];
  42424. videojs.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
  42425. stopLoaders(segmentLoader, mediaType);
  42426. var track = mediaType.activeTrack();
  42427. if (track) {
  42428. track.mode = 'disabled';
  42429. }
  42430. mediaType.onTrackChanged();
  42431. };
  42432. }
  42433. };
  42434. var setupListeners = {
  42435. /**
  42436. * Setup event listeners for audio playlist loader
  42437. *
  42438. * @param {string} type
  42439. * MediaGroup type
  42440. * @param {PlaylistLoader|null} playlistLoader
  42441. * PlaylistLoader to register listeners on
  42442. * @param {Object} settings
  42443. * Object containing required information for media groups
  42444. * @function setupListeners.AUDIO
  42445. */
  42446. AUDIO: function AUDIO(type, playlistLoader, settings) {
  42447. if (!playlistLoader) {
  42448. // no playlist loader means audio will be muxed with the video
  42449. return;
  42450. }
  42451. var tech = settings.tech,
  42452. requestOptions = settings.requestOptions,
  42453. segmentLoader = settings.segmentLoaders[type];
  42454. playlistLoader.on('loadedmetadata', function () {
  42455. var media = playlistLoader.media();
  42456. segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
  42457. // permits, start downloading segments
  42458. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  42459. segmentLoader.load();
  42460. }
  42461. });
  42462. playlistLoader.on('loadedplaylist', function () {
  42463. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  42464. if (!tech.paused()) {
  42465. segmentLoader.load();
  42466. }
  42467. });
  42468. playlistLoader.on('error', onError[type](type, settings));
  42469. },
  42470. /**
  42471. * Setup event listeners for subtitle playlist loader
  42472. *
  42473. * @param {string} type
  42474. * MediaGroup type
  42475. * @param {PlaylistLoader|null} playlistLoader
  42476. * PlaylistLoader to register listeners on
  42477. * @param {Object} settings
  42478. * Object containing required information for media groups
  42479. * @function setupListeners.SUBTITLES
  42480. */
  42481. SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
  42482. var tech = settings.tech,
  42483. requestOptions = settings.requestOptions,
  42484. segmentLoader = settings.segmentLoaders[type],
  42485. mediaType = settings.mediaTypes[type];
  42486. playlistLoader.on('loadedmetadata', function () {
  42487. var media = playlistLoader.media();
  42488. segmentLoader.playlist(media, requestOptions);
  42489. segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
  42490. // permits, start downloading segments
  42491. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  42492. segmentLoader.load();
  42493. }
  42494. });
  42495. playlistLoader.on('loadedplaylist', function () {
  42496. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  42497. if (!tech.paused()) {
  42498. segmentLoader.load();
  42499. }
  42500. });
  42501. playlistLoader.on('error', onError[type](type, settings));
  42502. }
  42503. };
  42504. var initialize = {
  42505. /**
  42506. * Setup PlaylistLoaders and AudioTracks for the audio groups
  42507. *
  42508. * @param {string} type
  42509. * MediaGroup type
  42510. * @param {Object} settings
  42511. * Object containing required information for media groups
  42512. * @function initialize.AUDIO
  42513. */
  42514. 'AUDIO': function AUDIO(type, settings) {
  42515. var vhs = settings.vhs,
  42516. sourceType = settings.sourceType,
  42517. segmentLoader = settings.segmentLoaders[type],
  42518. requestOptions = settings.requestOptions,
  42519. mediaGroups = settings.master.mediaGroups,
  42520. _settings$mediaTypes$ = settings.mediaTypes[type],
  42521. groups = _settings$mediaTypes$.groups,
  42522. tracks = _settings$mediaTypes$.tracks,
  42523. logger_ = _settings$mediaTypes$.logger_,
  42524. masterPlaylistLoader = settings.masterPlaylistLoader;
  42525. var audioOnlyMaster = isAudioOnly(masterPlaylistLoader.master); // force a default if we have none
  42526. if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
  42527. mediaGroups[type] = {
  42528. main: {
  42529. "default": {
  42530. "default": true
  42531. }
  42532. }
  42533. };
  42534. if (audioOnlyMaster) {
  42535. mediaGroups[type].main["default"].playlists = masterPlaylistLoader.master.playlists;
  42536. }
  42537. }
  42538. for (var groupId in mediaGroups[type]) {
  42539. if (!groups[groupId]) {
  42540. groups[groupId] = [];
  42541. }
  42542. for (var variantLabel in mediaGroups[type][groupId]) {
  42543. var properties = mediaGroups[type][groupId][variantLabel];
  42544. var playlistLoader = void 0;
  42545. if (audioOnlyMaster) {
  42546. logger_("AUDIO group '" + groupId + "' label '" + variantLabel + "' is a master playlist");
  42547. properties.isMasterPlaylist = true;
  42548. playlistLoader = null; // if vhs-json was provided as the source, and the media playlist was resolved,
  42549. // use the resolved media playlist object
  42550. } else if (sourceType === 'vhs-json' && properties.playlists) {
  42551. playlistLoader = new PlaylistLoader(properties.playlists[0], vhs, requestOptions);
  42552. } else if (properties.resolvedUri) {
  42553. playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions); // TODO: dash isn't the only type with properties.playlists
  42554. // should we even have properties.playlists in this check.
  42555. } else if (properties.playlists && sourceType === 'dash') {
  42556. playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, masterPlaylistLoader);
  42557. } else {
  42558. // no resolvedUri means the audio is muxed with the video when using this
  42559. // audio track
  42560. playlistLoader = null;
  42561. }
  42562. properties = videojs.mergeOptions({
  42563. id: variantLabel,
  42564. playlistLoader: playlistLoader
  42565. }, properties);
  42566. setupListeners[type](type, properties.playlistLoader, settings);
  42567. groups[groupId].push(properties);
  42568. if (typeof tracks[variantLabel] === 'undefined') {
  42569. var track = new videojs.AudioTrack({
  42570. id: variantLabel,
  42571. kind: audioTrackKind_(properties),
  42572. enabled: false,
  42573. language: properties.language,
  42574. "default": properties["default"],
  42575. label: variantLabel
  42576. });
  42577. tracks[variantLabel] = track;
  42578. }
  42579. }
  42580. } // setup single error event handler for the segment loader
  42581. segmentLoader.on('error', onError[type](type, settings));
  42582. },
  42583. /**
  42584. * Setup PlaylistLoaders and TextTracks for the subtitle groups
  42585. *
  42586. * @param {string} type
  42587. * MediaGroup type
  42588. * @param {Object} settings
  42589. * Object containing required information for media groups
  42590. * @function initialize.SUBTITLES
  42591. */
  42592. 'SUBTITLES': function SUBTITLES(type, settings) {
  42593. var tech = settings.tech,
  42594. vhs = settings.vhs,
  42595. sourceType = settings.sourceType,
  42596. segmentLoader = settings.segmentLoaders[type],
  42597. requestOptions = settings.requestOptions,
  42598. mediaGroups = settings.master.mediaGroups,
  42599. _settings$mediaTypes$2 = settings.mediaTypes[type],
  42600. groups = _settings$mediaTypes$2.groups,
  42601. tracks = _settings$mediaTypes$2.tracks,
  42602. masterPlaylistLoader = settings.masterPlaylistLoader;
  42603. for (var groupId in mediaGroups[type]) {
  42604. if (!groups[groupId]) {
  42605. groups[groupId] = [];
  42606. }
  42607. for (var variantLabel in mediaGroups[type][groupId]) {
  42608. if (mediaGroups[type][groupId][variantLabel].forced) {
  42609. // Subtitle playlists with the forced attribute are not selectable in Safari.
  42610. // According to Apple's HLS Authoring Specification:
  42611. // If content has forced subtitles and regular subtitles in a given language,
  42612. // the regular subtitles track in that language MUST contain both the forced
  42613. // subtitles and the regular subtitles for that language.
  42614. // Because of this requirement and that Safari does not add forced subtitles,
  42615. // forced subtitles are skipped here to maintain consistent experience across
  42616. // all platforms
  42617. continue;
  42618. }
  42619. var properties = mediaGroups[type][groupId][variantLabel];
  42620. var playlistLoader = void 0;
  42621. if (sourceType === 'hls') {
  42622. playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions);
  42623. } else if (sourceType === 'dash') {
  42624. var playlists = properties.playlists.filter(function (p) {
  42625. return p.excludeUntil !== Infinity;
  42626. });
  42627. if (!playlists.length) {
  42628. return;
  42629. }
  42630. playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, masterPlaylistLoader);
  42631. } else if (sourceType === 'vhs-json') {
  42632. playlistLoader = new PlaylistLoader( // if the vhs-json object included the media playlist, use the media playlist
  42633. // as provided, otherwise use the resolved URI to load the playlist
  42634. properties.playlists ? properties.playlists[0] : properties.resolvedUri, vhs, requestOptions);
  42635. }
  42636. properties = videojs.mergeOptions({
  42637. id: variantLabel,
  42638. playlistLoader: playlistLoader
  42639. }, properties);
  42640. setupListeners[type](type, properties.playlistLoader, settings);
  42641. groups[groupId].push(properties);
  42642. if (typeof tracks[variantLabel] === 'undefined') {
  42643. var track = tech.addRemoteTextTrack({
  42644. id: variantLabel,
  42645. kind: 'subtitles',
  42646. "default": properties["default"] && properties.autoselect,
  42647. language: properties.language,
  42648. label: variantLabel
  42649. }, false).track;
  42650. tracks[variantLabel] = track;
  42651. }
  42652. }
  42653. } // setup single error event handler for the segment loader
  42654. segmentLoader.on('error', onError[type](type, settings));
  42655. },
  42656. /**
  42657. * Setup TextTracks for the closed-caption groups
  42658. *
  42659. * @param {String} type
  42660. * MediaGroup type
  42661. * @param {Object} settings
  42662. * Object containing required information for media groups
  42663. * @function initialize['CLOSED-CAPTIONS']
  42664. */
  42665. 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
  42666. var tech = settings.tech,
  42667. mediaGroups = settings.master.mediaGroups,
  42668. _settings$mediaTypes$3 = settings.mediaTypes[type],
  42669. groups = _settings$mediaTypes$3.groups,
  42670. tracks = _settings$mediaTypes$3.tracks;
  42671. for (var groupId in mediaGroups[type]) {
  42672. if (!groups[groupId]) {
  42673. groups[groupId] = [];
  42674. }
  42675. for (var variantLabel in mediaGroups[type][groupId]) {
  42676. var properties = mediaGroups[type][groupId][variantLabel]; // Look for either 608 (CCn) or 708 (SERVICEn) caption services
  42677. if (!/^(?:CC|SERVICE)/.test(properties.instreamId)) {
  42678. continue;
  42679. }
  42680. var captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
  42681. var newProps = {
  42682. label: variantLabel,
  42683. language: properties.language,
  42684. instreamId: properties.instreamId,
  42685. "default": properties["default"] && properties.autoselect
  42686. };
  42687. if (captionServices[newProps.instreamId]) {
  42688. newProps = videojs.mergeOptions(newProps, captionServices[newProps.instreamId]);
  42689. }
  42690. if (newProps["default"] === undefined) {
  42691. delete newProps["default"];
  42692. } // No PlaylistLoader is required for Closed-Captions because the captions are
  42693. // embedded within the video stream
  42694. groups[groupId].push(videojs.mergeOptions({
  42695. id: variantLabel
  42696. }, properties));
  42697. if (typeof tracks[variantLabel] === 'undefined') {
  42698. var track = tech.addRemoteTextTrack({
  42699. id: newProps.instreamId,
  42700. kind: 'captions',
  42701. "default": newProps["default"],
  42702. language: newProps.language,
  42703. label: newProps.label
  42704. }, false).track;
  42705. tracks[variantLabel] = track;
  42706. }
  42707. }
  42708. }
  42709. }
  42710. };
  42711. var groupMatch = function groupMatch(list, media) {
  42712. for (var i = 0; i < list.length; i++) {
  42713. if (playlistMatch(media, list[i])) {
  42714. return true;
  42715. }
  42716. if (list[i].playlists && groupMatch(list[i].playlists, media)) {
  42717. return true;
  42718. }
  42719. }
  42720. return false;
  42721. };
  42722. /**
  42723. * Returns a function used to get the active group of the provided type
  42724. *
  42725. * @param {string} type
  42726. * MediaGroup type
  42727. * @param {Object} settings
  42728. * Object containing required information for media groups
  42729. * @return {Function}
  42730. * Function that returns the active media group for the provided type. Takes an
  42731. * optional parameter {TextTrack} track. If no track is provided, a list of all
  42732. * variants in the group, otherwise the variant corresponding to the provided
  42733. * track is returned.
  42734. * @function activeGroup
  42735. */
  42736. var activeGroup = function activeGroup(type, settings) {
  42737. return function (track) {
  42738. var masterPlaylistLoader = settings.masterPlaylistLoader,
  42739. groups = settings.mediaTypes[type].groups;
  42740. var media = masterPlaylistLoader.media();
  42741. if (!media) {
  42742. return null;
  42743. }
  42744. var variants = null; // set to variants to main media active group
  42745. if (media.attributes[type]) {
  42746. variants = groups[media.attributes[type]];
  42747. }
  42748. var groupKeys = Object.keys(groups);
  42749. if (!variants) {
  42750. // find the masterPlaylistLoader media
  42751. // that is in a media group if we are dealing
  42752. // with audio only
  42753. if (type === 'AUDIO' && groupKeys.length > 1 && isAudioOnly(settings.master)) {
  42754. for (var i = 0; i < groupKeys.length; i++) {
  42755. var groupPropertyList = groups[groupKeys[i]];
  42756. if (groupMatch(groupPropertyList, media)) {
  42757. variants = groupPropertyList;
  42758. break;
  42759. }
  42760. } // use the main group if it exists
  42761. } else if (groups.main) {
  42762. variants = groups.main; // only one group, use that one
  42763. } else if (groupKeys.length === 1) {
  42764. variants = groups[groupKeys[0]];
  42765. }
  42766. }
  42767. if (typeof track === 'undefined') {
  42768. return variants;
  42769. }
  42770. if (track === null || !variants) {
  42771. // An active track was specified so a corresponding group is expected. track === null
  42772. // means no track is currently active so there is no corresponding group
  42773. return null;
  42774. }
  42775. return variants.filter(function (props) {
  42776. return props.id === track.id;
  42777. })[0] || null;
  42778. };
  42779. };
  42780. var activeTrack = {
  42781. /**
  42782. * Returns a function used to get the active track of type provided
  42783. *
  42784. * @param {string} type
  42785. * MediaGroup type
  42786. * @param {Object} settings
  42787. * Object containing required information for media groups
  42788. * @return {Function}
  42789. * Function that returns the active media track for the provided type. Returns
  42790. * null if no track is active
  42791. * @function activeTrack.AUDIO
  42792. */
  42793. AUDIO: function AUDIO(type, settings) {
  42794. return function () {
  42795. var tracks = settings.mediaTypes[type].tracks;
  42796. for (var id in tracks) {
  42797. if (tracks[id].enabled) {
  42798. return tracks[id];
  42799. }
  42800. }
  42801. return null;
  42802. };
  42803. },
  42804. /**
  42805. * Returns a function used to get the active track of type provided
  42806. *
  42807. * @param {string} type
  42808. * MediaGroup type
  42809. * @param {Object} settings
  42810. * Object containing required information for media groups
  42811. * @return {Function}
  42812. * Function that returns the active media track for the provided type. Returns
  42813. * null if no track is active
  42814. * @function activeTrack.SUBTITLES
  42815. */
  42816. SUBTITLES: function SUBTITLES(type, settings) {
  42817. return function () {
  42818. var tracks = settings.mediaTypes[type].tracks;
  42819. for (var id in tracks) {
  42820. if (tracks[id].mode === 'showing' || tracks[id].mode === 'hidden') {
  42821. return tracks[id];
  42822. }
  42823. }
  42824. return null;
  42825. };
  42826. }
  42827. };
  42828. var getActiveGroup = function getActiveGroup(type, _ref) {
  42829. var mediaTypes = _ref.mediaTypes;
  42830. return function () {
  42831. var activeTrack_ = mediaTypes[type].activeTrack();
  42832. if (!activeTrack_) {
  42833. return null;
  42834. }
  42835. return mediaTypes[type].activeGroup(activeTrack_);
  42836. };
  42837. };
  42838. /**
  42839. * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
  42840. * Closed-Captions) specified in the master manifest.
  42841. *
  42842. * @param {Object} settings
  42843. * Object containing required information for setting up the media groups
  42844. * @param {Tech} settings.tech
  42845. * The tech of the player
  42846. * @param {Object} settings.requestOptions
  42847. * XHR request options used by the segment loaders
  42848. * @param {PlaylistLoader} settings.masterPlaylistLoader
  42849. * PlaylistLoader for the master source
  42850. * @param {VhsHandler} settings.vhs
  42851. * VHS SourceHandler
  42852. * @param {Object} settings.master
  42853. * The parsed master manifest
  42854. * @param {Object} settings.mediaTypes
  42855. * Object to store the loaders, tracks, and utility methods for each media type
  42856. * @param {Function} settings.blacklistCurrentPlaylist
  42857. * Blacklists the current rendition and forces a rendition switch.
  42858. * @function setupMediaGroups
  42859. */
  42860. var setupMediaGroups = function setupMediaGroups(settings) {
  42861. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  42862. initialize[type](type, settings);
  42863. });
  42864. var mediaTypes = settings.mediaTypes,
  42865. masterPlaylistLoader = settings.masterPlaylistLoader,
  42866. tech = settings.tech,
  42867. vhs = settings.vhs,
  42868. _settings$segmentLoad3 = settings.segmentLoaders,
  42869. audioSegmentLoader = _settings$segmentLoad3['AUDIO'],
  42870. mainSegmentLoader = _settings$segmentLoad3.main; // setup active group and track getters and change event handlers
  42871. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  42872. mediaTypes[type].activeGroup = activeGroup(type, settings);
  42873. mediaTypes[type].activeTrack = activeTrack[type](type, settings);
  42874. mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
  42875. mediaTypes[type].onGroupChanging = onGroupChanging(type, settings);
  42876. mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
  42877. mediaTypes[type].getActiveGroup = getActiveGroup(type, settings);
  42878. }); // DO NOT enable the default subtitle or caption track.
  42879. // DO enable the default audio track
  42880. var audioGroup = mediaTypes.AUDIO.activeGroup();
  42881. if (audioGroup) {
  42882. var groupId = (audioGroup.filter(function (group) {
  42883. return group["default"];
  42884. })[0] || audioGroup[0]).id;
  42885. mediaTypes.AUDIO.tracks[groupId].enabled = true;
  42886. mediaTypes.AUDIO.onGroupChanged();
  42887. mediaTypes.AUDIO.onTrackChanged();
  42888. var activeAudioGroup = mediaTypes.AUDIO.getActiveGroup(); // a similar check for handling setAudio on each loader is run again each time the
  42889. // track is changed, but needs to be handled here since the track may not be considered
  42890. // changed on the first call to onTrackChanged
  42891. if (!activeAudioGroup.playlistLoader) {
  42892. // either audio is muxed with video or the stream is audio only
  42893. mainSegmentLoader.setAudio(true);
  42894. } else {
  42895. // audio is demuxed
  42896. mainSegmentLoader.setAudio(false);
  42897. audioSegmentLoader.setAudio(true);
  42898. }
  42899. }
  42900. masterPlaylistLoader.on('mediachange', function () {
  42901. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  42902. return mediaTypes[type].onGroupChanged();
  42903. });
  42904. });
  42905. masterPlaylistLoader.on('mediachanging', function () {
  42906. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  42907. return mediaTypes[type].onGroupChanging();
  42908. });
  42909. }); // custom audio track change event handler for usage event
  42910. var onAudioTrackChanged = function onAudioTrackChanged() {
  42911. mediaTypes.AUDIO.onTrackChanged();
  42912. tech.trigger({
  42913. type: 'usage',
  42914. name: 'vhs-audio-change'
  42915. });
  42916. tech.trigger({
  42917. type: 'usage',
  42918. name: 'hls-audio-change'
  42919. });
  42920. };
  42921. tech.audioTracks().addEventListener('change', onAudioTrackChanged);
  42922. tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  42923. vhs.on('dispose', function () {
  42924. tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
  42925. tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  42926. }); // clear existing audio tracks and add the ones we just created
  42927. tech.clearTracks('audio');
  42928. for (var id in mediaTypes.AUDIO.tracks) {
  42929. tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
  42930. }
  42931. };
  42932. /**
  42933. * Creates skeleton object used to store the loaders, tracks, and utility methods for each
  42934. * media type
  42935. *
  42936. * @return {Object}
  42937. * Object to store the loaders, tracks, and utility methods for each media type
  42938. * @function createMediaTypes
  42939. */
  42940. var createMediaTypes = function createMediaTypes() {
  42941. var mediaTypes = {};
  42942. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  42943. mediaTypes[type] = {
  42944. groups: {},
  42945. tracks: {},
  42946. activePlaylistLoader: null,
  42947. activeGroup: noop,
  42948. activeTrack: noop,
  42949. getActiveGroup: noop,
  42950. onGroupChanged: noop,
  42951. onTrackChanged: noop,
  42952. lastTrack_: null,
  42953. logger_: logger("MediaGroups[" + type + "]")
  42954. };
  42955. });
  42956. return mediaTypes;
  42957. };
  42958. var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
  42959. var Vhs$1; // SegmentLoader stats that need to have each loader's
  42960. // values summed to calculate the final value
  42961. var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred', 'mediaAppends'];
  42962. var sumLoaderStat = function sumLoaderStat(stat) {
  42963. return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
  42964. };
  42965. var shouldSwitchToMedia = function shouldSwitchToMedia(_ref) {
  42966. var currentPlaylist = _ref.currentPlaylist,
  42967. buffered = _ref.buffered,
  42968. currentTime = _ref.currentTime,
  42969. nextPlaylist = _ref.nextPlaylist,
  42970. bufferLowWaterLine = _ref.bufferLowWaterLine,
  42971. bufferHighWaterLine = _ref.bufferHighWaterLine,
  42972. duration = _ref.duration,
  42973. experimentalBufferBasedABR = _ref.experimentalBufferBasedABR,
  42974. log = _ref.log; // we have no other playlist to switch to
  42975. if (!nextPlaylist) {
  42976. videojs.log.warn('We received no playlist to switch to. Please check your stream.');
  42977. return false;
  42978. }
  42979. var sharedLogLine = "allowing switch " + (currentPlaylist && currentPlaylist.id || 'null') + " -> " + nextPlaylist.id;
  42980. if (!currentPlaylist) {
  42981. log(sharedLogLine + " as current playlist is not set");
  42982. return true;
  42983. } // no need to switch if playlist is the same
  42984. if (nextPlaylist.id === currentPlaylist.id) {
  42985. return false;
  42986. } // determine if current time is in a buffered range.
  42987. var isBuffered = Boolean(findRange(buffered, currentTime).length); // If the playlist is live, then we want to not take low water line into account.
  42988. // This is because in LIVE, the player plays 3 segments from the end of the
  42989. // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
  42990. // in those segments, a viewer will never experience a rendition upswitch.
  42991. if (!currentPlaylist.endList) {
  42992. // For LLHLS live streams, don't switch renditions before playback has started, as it almost
  42993. // doubles the time to first playback.
  42994. if (!isBuffered && typeof currentPlaylist.partTargetDuration === 'number') {
  42995. log("not " + sharedLogLine + " as current playlist is live llhls, but currentTime isn't in buffered.");
  42996. return false;
  42997. }
  42998. log(sharedLogLine + " as current playlist is live");
  42999. return true;
  43000. }
  43001. var forwardBuffer = timeAheadOf(buffered, currentTime);
  43002. var maxBufferLowWaterLine = experimentalBufferBasedABR ? Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE : Config.MAX_BUFFER_LOW_WATER_LINE; // For the same reason as LIVE, we ignore the low water line when the VOD
  43003. // duration is below the max potential low water line
  43004. if (duration < maxBufferLowWaterLine) {
  43005. log(sharedLogLine + " as duration < max low water line (" + duration + " < " + maxBufferLowWaterLine + ")");
  43006. return true;
  43007. }
  43008. var nextBandwidth = nextPlaylist.attributes.BANDWIDTH;
  43009. var currBandwidth = currentPlaylist.attributes.BANDWIDTH; // when switching down, if our buffer is lower than the high water line,
  43010. // we can switch down
  43011. if (nextBandwidth < currBandwidth && (!experimentalBufferBasedABR || forwardBuffer < bufferHighWaterLine)) {
  43012. var logLine = sharedLogLine + " as next bandwidth < current bandwidth (" + nextBandwidth + " < " + currBandwidth + ")";
  43013. if (experimentalBufferBasedABR) {
  43014. logLine += " and forwardBuffer < bufferHighWaterLine (" + forwardBuffer + " < " + bufferHighWaterLine + ")";
  43015. }
  43016. log(logLine);
  43017. return true;
  43018. } // and if our buffer is higher than the low water line,
  43019. // we can switch up
  43020. if ((!experimentalBufferBasedABR || nextBandwidth > currBandwidth) && forwardBuffer >= bufferLowWaterLine) {
  43021. var _logLine = sharedLogLine + " as forwardBuffer >= bufferLowWaterLine (" + forwardBuffer + " >= " + bufferLowWaterLine + ")";
  43022. if (experimentalBufferBasedABR) {
  43023. _logLine += " and next bandwidth > current bandwidth (" + nextBandwidth + " > " + currBandwidth + ")";
  43024. }
  43025. log(_logLine);
  43026. return true;
  43027. }
  43028. log("not " + sharedLogLine + " as no switching criteria met");
  43029. return false;
  43030. };
  43031. /**
  43032. * the master playlist controller controller all interactons
  43033. * between playlists and segmentloaders. At this time this mainly
  43034. * involves a master playlist and a series of audio playlists
  43035. * if they are available
  43036. *
  43037. * @class MasterPlaylistController
  43038. * @extends videojs.EventTarget
  43039. */
  43040. var MasterPlaylistController = /*#__PURE__*/function (_videojs$EventTarget) {
  43041. _inheritsLoose(MasterPlaylistController, _videojs$EventTarget);
  43042. function MasterPlaylistController(options) {
  43043. var _this;
  43044. _this = _videojs$EventTarget.call(this) || this;
  43045. var src = options.src,
  43046. handleManifestRedirects = options.handleManifestRedirects,
  43047. withCredentials = options.withCredentials,
  43048. tech = options.tech,
  43049. bandwidth = options.bandwidth,
  43050. externVhs = options.externVhs,
  43051. useCueTags = options.useCueTags,
  43052. blacklistDuration = options.blacklistDuration,
  43053. enableLowInitialPlaylist = options.enableLowInitialPlaylist,
  43054. sourceType = options.sourceType,
  43055. cacheEncryptionKeys = options.cacheEncryptionKeys,
  43056. experimentalBufferBasedABR = options.experimentalBufferBasedABR,
  43057. experimentalLeastPixelDiffSelector = options.experimentalLeastPixelDiffSelector,
  43058. captionServices = options.captionServices;
  43059. if (!src) {
  43060. throw new Error('A non-empty playlist URL or JSON manifest string is required');
  43061. }
  43062. var maxPlaylistRetries = options.maxPlaylistRetries;
  43063. if (maxPlaylistRetries === null || typeof maxPlaylistRetries === 'undefined') {
  43064. maxPlaylistRetries = Infinity;
  43065. }
  43066. Vhs$1 = externVhs;
  43067. _this.experimentalBufferBasedABR = Boolean(experimentalBufferBasedABR);
  43068. _this.experimentalLeastPixelDiffSelector = Boolean(experimentalLeastPixelDiffSelector);
  43069. _this.withCredentials = withCredentials;
  43070. _this.tech_ = tech;
  43071. _this.vhs_ = tech.vhs;
  43072. _this.sourceType_ = sourceType;
  43073. _this.useCueTags_ = useCueTags;
  43074. _this.blacklistDuration = blacklistDuration;
  43075. _this.maxPlaylistRetries = maxPlaylistRetries;
  43076. _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
  43077. if (_this.useCueTags_) {
  43078. _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
  43079. _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
  43080. }
  43081. _this.requestOptions_ = {
  43082. withCredentials: withCredentials,
  43083. handleManifestRedirects: handleManifestRedirects,
  43084. maxPlaylistRetries: maxPlaylistRetries,
  43085. timeout: null
  43086. };
  43087. _this.on('error', _this.pauseLoading);
  43088. _this.mediaTypes_ = createMediaTypes();
  43089. _this.mediaSource = new window$1.MediaSource();
  43090. _this.handleDurationChange_ = _this.handleDurationChange_.bind(_assertThisInitialized(_this));
  43091. _this.handleSourceOpen_ = _this.handleSourceOpen_.bind(_assertThisInitialized(_this));
  43092. _this.handleSourceEnded_ = _this.handleSourceEnded_.bind(_assertThisInitialized(_this));
  43093. _this.mediaSource.addEventListener('durationchange', _this.handleDurationChange_); // load the media source into the player
  43094. _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_);
  43095. _this.mediaSource.addEventListener('sourceended', _this.handleSourceEnded_); // we don't have to handle sourceclose since dispose will handle termination of
  43096. // everything, and the MediaSource should not be detached without a proper disposal
  43097. _this.seekable_ = videojs.createTimeRanges();
  43098. _this.hasPlayed_ = false;
  43099. _this.syncController_ = new SyncController(options);
  43100. _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
  43101. kind: 'metadata',
  43102. label: 'segment-metadata'
  43103. }, false).track;
  43104. _this.decrypter_ = new Decrypter();
  43105. _this.sourceUpdater_ = new SourceUpdater(_this.mediaSource);
  43106. _this.inbandTextTracks_ = {};
  43107. _this.timelineChangeController_ = new TimelineChangeController();
  43108. var segmentLoaderSettings = {
  43109. vhs: _this.vhs_,
  43110. parse708captions: options.parse708captions,
  43111. useDtsForTimestampOffset: options.useDtsForTimestampOffset,
  43112. captionServices: captionServices,
  43113. mediaSource: _this.mediaSource,
  43114. currentTime: _this.tech_.currentTime.bind(_this.tech_),
  43115. seekable: function seekable() {
  43116. return _this.seekable();
  43117. },
  43118. seeking: function seeking() {
  43119. return _this.tech_.seeking();
  43120. },
  43121. duration: function duration() {
  43122. return _this.duration();
  43123. },
  43124. hasPlayed: function hasPlayed() {
  43125. return _this.hasPlayed_;
  43126. },
  43127. goalBufferLength: function goalBufferLength() {
  43128. return _this.goalBufferLength();
  43129. },
  43130. bandwidth: bandwidth,
  43131. syncController: _this.syncController_,
  43132. decrypter: _this.decrypter_,
  43133. sourceType: _this.sourceType_,
  43134. inbandTextTracks: _this.inbandTextTracks_,
  43135. cacheEncryptionKeys: cacheEncryptionKeys,
  43136. sourceUpdater: _this.sourceUpdater_,
  43137. timelineChangeController: _this.timelineChangeController_,
  43138. experimentalExactManifestTimings: options.experimentalExactManifestTimings
  43139. }; // The source type check not only determines whether a special DASH playlist loader
  43140. // should be used, but also covers the case where the provided src is a vhs-json
  43141. // manifest object (instead of a URL). In the case of vhs-json, the default
  43142. // PlaylistLoader should be used.
  43143. _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(src, _this.vhs_, _this.requestOptions_) : new PlaylistLoader(src, _this.vhs_, _this.requestOptions_);
  43144. _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders
  43145. // combined audio/video or just video when alternate audio track is selected
  43146. _this.mainSegmentLoader_ = new SegmentLoader(videojs.mergeOptions(segmentLoaderSettings, {
  43147. segmentMetadataTrack: _this.segmentMetadataTrack_,
  43148. loaderType: 'main'
  43149. }), options); // alternate audio track
  43150. _this.audioSegmentLoader_ = new SegmentLoader(videojs.mergeOptions(segmentLoaderSettings, {
  43151. loaderType: 'audio'
  43152. }), options);
  43153. _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs.mergeOptions(segmentLoaderSettings, {
  43154. loaderType: 'vtt',
  43155. featuresNativeTextTracks: _this.tech_.featuresNativeTextTracks,
  43156. loadVttJs: function loadVttJs() {
  43157. return new Promise(function (resolve, reject) {
  43158. function onLoad() {
  43159. tech.off('vttjserror', onError);
  43160. resolve();
  43161. }
  43162. function onError() {
  43163. tech.off('vttjsloaded', onLoad);
  43164. reject();
  43165. }
  43166. tech.one('vttjsloaded', onLoad);
  43167. tech.one('vttjserror', onError); // safe to call multiple times, script will be loaded only once:
  43168. tech.addWebVttScript_();
  43169. });
  43170. }
  43171. }), options);
  43172. _this.setupSegmentLoaderListeners_();
  43173. if (_this.experimentalBufferBasedABR) {
  43174. _this.masterPlaylistLoader_.one('loadedplaylist', function () {
  43175. return _this.startABRTimer_();
  43176. });
  43177. _this.tech_.on('pause', function () {
  43178. return _this.stopABRTimer_();
  43179. });
  43180. _this.tech_.on('play', function () {
  43181. return _this.startABRTimer_();
  43182. });
  43183. } // Create SegmentLoader stat-getters
  43184. // mediaRequests_
  43185. // mediaRequestsAborted_
  43186. // mediaRequestsTimedout_
  43187. // mediaRequestsErrored_
  43188. // mediaTransferDuration_
  43189. // mediaBytesTransferred_
  43190. // mediaAppends_
  43191. loaderStats.forEach(function (stat) {
  43192. _this[stat + '_'] = sumLoaderStat.bind(_assertThisInitialized(_this), stat);
  43193. });
  43194. _this.logger_ = logger('MPC');
  43195. _this.triggeredFmp4Usage = false;
  43196. if (_this.tech_.preload() === 'none') {
  43197. _this.loadOnPlay_ = function () {
  43198. _this.loadOnPlay_ = null;
  43199. _this.masterPlaylistLoader_.load();
  43200. };
  43201. _this.tech_.one('play', _this.loadOnPlay_);
  43202. } else {
  43203. _this.masterPlaylistLoader_.load();
  43204. }
  43205. _this.timeToLoadedData__ = -1;
  43206. _this.mainAppendsToLoadedData__ = -1;
  43207. _this.audioAppendsToLoadedData__ = -1;
  43208. var event = _this.tech_.preload() === 'none' ? 'play' : 'loadstart'; // start the first frame timer on loadstart or play (for preload none)
  43209. _this.tech_.one(event, function () {
  43210. var timeToLoadedDataStart = Date.now();
  43211. _this.tech_.one('loadeddata', function () {
  43212. _this.timeToLoadedData__ = Date.now() - timeToLoadedDataStart;
  43213. _this.mainAppendsToLoadedData__ = _this.mainSegmentLoader_.mediaAppends;
  43214. _this.audioAppendsToLoadedData__ = _this.audioSegmentLoader_.mediaAppends;
  43215. });
  43216. });
  43217. return _this;
  43218. }
  43219. var _proto = MasterPlaylistController.prototype;
  43220. _proto.mainAppendsToLoadedData_ = function mainAppendsToLoadedData_() {
  43221. return this.mainAppendsToLoadedData__;
  43222. };
  43223. _proto.audioAppendsToLoadedData_ = function audioAppendsToLoadedData_() {
  43224. return this.audioAppendsToLoadedData__;
  43225. };
  43226. _proto.appendsToLoadedData_ = function appendsToLoadedData_() {
  43227. var main = this.mainAppendsToLoadedData_();
  43228. var audio = this.audioAppendsToLoadedData_();
  43229. if (main === -1 || audio === -1) {
  43230. return -1;
  43231. }
  43232. return main + audio;
  43233. };
  43234. _proto.timeToLoadedData_ = function timeToLoadedData_() {
  43235. return this.timeToLoadedData__;
  43236. }
  43237. /**
  43238. * Run selectPlaylist and switch to the new playlist if we should
  43239. *
  43240. * @param {string} [reason=abr] a reason for why the ABR check is made
  43241. * @private
  43242. */
  43243. ;
  43244. _proto.checkABR_ = function checkABR_(reason) {
  43245. if (reason === void 0) {
  43246. reason = 'abr';
  43247. }
  43248. var nextPlaylist = this.selectPlaylist();
  43249. if (nextPlaylist && this.shouldSwitchToMedia_(nextPlaylist)) {
  43250. this.switchMedia_(nextPlaylist, reason);
  43251. }
  43252. };
  43253. _proto.switchMedia_ = function switchMedia_(playlist, cause, delay) {
  43254. var oldMedia = this.media();
  43255. var oldId = oldMedia && (oldMedia.id || oldMedia.uri);
  43256. var newId = playlist.id || playlist.uri;
  43257. if (oldId && oldId !== newId) {
  43258. this.logger_("switch media " + oldId + " -> " + newId + " from " + cause);
  43259. this.tech_.trigger({
  43260. type: 'usage',
  43261. name: "vhs-rendition-change-" + cause
  43262. });
  43263. }
  43264. this.masterPlaylistLoader_.media(playlist, delay);
  43265. }
  43266. /**
  43267. * Start a timer that periodically calls checkABR_
  43268. *
  43269. * @private
  43270. */
  43271. ;
  43272. _proto.startABRTimer_ = function startABRTimer_() {
  43273. var _this2 = this;
  43274. this.stopABRTimer_();
  43275. this.abrTimer_ = window$1.setInterval(function () {
  43276. return _this2.checkABR_();
  43277. }, 250);
  43278. }
  43279. /**
  43280. * Stop the timer that periodically calls checkABR_
  43281. *
  43282. * @private
  43283. */
  43284. ;
  43285. _proto.stopABRTimer_ = function stopABRTimer_() {
  43286. // if we're scrubbing, we don't need to pause.
  43287. // This getter will be added to Video.js in version 7.11.
  43288. if (this.tech_.scrubbing && this.tech_.scrubbing()) {
  43289. return;
  43290. }
  43291. window$1.clearInterval(this.abrTimer_);
  43292. this.abrTimer_ = null;
  43293. }
  43294. /**
  43295. * Get a list of playlists for the currently selected audio playlist
  43296. *
  43297. * @return {Array} the array of audio playlists
  43298. */
  43299. ;
  43300. _proto.getAudioTrackPlaylists_ = function getAudioTrackPlaylists_() {
  43301. var master = this.master();
  43302. var defaultPlaylists = master && master.playlists || []; // if we don't have any audio groups then we can only
  43303. // assume that the audio tracks are contained in masters
  43304. // playlist array, use that or an empty array.
  43305. if (!master || !master.mediaGroups || !master.mediaGroups.AUDIO) {
  43306. return defaultPlaylists;
  43307. }
  43308. var AUDIO = master.mediaGroups.AUDIO;
  43309. var groupKeys = Object.keys(AUDIO);
  43310. var track; // get the current active track
  43311. if (Object.keys(this.mediaTypes_.AUDIO.groups).length) {
  43312. track = this.mediaTypes_.AUDIO.activeTrack(); // or get the default track from master if mediaTypes_ isn't setup yet
  43313. } else {
  43314. // default group is `main` or just the first group.
  43315. var defaultGroup = AUDIO.main || groupKeys.length && AUDIO[groupKeys[0]];
  43316. for (var label in defaultGroup) {
  43317. if (defaultGroup[label]["default"]) {
  43318. track = {
  43319. label: label
  43320. };
  43321. break;
  43322. }
  43323. }
  43324. } // no active track no playlists.
  43325. if (!track) {
  43326. return defaultPlaylists;
  43327. }
  43328. var playlists = []; // get all of the playlists that are possible for the
  43329. // active track.
  43330. for (var group in AUDIO) {
  43331. if (AUDIO[group][track.label]) {
  43332. var properties = AUDIO[group][track.label];
  43333. if (properties.playlists && properties.playlists.length) {
  43334. playlists.push.apply(playlists, properties.playlists);
  43335. } else if (properties.uri) {
  43336. playlists.push(properties);
  43337. } else if (master.playlists.length) {
  43338. // if an audio group does not have a uri
  43339. // see if we have main playlists that use it as a group.
  43340. // if we do then add those to the playlists list.
  43341. for (var i = 0; i < master.playlists.length; i++) {
  43342. var playlist = master.playlists[i];
  43343. if (playlist.attributes && playlist.attributes.AUDIO && playlist.attributes.AUDIO === group) {
  43344. playlists.push(playlist);
  43345. }
  43346. }
  43347. }
  43348. }
  43349. }
  43350. if (!playlists.length) {
  43351. return defaultPlaylists;
  43352. }
  43353. return playlists;
  43354. }
  43355. /**
  43356. * Register event handlers on the master playlist loader. A helper
  43357. * function for construction time.
  43358. *
  43359. * @private
  43360. */
  43361. ;
  43362. _proto.setupMasterPlaylistLoaderListeners_ = function setupMasterPlaylistLoaderListeners_() {
  43363. var _this3 = this;
  43364. this.masterPlaylistLoader_.on('loadedmetadata', function () {
  43365. var media = _this3.masterPlaylistLoader_.media();
  43366. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  43367. // timeout the request.
  43368. if (isLowestEnabledRendition(_this3.masterPlaylistLoader_.master, _this3.masterPlaylistLoader_.media())) {
  43369. _this3.requestOptions_.timeout = 0;
  43370. } else {
  43371. _this3.requestOptions_.timeout = requestTimeout;
  43372. } // if this isn't a live video and preload permits, start
  43373. // downloading segments
  43374. if (media.endList && _this3.tech_.preload() !== 'none') {
  43375. _this3.mainSegmentLoader_.playlist(media, _this3.requestOptions_);
  43376. _this3.mainSegmentLoader_.load();
  43377. }
  43378. setupMediaGroups({
  43379. sourceType: _this3.sourceType_,
  43380. segmentLoaders: {
  43381. AUDIO: _this3.audioSegmentLoader_,
  43382. SUBTITLES: _this3.subtitleSegmentLoader_,
  43383. main: _this3.mainSegmentLoader_
  43384. },
  43385. tech: _this3.tech_,
  43386. requestOptions: _this3.requestOptions_,
  43387. masterPlaylistLoader: _this3.masterPlaylistLoader_,
  43388. vhs: _this3.vhs_,
  43389. master: _this3.master(),
  43390. mediaTypes: _this3.mediaTypes_,
  43391. blacklistCurrentPlaylist: _this3.blacklistCurrentPlaylist.bind(_this3)
  43392. });
  43393. _this3.triggerPresenceUsage_(_this3.master(), media);
  43394. _this3.setupFirstPlay();
  43395. if (!_this3.mediaTypes_.AUDIO.activePlaylistLoader || _this3.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
  43396. _this3.trigger('selectedinitialmedia');
  43397. } else {
  43398. // We must wait for the active audio playlist loader to
  43399. // finish setting up before triggering this event so the
  43400. // representations API and EME setup is correct
  43401. _this3.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () {
  43402. _this3.trigger('selectedinitialmedia');
  43403. });
  43404. }
  43405. });
  43406. this.masterPlaylistLoader_.on('loadedplaylist', function () {
  43407. if (_this3.loadOnPlay_) {
  43408. _this3.tech_.off('play', _this3.loadOnPlay_);
  43409. }
  43410. var updatedPlaylist = _this3.masterPlaylistLoader_.media();
  43411. if (!updatedPlaylist) {
  43412. // exclude any variants that are not supported by the browser before selecting
  43413. // an initial media as the playlist selectors do not consider browser support
  43414. _this3.excludeUnsupportedVariants_();
  43415. var selectedMedia;
  43416. if (_this3.enableLowInitialPlaylist) {
  43417. selectedMedia = _this3.selectInitialPlaylist();
  43418. }
  43419. if (!selectedMedia) {
  43420. selectedMedia = _this3.selectPlaylist();
  43421. }
  43422. if (!selectedMedia || !_this3.shouldSwitchToMedia_(selectedMedia)) {
  43423. return;
  43424. }
  43425. _this3.initialMedia_ = selectedMedia;
  43426. _this3.switchMedia_(_this3.initialMedia_, 'initial'); // Under the standard case where a source URL is provided, loadedplaylist will
  43427. // fire again since the playlist will be requested. In the case of vhs-json
  43428. // (where the manifest object is provided as the source), when the media
  43429. // playlist's `segments` list is already available, a media playlist won't be
  43430. // requested, and loadedplaylist won't fire again, so the playlist handler must be
  43431. // called on its own here.
  43432. var haveJsonSource = _this3.sourceType_ === 'vhs-json' && _this3.initialMedia_.segments;
  43433. if (!haveJsonSource) {
  43434. return;
  43435. }
  43436. updatedPlaylist = _this3.initialMedia_;
  43437. }
  43438. _this3.handleUpdatedMediaPlaylist(updatedPlaylist);
  43439. });
  43440. this.masterPlaylistLoader_.on('error', function () {
  43441. _this3.blacklistCurrentPlaylist(_this3.masterPlaylistLoader_.error);
  43442. });
  43443. this.masterPlaylistLoader_.on('mediachanging', function () {
  43444. _this3.mainSegmentLoader_.abort();
  43445. _this3.mainSegmentLoader_.pause();
  43446. });
  43447. this.masterPlaylistLoader_.on('mediachange', function () {
  43448. var media = _this3.masterPlaylistLoader_.media();
  43449. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  43450. // timeout the request.
  43451. if (isLowestEnabledRendition(_this3.masterPlaylistLoader_.master, _this3.masterPlaylistLoader_.media())) {
  43452. _this3.requestOptions_.timeout = 0;
  43453. } else {
  43454. _this3.requestOptions_.timeout = requestTimeout;
  43455. }
  43456. _this3.masterPlaylistLoader_.load(); // TODO: Create a new event on the PlaylistLoader that signals
  43457. // that the segments have changed in some way and use that to
  43458. // update the SegmentLoader instead of doing it twice here and
  43459. // on `loadedplaylist`
  43460. _this3.mainSegmentLoader_.playlist(media, _this3.requestOptions_);
  43461. _this3.mainSegmentLoader_.load();
  43462. _this3.tech_.trigger({
  43463. type: 'mediachange',
  43464. bubbles: true
  43465. });
  43466. });
  43467. this.masterPlaylistLoader_.on('playlistunchanged', function () {
  43468. var updatedPlaylist = _this3.masterPlaylistLoader_.media(); // ignore unchanged playlists that have already been
  43469. // excluded for not-changing. We likely just have a really slowly updating
  43470. // playlist.
  43471. if (updatedPlaylist.lastExcludeReason_ === 'playlist-unchanged') {
  43472. return;
  43473. }
  43474. var playlistOutdated = _this3.stuckAtPlaylistEnd_(updatedPlaylist);
  43475. if (playlistOutdated) {
  43476. // Playlist has stopped updating and we're stuck at its end. Try to
  43477. // blacklist it and switch to another playlist in the hope that that
  43478. // one is updating (and give the player a chance to re-adjust to the
  43479. // safe live point).
  43480. _this3.blacklistCurrentPlaylist({
  43481. message: 'Playlist no longer updating.',
  43482. reason: 'playlist-unchanged'
  43483. }); // useful for monitoring QoS
  43484. _this3.tech_.trigger('playliststuck');
  43485. }
  43486. });
  43487. this.masterPlaylistLoader_.on('renditiondisabled', function () {
  43488. _this3.tech_.trigger({
  43489. type: 'usage',
  43490. name: 'vhs-rendition-disabled'
  43491. });
  43492. _this3.tech_.trigger({
  43493. type: 'usage',
  43494. name: 'hls-rendition-disabled'
  43495. });
  43496. });
  43497. this.masterPlaylistLoader_.on('renditionenabled', function () {
  43498. _this3.tech_.trigger({
  43499. type: 'usage',
  43500. name: 'vhs-rendition-enabled'
  43501. });
  43502. _this3.tech_.trigger({
  43503. type: 'usage',
  43504. name: 'hls-rendition-enabled'
  43505. });
  43506. });
  43507. }
  43508. /**
  43509. * Given an updated media playlist (whether it was loaded for the first time, or
  43510. * refreshed for live playlists), update any relevant properties and state to reflect
  43511. * changes in the media that should be accounted for (e.g., cues and duration).
  43512. *
  43513. * @param {Object} updatedPlaylist the updated media playlist object
  43514. *
  43515. * @private
  43516. */
  43517. ;
  43518. _proto.handleUpdatedMediaPlaylist = function handleUpdatedMediaPlaylist(updatedPlaylist) {
  43519. if (this.useCueTags_) {
  43520. this.updateAdCues_(updatedPlaylist);
  43521. } // TODO: Create a new event on the PlaylistLoader that signals
  43522. // that the segments have changed in some way and use that to
  43523. // update the SegmentLoader instead of doing it twice here and
  43524. // on `mediachange`
  43525. this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_);
  43526. this.updateDuration(!updatedPlaylist.endList); // If the player isn't paused, ensure that the segment loader is running,
  43527. // as it is possible that it was temporarily stopped while waiting for
  43528. // a playlist (e.g., in case the playlist errored and we re-requested it).
  43529. if (!this.tech_.paused()) {
  43530. this.mainSegmentLoader_.load();
  43531. if (this.audioSegmentLoader_) {
  43532. this.audioSegmentLoader_.load();
  43533. }
  43534. }
  43535. }
  43536. /**
  43537. * A helper function for triggerring presence usage events once per source
  43538. *
  43539. * @private
  43540. */
  43541. ;
  43542. _proto.triggerPresenceUsage_ = function triggerPresenceUsage_(master, media) {
  43543. var mediaGroups = master.mediaGroups || {};
  43544. var defaultDemuxed = true;
  43545. var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
  43546. for (var mediaGroup in mediaGroups.AUDIO) {
  43547. for (var label in mediaGroups.AUDIO[mediaGroup]) {
  43548. var properties = mediaGroups.AUDIO[mediaGroup][label];
  43549. if (!properties.uri) {
  43550. defaultDemuxed = false;
  43551. }
  43552. }
  43553. }
  43554. if (defaultDemuxed) {
  43555. this.tech_.trigger({
  43556. type: 'usage',
  43557. name: 'vhs-demuxed'
  43558. });
  43559. this.tech_.trigger({
  43560. type: 'usage',
  43561. name: 'hls-demuxed'
  43562. });
  43563. }
  43564. if (Object.keys(mediaGroups.SUBTITLES).length) {
  43565. this.tech_.trigger({
  43566. type: 'usage',
  43567. name: 'vhs-webvtt'
  43568. });
  43569. this.tech_.trigger({
  43570. type: 'usage',
  43571. name: 'hls-webvtt'
  43572. });
  43573. }
  43574. if (Vhs$1.Playlist.isAes(media)) {
  43575. this.tech_.trigger({
  43576. type: 'usage',
  43577. name: 'vhs-aes'
  43578. });
  43579. this.tech_.trigger({
  43580. type: 'usage',
  43581. name: 'hls-aes'
  43582. });
  43583. }
  43584. if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
  43585. this.tech_.trigger({
  43586. type: 'usage',
  43587. name: 'vhs-alternate-audio'
  43588. });
  43589. this.tech_.trigger({
  43590. type: 'usage',
  43591. name: 'hls-alternate-audio'
  43592. });
  43593. }
  43594. if (this.useCueTags_) {
  43595. this.tech_.trigger({
  43596. type: 'usage',
  43597. name: 'vhs-playlist-cue-tags'
  43598. });
  43599. this.tech_.trigger({
  43600. type: 'usage',
  43601. name: 'hls-playlist-cue-tags'
  43602. });
  43603. }
  43604. };
  43605. _proto.shouldSwitchToMedia_ = function shouldSwitchToMedia_(nextPlaylist) {
  43606. var currentPlaylist = this.masterPlaylistLoader_.media() || this.masterPlaylistLoader_.pendingMedia_;
  43607. var currentTime = this.tech_.currentTime();
  43608. var bufferLowWaterLine = this.bufferLowWaterLine();
  43609. var bufferHighWaterLine = this.bufferHighWaterLine();
  43610. var buffered = this.tech_.buffered();
  43611. return shouldSwitchToMedia({
  43612. buffered: buffered,
  43613. currentTime: currentTime,
  43614. currentPlaylist: currentPlaylist,
  43615. nextPlaylist: nextPlaylist,
  43616. bufferLowWaterLine: bufferLowWaterLine,
  43617. bufferHighWaterLine: bufferHighWaterLine,
  43618. duration: this.duration(),
  43619. experimentalBufferBasedABR: this.experimentalBufferBasedABR,
  43620. log: this.logger_
  43621. });
  43622. }
  43623. /**
  43624. * Register event handlers on the segment loaders. A helper function
  43625. * for construction time.
  43626. *
  43627. * @private
  43628. */
  43629. ;
  43630. _proto.setupSegmentLoaderListeners_ = function setupSegmentLoaderListeners_() {
  43631. var _this4 = this;
  43632. this.mainSegmentLoader_.on('bandwidthupdate', function () {
  43633. // Whether or not buffer based ABR or another ABR is used, on a bandwidth change it's
  43634. // useful to check to see if a rendition switch should be made.
  43635. _this4.checkABR_('bandwidthupdate');
  43636. _this4.tech_.trigger('bandwidthupdate');
  43637. });
  43638. this.mainSegmentLoader_.on('timeout', function () {
  43639. if (_this4.experimentalBufferBasedABR) {
  43640. // If a rendition change is needed, then it would've be done on `bandwidthupdate`.
  43641. // Here the only consideration is that for buffer based ABR there's no guarantee
  43642. // of an immediate switch (since the bandwidth is averaged with a timeout
  43643. // bandwidth value of 1), so force a load on the segment loader to keep it going.
  43644. _this4.mainSegmentLoader_.load();
  43645. }
  43646. }); // `progress` events are not reliable enough of a bandwidth measure to trigger buffer
  43647. // based ABR.
  43648. if (!this.experimentalBufferBasedABR) {
  43649. this.mainSegmentLoader_.on('progress', function () {
  43650. _this4.trigger('progress');
  43651. });
  43652. }
  43653. this.mainSegmentLoader_.on('error', function () {
  43654. _this4.blacklistCurrentPlaylist(_this4.mainSegmentLoader_.error());
  43655. });
  43656. this.mainSegmentLoader_.on('appenderror', function () {
  43657. _this4.error = _this4.mainSegmentLoader_.error_;
  43658. _this4.trigger('error');
  43659. });
  43660. this.mainSegmentLoader_.on('syncinfoupdate', function () {
  43661. _this4.onSyncInfoUpdate_();
  43662. });
  43663. this.mainSegmentLoader_.on('timestampoffset', function () {
  43664. _this4.tech_.trigger({
  43665. type: 'usage',
  43666. name: 'vhs-timestamp-offset'
  43667. });
  43668. _this4.tech_.trigger({
  43669. type: 'usage',
  43670. name: 'hls-timestamp-offset'
  43671. });
  43672. });
  43673. this.audioSegmentLoader_.on('syncinfoupdate', function () {
  43674. _this4.onSyncInfoUpdate_();
  43675. });
  43676. this.audioSegmentLoader_.on('appenderror', function () {
  43677. _this4.error = _this4.audioSegmentLoader_.error_;
  43678. _this4.trigger('error');
  43679. });
  43680. this.mainSegmentLoader_.on('ended', function () {
  43681. _this4.logger_('main segment loader ended');
  43682. _this4.onEndOfStream();
  43683. });
  43684. this.mainSegmentLoader_.on('earlyabort', function (event) {
  43685. // never try to early abort with the new ABR algorithm
  43686. if (_this4.experimentalBufferBasedABR) {
  43687. return;
  43688. }
  43689. _this4.delegateLoaders_('all', ['abort']);
  43690. _this4.blacklistCurrentPlaylist({
  43691. message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
  43692. }, ABORT_EARLY_BLACKLIST_SECONDS);
  43693. });
  43694. var updateCodecs = function updateCodecs() {
  43695. if (!_this4.sourceUpdater_.hasCreatedSourceBuffers()) {
  43696. return _this4.tryToCreateSourceBuffers_();
  43697. }
  43698. var codecs = _this4.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
  43699. if (!codecs) {
  43700. return;
  43701. }
  43702. _this4.sourceUpdater_.addOrChangeSourceBuffers(codecs);
  43703. };
  43704. this.mainSegmentLoader_.on('trackinfo', updateCodecs);
  43705. this.audioSegmentLoader_.on('trackinfo', updateCodecs);
  43706. this.mainSegmentLoader_.on('fmp4', function () {
  43707. if (!_this4.triggeredFmp4Usage) {
  43708. _this4.tech_.trigger({
  43709. type: 'usage',
  43710. name: 'vhs-fmp4'
  43711. });
  43712. _this4.tech_.trigger({
  43713. type: 'usage',
  43714. name: 'hls-fmp4'
  43715. });
  43716. _this4.triggeredFmp4Usage = true;
  43717. }
  43718. });
  43719. this.audioSegmentLoader_.on('fmp4', function () {
  43720. if (!_this4.triggeredFmp4Usage) {
  43721. _this4.tech_.trigger({
  43722. type: 'usage',
  43723. name: 'vhs-fmp4'
  43724. });
  43725. _this4.tech_.trigger({
  43726. type: 'usage',
  43727. name: 'hls-fmp4'
  43728. });
  43729. _this4.triggeredFmp4Usage = true;
  43730. }
  43731. });
  43732. this.audioSegmentLoader_.on('ended', function () {
  43733. _this4.logger_('audioSegmentLoader ended');
  43734. _this4.onEndOfStream();
  43735. });
  43736. };
  43737. _proto.mediaSecondsLoaded_ = function mediaSecondsLoaded_() {
  43738. return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
  43739. }
  43740. /**
  43741. * Call load on our SegmentLoaders
  43742. */
  43743. ;
  43744. _proto.load = function load() {
  43745. this.mainSegmentLoader_.load();
  43746. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  43747. this.audioSegmentLoader_.load();
  43748. }
  43749. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  43750. this.subtitleSegmentLoader_.load();
  43751. }
  43752. }
  43753. /**
  43754. * Re-tune playback quality level for the current player
  43755. * conditions without performing destructive actions, like
  43756. * removing already buffered content
  43757. *
  43758. * @private
  43759. * @deprecated
  43760. */
  43761. ;
  43762. _proto.smoothQualityChange_ = function smoothQualityChange_(media) {
  43763. if (media === void 0) {
  43764. media = this.selectPlaylist();
  43765. }
  43766. this.fastQualityChange_(media);
  43767. }
  43768. /**
  43769. * Re-tune playback quality level for the current player
  43770. * conditions. This method will perform destructive actions like removing
  43771. * already buffered content in order to readjust the currently active
  43772. * playlist quickly. This is good for manual quality changes
  43773. *
  43774. * @private
  43775. */
  43776. ;
  43777. _proto.fastQualityChange_ = function fastQualityChange_(media) {
  43778. var _this5 = this;
  43779. if (media === void 0) {
  43780. media = this.selectPlaylist();
  43781. }
  43782. if (media === this.masterPlaylistLoader_.media()) {
  43783. this.logger_('skipping fastQualityChange because new media is same as old');
  43784. return;
  43785. }
  43786. this.switchMedia_(media, 'fast-quality'); // Delete all buffered data to allow an immediate quality switch, then seek to give
  43787. // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
  43788. // ahead is roughly the minimum that will accomplish this across a variety of content
  43789. // in IE and Edge, but seeking in place is sufficient on all other browsers)
  43790. // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
  43791. // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
  43792. this.mainSegmentLoader_.resetEverything(function () {
  43793. // Since this is not a typical seek, we avoid the seekTo method which can cause segments
  43794. // from the previously enabled rendition to load before the new playlist has finished loading
  43795. if (videojs.browser.IE_VERSION || videojs.browser.IS_EDGE) {
  43796. _this5.tech_.setCurrentTime(_this5.tech_.currentTime() + 0.04);
  43797. } else {
  43798. _this5.tech_.setCurrentTime(_this5.tech_.currentTime());
  43799. }
  43800. }); // don't need to reset audio as it is reset when media changes
  43801. }
  43802. /**
  43803. * Begin playback.
  43804. */
  43805. ;
  43806. _proto.play = function play() {
  43807. if (this.setupFirstPlay()) {
  43808. return;
  43809. }
  43810. if (this.tech_.ended()) {
  43811. this.tech_.setCurrentTime(0);
  43812. }
  43813. if (this.hasPlayed_) {
  43814. this.load();
  43815. }
  43816. var seekable = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
  43817. // seek forward to the live point
  43818. if (this.tech_.duration() === Infinity) {
  43819. if (this.tech_.currentTime() < seekable.start(0)) {
  43820. return this.tech_.setCurrentTime(seekable.end(seekable.length - 1));
  43821. }
  43822. }
  43823. }
  43824. /**
  43825. * Seek to the latest media position if this is a live video and the
  43826. * player and video are loaded and initialized.
  43827. */
  43828. ;
  43829. _proto.setupFirstPlay = function setupFirstPlay() {
  43830. var _this6 = this;
  43831. var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
  43832. // If 1) there is no active media
  43833. // 2) the player is paused
  43834. // 3) the first play has already been setup
  43835. // then exit early
  43836. if (!media || this.tech_.paused() || this.hasPlayed_) {
  43837. return false;
  43838. } // when the video is a live stream
  43839. if (!media.endList) {
  43840. var seekable = this.seekable();
  43841. if (!seekable.length) {
  43842. // without a seekable range, the player cannot seek to begin buffering at the live
  43843. // point
  43844. return false;
  43845. }
  43846. if (videojs.browser.IE_VERSION && this.tech_.readyState() === 0) {
  43847. // IE11 throws an InvalidStateError if you try to set currentTime while the
  43848. // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
  43849. this.tech_.one('loadedmetadata', function () {
  43850. _this6.trigger('firstplay');
  43851. _this6.tech_.setCurrentTime(seekable.end(0));
  43852. _this6.hasPlayed_ = true;
  43853. });
  43854. return false;
  43855. } // trigger firstplay to inform the source handler to ignore the next seek event
  43856. this.trigger('firstplay'); // seek to the live point
  43857. this.tech_.setCurrentTime(seekable.end(0));
  43858. }
  43859. this.hasPlayed_ = true; // we can begin loading now that everything is ready
  43860. this.load();
  43861. return true;
  43862. }
  43863. /**
  43864. * handle the sourceopen event on the MediaSource
  43865. *
  43866. * @private
  43867. */
  43868. ;
  43869. _proto.handleSourceOpen_ = function handleSourceOpen_() {
  43870. // Only attempt to create the source buffer if none already exist.
  43871. // handleSourceOpen is also called when we are "re-opening" a source buffer
  43872. // after `endOfStream` has been called (in response to a seek for instance)
  43873. this.tryToCreateSourceBuffers_(); // if autoplay is enabled, begin playback. This is duplicative of
  43874. // code in video.js but is required because play() must be invoked
  43875. // *after* the media source has opened.
  43876. if (this.tech_.autoplay()) {
  43877. var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
  43878. // on browsers which return a promise
  43879. if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
  43880. playPromise.then(null, function (e) {});
  43881. }
  43882. }
  43883. this.trigger('sourceopen');
  43884. }
  43885. /**
  43886. * handle the sourceended event on the MediaSource
  43887. *
  43888. * @private
  43889. */
  43890. ;
  43891. _proto.handleSourceEnded_ = function handleSourceEnded_() {
  43892. if (!this.inbandTextTracks_.metadataTrack_) {
  43893. return;
  43894. }
  43895. var cues = this.inbandTextTracks_.metadataTrack_.cues;
  43896. if (!cues || !cues.length) {
  43897. return;
  43898. }
  43899. var duration = this.duration();
  43900. cues[cues.length - 1].endTime = isNaN(duration) || Math.abs(duration) === Infinity ? Number.MAX_VALUE : duration;
  43901. }
  43902. /**
  43903. * handle the durationchange event on the MediaSource
  43904. *
  43905. * @private
  43906. */
  43907. ;
  43908. _proto.handleDurationChange_ = function handleDurationChange_() {
  43909. this.tech_.trigger('durationchange');
  43910. }
  43911. /**
  43912. * Calls endOfStream on the media source when all active stream types have called
  43913. * endOfStream
  43914. *
  43915. * @param {string} streamType
  43916. * Stream type of the segment loader that called endOfStream
  43917. * @private
  43918. */
  43919. ;
  43920. _proto.onEndOfStream = function onEndOfStream() {
  43921. var isEndOfStream = this.mainSegmentLoader_.ended_;
  43922. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  43923. var mainMediaInfo = this.mainSegmentLoader_.getCurrentMediaInfo_(); // if the audio playlist loader exists, then alternate audio is active
  43924. if (!mainMediaInfo || mainMediaInfo.hasVideo) {
  43925. // if we do not know if the main segment loader contains video yet or if we
  43926. // definitively know the main segment loader contains video, then we need to wait
  43927. // for both main and audio segment loaders to call endOfStream
  43928. isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
  43929. } else {
  43930. // otherwise just rely on the audio loader
  43931. isEndOfStream = this.audioSegmentLoader_.ended_;
  43932. }
  43933. }
  43934. if (!isEndOfStream) {
  43935. return;
  43936. }
  43937. this.stopABRTimer_();
  43938. this.sourceUpdater_.endOfStream();
  43939. }
  43940. /**
  43941. * Check if a playlist has stopped being updated
  43942. *
  43943. * @param {Object} playlist the media playlist object
  43944. * @return {boolean} whether the playlist has stopped being updated or not
  43945. */
  43946. ;
  43947. _proto.stuckAtPlaylistEnd_ = function stuckAtPlaylistEnd_(playlist) {
  43948. var seekable = this.seekable();
  43949. if (!seekable.length) {
  43950. // playlist doesn't have enough information to determine whether we are stuck
  43951. return false;
  43952. }
  43953. var expired = this.syncController_.getExpiredTime(playlist, this.duration());
  43954. if (expired === null) {
  43955. return false;
  43956. } // does not use the safe live end to calculate playlist end, since we
  43957. // don't want to say we are stuck while there is still content
  43958. var absolutePlaylistEnd = Vhs$1.Playlist.playlistEnd(playlist, expired);
  43959. var currentTime = this.tech_.currentTime();
  43960. var buffered = this.tech_.buffered();
  43961. if (!buffered.length) {
  43962. // return true if the playhead reached the absolute end of the playlist
  43963. return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
  43964. }
  43965. var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
  43966. // end of playlist
  43967. return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
  43968. }
  43969. /**
  43970. * Blacklists a playlist when an error occurs for a set amount of time
  43971. * making it unavailable for selection by the rendition selection algorithm
  43972. * and then forces a new playlist (rendition) selection.
  43973. *
  43974. * @param {Object=} error an optional error that may include the playlist
  43975. * to blacklist
  43976. * @param {number=} blacklistDuration an optional number of seconds to blacklist the
  43977. * playlist
  43978. */
  43979. ;
  43980. _proto.blacklistCurrentPlaylist = function blacklistCurrentPlaylist(error, blacklistDuration) {
  43981. if (error === void 0) {
  43982. error = {};
  43983. } // If the `error` was generated by the playlist loader, it will contain
  43984. // the playlist we were trying to load (but failed) and that should be
  43985. // blacklisted instead of the currently selected playlist which is likely
  43986. // out-of-date in this scenario
  43987. var currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
  43988. blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were
  43989. // trying to load the master OR while we were disposing of the tech
  43990. if (!currentPlaylist) {
  43991. this.error = error;
  43992. if (this.mediaSource.readyState !== 'open') {
  43993. this.trigger('error');
  43994. } else {
  43995. this.sourceUpdater_.endOfStream('network');
  43996. }
  43997. return;
  43998. }
  43999. currentPlaylist.playlistErrors_++;
  44000. var playlists = this.masterPlaylistLoader_.master.playlists;
  44001. var enabledPlaylists = playlists.filter(isEnabled);
  44002. var isFinalRendition = enabledPlaylists.length === 1 && enabledPlaylists[0] === currentPlaylist; // Don't blacklist the only playlist unless it was blacklisted
  44003. // forever
  44004. if (playlists.length === 1 && blacklistDuration !== Infinity) {
  44005. videojs.log.warn("Problem encountered with playlist " + currentPlaylist.id + ". " + 'Trying again since it is the only playlist.');
  44006. this.tech_.trigger('retryplaylist'); // if this is a final rendition, we should delay
  44007. return this.masterPlaylistLoader_.load(isFinalRendition);
  44008. }
  44009. if (isFinalRendition) {
  44010. // Since we're on the final non-blacklisted playlist, and we're about to blacklist
  44011. // it, instead of erring the player or retrying this playlist, clear out the current
  44012. // blacklist. This allows other playlists to be attempted in case any have been
  44013. // fixed.
  44014. var reincluded = false;
  44015. playlists.forEach(function (playlist) {
  44016. // skip current playlist which is about to be blacklisted
  44017. if (playlist === currentPlaylist) {
  44018. return;
  44019. }
  44020. var excludeUntil = playlist.excludeUntil; // a playlist cannot be reincluded if it wasn't excluded to begin with.
  44021. if (typeof excludeUntil !== 'undefined' && excludeUntil !== Infinity) {
  44022. reincluded = true;
  44023. delete playlist.excludeUntil;
  44024. }
  44025. });
  44026. if (reincluded) {
  44027. videojs.log.warn('Removing other playlists from the exclusion list because the last ' + 'rendition is about to be excluded.'); // Technically we are retrying a playlist, in that we are simply retrying a previous
  44028. // playlist. This is needed for users relying on the retryplaylist event to catch a
  44029. // case where the player might be stuck and looping through "dead" playlists.
  44030. this.tech_.trigger('retryplaylist');
  44031. }
  44032. } // Blacklist this playlist
  44033. var excludeUntil;
  44034. if (currentPlaylist.playlistErrors_ > this.maxPlaylistRetries) {
  44035. excludeUntil = Infinity;
  44036. } else {
  44037. excludeUntil = Date.now() + blacklistDuration * 1000;
  44038. }
  44039. currentPlaylist.excludeUntil = excludeUntil;
  44040. if (error.reason) {
  44041. currentPlaylist.lastExcludeReason_ = error.reason;
  44042. }
  44043. this.tech_.trigger('blacklistplaylist');
  44044. this.tech_.trigger({
  44045. type: 'usage',
  44046. name: 'vhs-rendition-blacklisted'
  44047. });
  44048. this.tech_.trigger({
  44049. type: 'usage',
  44050. name: 'hls-rendition-blacklisted'
  44051. }); // TODO: should we select a new playlist if this blacklist wasn't for the currentPlaylist?
  44052. // Would be something like media().id !=== currentPlaylist.id and we would need something
  44053. // like `pendingMedia` in playlist loaders to check against that too. This will prevent us
  44054. // from loading a new playlist on any blacklist.
  44055. // Select a new playlist
  44056. var nextPlaylist = this.selectPlaylist();
  44057. if (!nextPlaylist) {
  44058. this.error = 'Playback cannot continue. No available working or supported playlists.';
  44059. this.trigger('error');
  44060. return;
  44061. }
  44062. var logFn = error.internal ? this.logger_ : videojs.log.warn;
  44063. var errorMessage = error.message ? ' ' + error.message : '';
  44064. logFn((error.internal ? 'Internal problem' : 'Problem') + " encountered with playlist " + currentPlaylist.id + "." + (errorMessage + " Switching to playlist " + nextPlaylist.id + ".")); // if audio group changed reset audio loaders
  44065. if (nextPlaylist.attributes.AUDIO !== currentPlaylist.attributes.AUDIO) {
  44066. this.delegateLoaders_('audio', ['abort', 'pause']);
  44067. } // if subtitle group changed reset subtitle loaders
  44068. if (nextPlaylist.attributes.SUBTITLES !== currentPlaylist.attributes.SUBTITLES) {
  44069. this.delegateLoaders_('subtitle', ['abort', 'pause']);
  44070. }
  44071. this.delegateLoaders_('main', ['abort', 'pause']);
  44072. var delayDuration = nextPlaylist.targetDuration / 2 * 1000 || 5 * 1000;
  44073. var shouldDelay = typeof nextPlaylist.lastRequest === 'number' && Date.now() - nextPlaylist.lastRequest <= delayDuration; // delay if it's a final rendition or if the last refresh is sooner than half targetDuration
  44074. return this.switchMedia_(nextPlaylist, 'exclude', isFinalRendition || shouldDelay);
  44075. }
  44076. /**
  44077. * Pause all segment/playlist loaders
  44078. */
  44079. ;
  44080. _proto.pauseLoading = function pauseLoading() {
  44081. this.delegateLoaders_('all', ['abort', 'pause']);
  44082. this.stopABRTimer_();
  44083. }
  44084. /**
  44085. * Call a set of functions in order on playlist loaders, segment loaders,
  44086. * or both types of loaders.
  44087. *
  44088. * @param {string} filter
  44089. * Filter loaders that should call fnNames using a string. Can be:
  44090. * * all - run on all loaders
  44091. * * audio - run on all audio loaders
  44092. * * subtitle - run on all subtitle loaders
  44093. * * main - run on the main/master loaders
  44094. *
  44095. * @param {Array|string} fnNames
  44096. * A string or array of function names to call.
  44097. */
  44098. ;
  44099. _proto.delegateLoaders_ = function delegateLoaders_(filter, fnNames) {
  44100. var _this7 = this;
  44101. var loaders = [];
  44102. var dontFilterPlaylist = filter === 'all';
  44103. if (dontFilterPlaylist || filter === 'main') {
  44104. loaders.push(this.masterPlaylistLoader_);
  44105. }
  44106. var mediaTypes = [];
  44107. if (dontFilterPlaylist || filter === 'audio') {
  44108. mediaTypes.push('AUDIO');
  44109. }
  44110. if (dontFilterPlaylist || filter === 'subtitle') {
  44111. mediaTypes.push('CLOSED-CAPTIONS');
  44112. mediaTypes.push('SUBTITLES');
  44113. }
  44114. mediaTypes.forEach(function (mediaType) {
  44115. var loader = _this7.mediaTypes_[mediaType] && _this7.mediaTypes_[mediaType].activePlaylistLoader;
  44116. if (loader) {
  44117. loaders.push(loader);
  44118. }
  44119. });
  44120. ['main', 'audio', 'subtitle'].forEach(function (name) {
  44121. var loader = _this7[name + "SegmentLoader_"];
  44122. if (loader && (filter === name || filter === 'all')) {
  44123. loaders.push(loader);
  44124. }
  44125. });
  44126. loaders.forEach(function (loader) {
  44127. return fnNames.forEach(function (fnName) {
  44128. if (typeof loader[fnName] === 'function') {
  44129. loader[fnName]();
  44130. }
  44131. });
  44132. });
  44133. }
  44134. /**
  44135. * set the current time on all segment loaders
  44136. *
  44137. * @param {TimeRange} currentTime the current time to set
  44138. * @return {TimeRange} the current time
  44139. */
  44140. ;
  44141. _proto.setCurrentTime = function setCurrentTime(currentTime) {
  44142. var buffered = findRange(this.tech_.buffered(), currentTime);
  44143. if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
  44144. // return immediately if the metadata is not ready yet
  44145. return 0;
  44146. } // it's clearly an edge-case but don't thrown an error if asked to
  44147. // seek within an empty playlist
  44148. if (!this.masterPlaylistLoader_.media().segments) {
  44149. return 0;
  44150. } // if the seek location is already buffered, continue buffering as usual
  44151. if (buffered && buffered.length) {
  44152. return currentTime;
  44153. } // cancel outstanding requests so we begin buffering at the new
  44154. // location
  44155. this.mainSegmentLoader_.resetEverything();
  44156. this.mainSegmentLoader_.abort();
  44157. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  44158. this.audioSegmentLoader_.resetEverything();
  44159. this.audioSegmentLoader_.abort();
  44160. }
  44161. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  44162. this.subtitleSegmentLoader_.resetEverything();
  44163. this.subtitleSegmentLoader_.abort();
  44164. } // start segment loader loading in case they are paused
  44165. this.load();
  44166. }
  44167. /**
  44168. * get the current duration
  44169. *
  44170. * @return {TimeRange} the duration
  44171. */
  44172. ;
  44173. _proto.duration = function duration() {
  44174. if (!this.masterPlaylistLoader_) {
  44175. return 0;
  44176. }
  44177. var media = this.masterPlaylistLoader_.media();
  44178. if (!media) {
  44179. // no playlists loaded yet, so can't determine a duration
  44180. return 0;
  44181. } // Don't rely on the media source for duration in the case of a live playlist since
  44182. // setting the native MediaSource's duration to infinity ends up with consequences to
  44183. // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
  44184. //
  44185. // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
  44186. // however, few browsers have support for setLiveSeekableRange()
  44187. // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
  44188. //
  44189. // Until a time when the duration of the media source can be set to infinity, and a
  44190. // seekable range specified across browsers, just return Infinity.
  44191. if (!media.endList) {
  44192. return Infinity;
  44193. } // Since this is a VOD video, it is safe to rely on the media source's duration (if
  44194. // available). If it's not available, fall back to a playlist-calculated estimate.
  44195. if (this.mediaSource) {
  44196. return this.mediaSource.duration;
  44197. }
  44198. return Vhs$1.Playlist.duration(media);
  44199. }
  44200. /**
  44201. * check the seekable range
  44202. *
  44203. * @return {TimeRange} the seekable range
  44204. */
  44205. ;
  44206. _proto.seekable = function seekable() {
  44207. return this.seekable_;
  44208. };
  44209. _proto.onSyncInfoUpdate_ = function onSyncInfoUpdate_() {
  44210. var audioSeekable; // TODO check for creation of both source buffers before updating seekable
  44211. //
  44212. // A fix was made to this function where a check for
  44213. // this.sourceUpdater_.hasCreatedSourceBuffers
  44214. // was added to ensure that both source buffers were created before seekable was
  44215. // updated. However, it originally had a bug where it was checking for a true and
  44216. // returning early instead of checking for false. Setting it to check for false to
  44217. // return early though created other issues. A call to play() would check for seekable
  44218. // end without verifying that a seekable range was present. In addition, even checking
  44219. // for that didn't solve some issues, as handleFirstPlay is sometimes worked around
  44220. // due to a media update calling load on the segment loaders, skipping a seek to live,
  44221. // thereby starting live streams at the beginning of the stream rather than at the end.
  44222. //
  44223. // This conditional should be fixed to wait for the creation of two source buffers at
  44224. // the same time as the other sections of code are fixed to properly seek to live and
  44225. // not throw an error due to checking for a seekable end when no seekable range exists.
  44226. //
  44227. // For now, fall back to the older behavior, with the understanding that the seekable
  44228. // range may not be completely correct, leading to a suboptimal initial live point.
  44229. if (!this.masterPlaylistLoader_) {
  44230. return;
  44231. }
  44232. var media = this.masterPlaylistLoader_.media();
  44233. if (!media) {
  44234. return;
  44235. }
  44236. var expired = this.syncController_.getExpiredTime(media, this.duration());
  44237. if (expired === null) {
  44238. // not enough information to update seekable
  44239. return;
  44240. }
  44241. var master = this.masterPlaylistLoader_.master;
  44242. var mainSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(master, media));
  44243. if (mainSeekable.length === 0) {
  44244. return;
  44245. }
  44246. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  44247. media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
  44248. expired = this.syncController_.getExpiredTime(media, this.duration());
  44249. if (expired === null) {
  44250. return;
  44251. }
  44252. audioSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(master, media));
  44253. if (audioSeekable.length === 0) {
  44254. return;
  44255. }
  44256. }
  44257. var oldEnd;
  44258. var oldStart;
  44259. if (this.seekable_ && this.seekable_.length) {
  44260. oldEnd = this.seekable_.end(0);
  44261. oldStart = this.seekable_.start(0);
  44262. }
  44263. if (!audioSeekable) {
  44264. // seekable has been calculated based on buffering video data so it
  44265. // can be returned directly
  44266. this.seekable_ = mainSeekable;
  44267. } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
  44268. // seekables are pretty far off, rely on main
  44269. this.seekable_ = mainSeekable;
  44270. } else {
  44271. this.seekable_ = videojs.createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]);
  44272. } // seekable is the same as last time
  44273. if (this.seekable_ && this.seekable_.length) {
  44274. if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
  44275. return;
  44276. }
  44277. }
  44278. this.logger_("seekable updated [" + printableRange(this.seekable_) + "]");
  44279. this.tech_.trigger('seekablechanged');
  44280. }
  44281. /**
  44282. * Update the player duration
  44283. */
  44284. ;
  44285. _proto.updateDuration = function updateDuration(isLive) {
  44286. if (this.updateDuration_) {
  44287. this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
  44288. this.updateDuration_ = null;
  44289. }
  44290. if (this.mediaSource.readyState !== 'open') {
  44291. this.updateDuration_ = this.updateDuration.bind(this, isLive);
  44292. this.mediaSource.addEventListener('sourceopen', this.updateDuration_);
  44293. return;
  44294. }
  44295. if (isLive) {
  44296. var seekable = this.seekable();
  44297. if (!seekable.length) {
  44298. return;
  44299. } // Even in the case of a live playlist, the native MediaSource's duration should not
  44300. // be set to Infinity (even though this would be expected for a live playlist), since
  44301. // setting the native MediaSource's duration to infinity ends up with consequences to
  44302. // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
  44303. //
  44304. // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
  44305. // however, few browsers have support for setLiveSeekableRange()
  44306. // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
  44307. //
  44308. // Until a time when the duration of the media source can be set to infinity, and a
  44309. // seekable range specified across browsers, the duration should be greater than or
  44310. // equal to the last possible seekable value.
  44311. // MediaSource duration starts as NaN
  44312. // It is possible (and probable) that this case will never be reached for many
  44313. // sources, since the MediaSource reports duration as the highest value without
  44314. // accounting for timestamp offset. For example, if the timestamp offset is -100 and
  44315. // we buffered times 0 to 100 with real times of 100 to 200, even though current
  44316. // time will be between 0 and 100, the native media source may report the duration
  44317. // as 200. However, since we report duration separate from the media source (as
  44318. // Infinity), and as long as the native media source duration value is greater than
  44319. // our reported seekable range, seeks will work as expected. The large number as
  44320. // duration for live is actually a strategy used by some players to work around the
  44321. // issue of live seekable ranges cited above.
  44322. if (isNaN(this.mediaSource.duration) || this.mediaSource.duration < seekable.end(seekable.length - 1)) {
  44323. this.sourceUpdater_.setDuration(seekable.end(seekable.length - 1));
  44324. }
  44325. return;
  44326. }
  44327. var buffered = this.tech_.buffered();
  44328. var duration = Vhs$1.Playlist.duration(this.masterPlaylistLoader_.media());
  44329. if (buffered.length > 0) {
  44330. duration = Math.max(duration, buffered.end(buffered.length - 1));
  44331. }
  44332. if (this.mediaSource.duration !== duration) {
  44333. this.sourceUpdater_.setDuration(duration);
  44334. }
  44335. }
  44336. /**
  44337. * dispose of the MasterPlaylistController and everything
  44338. * that it controls
  44339. */
  44340. ;
  44341. _proto.dispose = function dispose() {
  44342. var _this8 = this;
  44343. this.trigger('dispose');
  44344. this.decrypter_.terminate();
  44345. this.masterPlaylistLoader_.dispose();
  44346. this.mainSegmentLoader_.dispose();
  44347. if (this.loadOnPlay_) {
  44348. this.tech_.off('play', this.loadOnPlay_);
  44349. }
  44350. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  44351. var groups = _this8.mediaTypes_[type].groups;
  44352. for (var id in groups) {
  44353. groups[id].forEach(function (group) {
  44354. if (group.playlistLoader) {
  44355. group.playlistLoader.dispose();
  44356. }
  44357. });
  44358. }
  44359. });
  44360. this.audioSegmentLoader_.dispose();
  44361. this.subtitleSegmentLoader_.dispose();
  44362. this.sourceUpdater_.dispose();
  44363. this.timelineChangeController_.dispose();
  44364. this.stopABRTimer_();
  44365. if (this.updateDuration_) {
  44366. this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
  44367. }
  44368. this.mediaSource.removeEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
  44369. this.mediaSource.removeEventListener('sourceopen', this.handleSourceOpen_);
  44370. this.mediaSource.removeEventListener('sourceended', this.handleSourceEnded_);
  44371. this.off();
  44372. }
  44373. /**
  44374. * return the master playlist object if we have one
  44375. *
  44376. * @return {Object} the master playlist object that we parsed
  44377. */
  44378. ;
  44379. _proto.master = function master() {
  44380. return this.masterPlaylistLoader_.master;
  44381. }
  44382. /**
  44383. * return the currently selected playlist
  44384. *
  44385. * @return {Object} the currently selected playlist object that we parsed
  44386. */
  44387. ;
  44388. _proto.media = function media() {
  44389. // playlist loader will not return media if it has not been fully loaded
  44390. return this.masterPlaylistLoader_.media() || this.initialMedia_;
  44391. };
  44392. _proto.areMediaTypesKnown_ = function areMediaTypesKnown_() {
  44393. var usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
  44394. var hasMainMediaInfo = !!this.mainSegmentLoader_.getCurrentMediaInfo_(); // if we are not using an audio loader, then we have audio media info
  44395. // otherwise check on the segment loader.
  44396. var hasAudioMediaInfo = !usingAudioLoader ? true : !!this.audioSegmentLoader_.getCurrentMediaInfo_(); // one or both loaders has not loaded sufficently to get codecs
  44397. if (!hasMainMediaInfo || !hasAudioMediaInfo) {
  44398. return false;
  44399. }
  44400. return true;
  44401. };
  44402. _proto.getCodecsOrExclude_ = function getCodecsOrExclude_() {
  44403. var _this9 = this;
  44404. var media = {
  44405. main: this.mainSegmentLoader_.getCurrentMediaInfo_() || {},
  44406. audio: this.audioSegmentLoader_.getCurrentMediaInfo_() || {}
  44407. };
  44408. var playlist = this.mainSegmentLoader_.getPendingSegmentPlaylist() || this.media(); // set "main" media equal to video
  44409. media.video = media.main;
  44410. var playlistCodecs = codecsForPlaylist(this.master(), playlist);
  44411. var codecs = {};
  44412. var usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
  44413. if (media.main.hasVideo) {
  44414. codecs.video = playlistCodecs.video || media.main.videoCodec || DEFAULT_VIDEO_CODEC;
  44415. }
  44416. if (media.main.isMuxed) {
  44417. codecs.video += "," + (playlistCodecs.audio || media.main.audioCodec || DEFAULT_AUDIO_CODEC);
  44418. }
  44419. if (media.main.hasAudio && !media.main.isMuxed || media.audio.hasAudio || usingAudioLoader) {
  44420. codecs.audio = playlistCodecs.audio || media.main.audioCodec || media.audio.audioCodec || DEFAULT_AUDIO_CODEC; // set audio isFmp4 so we use the correct "supports" function below
  44421. media.audio.isFmp4 = media.main.hasAudio && !media.main.isMuxed ? media.main.isFmp4 : media.audio.isFmp4;
  44422. } // no codecs, no playback.
  44423. if (!codecs.audio && !codecs.video) {
  44424. this.blacklistCurrentPlaylist({
  44425. playlist: playlist,
  44426. message: 'Could not determine codecs for playlist.',
  44427. blacklistDuration: Infinity
  44428. });
  44429. return;
  44430. } // fmp4 relies on browser support, while ts relies on muxer support
  44431. var supportFunction = function supportFunction(isFmp4, codec) {
  44432. return isFmp4 ? browserSupportsCodec(codec) : muxerSupportsCodec(codec);
  44433. };
  44434. var unsupportedCodecs = {};
  44435. var unsupportedAudio;
  44436. ['video', 'audio'].forEach(function (type) {
  44437. if (codecs.hasOwnProperty(type) && !supportFunction(media[type].isFmp4, codecs[type])) {
  44438. var supporter = media[type].isFmp4 ? 'browser' : 'muxer';
  44439. unsupportedCodecs[supporter] = unsupportedCodecs[supporter] || [];
  44440. unsupportedCodecs[supporter].push(codecs[type]);
  44441. if (type === 'audio') {
  44442. unsupportedAudio = supporter;
  44443. }
  44444. }
  44445. });
  44446. if (usingAudioLoader && unsupportedAudio && playlist.attributes.AUDIO) {
  44447. var audioGroup = playlist.attributes.AUDIO;
  44448. this.master().playlists.forEach(function (variant) {
  44449. var variantAudioGroup = variant.attributes && variant.attributes.AUDIO;
  44450. if (variantAudioGroup === audioGroup && variant !== playlist) {
  44451. variant.excludeUntil = Infinity;
  44452. }
  44453. });
  44454. this.logger_("excluding audio group " + audioGroup + " as " + unsupportedAudio + " does not support codec(s): \"" + codecs.audio + "\"");
  44455. } // if we have any unsupported codecs blacklist this playlist.
  44456. if (Object.keys(unsupportedCodecs).length) {
  44457. var message = Object.keys(unsupportedCodecs).reduce(function (acc, supporter) {
  44458. if (acc) {
  44459. acc += ', ';
  44460. }
  44461. acc += supporter + " does not support codec(s): \"" + unsupportedCodecs[supporter].join(',') + "\"";
  44462. return acc;
  44463. }, '') + '.';
  44464. this.blacklistCurrentPlaylist({
  44465. playlist: playlist,
  44466. internal: true,
  44467. message: message,
  44468. blacklistDuration: Infinity
  44469. });
  44470. return;
  44471. } // check if codec switching is happening
  44472. if (this.sourceUpdater_.hasCreatedSourceBuffers() && !this.sourceUpdater_.canChangeType()) {
  44473. var switchMessages = [];
  44474. ['video', 'audio'].forEach(function (type) {
  44475. var newCodec = (parseCodecs(_this9.sourceUpdater_.codecs[type] || '')[0] || {}).type;
  44476. var oldCodec = (parseCodecs(codecs[type] || '')[0] || {}).type;
  44477. if (newCodec && oldCodec && newCodec.toLowerCase() !== oldCodec.toLowerCase()) {
  44478. switchMessages.push("\"" + _this9.sourceUpdater_.codecs[type] + "\" -> \"" + codecs[type] + "\"");
  44479. }
  44480. });
  44481. if (switchMessages.length) {
  44482. this.blacklistCurrentPlaylist({
  44483. playlist: playlist,
  44484. message: "Codec switching not supported: " + switchMessages.join(', ') + ".",
  44485. blacklistDuration: Infinity,
  44486. internal: true
  44487. });
  44488. return;
  44489. }
  44490. } // TODO: when using the muxer shouldn't we just return
  44491. // the codecs that the muxer outputs?
  44492. return codecs;
  44493. }
  44494. /**
  44495. * Create source buffers and exlude any incompatible renditions.
  44496. *
  44497. * @private
  44498. */
  44499. ;
  44500. _proto.tryToCreateSourceBuffers_ = function tryToCreateSourceBuffers_() {
  44501. // media source is not ready yet or sourceBuffers are already
  44502. // created.
  44503. if (this.mediaSource.readyState !== 'open' || this.sourceUpdater_.hasCreatedSourceBuffers()) {
  44504. return;
  44505. }
  44506. if (!this.areMediaTypesKnown_()) {
  44507. return;
  44508. }
  44509. var codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
  44510. if (!codecs) {
  44511. return;
  44512. }
  44513. this.sourceUpdater_.createSourceBuffers(codecs);
  44514. var codecString = [codecs.video, codecs.audio].filter(Boolean).join(',');
  44515. this.excludeIncompatibleVariants_(codecString);
  44516. }
  44517. /**
  44518. * Excludes playlists with codecs that are unsupported by the muxer and browser.
  44519. */
  44520. ;
  44521. _proto.excludeUnsupportedVariants_ = function excludeUnsupportedVariants_() {
  44522. var _this10 = this;
  44523. var playlists = this.master().playlists;
  44524. var ids = []; // TODO: why don't we have a property to loop through all
  44525. // playlist? Why did we ever mix indexes and keys?
  44526. Object.keys(playlists).forEach(function (key) {
  44527. var variant = playlists[key]; // check if we already processed this playlist.
  44528. if (ids.indexOf(variant.id) !== -1) {
  44529. return;
  44530. }
  44531. ids.push(variant.id);
  44532. var codecs = codecsForPlaylist(_this10.master, variant);
  44533. var unsupported = [];
  44534. if (codecs.audio && !muxerSupportsCodec(codecs.audio) && !browserSupportsCodec(codecs.audio)) {
  44535. unsupported.push("audio codec " + codecs.audio);
  44536. }
  44537. if (codecs.video && !muxerSupportsCodec(codecs.video) && !browserSupportsCodec(codecs.video)) {
  44538. unsupported.push("video codec " + codecs.video);
  44539. }
  44540. if (codecs.text && codecs.text === 'stpp.ttml.im1t') {
  44541. unsupported.push("text codec " + codecs.text);
  44542. }
  44543. if (unsupported.length) {
  44544. variant.excludeUntil = Infinity;
  44545. _this10.logger_("excluding " + variant.id + " for unsupported: " + unsupported.join(', '));
  44546. }
  44547. });
  44548. }
  44549. /**
  44550. * Blacklist playlists that are known to be codec or
  44551. * stream-incompatible with the SourceBuffer configuration. For
  44552. * instance, Media Source Extensions would cause the video element to
  44553. * stall waiting for video data if you switched from a variant with
  44554. * video and audio to an audio-only one.
  44555. *
  44556. * @param {Object} media a media playlist compatible with the current
  44557. * set of SourceBuffers. Variants in the current master playlist that
  44558. * do not appear to have compatible codec or stream configurations
  44559. * will be excluded from the default playlist selection algorithm
  44560. * indefinitely.
  44561. * @private
  44562. */
  44563. ;
  44564. _proto.excludeIncompatibleVariants_ = function excludeIncompatibleVariants_(codecString) {
  44565. var _this11 = this;
  44566. var ids = [];
  44567. var playlists = this.master().playlists;
  44568. var codecs = unwrapCodecList(parseCodecs(codecString));
  44569. var codecCount_ = codecCount(codecs);
  44570. var videoDetails = codecs.video && parseCodecs(codecs.video)[0] || null;
  44571. var audioDetails = codecs.audio && parseCodecs(codecs.audio)[0] || null;
  44572. Object.keys(playlists).forEach(function (key) {
  44573. var variant = playlists[key]; // check if we already processed this playlist.
  44574. // or it if it is already excluded forever.
  44575. if (ids.indexOf(variant.id) !== -1 || variant.excludeUntil === Infinity) {
  44576. return;
  44577. }
  44578. ids.push(variant.id);
  44579. var blacklistReasons = []; // get codecs from the playlist for this variant
  44580. var variantCodecs = codecsForPlaylist(_this11.masterPlaylistLoader_.master, variant);
  44581. var variantCodecCount = codecCount(variantCodecs); // if no codecs are listed, we cannot determine that this
  44582. // variant is incompatible. Wait for mux.js to probe
  44583. if (!variantCodecs.audio && !variantCodecs.video) {
  44584. return;
  44585. } // TODO: we can support this by removing the
  44586. // old media source and creating a new one, but it will take some work.
  44587. // The number of streams cannot change
  44588. if (variantCodecCount !== codecCount_) {
  44589. blacklistReasons.push("codec count \"" + variantCodecCount + "\" !== \"" + codecCount_ + "\"");
  44590. } // only exclude playlists by codec change, if codecs cannot switch
  44591. // during playback.
  44592. if (!_this11.sourceUpdater_.canChangeType()) {
  44593. var variantVideoDetails = variantCodecs.video && parseCodecs(variantCodecs.video)[0] || null;
  44594. var variantAudioDetails = variantCodecs.audio && parseCodecs(variantCodecs.audio)[0] || null; // the video codec cannot change
  44595. if (variantVideoDetails && videoDetails && variantVideoDetails.type.toLowerCase() !== videoDetails.type.toLowerCase()) {
  44596. blacklistReasons.push("video codec \"" + variantVideoDetails.type + "\" !== \"" + videoDetails.type + "\"");
  44597. } // the audio codec cannot change
  44598. if (variantAudioDetails && audioDetails && variantAudioDetails.type.toLowerCase() !== audioDetails.type.toLowerCase()) {
  44599. blacklistReasons.push("audio codec \"" + variantAudioDetails.type + "\" !== \"" + audioDetails.type + "\"");
  44600. }
  44601. }
  44602. if (blacklistReasons.length) {
  44603. variant.excludeUntil = Infinity;
  44604. _this11.logger_("blacklisting " + variant.id + ": " + blacklistReasons.join(' && '));
  44605. }
  44606. });
  44607. };
  44608. _proto.updateAdCues_ = function updateAdCues_(media) {
  44609. var offset = 0;
  44610. var seekable = this.seekable();
  44611. if (seekable.length) {
  44612. offset = seekable.start(0);
  44613. }
  44614. updateAdCues(media, this.cueTagsTrack_, offset);
  44615. }
  44616. /**
  44617. * Calculates the desired forward buffer length based on current time
  44618. *
  44619. * @return {number} Desired forward buffer length in seconds
  44620. */
  44621. ;
  44622. _proto.goalBufferLength = function goalBufferLength() {
  44623. var currentTime = this.tech_.currentTime();
  44624. var initial = Config.GOAL_BUFFER_LENGTH;
  44625. var rate = Config.GOAL_BUFFER_LENGTH_RATE;
  44626. var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
  44627. return Math.min(initial + currentTime * rate, max);
  44628. }
  44629. /**
  44630. * Calculates the desired buffer low water line based on current time
  44631. *
  44632. * @return {number} Desired buffer low water line in seconds
  44633. */
  44634. ;
  44635. _proto.bufferLowWaterLine = function bufferLowWaterLine() {
  44636. var currentTime = this.tech_.currentTime();
  44637. var initial = Config.BUFFER_LOW_WATER_LINE;
  44638. var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
  44639. var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
  44640. var newMax = Math.max(initial, Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE);
  44641. return Math.min(initial + currentTime * rate, this.experimentalBufferBasedABR ? newMax : max);
  44642. };
  44643. _proto.bufferHighWaterLine = function bufferHighWaterLine() {
  44644. return Config.BUFFER_HIGH_WATER_LINE;
  44645. };
  44646. return MasterPlaylistController;
  44647. }(videojs.EventTarget);
  44648. /**
  44649. * Returns a function that acts as the Enable/disable playlist function.
  44650. *
  44651. * @param {PlaylistLoader} loader - The master playlist loader
  44652. * @param {string} playlistID - id of the playlist
  44653. * @param {Function} changePlaylistFn - A function to be called after a
  44654. * playlist's enabled-state has been changed. Will NOT be called if a
  44655. * playlist's enabled-state is unchanged
  44656. * @param {boolean=} enable - Value to set the playlist enabled-state to
  44657. * or if undefined returns the current enabled-state for the playlist
  44658. * @return {Function} Function for setting/getting enabled
  44659. */
  44660. var enableFunction = function enableFunction(loader, playlistID, changePlaylistFn) {
  44661. return function (enable) {
  44662. var playlist = loader.master.playlists[playlistID];
  44663. var incompatible = isIncompatible(playlist);
  44664. var currentlyEnabled = isEnabled(playlist);
  44665. if (typeof enable === 'undefined') {
  44666. return currentlyEnabled;
  44667. }
  44668. if (enable) {
  44669. delete playlist.disabled;
  44670. } else {
  44671. playlist.disabled = true;
  44672. }
  44673. if (enable !== currentlyEnabled && !incompatible) {
  44674. // Ensure the outside world knows about our changes
  44675. changePlaylistFn();
  44676. if (enable) {
  44677. loader.trigger('renditionenabled');
  44678. } else {
  44679. loader.trigger('renditiondisabled');
  44680. }
  44681. }
  44682. return enable;
  44683. };
  44684. };
  44685. /**
  44686. * The representation object encapsulates the publicly visible information
  44687. * in a media playlist along with a setter/getter-type function (enabled)
  44688. * for changing the enabled-state of a particular playlist entry
  44689. *
  44690. * @class Representation
  44691. */
  44692. var Representation = function Representation(vhsHandler, playlist, id) {
  44693. var mpc = vhsHandler.masterPlaylistController_,
  44694. smoothQualityChange = vhsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function
  44695. var changeType = smoothQualityChange ? 'smooth' : 'fast';
  44696. var qualityChangeFunction = mpc[changeType + "QualityChange_"].bind(mpc); // some playlist attributes are optional
  44697. if (playlist.attributes) {
  44698. var resolution = playlist.attributes.RESOLUTION;
  44699. this.width = resolution && resolution.width;
  44700. this.height = resolution && resolution.height;
  44701. this.bandwidth = playlist.attributes.BANDWIDTH;
  44702. this.frameRate = playlist.attributes['FRAME-RATE'];
  44703. }
  44704. this.codecs = codecsForPlaylist(mpc.master(), playlist);
  44705. this.playlist = playlist; // The id is simply the ordinality of the media playlist
  44706. // within the master playlist
  44707. this.id = id; // Partially-apply the enableFunction to create a playlist-
  44708. // specific variant
  44709. this.enabled = enableFunction(vhsHandler.playlists, playlist.id, qualityChangeFunction);
  44710. };
  44711. /**
  44712. * A mixin function that adds the `representations` api to an instance
  44713. * of the VhsHandler class
  44714. *
  44715. * @param {VhsHandler} vhsHandler - An instance of VhsHandler to add the
  44716. * representation API into
  44717. */
  44718. var renditionSelectionMixin = function renditionSelectionMixin(vhsHandler) {
  44719. // Add a single API-specific function to the VhsHandler instance
  44720. vhsHandler.representations = function () {
  44721. var master = vhsHandler.masterPlaylistController_.master();
  44722. var playlists = isAudioOnly(master) ? vhsHandler.masterPlaylistController_.getAudioTrackPlaylists_() : master.playlists;
  44723. if (!playlists) {
  44724. return [];
  44725. }
  44726. return playlists.filter(function (media) {
  44727. return !isIncompatible(media);
  44728. }).map(function (e, i) {
  44729. return new Representation(vhsHandler, e, e.id);
  44730. });
  44731. };
  44732. };
  44733. /**
  44734. * @file playback-watcher.js
  44735. *
  44736. * Playback starts, and now my watch begins. It shall not end until my death. I shall
  44737. * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
  44738. * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
  44739. * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
  44740. * my life and honor to the Playback Watch, for this Player and all the Players to come.
  44741. */
  44742. var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
  44743. /**
  44744. * @class PlaybackWatcher
  44745. */
  44746. var PlaybackWatcher = /*#__PURE__*/function () {
  44747. /**
  44748. * Represents an PlaybackWatcher object.
  44749. *
  44750. * @class
  44751. * @param {Object} options an object that includes the tech and settings
  44752. */
  44753. function PlaybackWatcher(options) {
  44754. var _this = this;
  44755. this.masterPlaylistController_ = options.masterPlaylistController;
  44756. this.tech_ = options.tech;
  44757. this.seekable = options.seekable;
  44758. this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
  44759. this.liveRangeSafeTimeDelta = options.liveRangeSafeTimeDelta;
  44760. this.media = options.media;
  44761. this.consecutiveUpdates = 0;
  44762. this.lastRecordedTime = null;
  44763. this.timer_ = null;
  44764. this.checkCurrentTimeTimeout_ = null;
  44765. this.logger_ = logger('PlaybackWatcher');
  44766. this.logger_('initialize');
  44767. var playHandler = function playHandler() {
  44768. return _this.monitorCurrentTime_();
  44769. };
  44770. var canPlayHandler = function canPlayHandler() {
  44771. return _this.monitorCurrentTime_();
  44772. };
  44773. var waitingHandler = function waitingHandler() {
  44774. return _this.techWaiting_();
  44775. };
  44776. var cancelTimerHandler = function cancelTimerHandler() {
  44777. return _this.cancelTimer_();
  44778. };
  44779. var mpc = this.masterPlaylistController_;
  44780. var loaderTypes = ['main', 'subtitle', 'audio'];
  44781. var loaderChecks = {};
  44782. loaderTypes.forEach(function (type) {
  44783. loaderChecks[type] = {
  44784. reset: function reset() {
  44785. return _this.resetSegmentDownloads_(type);
  44786. },
  44787. updateend: function updateend() {
  44788. return _this.checkSegmentDownloads_(type);
  44789. }
  44790. };
  44791. mpc[type + "SegmentLoader_"].on('appendsdone', loaderChecks[type].updateend); // If a rendition switch happens during a playback stall where the buffer
  44792. // isn't changing we want to reset. We cannot assume that the new rendition
  44793. // will also be stalled, until after new appends.
  44794. mpc[type + "SegmentLoader_"].on('playlistupdate', loaderChecks[type].reset); // Playback stalls should not be detected right after seeking.
  44795. // This prevents one segment playlists (single vtt or single segment content)
  44796. // from being detected as stalling. As the buffer will not change in those cases, since
  44797. // the buffer is the entire video duration.
  44798. _this.tech_.on(['seeked', 'seeking'], loaderChecks[type].reset);
  44799. });
  44800. /**
  44801. * We check if a seek was into a gap through the following steps:
  44802. * 1. We get a seeking event and we do not get a seeked event. This means that
  44803. * a seek was attempted but not completed.
  44804. * 2. We run `fixesBadSeeks_` on segment loader appends. This means that we already
  44805. * removed everything from our buffer and appended a segment, and should be ready
  44806. * to check for gaps.
  44807. */
  44808. var setSeekingHandlers = function setSeekingHandlers(fn) {
  44809. ['main', 'audio'].forEach(function (type) {
  44810. mpc[type + "SegmentLoader_"][fn]('appended', _this.seekingAppendCheck_);
  44811. });
  44812. };
  44813. this.seekingAppendCheck_ = function () {
  44814. if (_this.fixesBadSeeks_()) {
  44815. _this.consecutiveUpdates = 0;
  44816. _this.lastRecordedTime = _this.tech_.currentTime();
  44817. setSeekingHandlers('off');
  44818. }
  44819. };
  44820. this.clearSeekingAppendCheck_ = function () {
  44821. return setSeekingHandlers('off');
  44822. };
  44823. this.watchForBadSeeking_ = function () {
  44824. _this.clearSeekingAppendCheck_();
  44825. setSeekingHandlers('on');
  44826. };
  44827. this.tech_.on('seeked', this.clearSeekingAppendCheck_);
  44828. this.tech_.on('seeking', this.watchForBadSeeking_);
  44829. this.tech_.on('waiting', waitingHandler);
  44830. this.tech_.on(timerCancelEvents, cancelTimerHandler);
  44831. this.tech_.on('canplay', canPlayHandler);
  44832. /*
  44833. An edge case exists that results in gaps not being skipped when they exist at the beginning of a stream. This case
  44834. is surfaced in one of two ways:
  44835. 1) The `waiting` event is fired before the player has buffered content, making it impossible
  44836. to find or skip the gap. The `waiting` event is followed by a `play` event. On first play
  44837. we can check if playback is stalled due to a gap, and skip the gap if necessary.
  44838. 2) A source with a gap at the beginning of the stream is loaded programatically while the player
  44839. is in a playing state. To catch this case, it's important that our one-time play listener is setup
  44840. even if the player is in a playing state
  44841. */
  44842. this.tech_.one('play', playHandler); // Define the dispose function to clean up our events
  44843. this.dispose = function () {
  44844. _this.clearSeekingAppendCheck_();
  44845. _this.logger_('dispose');
  44846. _this.tech_.off('waiting', waitingHandler);
  44847. _this.tech_.off(timerCancelEvents, cancelTimerHandler);
  44848. _this.tech_.off('canplay', canPlayHandler);
  44849. _this.tech_.off('play', playHandler);
  44850. _this.tech_.off('seeking', _this.watchForBadSeeking_);
  44851. _this.tech_.off('seeked', _this.clearSeekingAppendCheck_);
  44852. loaderTypes.forEach(function (type) {
  44853. mpc[type + "SegmentLoader_"].off('appendsdone', loaderChecks[type].updateend);
  44854. mpc[type + "SegmentLoader_"].off('playlistupdate', loaderChecks[type].reset);
  44855. _this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset);
  44856. });
  44857. if (_this.checkCurrentTimeTimeout_) {
  44858. window$1.clearTimeout(_this.checkCurrentTimeTimeout_);
  44859. }
  44860. _this.cancelTimer_();
  44861. };
  44862. }
  44863. /**
  44864. * Periodically check current time to see if playback stopped
  44865. *
  44866. * @private
  44867. */
  44868. var _proto = PlaybackWatcher.prototype;
  44869. _proto.monitorCurrentTime_ = function monitorCurrentTime_() {
  44870. this.checkCurrentTime_();
  44871. if (this.checkCurrentTimeTimeout_) {
  44872. window$1.clearTimeout(this.checkCurrentTimeTimeout_);
  44873. } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  44874. this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
  44875. }
  44876. /**
  44877. * Reset stalled download stats for a specific type of loader
  44878. *
  44879. * @param {string} type
  44880. * The segment loader type to check.
  44881. *
  44882. * @listens SegmentLoader#playlistupdate
  44883. * @listens Tech#seeking
  44884. * @listens Tech#seeked
  44885. */
  44886. ;
  44887. _proto.resetSegmentDownloads_ = function resetSegmentDownloads_(type) {
  44888. var loader = this.masterPlaylistController_[type + "SegmentLoader_"];
  44889. if (this[type + "StalledDownloads_"] > 0) {
  44890. this.logger_("resetting possible stalled download count for " + type + " loader");
  44891. }
  44892. this[type + "StalledDownloads_"] = 0;
  44893. this[type + "Buffered_"] = loader.buffered_();
  44894. }
  44895. /**
  44896. * Checks on every segment `appendsdone` to see
  44897. * if segment appends are making progress. If they are not
  44898. * and we are still downloading bytes. We blacklist the playlist.
  44899. *
  44900. * @param {string} type
  44901. * The segment loader type to check.
  44902. *
  44903. * @listens SegmentLoader#appendsdone
  44904. */
  44905. ;
  44906. _proto.checkSegmentDownloads_ = function checkSegmentDownloads_(type) {
  44907. var mpc = this.masterPlaylistController_;
  44908. var loader = mpc[type + "SegmentLoader_"];
  44909. var buffered = loader.buffered_();
  44910. var isBufferedDifferent = isRangeDifferent(this[type + "Buffered_"], buffered);
  44911. this[type + "Buffered_"] = buffered; // if another watcher is going to fix the issue or
  44912. // the buffered value for this loader changed
  44913. // appends are working
  44914. if (isBufferedDifferent) {
  44915. this.resetSegmentDownloads_(type);
  44916. return;
  44917. }
  44918. this[type + "StalledDownloads_"]++;
  44919. this.logger_("found #" + this[type + "StalledDownloads_"] + " " + type + " appends that did not increase buffer (possible stalled download)", {
  44920. playlistId: loader.playlist_ && loader.playlist_.id,
  44921. buffered: timeRangesToArray(buffered)
  44922. }); // after 10 possibly stalled appends with no reset, exclude
  44923. if (this[type + "StalledDownloads_"] < 10) {
  44924. return;
  44925. }
  44926. this.logger_(type + " loader stalled download exclusion");
  44927. this.resetSegmentDownloads_(type);
  44928. this.tech_.trigger({
  44929. type: 'usage',
  44930. name: "vhs-" + type + "-download-exclusion"
  44931. });
  44932. if (type === 'subtitle') {
  44933. return;
  44934. } // TODO: should we exclude audio tracks rather than main tracks
  44935. // when type is audio?
  44936. mpc.blacklistCurrentPlaylist({
  44937. message: "Excessive " + type + " segment downloading detected."
  44938. }, Infinity);
  44939. }
  44940. /**
  44941. * The purpose of this function is to emulate the "waiting" event on
  44942. * browsers that do not emit it when they are waiting for more
  44943. * data to continue playback
  44944. *
  44945. * @private
  44946. */
  44947. ;
  44948. _proto.checkCurrentTime_ = function checkCurrentTime_() {
  44949. if (this.tech_.paused() || this.tech_.seeking()) {
  44950. return;
  44951. }
  44952. var currentTime = this.tech_.currentTime();
  44953. var buffered = this.tech_.buffered();
  44954. if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
  44955. // If current time is at the end of the final buffered region, then any playback
  44956. // stall is most likely caused by buffering in a low bandwidth environment. The tech
  44957. // should fire a `waiting` event in this scenario, but due to browser and tech
  44958. // inconsistencies. Calling `techWaiting_` here allows us to simulate
  44959. // responding to a native `waiting` event when the tech fails to emit one.
  44960. return this.techWaiting_();
  44961. }
  44962. if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
  44963. this.consecutiveUpdates++;
  44964. this.waiting_();
  44965. } else if (currentTime === this.lastRecordedTime) {
  44966. this.consecutiveUpdates++;
  44967. } else {
  44968. this.consecutiveUpdates = 0;
  44969. this.lastRecordedTime = currentTime;
  44970. }
  44971. }
  44972. /**
  44973. * Cancels any pending timers and resets the 'timeupdate' mechanism
  44974. * designed to detect that we are stalled
  44975. *
  44976. * @private
  44977. */
  44978. ;
  44979. _proto.cancelTimer_ = function cancelTimer_() {
  44980. this.consecutiveUpdates = 0;
  44981. if (this.timer_) {
  44982. this.logger_('cancelTimer_');
  44983. clearTimeout(this.timer_);
  44984. }
  44985. this.timer_ = null;
  44986. }
  44987. /**
  44988. * Fixes situations where there's a bad seek
  44989. *
  44990. * @return {boolean} whether an action was taken to fix the seek
  44991. * @private
  44992. */
  44993. ;
  44994. _proto.fixesBadSeeks_ = function fixesBadSeeks_() {
  44995. var seeking = this.tech_.seeking();
  44996. if (!seeking) {
  44997. return false;
  44998. } // TODO: It's possible that these seekable checks should be moved out of this function
  44999. // and into a function that runs on seekablechange. It's also possible that we only need
  45000. // afterSeekableWindow as the buffered check at the bottom is good enough to handle before
  45001. // seekable range.
  45002. var seekable = this.seekable();
  45003. var currentTime = this.tech_.currentTime();
  45004. var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
  45005. var seekTo;
  45006. if (isAfterSeekableRange) {
  45007. var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
  45008. seekTo = seekableEnd;
  45009. }
  45010. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  45011. var seekableStart = seekable.start(0); // sync to the beginning of the live window
  45012. // provide a buffer of .1 seconds to handle rounding/imprecise numbers
  45013. seekTo = seekableStart + ( // if the playlist is too short and the seekable range is an exact time (can
  45014. // happen in live with a 3 segment playlist), then don't use a time delta
  45015. seekableStart === seekable.end(0) ? 0 : SAFE_TIME_DELTA);
  45016. }
  45017. if (typeof seekTo !== 'undefined') {
  45018. this.logger_("Trying to seek outside of seekable at time " + currentTime + " with " + ("seekable range " + printableRange(seekable) + ". Seeking to ") + (seekTo + "."));
  45019. this.tech_.setCurrentTime(seekTo);
  45020. return true;
  45021. }
  45022. var sourceUpdater = this.masterPlaylistController_.sourceUpdater_;
  45023. var buffered = this.tech_.buffered();
  45024. var audioBuffered = sourceUpdater.audioBuffer ? sourceUpdater.audioBuffered() : null;
  45025. var videoBuffered = sourceUpdater.videoBuffer ? sourceUpdater.videoBuffered() : null;
  45026. var media = this.media(); // verify that at least two segment durations or one part duration have been
  45027. // appended before checking for a gap.
  45028. var minAppendedDuration = media.partTargetDuration ? media.partTargetDuration : (media.targetDuration - TIME_FUDGE_FACTOR) * 2; // verify that at least two segment durations have been
  45029. // appended before checking for a gap.
  45030. var bufferedToCheck = [audioBuffered, videoBuffered];
  45031. for (var i = 0; i < bufferedToCheck.length; i++) {
  45032. // skip null buffered
  45033. if (!bufferedToCheck[i]) {
  45034. continue;
  45035. }
  45036. var timeAhead = timeAheadOf(bufferedToCheck[i], currentTime); // if we are less than two video/audio segment durations or one part
  45037. // duration behind we haven't appended enough to call this a bad seek.
  45038. if (timeAhead < minAppendedDuration) {
  45039. return false;
  45040. }
  45041. }
  45042. var nextRange = findNextRange(buffered, currentTime); // we have appended enough content, but we don't have anything buffered
  45043. // to seek over the gap
  45044. if (nextRange.length === 0) {
  45045. return false;
  45046. }
  45047. seekTo = nextRange.start(0) + SAFE_TIME_DELTA;
  45048. this.logger_("Buffered region starts (" + nextRange.start(0) + ") " + (" just beyond seek point (" + currentTime + "). Seeking to " + seekTo + "."));
  45049. this.tech_.setCurrentTime(seekTo);
  45050. return true;
  45051. }
  45052. /**
  45053. * Handler for situations when we determine the player is waiting.
  45054. *
  45055. * @private
  45056. */
  45057. ;
  45058. _proto.waiting_ = function waiting_() {
  45059. if (this.techWaiting_()) {
  45060. return;
  45061. } // All tech waiting checks failed. Use last resort correction
  45062. var currentTime = this.tech_.currentTime();
  45063. var buffered = this.tech_.buffered();
  45064. var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
  45065. // region with no indication that anything is amiss (seen in Firefox). Seeking to
  45066. // currentTime is usually enough to kickstart the player. This checks that the player
  45067. // is currently within a buffered region before attempting a corrective seek.
  45068. // Chrome does not appear to continue `timeupdate` events after a `waiting` event
  45069. // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
  45070. // make sure there is ~3 seconds of forward buffer before taking any corrective action
  45071. // to avoid triggering an `unknownwaiting` event when the network is slow.
  45072. if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
  45073. this.cancelTimer_();
  45074. this.tech_.setCurrentTime(currentTime);
  45075. this.logger_("Stopped at " + currentTime + " while inside a buffered region " + ("[" + currentRange.start(0) + " -> " + currentRange.end(0) + "]. Attempting to resume ") + 'playback by seeking to the current time.'); // unknown waiting corrections may be useful for monitoring QoS
  45076. this.tech_.trigger({
  45077. type: 'usage',
  45078. name: 'vhs-unknown-waiting'
  45079. });
  45080. this.tech_.trigger({
  45081. type: 'usage',
  45082. name: 'hls-unknown-waiting'
  45083. });
  45084. return;
  45085. }
  45086. }
  45087. /**
  45088. * Handler for situations when the tech fires a `waiting` event
  45089. *
  45090. * @return {boolean}
  45091. * True if an action (or none) was needed to correct the waiting. False if no
  45092. * checks passed
  45093. * @private
  45094. */
  45095. ;
  45096. _proto.techWaiting_ = function techWaiting_() {
  45097. var seekable = this.seekable();
  45098. var currentTime = this.tech_.currentTime();
  45099. if (this.tech_.seeking() || this.timer_ !== null) {
  45100. // Tech is seeking or already waiting on another action, no action needed
  45101. return true;
  45102. }
  45103. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  45104. var livePoint = seekable.end(seekable.length - 1);
  45105. this.logger_("Fell out of live window at time " + currentTime + ". Seeking to " + ("live point (seekable end) " + livePoint));
  45106. this.cancelTimer_();
  45107. this.tech_.setCurrentTime(livePoint); // live window resyncs may be useful for monitoring QoS
  45108. this.tech_.trigger({
  45109. type: 'usage',
  45110. name: 'vhs-live-resync'
  45111. });
  45112. this.tech_.trigger({
  45113. type: 'usage',
  45114. name: 'hls-live-resync'
  45115. });
  45116. return true;
  45117. }
  45118. var sourceUpdater = this.tech_.vhs.masterPlaylistController_.sourceUpdater_;
  45119. var buffered = this.tech_.buffered();
  45120. var videoUnderflow = this.videoUnderflow_({
  45121. audioBuffered: sourceUpdater.audioBuffered(),
  45122. videoBuffered: sourceUpdater.videoBuffered(),
  45123. currentTime: currentTime
  45124. });
  45125. if (videoUnderflow) {
  45126. // Even though the video underflowed and was stuck in a gap, the audio overplayed
  45127. // the gap, leading currentTime into a buffered range. Seeking to currentTime
  45128. // allows the video to catch up to the audio position without losing any audio
  45129. // (only suffering ~3 seconds of frozen video and a pause in audio playback).
  45130. this.cancelTimer_();
  45131. this.tech_.setCurrentTime(currentTime); // video underflow may be useful for monitoring QoS
  45132. this.tech_.trigger({
  45133. type: 'usage',
  45134. name: 'vhs-video-underflow'
  45135. });
  45136. this.tech_.trigger({
  45137. type: 'usage',
  45138. name: 'hls-video-underflow'
  45139. });
  45140. return true;
  45141. }
  45142. var nextRange = findNextRange(buffered, currentTime); // check for gap
  45143. if (nextRange.length > 0) {
  45144. var difference = nextRange.start(0) - currentTime;
  45145. this.logger_("Stopped at " + currentTime + ", setting timer for " + difference + ", seeking " + ("to " + nextRange.start(0)));
  45146. this.cancelTimer_();
  45147. this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
  45148. return true;
  45149. } // All checks failed. Returning false to indicate failure to correct waiting
  45150. return false;
  45151. };
  45152. _proto.afterSeekableWindow_ = function afterSeekableWindow_(seekable, currentTime, playlist, allowSeeksWithinUnsafeLiveWindow) {
  45153. if (allowSeeksWithinUnsafeLiveWindow === void 0) {
  45154. allowSeeksWithinUnsafeLiveWindow = false;
  45155. }
  45156. if (!seekable.length) {
  45157. // we can't make a solid case if there's no seekable, default to false
  45158. return false;
  45159. }
  45160. var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
  45161. var isLive = !playlist.endList;
  45162. if (isLive && allowSeeksWithinUnsafeLiveWindow) {
  45163. allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
  45164. }
  45165. if (currentTime > allowedEnd) {
  45166. return true;
  45167. }
  45168. return false;
  45169. };
  45170. _proto.beforeSeekableWindow_ = function beforeSeekableWindow_(seekable, currentTime) {
  45171. if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream
  45172. seekable.start(0) > 0 && currentTime < seekable.start(0) - this.liveRangeSafeTimeDelta) {
  45173. return true;
  45174. }
  45175. return false;
  45176. };
  45177. _proto.videoUnderflow_ = function videoUnderflow_(_ref) {
  45178. var videoBuffered = _ref.videoBuffered,
  45179. audioBuffered = _ref.audioBuffered,
  45180. currentTime = _ref.currentTime; // audio only content will not have video underflow :)
  45181. if (!videoBuffered) {
  45182. return;
  45183. }
  45184. var gap; // find a gap in demuxed content.
  45185. if (videoBuffered.length && audioBuffered.length) {
  45186. // in Chrome audio will continue to play for ~3s when we run out of video
  45187. // so we have to check that the video buffer did have some buffer in the
  45188. // past.
  45189. var lastVideoRange = findRange(videoBuffered, currentTime - 3);
  45190. var videoRange = findRange(videoBuffered, currentTime);
  45191. var audioRange = findRange(audioBuffered, currentTime);
  45192. if (audioRange.length && !videoRange.length && lastVideoRange.length) {
  45193. gap = {
  45194. start: lastVideoRange.end(0),
  45195. end: audioRange.end(0)
  45196. };
  45197. } // find a gap in muxed content.
  45198. } else {
  45199. var nextRange = findNextRange(videoBuffered, currentTime); // Even if there is no available next range, there is still a possibility we are
  45200. // stuck in a gap due to video underflow.
  45201. if (!nextRange.length) {
  45202. gap = this.gapFromVideoUnderflow_(videoBuffered, currentTime);
  45203. }
  45204. }
  45205. if (gap) {
  45206. this.logger_("Encountered a gap in video from " + gap.start + " to " + gap.end + ". " + ("Seeking to current time " + currentTime));
  45207. return true;
  45208. }
  45209. return false;
  45210. }
  45211. /**
  45212. * Timer callback. If playback still has not proceeded, then we seek
  45213. * to the start of the next buffered region.
  45214. *
  45215. * @private
  45216. */
  45217. ;
  45218. _proto.skipTheGap_ = function skipTheGap_(scheduledCurrentTime) {
  45219. var buffered = this.tech_.buffered();
  45220. var currentTime = this.tech_.currentTime();
  45221. var nextRange = findNextRange(buffered, currentTime);
  45222. this.cancelTimer_();
  45223. if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
  45224. return;
  45225. }
  45226. this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
  45227. this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR);
  45228. this.tech_.trigger({
  45229. type: 'usage',
  45230. name: 'vhs-gap-skip'
  45231. });
  45232. this.tech_.trigger({
  45233. type: 'usage',
  45234. name: 'hls-gap-skip'
  45235. });
  45236. };
  45237. _proto.gapFromVideoUnderflow_ = function gapFromVideoUnderflow_(buffered, currentTime) {
  45238. // At least in Chrome, if there is a gap in the video buffer, the audio will continue
  45239. // playing for ~3 seconds after the video gap starts. This is done to account for
  45240. // video buffer underflow/underrun (note that this is not done when there is audio
  45241. // buffer underflow/underrun -- in that case the video will stop as soon as it
  45242. // encounters the gap, as audio stalls are more noticeable/jarring to a user than
  45243. // video stalls). The player's time will reflect the playthrough of audio, so the
  45244. // time will appear as if we are in a buffered region, even if we are stuck in a
  45245. // "gap."
  45246. //
  45247. // Example:
  45248. // video buffer: 0 => 10.1, 10.2 => 20
  45249. // audio buffer: 0 => 20
  45250. // overall buffer: 0 => 10.1, 10.2 => 20
  45251. // current time: 13
  45252. //
  45253. // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
  45254. // however, the audio continued playing until it reached ~3 seconds past the gap
  45255. // (13 seconds), at which point it stops as well. Since current time is past the
  45256. // gap, findNextRange will return no ranges.
  45257. //
  45258. // To check for this issue, we see if there is a gap that starts somewhere within
  45259. // a 3 second range (3 seconds +/- 1 second) back from our current time.
  45260. var gaps = findGaps(buffered);
  45261. for (var i = 0; i < gaps.length; i++) {
  45262. var start = gaps.start(i);
  45263. var end = gaps.end(i); // gap is starts no more than 4 seconds back
  45264. if (currentTime - start < 4 && currentTime - start > 2) {
  45265. return {
  45266. start: start,
  45267. end: end
  45268. };
  45269. }
  45270. }
  45271. return null;
  45272. };
  45273. return PlaybackWatcher;
  45274. }();
  45275. var defaultOptions = {
  45276. errorInterval: 30,
  45277. getSource: function getSource(next) {
  45278. var tech = this.tech({
  45279. IWillNotUseThisInPlugins: true
  45280. });
  45281. var sourceObj = tech.currentSource_ || this.currentSource();
  45282. return next(sourceObj);
  45283. }
  45284. };
  45285. /**
  45286. * Main entry point for the plugin
  45287. *
  45288. * @param {Player} player a reference to a videojs Player instance
  45289. * @param {Object} [options] an object with plugin options
  45290. * @private
  45291. */
  45292. var initPlugin = function initPlugin(player, options) {
  45293. var lastCalled = 0;
  45294. var seekTo = 0;
  45295. var localOptions = videojs.mergeOptions(defaultOptions, options);
  45296. player.ready(function () {
  45297. player.trigger({
  45298. type: 'usage',
  45299. name: 'vhs-error-reload-initialized'
  45300. });
  45301. player.trigger({
  45302. type: 'usage',
  45303. name: 'hls-error-reload-initialized'
  45304. });
  45305. });
  45306. /**
  45307. * Player modifications to perform that must wait until `loadedmetadata`
  45308. * has been triggered
  45309. *
  45310. * @private
  45311. */
  45312. var loadedMetadataHandler = function loadedMetadataHandler() {
  45313. if (seekTo) {
  45314. player.currentTime(seekTo);
  45315. }
  45316. };
  45317. /**
  45318. * Set the source on the player element, play, and seek if necessary
  45319. *
  45320. * @param {Object} sourceObj An object specifying the source url and mime-type to play
  45321. * @private
  45322. */
  45323. var setSource = function setSource(sourceObj) {
  45324. if (sourceObj === null || sourceObj === undefined) {
  45325. return;
  45326. }
  45327. seekTo = player.duration() !== Infinity && player.currentTime() || 0;
  45328. player.one('loadedmetadata', loadedMetadataHandler);
  45329. player.src(sourceObj);
  45330. player.trigger({
  45331. type: 'usage',
  45332. name: 'vhs-error-reload'
  45333. });
  45334. player.trigger({
  45335. type: 'usage',
  45336. name: 'hls-error-reload'
  45337. });
  45338. player.play();
  45339. };
  45340. /**
  45341. * Attempt to get a source from either the built-in getSource function
  45342. * or a custom function provided via the options
  45343. *
  45344. * @private
  45345. */
  45346. var errorHandler = function errorHandler() {
  45347. // Do not attempt to reload the source if a source-reload occurred before
  45348. // 'errorInterval' time has elapsed since the last source-reload
  45349. if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
  45350. player.trigger({
  45351. type: 'usage',
  45352. name: 'vhs-error-reload-canceled'
  45353. });
  45354. player.trigger({
  45355. type: 'usage',
  45356. name: 'hls-error-reload-canceled'
  45357. });
  45358. return;
  45359. }
  45360. if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
  45361. videojs.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
  45362. return;
  45363. }
  45364. lastCalled = Date.now();
  45365. return localOptions.getSource.call(player, setSource);
  45366. };
  45367. /**
  45368. * Unbind any event handlers that were bound by the plugin
  45369. *
  45370. * @private
  45371. */
  45372. var cleanupEvents = function cleanupEvents() {
  45373. player.off('loadedmetadata', loadedMetadataHandler);
  45374. player.off('error', errorHandler);
  45375. player.off('dispose', cleanupEvents);
  45376. };
  45377. /**
  45378. * Cleanup before re-initializing the plugin
  45379. *
  45380. * @param {Object} [newOptions] an object with plugin options
  45381. * @private
  45382. */
  45383. var reinitPlugin = function reinitPlugin(newOptions) {
  45384. cleanupEvents();
  45385. initPlugin(player, newOptions);
  45386. };
  45387. player.on('error', errorHandler);
  45388. player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
  45389. // initializing the plugin
  45390. player.reloadSourceOnError = reinitPlugin;
  45391. };
  45392. /**
  45393. * Reload the source when an error is detected as long as there
  45394. * wasn't an error previously within the last 30 seconds
  45395. *
  45396. * @param {Object} [options] an object with plugin options
  45397. */
  45398. var reloadSourceOnError = function reloadSourceOnError(options) {
  45399. initPlugin(this, options);
  45400. };
  45401. var version$4 = "2.16.2";
  45402. var version$3 = "6.0.1";
  45403. var version$2 = "0.22.1";
  45404. var version$1 = "4.8.0";
  45405. var version = "3.1.3";
  45406. var Vhs = {
  45407. PlaylistLoader: PlaylistLoader,
  45408. Playlist: Playlist,
  45409. utils: utils,
  45410. STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
  45411. INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
  45412. lastBandwidthSelector: lastBandwidthSelector,
  45413. movingAverageBandwidthSelector: movingAverageBandwidthSelector,
  45414. comparePlaylistBandwidth: comparePlaylistBandwidth,
  45415. comparePlaylistResolution: comparePlaylistResolution,
  45416. xhr: xhrFactory()
  45417. }; // Define getter/setters for config properties
  45418. Object.keys(Config).forEach(function (prop) {
  45419. Object.defineProperty(Vhs, prop, {
  45420. get: function get() {
  45421. videojs.log.warn("using Vhs." + prop + " is UNSAFE be sure you know what you are doing");
  45422. return Config[prop];
  45423. },
  45424. set: function set(value) {
  45425. videojs.log.warn("using Vhs." + prop + " is UNSAFE be sure you know what you are doing");
  45426. if (typeof value !== 'number' || value < 0) {
  45427. videojs.log.warn("value of Vhs." + prop + " must be greater than or equal to 0");
  45428. return;
  45429. }
  45430. Config[prop] = value;
  45431. }
  45432. });
  45433. });
  45434. var LOCAL_STORAGE_KEY = 'videojs-vhs';
  45435. /**
  45436. * Updates the selectedIndex of the QualityLevelList when a mediachange happens in vhs.
  45437. *
  45438. * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
  45439. * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
  45440. * @function handleVhsMediaChange
  45441. */
  45442. var handleVhsMediaChange = function handleVhsMediaChange(qualityLevels, playlistLoader) {
  45443. var newPlaylist = playlistLoader.media();
  45444. var selectedIndex = -1;
  45445. for (var i = 0; i < qualityLevels.length; i++) {
  45446. if (qualityLevels[i].id === newPlaylist.id) {
  45447. selectedIndex = i;
  45448. break;
  45449. }
  45450. }
  45451. qualityLevels.selectedIndex_ = selectedIndex;
  45452. qualityLevels.trigger({
  45453. selectedIndex: selectedIndex,
  45454. type: 'change'
  45455. });
  45456. };
  45457. /**
  45458. * Adds quality levels to list once playlist metadata is available
  45459. *
  45460. * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
  45461. * @param {Object} vhs Vhs object to listen to for media events.
  45462. * @function handleVhsLoadedMetadata
  45463. */
  45464. var handleVhsLoadedMetadata = function handleVhsLoadedMetadata(qualityLevels, vhs) {
  45465. vhs.representations().forEach(function (rep) {
  45466. qualityLevels.addQualityLevel(rep);
  45467. });
  45468. handleVhsMediaChange(qualityLevels, vhs.playlists);
  45469. }; // HLS is a source handler, not a tech. Make sure attempts to use it
  45470. // as one do not cause exceptions.
  45471. Vhs.canPlaySource = function () {
  45472. return videojs.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  45473. };
  45474. var emeKeySystems = function emeKeySystems(keySystemOptions, mainPlaylist, audioPlaylist) {
  45475. if (!keySystemOptions) {
  45476. return keySystemOptions;
  45477. }
  45478. var codecs = {};
  45479. if (mainPlaylist && mainPlaylist.attributes && mainPlaylist.attributes.CODECS) {
  45480. codecs = unwrapCodecList(parseCodecs(mainPlaylist.attributes.CODECS));
  45481. }
  45482. if (audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS) {
  45483. codecs.audio = audioPlaylist.attributes.CODECS;
  45484. }
  45485. var videoContentType = getMimeForCodec(codecs.video);
  45486. var audioContentType = getMimeForCodec(codecs.audio); // upsert the content types based on the selected playlist
  45487. var keySystemContentTypes = {};
  45488. for (var keySystem in keySystemOptions) {
  45489. keySystemContentTypes[keySystem] = {};
  45490. if (audioContentType) {
  45491. keySystemContentTypes[keySystem].audioContentType = audioContentType;
  45492. }
  45493. if (videoContentType) {
  45494. keySystemContentTypes[keySystem].videoContentType = videoContentType;
  45495. } // Default to using the video playlist's PSSH even though they may be different, as
  45496. // videojs-contrib-eme will only accept one in the options.
  45497. //
  45498. // This shouldn't be an issue for most cases as early intialization will handle all
  45499. // unique PSSH values, and if they aren't, then encrypted events should have the
  45500. // specific information needed for the unique license.
  45501. if (mainPlaylist.contentProtection && mainPlaylist.contentProtection[keySystem] && mainPlaylist.contentProtection[keySystem].pssh) {
  45502. keySystemContentTypes[keySystem].pssh = mainPlaylist.contentProtection[keySystem].pssh;
  45503. } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
  45504. // so we need to prevent overwriting the URL entirely
  45505. if (typeof keySystemOptions[keySystem] === 'string') {
  45506. keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
  45507. }
  45508. }
  45509. return videojs.mergeOptions(keySystemOptions, keySystemContentTypes);
  45510. };
  45511. /**
  45512. * @typedef {Object} KeySystems
  45513. *
  45514. * keySystems configuration for https://github.com/videojs/videojs-contrib-eme
  45515. * Note: not all options are listed here.
  45516. *
  45517. * @property {Uint8Array} [pssh]
  45518. * Protection System Specific Header
  45519. */
  45520. /**
  45521. * Goes through all the playlists and collects an array of KeySystems options objects
  45522. * containing each playlist's keySystems and their pssh values, if available.
  45523. *
  45524. * @param {Object[]} playlists
  45525. * The playlists to look through
  45526. * @param {string[]} keySystems
  45527. * The keySystems to collect pssh values for
  45528. *
  45529. * @return {KeySystems[]}
  45530. * An array of KeySystems objects containing available key systems and their
  45531. * pssh values
  45532. */
  45533. var getAllPsshKeySystemsOptions = function getAllPsshKeySystemsOptions(playlists, keySystems) {
  45534. return playlists.reduce(function (keySystemsArr, playlist) {
  45535. if (!playlist.contentProtection) {
  45536. return keySystemsArr;
  45537. }
  45538. var keySystemsOptions = keySystems.reduce(function (keySystemsObj, keySystem) {
  45539. var keySystemOptions = playlist.contentProtection[keySystem];
  45540. if (keySystemOptions && keySystemOptions.pssh) {
  45541. keySystemsObj[keySystem] = {
  45542. pssh: keySystemOptions.pssh
  45543. };
  45544. }
  45545. return keySystemsObj;
  45546. }, {});
  45547. if (Object.keys(keySystemsOptions).length) {
  45548. keySystemsArr.push(keySystemsOptions);
  45549. }
  45550. return keySystemsArr;
  45551. }, []);
  45552. };
  45553. /**
  45554. * Returns a promise that waits for the
  45555. * [eme plugin](https://github.com/videojs/videojs-contrib-eme) to create a key session.
  45556. *
  45557. * Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 in non-IE11
  45558. * browsers.
  45559. *
  45560. * As per the above ticket, this is particularly important for Chrome, where, if
  45561. * unencrypted content is appended before encrypted content and the key session has not
  45562. * been created, a MEDIA_ERR_DECODE will be thrown once the encrypted content is reached
  45563. * during playback.
  45564. *
  45565. * @param {Object} player
  45566. * The player instance
  45567. * @param {Object[]} sourceKeySystems
  45568. * The key systems options from the player source
  45569. * @param {Object} [audioMedia]
  45570. * The active audio media playlist (optional)
  45571. * @param {Object[]} mainPlaylists
  45572. * The playlists found on the master playlist object
  45573. *
  45574. * @return {Object}
  45575. * Promise that resolves when the key session has been created
  45576. */
  45577. var waitForKeySessionCreation = function waitForKeySessionCreation(_ref) {
  45578. var player = _ref.player,
  45579. sourceKeySystems = _ref.sourceKeySystems,
  45580. audioMedia = _ref.audioMedia,
  45581. mainPlaylists = _ref.mainPlaylists;
  45582. if (!player.eme.initializeMediaKeys) {
  45583. return Promise.resolve();
  45584. } // TODO should all audio PSSH values be initialized for DRM?
  45585. //
  45586. // All unique video rendition pssh values are initialized for DRM, but here only
  45587. // the initial audio playlist license is initialized. In theory, an encrypted
  45588. // event should be fired if the user switches to an alternative audio playlist
  45589. // where a license is required, but this case hasn't yet been tested. In addition, there
  45590. // may be many alternate audio playlists unlikely to be used (e.g., multiple different
  45591. // languages).
  45592. var playlists = audioMedia ? mainPlaylists.concat([audioMedia]) : mainPlaylists;
  45593. var keySystemsOptionsArr = getAllPsshKeySystemsOptions(playlists, Object.keys(sourceKeySystems));
  45594. var initializationFinishedPromises = [];
  45595. var keySessionCreatedPromises = []; // Since PSSH values are interpreted as initData, EME will dedupe any duplicates. The
  45596. // only place where it should not be deduped is for ms-prefixed APIs, but the early
  45597. // return for IE11 above, and the existence of modern EME APIs in addition to
  45598. // ms-prefixed APIs on Edge should prevent this from being a concern.
  45599. // initializeMediaKeys also won't use the webkit-prefixed APIs.
  45600. keySystemsOptionsArr.forEach(function (keySystemsOptions) {
  45601. keySessionCreatedPromises.push(new Promise(function (resolve, reject) {
  45602. player.tech_.one('keysessioncreated', resolve);
  45603. }));
  45604. initializationFinishedPromises.push(new Promise(function (resolve, reject) {
  45605. player.eme.initializeMediaKeys({
  45606. keySystems: keySystemsOptions
  45607. }, function (err) {
  45608. if (err) {
  45609. reject(err);
  45610. return;
  45611. }
  45612. resolve();
  45613. });
  45614. }));
  45615. }); // The reasons Promise.race is chosen over Promise.any:
  45616. //
  45617. // * Promise.any is only available in Safari 14+.
  45618. // * None of these promises are expected to reject. If they do reject, it might be
  45619. // better here for the race to surface the rejection, rather than mask it by using
  45620. // Promise.any.
  45621. return Promise.race([// If a session was previously created, these will all finish resolving without
  45622. // creating a new session, otherwise it will take until the end of all license
  45623. // requests, which is why the key session check is used (to make setup much faster).
  45624. Promise.all(initializationFinishedPromises), // Once a single session is created, the browser knows DRM will be used.
  45625. Promise.race(keySessionCreatedPromises)]);
  45626. };
  45627. /**
  45628. * If the [eme](https://github.com/videojs/videojs-contrib-eme) plugin is available, and
  45629. * there are keySystems on the source, sets up source options to prepare the source for
  45630. * eme.
  45631. *
  45632. * @param {Object} player
  45633. * The player instance
  45634. * @param {Object[]} sourceKeySystems
  45635. * The key systems options from the player source
  45636. * @param {Object} media
  45637. * The active media playlist
  45638. * @param {Object} [audioMedia]
  45639. * The active audio media playlist (optional)
  45640. *
  45641. * @return {boolean}
  45642. * Whether or not options were configured and EME is available
  45643. */
  45644. var setupEmeOptions = function setupEmeOptions(_ref2) {
  45645. var player = _ref2.player,
  45646. sourceKeySystems = _ref2.sourceKeySystems,
  45647. media = _ref2.media,
  45648. audioMedia = _ref2.audioMedia;
  45649. var sourceOptions = emeKeySystems(sourceKeySystems, media, audioMedia);
  45650. if (!sourceOptions) {
  45651. return false;
  45652. }
  45653. player.currentSource().keySystems = sourceOptions; // eme handles the rest of the setup, so if it is missing
  45654. // do nothing.
  45655. if (sourceOptions && !player.eme) {
  45656. videojs.log.warn('DRM encrypted source cannot be decrypted without a DRM plugin');
  45657. return false;
  45658. }
  45659. return true;
  45660. };
  45661. var getVhsLocalStorage = function getVhsLocalStorage() {
  45662. if (!window$1.localStorage) {
  45663. return null;
  45664. }
  45665. var storedObject = window$1.localStorage.getItem(LOCAL_STORAGE_KEY);
  45666. if (!storedObject) {
  45667. return null;
  45668. }
  45669. try {
  45670. return JSON.parse(storedObject);
  45671. } catch (e) {
  45672. // someone may have tampered with the value
  45673. return null;
  45674. }
  45675. };
  45676. var updateVhsLocalStorage = function updateVhsLocalStorage(options) {
  45677. if (!window$1.localStorage) {
  45678. return false;
  45679. }
  45680. var objectToStore = getVhsLocalStorage();
  45681. objectToStore = objectToStore ? videojs.mergeOptions(objectToStore, options) : options;
  45682. try {
  45683. window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(objectToStore));
  45684. } catch (e) {
  45685. // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
  45686. // storage is set to 0).
  45687. // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
  45688. // No need to perform any operation.
  45689. return false;
  45690. }
  45691. return objectToStore;
  45692. };
  45693. /**
  45694. * Parses VHS-supported media types from data URIs. See
  45695. * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
  45696. * for information on data URIs.
  45697. *
  45698. * @param {string} dataUri
  45699. * The data URI
  45700. *
  45701. * @return {string|Object}
  45702. * The parsed object/string, or the original string if no supported media type
  45703. * was found
  45704. */
  45705. var expandDataUri = function expandDataUri(dataUri) {
  45706. if (dataUri.toLowerCase().indexOf('data:application/vnd.videojs.vhs+json,') === 0) {
  45707. return JSON.parse(dataUri.substring(dataUri.indexOf(',') + 1));
  45708. } // no known case for this data URI, return the string as-is
  45709. return dataUri;
  45710. };
  45711. /**
  45712. * Whether the browser has built-in HLS support.
  45713. */
  45714. Vhs.supportsNativeHls = function () {
  45715. if (!document || !document.createElement) {
  45716. return false;
  45717. }
  45718. var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
  45719. if (!videojs.getTech('Html5').isSupported()) {
  45720. return false;
  45721. } // HLS manifests can go by many mime-types
  45722. var canPlay = [// Apple santioned
  45723. 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility
  45724. 'audio/mpegurl', // Very common
  45725. 'audio/x-mpegurl', // Very common
  45726. 'application/x-mpegurl', // Included for completeness
  45727. 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
  45728. return canPlay.some(function (canItPlay) {
  45729. return /maybe|probably/i.test(video.canPlayType(canItPlay));
  45730. });
  45731. }();
  45732. Vhs.supportsNativeDash = function () {
  45733. if (!document || !document.createElement || !videojs.getTech('Html5').isSupported()) {
  45734. return false;
  45735. }
  45736. return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
  45737. }();
  45738. Vhs.supportsTypeNatively = function (type) {
  45739. if (type === 'hls') {
  45740. return Vhs.supportsNativeHls;
  45741. }
  45742. if (type === 'dash') {
  45743. return Vhs.supportsNativeDash;
  45744. }
  45745. return false;
  45746. };
  45747. /**
  45748. * HLS is a source handler, not a tech. Make sure attempts to use it
  45749. * as one do not cause exceptions.
  45750. */
  45751. Vhs.isSupported = function () {
  45752. return videojs.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  45753. };
  45754. var Component = videojs.getComponent('Component');
  45755. /**
  45756. * The Vhs Handler object, where we orchestrate all of the parts
  45757. * of HLS to interact with video.js
  45758. *
  45759. * @class VhsHandler
  45760. * @extends videojs.Component
  45761. * @param {Object} source the soruce object
  45762. * @param {Tech} tech the parent tech object
  45763. * @param {Object} options optional and required options
  45764. */
  45765. var VhsHandler = /*#__PURE__*/function (_Component) {
  45766. _inheritsLoose(VhsHandler, _Component);
  45767. function VhsHandler(source, tech, options) {
  45768. var _this;
  45769. _this = _Component.call(this, tech, videojs.mergeOptions(options.hls, options.vhs)) || this;
  45770. if (options.hls && Object.keys(options.hls).length) {
  45771. videojs.log.warn('Using hls options is deprecated. Please rename `hls` to `vhs` in your options object.');
  45772. } // if a tech level `initialBandwidth` option was passed
  45773. // use that over the VHS level `bandwidth` option
  45774. if (typeof options.initialBandwidth === 'number') {
  45775. _this.options_.bandwidth = options.initialBandwidth;
  45776. }
  45777. _this.logger_ = logger('VhsHandler'); // tech.player() is deprecated but setup a reference to HLS for
  45778. // backwards-compatibility
  45779. if (tech.options_ && tech.options_.playerId) {
  45780. var _player = videojs(tech.options_.playerId);
  45781. if (!_player.hasOwnProperty('hls')) {
  45782. Object.defineProperty(_player, 'hls', {
  45783. get: function get() {
  45784. videojs.log.warn('player.hls is deprecated. Use player.tech().vhs instead.');
  45785. tech.trigger({
  45786. type: 'usage',
  45787. name: 'hls-player-access'
  45788. });
  45789. return _assertThisInitialized(_this);
  45790. },
  45791. configurable: true
  45792. });
  45793. }
  45794. if (!_player.hasOwnProperty('vhs')) {
  45795. Object.defineProperty(_player, 'vhs', {
  45796. get: function get() {
  45797. videojs.log.warn('player.vhs is deprecated. Use player.tech().vhs instead.');
  45798. tech.trigger({
  45799. type: 'usage',
  45800. name: 'vhs-player-access'
  45801. });
  45802. return _assertThisInitialized(_this);
  45803. },
  45804. configurable: true
  45805. });
  45806. }
  45807. if (!_player.hasOwnProperty('dash')) {
  45808. Object.defineProperty(_player, 'dash', {
  45809. get: function get() {
  45810. videojs.log.warn('player.dash is deprecated. Use player.tech().vhs instead.');
  45811. return _assertThisInitialized(_this);
  45812. },
  45813. configurable: true
  45814. });
  45815. }
  45816. _this.player_ = _player;
  45817. }
  45818. _this.tech_ = tech;
  45819. _this.source_ = source;
  45820. _this.stats = {};
  45821. _this.ignoreNextSeekingEvent_ = false;
  45822. _this.setOptions_();
  45823. if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
  45824. tech.overrideNativeAudioTracks(true);
  45825. tech.overrideNativeVideoTracks(true);
  45826. } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
  45827. // overriding native HLS only works if audio tracks have been emulated
  45828. // error early if we're misconfigured
  45829. throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
  45830. } // listen for fullscreenchange events for this player so that we
  45831. // can adjust our quality selection quickly
  45832. _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
  45833. var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
  45834. if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
  45835. _this.masterPlaylistController_.fastQualityChange_();
  45836. } else {
  45837. // When leaving fullscreen, since the in page pixel dimensions should be smaller
  45838. // than full screen, see if there should be a rendition switch down to preserve
  45839. // bandwidth.
  45840. _this.masterPlaylistController_.checkABR_();
  45841. }
  45842. });
  45843. _this.on(_this.tech_, 'seeking', function () {
  45844. if (this.ignoreNextSeekingEvent_) {
  45845. this.ignoreNextSeekingEvent_ = false;
  45846. return;
  45847. }
  45848. this.setCurrentTime(this.tech_.currentTime());
  45849. });
  45850. _this.on(_this.tech_, 'error', function () {
  45851. // verify that the error was real and we are loaded
  45852. // enough to have mpc loaded.
  45853. if (this.tech_.error() && this.masterPlaylistController_) {
  45854. this.masterPlaylistController_.pauseLoading();
  45855. }
  45856. });
  45857. _this.on(_this.tech_, 'play', _this.play);
  45858. return _this;
  45859. }
  45860. var _proto = VhsHandler.prototype;
  45861. _proto.setOptions_ = function setOptions_() {
  45862. var _this2 = this; // defaults
  45863. this.options_.withCredentials = this.options_.withCredentials || false;
  45864. this.options_.handleManifestRedirects = this.options_.handleManifestRedirects === false ? false : true;
  45865. this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
  45866. this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false;
  45867. this.options_.smoothQualityChange = this.options_.smoothQualityChange || false;
  45868. this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
  45869. this.options_.useNetworkInformationApi = this.options_.useNetworkInformationApi || false;
  45870. this.options_.useDtsForTimestampOffset = this.options_.useDtsForTimestampOffset || false;
  45871. this.options_.customTagParsers = this.options_.customTagParsers || [];
  45872. this.options_.customTagMappers = this.options_.customTagMappers || [];
  45873. this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
  45874. if (typeof this.options_.blacklistDuration !== 'number') {
  45875. this.options_.blacklistDuration = 5 * 60;
  45876. }
  45877. if (typeof this.options_.bandwidth !== 'number') {
  45878. if (this.options_.useBandwidthFromLocalStorage) {
  45879. var storedObject = getVhsLocalStorage();
  45880. if (storedObject && storedObject.bandwidth) {
  45881. this.options_.bandwidth = storedObject.bandwidth;
  45882. this.tech_.trigger({
  45883. type: 'usage',
  45884. name: 'vhs-bandwidth-from-local-storage'
  45885. });
  45886. this.tech_.trigger({
  45887. type: 'usage',
  45888. name: 'hls-bandwidth-from-local-storage'
  45889. });
  45890. }
  45891. if (storedObject && storedObject.throughput) {
  45892. this.options_.throughput = storedObject.throughput;
  45893. this.tech_.trigger({
  45894. type: 'usage',
  45895. name: 'vhs-throughput-from-local-storage'
  45896. });
  45897. this.tech_.trigger({
  45898. type: 'usage',
  45899. name: 'hls-throughput-from-local-storage'
  45900. });
  45901. }
  45902. }
  45903. } // if bandwidth was not set by options or pulled from local storage, start playlist
  45904. // selection at a reasonable bandwidth
  45905. if (typeof this.options_.bandwidth !== 'number') {
  45906. this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
  45907. } // If the bandwidth number is unchanged from the initial setting
  45908. // then this takes precedence over the enableLowInitialPlaylist option
  45909. this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
  45910. ['withCredentials', 'useDevicePixelRatio', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys', 'playlistSelector', 'initialPlaylistSelector', 'experimentalBufferBasedABR', 'liveRangeSafeTimeDelta', 'experimentalLLHLS', 'useNetworkInformationApi', 'useDtsForTimestampOffset', 'experimentalExactManifestTimings', 'experimentalLeastPixelDiffSelector'].forEach(function (option) {
  45911. if (typeof _this2.source_[option] !== 'undefined') {
  45912. _this2.options_[option] = _this2.source_[option];
  45913. }
  45914. });
  45915. this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
  45916. this.useDevicePixelRatio = this.options_.useDevicePixelRatio;
  45917. }
  45918. /**
  45919. * called when player.src gets called, handle a new source
  45920. *
  45921. * @param {Object} src the source object to handle
  45922. */
  45923. ;
  45924. _proto.src = function src(_src, type) {
  45925. var _this3 = this; // do nothing if the src is falsey
  45926. if (!_src) {
  45927. return;
  45928. }
  45929. this.setOptions_(); // add master playlist controller options
  45930. this.options_.src = expandDataUri(this.source_.src);
  45931. this.options_.tech = this.tech_;
  45932. this.options_.externVhs = Vhs;
  45933. this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update the tech
  45934. this.options_.seekTo = function (time) {
  45935. _this3.tech_.setCurrentTime(time);
  45936. };
  45937. if (this.options_.smoothQualityChange) {
  45938. videojs.log.warn('smoothQualityChange is deprecated and will be removed in the next major version');
  45939. }
  45940. this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
  45941. var playbackWatcherOptions = videojs.mergeOptions({
  45942. liveRangeSafeTimeDelta: SAFE_TIME_DELTA
  45943. }, this.options_, {
  45944. seekable: function seekable() {
  45945. return _this3.seekable();
  45946. },
  45947. media: function media() {
  45948. return _this3.masterPlaylistController_.media();
  45949. },
  45950. masterPlaylistController: this.masterPlaylistController_
  45951. });
  45952. this.playbackWatcher_ = new PlaybackWatcher(playbackWatcherOptions);
  45953. this.masterPlaylistController_.on('error', function () {
  45954. var player = videojs.players[_this3.tech_.options_.playerId];
  45955. var error = _this3.masterPlaylistController_.error;
  45956. if (typeof error === 'object' && !error.code) {
  45957. error.code = 3;
  45958. } else if (typeof error === 'string') {
  45959. error = {
  45960. message: error,
  45961. code: 3
  45962. };
  45963. }
  45964. player.error(error);
  45965. });
  45966. var defaultSelector = this.options_.experimentalBufferBasedABR ? Vhs.movingAverageBandwidthSelector(0.55) : Vhs.STANDARD_PLAYLIST_SELECTOR; // `this` in selectPlaylist should be the VhsHandler for backwards
  45967. // compatibility with < v2
  45968. this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : defaultSelector.bind(this);
  45969. this.masterPlaylistController_.selectInitialPlaylist = Vhs.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
  45970. this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
  45971. this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist
  45972. // controller. Using a custom property for backwards compatibility
  45973. // with < v2
  45974. Object.defineProperties(this, {
  45975. selectPlaylist: {
  45976. get: function get() {
  45977. return this.masterPlaylistController_.selectPlaylist;
  45978. },
  45979. set: function set(selectPlaylist) {
  45980. this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
  45981. }
  45982. },
  45983. throughput: {
  45984. get: function get() {
  45985. return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
  45986. },
  45987. set: function set(throughput) {
  45988. this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
  45989. // for the cumulative average
  45990. this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
  45991. }
  45992. },
  45993. bandwidth: {
  45994. get: function get() {
  45995. var playerBandwidthEst = this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
  45996. var networkInformation = window$1.navigator.connection || window$1.navigator.mozConnection || window$1.navigator.webkitConnection;
  45997. var tenMbpsAsBitsPerSecond = 10e6;
  45998. if (this.options_.useNetworkInformationApi && networkInformation) {
  45999. // downlink returns Mbps
  46000. // https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink
  46001. var networkInfoBandwidthEstBitsPerSec = networkInformation.downlink * 1000 * 1000; // downlink maxes out at 10 Mbps. In the event that both networkInformationApi and the player
  46002. // estimate a bandwidth greater than 10 Mbps, use the larger of the two estimates to ensure that
  46003. // high quality streams are not filtered out.
  46004. if (networkInfoBandwidthEstBitsPerSec >= tenMbpsAsBitsPerSecond && playerBandwidthEst >= tenMbpsAsBitsPerSecond) {
  46005. playerBandwidthEst = Math.max(playerBandwidthEst, networkInfoBandwidthEstBitsPerSec);
  46006. } else {
  46007. playerBandwidthEst = networkInfoBandwidthEstBitsPerSec;
  46008. }
  46009. }
  46010. return playerBandwidthEst;
  46011. },
  46012. set: function set(bandwidth) {
  46013. this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
  46014. // `count` is set to zero that current value of `rate` isn't included
  46015. // in the cumulative average
  46016. this.masterPlaylistController_.mainSegmentLoader_.throughput = {
  46017. rate: 0,
  46018. count: 0
  46019. };
  46020. }
  46021. },
  46022. /**
  46023. * `systemBandwidth` is a combination of two serial processes bit-rates. The first
  46024. * is the network bitrate provided by `bandwidth` and the second is the bitrate of
  46025. * the entire process after that - decryption, transmuxing, and appending - provided
  46026. * by `throughput`.
  46027. *
  46028. * Since the two process are serial, the overall system bandwidth is given by:
  46029. * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
  46030. */
  46031. systemBandwidth: {
  46032. get: function get() {
  46033. var invBandwidth = 1 / (this.bandwidth || 1);
  46034. var invThroughput;
  46035. if (this.throughput > 0) {
  46036. invThroughput = 1 / this.throughput;
  46037. } else {
  46038. invThroughput = 0;
  46039. }
  46040. var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
  46041. return systemBitrate;
  46042. },
  46043. set: function set() {
  46044. videojs.log.error('The "systemBandwidth" property is read-only');
  46045. }
  46046. }
  46047. });
  46048. if (this.options_.bandwidth) {
  46049. this.bandwidth = this.options_.bandwidth;
  46050. }
  46051. if (this.options_.throughput) {
  46052. this.throughput = this.options_.throughput;
  46053. }
  46054. Object.defineProperties(this.stats, {
  46055. bandwidth: {
  46056. get: function get() {
  46057. return _this3.bandwidth || 0;
  46058. },
  46059. enumerable: true
  46060. },
  46061. mediaRequests: {
  46062. get: function get() {
  46063. return _this3.masterPlaylistController_.mediaRequests_() || 0;
  46064. },
  46065. enumerable: true
  46066. },
  46067. mediaRequestsAborted: {
  46068. get: function get() {
  46069. return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
  46070. },
  46071. enumerable: true
  46072. },
  46073. mediaRequestsTimedout: {
  46074. get: function get() {
  46075. return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
  46076. },
  46077. enumerable: true
  46078. },
  46079. mediaRequestsErrored: {
  46080. get: function get() {
  46081. return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
  46082. },
  46083. enumerable: true
  46084. },
  46085. mediaTransferDuration: {
  46086. get: function get() {
  46087. return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
  46088. },
  46089. enumerable: true
  46090. },
  46091. mediaBytesTransferred: {
  46092. get: function get() {
  46093. return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
  46094. },
  46095. enumerable: true
  46096. },
  46097. mediaSecondsLoaded: {
  46098. get: function get() {
  46099. return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
  46100. },
  46101. enumerable: true
  46102. },
  46103. mediaAppends: {
  46104. get: function get() {
  46105. return _this3.masterPlaylistController_.mediaAppends_() || 0;
  46106. },
  46107. enumerable: true
  46108. },
  46109. mainAppendsToLoadedData: {
  46110. get: function get() {
  46111. return _this3.masterPlaylistController_.mainAppendsToLoadedData_() || 0;
  46112. },
  46113. enumerable: true
  46114. },
  46115. audioAppendsToLoadedData: {
  46116. get: function get() {
  46117. return _this3.masterPlaylistController_.audioAppendsToLoadedData_() || 0;
  46118. },
  46119. enumerable: true
  46120. },
  46121. appendsToLoadedData: {
  46122. get: function get() {
  46123. return _this3.masterPlaylistController_.appendsToLoadedData_() || 0;
  46124. },
  46125. enumerable: true
  46126. },
  46127. timeToLoadedData: {
  46128. get: function get() {
  46129. return _this3.masterPlaylistController_.timeToLoadedData_() || 0;
  46130. },
  46131. enumerable: true
  46132. },
  46133. buffered: {
  46134. get: function get() {
  46135. return timeRangesToArray(_this3.tech_.buffered());
  46136. },
  46137. enumerable: true
  46138. },
  46139. currentTime: {
  46140. get: function get() {
  46141. return _this3.tech_.currentTime();
  46142. },
  46143. enumerable: true
  46144. },
  46145. currentSource: {
  46146. get: function get() {
  46147. return _this3.tech_.currentSource_;
  46148. },
  46149. enumerable: true
  46150. },
  46151. currentTech: {
  46152. get: function get() {
  46153. return _this3.tech_.name_;
  46154. },
  46155. enumerable: true
  46156. },
  46157. duration: {
  46158. get: function get() {
  46159. return _this3.tech_.duration();
  46160. },
  46161. enumerable: true
  46162. },
  46163. master: {
  46164. get: function get() {
  46165. return _this3.playlists.master;
  46166. },
  46167. enumerable: true
  46168. },
  46169. playerDimensions: {
  46170. get: function get() {
  46171. return _this3.tech_.currentDimensions();
  46172. },
  46173. enumerable: true
  46174. },
  46175. seekable: {
  46176. get: function get() {
  46177. return timeRangesToArray(_this3.tech_.seekable());
  46178. },
  46179. enumerable: true
  46180. },
  46181. timestamp: {
  46182. get: function get() {
  46183. return Date.now();
  46184. },
  46185. enumerable: true
  46186. },
  46187. videoPlaybackQuality: {
  46188. get: function get() {
  46189. return _this3.tech_.getVideoPlaybackQuality();
  46190. },
  46191. enumerable: true
  46192. }
  46193. });
  46194. this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
  46195. this.tech_.on('bandwidthupdate', function () {
  46196. if (_this3.options_.useBandwidthFromLocalStorage) {
  46197. updateVhsLocalStorage({
  46198. bandwidth: _this3.bandwidth,
  46199. throughput: Math.round(_this3.throughput)
  46200. });
  46201. }
  46202. });
  46203. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  46204. // Add the manual rendition mix-in to VhsHandler
  46205. renditionSelectionMixin(_this3);
  46206. });
  46207. this.masterPlaylistController_.sourceUpdater_.on('createdsourcebuffers', function () {
  46208. _this3.setupEme_();
  46209. }); // the bandwidth of the primary segment loader is our best
  46210. // estimate of overall bandwidth
  46211. this.on(this.masterPlaylistController_, 'progress', function () {
  46212. this.tech_.trigger('progress');
  46213. }); // In the live case, we need to ignore the very first `seeking` event since
  46214. // that will be the result of the seek-to-live behavior
  46215. this.on(this.masterPlaylistController_, 'firstplay', function () {
  46216. this.ignoreNextSeekingEvent_ = true;
  46217. });
  46218. this.setupQualityLevels_(); // do nothing if the tech has been disposed already
  46219. // this can occur if someone sets the src in player.ready(), for instance
  46220. if (!this.tech_.el()) {
  46221. return;
  46222. }
  46223. this.mediaSourceUrl_ = window$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource);
  46224. this.tech_.src(this.mediaSourceUrl_);
  46225. };
  46226. _proto.createKeySessions_ = function createKeySessions_() {
  46227. var _this4 = this;
  46228. var audioPlaylistLoader = this.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader;
  46229. this.logger_('waiting for EME key session creation');
  46230. waitForKeySessionCreation({
  46231. player: this.player_,
  46232. sourceKeySystems: this.source_.keySystems,
  46233. audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
  46234. mainPlaylists: this.playlists.master.playlists
  46235. }).then(function () {
  46236. _this4.logger_('created EME key session');
  46237. _this4.masterPlaylistController_.sourceUpdater_.initializedEme();
  46238. })["catch"](function (err) {
  46239. _this4.logger_('error while creating EME key session', err);
  46240. _this4.player_.error({
  46241. message: 'Failed to initialize media keys for EME',
  46242. code: 3
  46243. });
  46244. });
  46245. };
  46246. _proto.handleWaitingForKey_ = function handleWaitingForKey_() {
  46247. // If waitingforkey is fired, it's possible that the data that's necessary to retrieve
  46248. // the key is in the manifest. While this should've happened on initial source load, it
  46249. // may happen again in live streams where the keys change, and the manifest info
  46250. // reflects the update.
  46251. //
  46252. // Because videojs-contrib-eme compares the PSSH data we send to that of PSSH data it's
  46253. // already requested keys for, we don't have to worry about this generating extraneous
  46254. // requests.
  46255. this.logger_('waitingforkey fired, attempting to create any new key sessions');
  46256. this.createKeySessions_();
  46257. }
  46258. /**
  46259. * If necessary and EME is available, sets up EME options and waits for key session
  46260. * creation.
  46261. *
  46262. * This function also updates the source updater so taht it can be used, as for some
  46263. * browsers, EME must be configured before content is appended (if appending unencrypted
  46264. * content before encrypted content).
  46265. */
  46266. ;
  46267. _proto.setupEme_ = function setupEme_() {
  46268. var _this5 = this;
  46269. var audioPlaylistLoader = this.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader;
  46270. var didSetupEmeOptions = setupEmeOptions({
  46271. player: this.player_,
  46272. sourceKeySystems: this.source_.keySystems,
  46273. media: this.playlists.media(),
  46274. audioMedia: audioPlaylistLoader && audioPlaylistLoader.media()
  46275. });
  46276. this.player_.tech_.on('keystatuschange', function (e) {
  46277. if (e.status !== 'output-restricted') {
  46278. return;
  46279. }
  46280. var masterPlaylist = _this5.masterPlaylistController_.master();
  46281. if (!masterPlaylist || !masterPlaylist.playlists) {
  46282. return;
  46283. }
  46284. var excludedHDPlaylists = []; // Assume all HD streams are unplayable and exclude them from ABR selection
  46285. masterPlaylist.playlists.forEach(function (playlist) {
  46286. if (playlist && playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height >= 720) {
  46287. if (!playlist.excludeUntil || playlist.excludeUntil < Infinity) {
  46288. playlist.excludeUntil = Infinity;
  46289. excludedHDPlaylists.push(playlist);
  46290. }
  46291. }
  46292. });
  46293. if (excludedHDPlaylists.length) {
  46294. var _videojs$log;
  46295. (_videojs$log = videojs.log).warn.apply(_videojs$log, ['DRM keystatus changed to "output-restricted." Removing the following HD playlists ' + 'that will most likely fail to play and clearing the buffer. ' + 'This may be due to HDCP restrictions on the stream and the capabilities of the current device.'].concat(excludedHDPlaylists)); // Clear the buffer before switching playlists, since it may already contain unplayable segments
  46296. _this5.masterPlaylistController_.fastQualityChange_();
  46297. }
  46298. });
  46299. this.handleWaitingForKey_ = this.handleWaitingForKey_.bind(this);
  46300. this.player_.tech_.on('waitingforkey', this.handleWaitingForKey_); // In IE11 this is too early to initialize media keys, and IE11 does not support
  46301. // promises.
  46302. if (videojs.browser.IE_VERSION === 11 || !didSetupEmeOptions) {
  46303. // If EME options were not set up, we've done all we could to initialize EME.
  46304. this.masterPlaylistController_.sourceUpdater_.initializedEme();
  46305. return;
  46306. }
  46307. this.createKeySessions_();
  46308. }
  46309. /**
  46310. * Initializes the quality levels and sets listeners to update them.
  46311. *
  46312. * @method setupQualityLevels_
  46313. * @private
  46314. */
  46315. ;
  46316. _proto.setupQualityLevels_ = function setupQualityLevels_() {
  46317. var _this6 = this;
  46318. var player = videojs.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin
  46319. // or qualityLevels_ listeners have already been setup, do nothing.
  46320. if (!player || !player.qualityLevels || this.qualityLevels_) {
  46321. return;
  46322. }
  46323. this.qualityLevels_ = player.qualityLevels();
  46324. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  46325. handleVhsLoadedMetadata(_this6.qualityLevels_, _this6);
  46326. });
  46327. this.playlists.on('mediachange', function () {
  46328. handleVhsMediaChange(_this6.qualityLevels_, _this6.playlists);
  46329. });
  46330. }
  46331. /**
  46332. * return the version
  46333. */
  46334. ;
  46335. VhsHandler.version = function version$5() {
  46336. return {
  46337. '@videojs/http-streaming': version$4,
  46338. 'mux.js': version$3,
  46339. 'mpd-parser': version$2,
  46340. 'm3u8-parser': version$1,
  46341. 'aes-decrypter': version
  46342. };
  46343. }
  46344. /**
  46345. * return the version
  46346. */
  46347. ;
  46348. _proto.version = function version() {
  46349. return this.constructor.version();
  46350. };
  46351. _proto.canChangeType = function canChangeType() {
  46352. return SourceUpdater.canChangeType();
  46353. }
  46354. /**
  46355. * Begin playing the video.
  46356. */
  46357. ;
  46358. _proto.play = function play() {
  46359. this.masterPlaylistController_.play();
  46360. }
  46361. /**
  46362. * a wrapper around the function in MasterPlaylistController
  46363. */
  46364. ;
  46365. _proto.setCurrentTime = function setCurrentTime(currentTime) {
  46366. this.masterPlaylistController_.setCurrentTime(currentTime);
  46367. }
  46368. /**
  46369. * a wrapper around the function in MasterPlaylistController
  46370. */
  46371. ;
  46372. _proto.duration = function duration() {
  46373. return this.masterPlaylistController_.duration();
  46374. }
  46375. /**
  46376. * a wrapper around the function in MasterPlaylistController
  46377. */
  46378. ;
  46379. _proto.seekable = function seekable() {
  46380. return this.masterPlaylistController_.seekable();
  46381. }
  46382. /**
  46383. * Abort all outstanding work and cleanup.
  46384. */
  46385. ;
  46386. _proto.dispose = function dispose() {
  46387. if (this.playbackWatcher_) {
  46388. this.playbackWatcher_.dispose();
  46389. }
  46390. if (this.masterPlaylistController_) {
  46391. this.masterPlaylistController_.dispose();
  46392. }
  46393. if (this.qualityLevels_) {
  46394. this.qualityLevels_.dispose();
  46395. }
  46396. if (this.player_) {
  46397. delete this.player_.vhs;
  46398. delete this.player_.dash;
  46399. delete this.player_.hls;
  46400. }
  46401. if (this.tech_ && this.tech_.vhs) {
  46402. delete this.tech_.vhs;
  46403. } // don't check this.tech_.hls as it will log a deprecated warning
  46404. if (this.tech_) {
  46405. delete this.tech_.hls;
  46406. }
  46407. if (this.mediaSourceUrl_ && window$1.URL.revokeObjectURL) {
  46408. window$1.URL.revokeObjectURL(this.mediaSourceUrl_);
  46409. this.mediaSourceUrl_ = null;
  46410. }
  46411. if (this.tech_) {
  46412. this.tech_.off('waitingforkey', this.handleWaitingForKey_);
  46413. }
  46414. _Component.prototype.dispose.call(this);
  46415. };
  46416. _proto.convertToProgramTime = function convertToProgramTime(time, callback) {
  46417. return getProgramTime({
  46418. playlist: this.masterPlaylistController_.media(),
  46419. time: time,
  46420. callback: callback
  46421. });
  46422. } // the player must be playing before calling this
  46423. ;
  46424. _proto.seekToProgramTime = function seekToProgramTime$1(programTime, callback, pauseAfterSeek, retryCount) {
  46425. if (pauseAfterSeek === void 0) {
  46426. pauseAfterSeek = true;
  46427. }
  46428. if (retryCount === void 0) {
  46429. retryCount = 2;
  46430. }
  46431. return seekToProgramTime({
  46432. programTime: programTime,
  46433. playlist: this.masterPlaylistController_.media(),
  46434. retryCount: retryCount,
  46435. pauseAfterSeek: pauseAfterSeek,
  46436. seekTo: this.options_.seekTo,
  46437. tech: this.options_.tech,
  46438. callback: callback
  46439. });
  46440. };
  46441. return VhsHandler;
  46442. }(Component);
  46443. /**
  46444. * The Source Handler object, which informs video.js what additional
  46445. * MIME types are supported and sets up playback. It is registered
  46446. * automatically to the appropriate tech based on the capabilities of
  46447. * the browser it is running in. It is not necessary to use or modify
  46448. * this object in normal usage.
  46449. */
  46450. var VhsSourceHandler = {
  46451. name: 'videojs-http-streaming',
  46452. VERSION: version$4,
  46453. canHandleSource: function canHandleSource(srcObj, options) {
  46454. if (options === void 0) {
  46455. options = {};
  46456. }
  46457. var localOptions = videojs.mergeOptions(videojs.options, options);
  46458. return VhsSourceHandler.canPlayType(srcObj.type, localOptions);
  46459. },
  46460. handleSource: function handleSource(source, tech, options) {
  46461. if (options === void 0) {
  46462. options = {};
  46463. }
  46464. var localOptions = videojs.mergeOptions(videojs.options, options);
  46465. tech.vhs = new VhsHandler(source, tech, localOptions);
  46466. if (!videojs.hasOwnProperty('hls')) {
  46467. Object.defineProperty(tech, 'hls', {
  46468. get: function get() {
  46469. videojs.log.warn('player.tech().hls is deprecated. Use player.tech().vhs instead.');
  46470. return tech.vhs;
  46471. },
  46472. configurable: true
  46473. });
  46474. }
  46475. tech.vhs.xhr = xhrFactory();
  46476. tech.vhs.src(source.src, source.type);
  46477. return tech.vhs;
  46478. },
  46479. canPlayType: function canPlayType(type, options) {
  46480. var simpleType = simpleTypeFromSourceType(type);
  46481. if (!simpleType) {
  46482. return '';
  46483. }
  46484. var overrideNative = VhsSourceHandler.getOverrideNative(options);
  46485. var supportsTypeNatively = Vhs.supportsTypeNatively(simpleType);
  46486. var canUseMsePlayback = !supportsTypeNatively || overrideNative;
  46487. return canUseMsePlayback ? 'maybe' : '';
  46488. },
  46489. getOverrideNative: function getOverrideNative(options) {
  46490. if (options === void 0) {
  46491. options = {};
  46492. }
  46493. var _options = options,
  46494. _options$vhs = _options.vhs,
  46495. vhs = _options$vhs === void 0 ? {} : _options$vhs,
  46496. _options$hls = _options.hls,
  46497. hls = _options$hls === void 0 ? {} : _options$hls;
  46498. var defaultOverrideNative = !(videojs.browser.IS_ANY_SAFARI || videojs.browser.IS_IOS);
  46499. var _vhs$overrideNative = vhs.overrideNative,
  46500. overrideNative = _vhs$overrideNative === void 0 ? defaultOverrideNative : _vhs$overrideNative;
  46501. var _hls$overrideNative = hls.overrideNative,
  46502. legacyOverrideNative = _hls$overrideNative === void 0 ? false : _hls$overrideNative;
  46503. return legacyOverrideNative || overrideNative;
  46504. }
  46505. };
  46506. /**
  46507. * Check to see if the native MediaSource object exists and supports
  46508. * an MP4 container with both H.264 video and AAC-LC audio.
  46509. *
  46510. * @return {boolean} if native media sources are supported
  46511. */
  46512. var supportsNativeMediaSources = function supportsNativeMediaSources() {
  46513. return browserSupportsCodec('avc1.4d400d,mp4a.40.2');
  46514. }; // register source handlers with the appropriate techs
  46515. if (supportsNativeMediaSources()) {
  46516. videojs.getTech('Html5').registerSourceHandler(VhsSourceHandler, 0);
  46517. }
  46518. videojs.VhsHandler = VhsHandler;
  46519. Object.defineProperty(videojs, 'HlsHandler', {
  46520. get: function get() {
  46521. videojs.log.warn('videojs.HlsHandler is deprecated. Use videojs.VhsHandler instead.');
  46522. return VhsHandler;
  46523. },
  46524. configurable: true
  46525. });
  46526. videojs.VhsSourceHandler = VhsSourceHandler;
  46527. Object.defineProperty(videojs, 'HlsSourceHandler', {
  46528. get: function get() {
  46529. videojs.log.warn('videojs.HlsSourceHandler is deprecated. ' + 'Use videojs.VhsSourceHandler instead.');
  46530. return VhsSourceHandler;
  46531. },
  46532. configurable: true
  46533. });
  46534. videojs.Vhs = Vhs;
  46535. Object.defineProperty(videojs, 'Hls', {
  46536. get: function get() {
  46537. videojs.log.warn('videojs.Hls is deprecated. Use videojs.Vhs instead.');
  46538. return Vhs;
  46539. },
  46540. configurable: true
  46541. });
  46542. if (!videojs.use) {
  46543. videojs.registerComponent('Hls', Vhs);
  46544. videojs.registerComponent('Vhs', Vhs);
  46545. }
  46546. videojs.options.vhs = videojs.options.vhs || {};
  46547. videojs.options.hls = videojs.options.hls || {};
  46548. if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) {
  46549. var registerPlugin = videojs.registerPlugin || videojs.plugin;
  46550. registerPlugin('reloadSourceOnError', reloadSourceOnError);
  46551. }
  46552. export default videojs;