No Description

video.js 1.8MB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890218912189221893218942189521896218972189821899219002190121902219032190421905219062190721908219092191021911219122191321914219152191621917219182191921920219212192221923219242192521926219272192821929219302193121932219332193421935219362193721938219392194021941219422194321944219452194621947219482194921950219512195221953219542195521956219572195821959219602196121962219632196421965219662196721968219692197021971219722197321974219752197621977219782197921980219812198221983219842198521986219872198821989219902199121992219932199421995219962199721998219992200022001220022200322004220052200622007220082200922010220112201222013220142201522016220172201822019220202202122022220232202422025220262202722028220292203022031220322203322034220352203622037220382203922040220412204222043220442204522046220472204822049220502205122052220532205422055220562205722058220592206022061220622206322064220652206622067220682206922070220712207222073220742207522076220772207822079220802208122082220832208422085220862208722088220892209022091220922209322094220952209622097220982209922100221012210222103221042210522106221072210822109221102211122112221132211422115221162211722118221192212022121221222212322124221252212622127221282212922130221312213222133221342213522136221372213822139221402214122142221432214422145221462214722148221492215022151221522215322154221552215622157221582215922160221612216222163221642216522166221672216822169221702217122172221732217422175221762217722178221792218022181221822218322184221852218622187221882218922190221912219222193221942219522196221972219822199222002220122202222032220422205222062220722208222092221022211222122221322214222152221622217222182221922220222212222222223222242222522226222272222822229222302223122232222332223422235222362223722238222392224022241222422224322244222452224622247222482224922250222512225222253222542225522256222572225822259222602226122262222632226422265222662226722268222692227022271222722227322274222752227622277222782227922280222812228222283222842228522286222872228822289222902229122292222932229422295222962229722298222992230022301223022230322304223052230622307223082230922310223112231222313223142231522316223172231822319223202232122322223232232422325223262232722328223292233022331223322233322334223352233622337223382233922340223412234222343223442234522346223472234822349223502235122352223532235422355223562235722358223592236022361223622236322364223652236622367223682236922370223712237222373223742237522376223772237822379223802238122382223832238422385223862238722388223892239022391223922239322394223952239622397223982239922400224012240222403224042240522406224072240822409224102241122412224132241422415224162241722418224192242022421224222242322424224252242622427224282242922430224312243222433224342243522436224372243822439224402244122442224432244422445224462244722448224492245022451224522245322454224552245622457224582245922460224612246222463224642246522466224672246822469224702247122472224732247422475224762247722478224792248022481224822248322484224852248622487224882248922490224912249222493224942249522496224972249822499225002250122502225032250422505225062250722508225092251022511225122251322514225152251622517225182251922520225212252222523225242252522526225272252822529225302253122532225332253422535225362253722538225392254022541225422254322544225452254622547225482254922550225512255222553225542255522556225572255822559225602256122562225632256422565225662256722568225692257022571225722257322574225752257622577225782257922580225812258222583225842258522586225872258822589225902259122592225932259422595225962259722598225992260022601226022260322604226052260622607226082260922610226112261222613226142261522616226172261822619226202262122622226232262422625226262262722628226292263022631226322263322634226352263622637226382263922640226412264222643226442264522646226472264822649226502265122652226532265422655226562265722658226592266022661226622266322664226652266622667226682266922670226712267222673226742267522676226772267822679226802268122682226832268422685226862268722688226892269022691226922269322694226952269622697226982269922700227012270222703227042270522706227072270822709227102271122712227132271422715227162271722718227192272022721227222272322724227252272622727227282272922730227312273222733227342273522736227372273822739227402274122742227432274422745227462274722748227492275022751227522275322754227552275622757227582275922760227612276222763227642276522766227672276822769227702277122772227732277422775227762277722778227792278022781227822278322784227852278622787227882278922790227912279222793227942279522796227972279822799228002280122802228032280422805228062280722808228092281022811228122281322814228152281622817228182281922820228212282222823228242282522826228272282822829228302283122832228332283422835228362283722838228392284022841228422284322844228452284622847228482284922850228512285222853228542285522856228572285822859228602286122862228632286422865228662286722868228692287022871228722287322874228752287622877228782287922880228812288222883228842288522886228872288822889228902289122892228932289422895228962289722898228992290022901229022290322904229052290622907229082290922910229112291222913229142291522916229172291822919229202292122922229232292422925229262292722928229292293022931229322293322934229352293622937229382293922940229412294222943229442294522946229472294822949229502295122952229532295422955229562295722958229592296022961229622296322964229652296622967229682296922970229712297222973229742297522976229772297822979229802298122982229832298422985229862298722988229892299022991229922299322994229952299622997229982299923000230012300223003230042300523006230072300823009230102301123012230132301423015230162301723018230192302023021230222302323024230252302623027230282302923030230312303223033230342303523036230372303823039230402304123042230432304423045230462304723048230492305023051230522305323054230552305623057230582305923060230612306223063230642306523066230672306823069230702307123072230732307423075230762307723078230792308023081230822308323084230852308623087230882308923090230912309223093230942309523096230972309823099231002310123102231032310423105231062310723108231092311023111231122311323114231152311623117231182311923120231212312223123231242312523126231272312823129231302313123132231332313423135231362313723138231392314023141231422314323144231452314623147231482314923150231512315223153231542315523156231572315823159231602316123162231632316423165231662316723168231692317023171231722317323174231752317623177231782317923180231812318223183231842318523186231872318823189231902319123192231932319423195231962319723198231992320023201232022320323204232052320623207232082320923210232112321223213232142321523216232172321823219232202322123222232232322423225232262322723228232292323023231232322323323234232352323623237232382323923240232412324223243232442324523246232472324823249232502325123252232532325423255232562325723258232592326023261232622326323264232652326623267232682326923270232712327223273232742327523276232772327823279232802328123282232832328423285232862328723288232892329023291232922329323294232952329623297232982329923300233012330223303233042330523306233072330823309233102331123312233132331423315233162331723318233192332023321233222332323324233252332623327233282332923330233312333223333233342333523336233372333823339233402334123342233432334423345233462334723348233492335023351233522335323354233552335623357233582335923360233612336223363233642336523366233672336823369233702337123372233732337423375233762337723378233792338023381233822338323384233852338623387233882338923390233912339223393233942339523396233972339823399234002340123402234032340423405234062340723408234092341023411234122341323414234152341623417234182341923420234212342223423234242342523426234272342823429234302343123432234332343423435234362343723438234392344023441234422344323444234452344623447234482344923450234512345223453234542345523456234572345823459234602346123462234632346423465234662346723468234692347023471234722347323474234752347623477234782347923480234812348223483234842348523486234872348823489234902349123492234932349423495234962349723498234992350023501235022350323504235052350623507235082350923510235112351223513235142351523516235172351823519235202352123522235232352423525235262352723528235292353023531235322353323534235352353623537235382353923540235412354223543235442354523546235472354823549235502355123552235532355423555235562355723558235592356023561235622356323564235652356623567235682356923570235712357223573235742357523576235772357823579235802358123582235832358423585235862358723588235892359023591235922359323594235952359623597235982359923600236012360223603236042360523606236072360823609236102361123612236132361423615236162361723618236192362023621236222362323624236252362623627236282362923630236312363223633236342363523636236372363823639236402364123642236432364423645236462364723648236492365023651236522365323654236552365623657236582365923660236612366223663236642366523666236672366823669236702367123672236732367423675236762367723678236792368023681236822368323684236852368623687236882368923690236912369223693236942369523696236972369823699237002370123702237032370423705237062370723708237092371023711237122371323714237152371623717237182371923720237212372223723237242372523726237272372823729237302373123732237332373423735237362373723738237392374023741237422374323744237452374623747237482374923750237512375223753237542375523756237572375823759237602376123762237632376423765237662376723768237692377023771237722377323774237752377623777237782377923780237812378223783237842378523786237872378823789237902379123792237932379423795237962379723798237992380023801238022380323804238052380623807238082380923810238112381223813238142381523816238172381823819238202382123822238232382423825238262382723828238292383023831238322383323834238352383623837238382383923840238412384223843238442384523846238472384823849238502385123852238532385423855238562385723858238592386023861238622386323864238652386623867238682386923870238712387223873238742387523876238772387823879238802388123882238832388423885238862388723888238892389023891238922389323894238952389623897238982389923900239012390223903239042390523906239072390823909239102391123912239132391423915239162391723918239192392023921239222392323924239252392623927239282392923930239312393223933239342393523936239372393823939239402394123942239432394423945239462394723948239492395023951239522395323954239552395623957239582395923960239612396223963239642396523966239672396823969239702397123972239732397423975239762397723978239792398023981239822398323984239852398623987239882398923990239912399223993239942399523996239972399823999240002400124002240032400424005240062400724008240092401024011240122401324014240152401624017240182401924020240212402224023240242402524026240272402824029240302403124032240332403424035240362403724038240392404024041240422404324044240452404624047240482404924050240512405224053240542405524056240572405824059240602406124062240632406424065240662406724068240692407024071240722407324074240752407624077240782407924080240812408224083240842408524086240872408824089240902409124092240932409424095240962409724098240992410024101241022410324104241052410624107241082410924110241112411224113241142411524116241172411824119241202412124122241232412424125241262412724128241292413024131241322413324134241352413624137241382413924140241412414224143241442414524146241472414824149241502415124152241532415424155241562415724158241592416024161241622416324164241652416624167241682416924170241712417224173241742417524176241772417824179241802418124182241832418424185241862418724188241892419024191241922419324194241952419624197241982419924200242012420224203242042420524206242072420824209242102421124212242132421424215242162421724218242192422024221242222422324224242252422624227242282422924230242312423224233242342423524236242372423824239242402424124242242432424424245242462424724248242492425024251242522425324254242552425624257242582425924260242612426224263242642426524266242672426824269242702427124272242732427424275242762427724278242792428024281242822428324284242852428624287242882428924290242912429224293242942429524296242972429824299243002430124302243032430424305243062430724308243092431024311243122431324314243152431624317243182431924320243212432224323243242432524326243272432824329243302433124332243332433424335243362433724338243392434024341243422434324344243452434624347243482434924350243512435224353243542435524356243572435824359243602436124362243632436424365243662436724368243692437024371243722437324374243752437624377243782437924380243812438224383243842438524386243872438824389243902439124392243932439424395243962439724398243992440024401244022440324404244052440624407244082440924410244112441224413244142441524416244172441824419244202442124422244232442424425244262442724428244292443024431244322443324434244352443624437244382443924440244412444224443244442444524446244472444824449244502445124452244532445424455244562445724458244592446024461244622446324464244652446624467244682446924470244712447224473244742447524476244772447824479244802448124482244832448424485244862448724488244892449024491244922449324494244952449624497244982449924500245012450224503245042450524506245072450824509245102451124512245132451424515245162451724518245192452024521245222452324524245252452624527245282452924530245312453224533245342453524536245372453824539245402454124542245432454424545245462454724548245492455024551245522455324554245552455624557245582455924560245612456224563245642456524566245672456824569245702457124572245732457424575245762457724578245792458024581245822458324584245852458624587245882458924590245912459224593245942459524596245972459824599246002460124602246032460424605246062460724608246092461024611246122461324614246152461624617246182461924620246212462224623246242462524626246272462824629246302463124632246332463424635246362463724638246392464024641246422464324644246452464624647246482464924650246512465224653246542465524656246572465824659246602466124662246632466424665246662466724668246692467024671246722467324674246752467624677246782467924680246812468224683246842468524686246872468824689246902469124692246932469424695246962469724698246992470024701247022470324704247052470624707247082470924710247112471224713247142471524716247172471824719247202472124722247232472424725247262472724728247292473024731247322473324734247352473624737247382473924740247412474224743247442474524746247472474824749247502475124752247532475424755247562475724758247592476024761247622476324764247652476624767247682476924770247712477224773247742477524776247772477824779247802478124782247832478424785247862478724788247892479024791247922479324794247952479624797247982479924800248012480224803248042480524806248072480824809248102481124812248132481424815248162481724818248192482024821248222482324824248252482624827248282482924830248312483224833248342483524836248372483824839248402484124842248432484424845248462484724848248492485024851248522485324854248552485624857248582485924860248612486224863248642486524866248672486824869248702487124872248732487424875248762487724878248792488024881248822488324884248852488624887248882488924890248912489224893248942489524896248972489824899249002490124902249032490424905249062490724908249092491024911249122491324914249152491624917249182491924920249212492224923249242492524926249272492824929249302493124932249332493424935249362493724938249392494024941249422494324944249452494624947249482494924950249512495224953249542495524956249572495824959249602496124962249632496424965249662496724968249692497024971249722497324974249752497624977249782497924980249812498224983249842498524986249872498824989249902499124992249932499424995249962499724998249992500025001250022500325004250052500625007250082500925010250112501225013250142501525016250172501825019250202502125022250232502425025250262502725028250292503025031250322503325034250352503625037250382503925040250412504225043250442504525046250472504825049250502505125052250532505425055250562505725058250592506025061250622506325064250652506625067250682506925070250712507225073250742507525076250772507825079250802508125082250832508425085250862508725088250892509025091250922509325094250952509625097250982509925100251012510225103251042510525106251072510825109251102511125112251132511425115251162511725118251192512025121251222512325124251252512625127251282512925130251312513225133251342513525136251372513825139251402514125142251432514425145251462514725148251492515025151251522515325154251552515625157251582515925160251612516225163251642516525166251672516825169251702517125172251732517425175251762517725178251792518025181251822518325184251852518625187251882518925190251912519225193251942519525196251972519825199252002520125202252032520425205252062520725208252092521025211252122521325214252152521625217252182521925220252212522225223252242522525226252272522825229252302523125232252332523425235252362523725238252392524025241252422524325244252452524625247252482524925250252512525225253252542525525256252572525825259252602526125262252632526425265252662526725268252692527025271252722527325274252752527625277252782527925280252812528225283252842528525286252872528825289252902529125292252932529425295252962529725298252992530025301253022530325304253052530625307253082530925310253112531225313253142531525316253172531825319253202532125322253232532425325253262532725328253292533025331253322533325334253352533625337253382533925340253412534225343253442534525346253472534825349253502535125352253532535425355253562535725358253592536025361253622536325364253652536625367253682536925370253712537225373253742537525376253772537825379253802538125382253832538425385253862538725388253892539025391253922539325394253952539625397253982539925400254012540225403254042540525406254072540825409254102541125412254132541425415254162541725418254192542025421254222542325424254252542625427254282542925430254312543225433254342543525436254372543825439254402544125442254432544425445254462544725448254492545025451254522545325454254552545625457254582545925460254612546225463254642546525466254672546825469254702547125472254732547425475254762547725478254792548025481254822548325484254852548625487254882548925490254912549225493254942549525496254972549825499255002550125502255032550425505255062550725508255092551025511255122551325514255152551625517255182551925520255212552225523255242552525526255272552825529255302553125532255332553425535255362553725538255392554025541255422554325544255452554625547255482554925550255512555225553255542555525556255572555825559255602556125562255632556425565255662556725568255692557025571255722557325574255752557625577255782557925580255812558225583255842558525586255872558825589255902559125592255932559425595255962559725598255992560025601256022560325604256052560625607256082560925610256112561225613256142561525616256172561825619256202562125622256232562425625256262562725628256292563025631256322563325634256352563625637256382563925640256412564225643256442564525646256472564825649256502565125652256532565425655256562565725658256592566025661256622566325664256652566625667256682566925670256712567225673256742567525676256772567825679256802568125682256832568425685256862568725688256892569025691256922569325694256952569625697256982569925700257012570225703257042570525706257072570825709257102571125712257132571425715257162571725718257192572025721257222572325724257252572625727257282572925730257312573225733257342573525736257372573825739257402574125742257432574425745257462574725748257492575025751257522575325754257552575625757257582575925760257612576225763257642576525766257672576825769257702577125772257732577425775257762577725778257792578025781257822578325784257852578625787257882578925790257912579225793257942579525796257972579825799258002580125802258032580425805258062580725808258092581025811258122581325814258152581625817258182581925820258212582225823258242582525826258272582825829258302583125832258332583425835258362583725838258392584025841258422584325844258452584625847258482584925850258512585225853258542585525856258572585825859258602586125862258632586425865258662586725868258692587025871258722587325874258752587625877258782587925880258812588225883258842588525886258872588825889258902589125892258932589425895258962589725898258992590025901259022590325904259052590625907259082590925910259112591225913259142591525916259172591825919259202592125922259232592425925259262592725928259292593025931259322593325934259352593625937259382593925940259412594225943259442594525946259472594825949259502595125952259532595425955259562595725958259592596025961259622596325964259652596625967259682596925970259712597225973259742597525976259772597825979259802598125982259832598425985259862598725988259892599025991259922599325994259952599625997259982599926000260012600226003260042600526006260072600826009260102601126012260132601426015260162601726018260192602026021260222602326024260252602626027260282602926030260312603226033260342603526036260372603826039260402604126042260432604426045260462604726048260492605026051260522605326054260552605626057260582605926060260612606226063260642606526066260672606826069260702607126072260732607426075260762607726078260792608026081260822608326084260852608626087260882608926090260912609226093260942609526096260972609826099261002610126102261032610426105261062610726108261092611026111261122611326114261152611626117261182611926120261212612226123261242612526126261272612826129261302613126132261332613426135261362613726138261392614026141261422614326144261452614626147261482614926150261512615226153261542615526156261572615826159261602616126162261632616426165261662616726168261692617026171261722617326174261752617626177261782617926180261812618226183261842618526186261872618826189261902619126192261932619426195261962619726198261992620026201262022620326204262052620626207262082620926210262112621226213262142621526216262172621826219262202622126222262232622426225262262622726228262292623026231262322623326234262352623626237262382623926240262412624226243262442624526246262472624826249262502625126252262532625426255262562625726258262592626026261262622626326264262652626626267262682626926270262712627226273262742627526276262772627826279262802628126282262832628426285262862628726288262892629026291262922629326294262952629626297262982629926300263012630226303263042630526306263072630826309263102631126312263132631426315263162631726318263192632026321263222632326324263252632626327263282632926330263312633226333263342633526336263372633826339263402634126342263432634426345263462634726348263492635026351263522635326354263552635626357263582635926360263612636226363263642636526366263672636826369263702637126372263732637426375263762637726378263792638026381263822638326384263852638626387263882638926390263912639226393263942639526396263972639826399264002640126402264032640426405264062640726408264092641026411264122641326414264152641626417264182641926420264212642226423264242642526426264272642826429264302643126432264332643426435264362643726438264392644026441264422644326444264452644626447264482644926450264512645226453264542645526456264572645826459264602646126462264632646426465264662646726468264692647026471264722647326474264752647626477264782647926480264812648226483264842648526486264872648826489264902649126492264932649426495264962649726498264992650026501265022650326504265052650626507265082650926510265112651226513265142651526516265172651826519265202652126522265232652426525265262652726528265292653026531265322653326534265352653626537265382653926540265412654226543265442654526546265472654826549265502655126552265532655426555265562655726558265592656026561265622656326564265652656626567265682656926570265712657226573265742657526576265772657826579265802658126582265832658426585265862658726588265892659026591265922659326594265952659626597265982659926600266012660226603266042660526606266072660826609266102661126612266132661426615266162661726618266192662026621266222662326624266252662626627266282662926630266312663226633266342663526636266372663826639266402664126642266432664426645266462664726648266492665026651266522665326654266552665626657266582665926660266612666226663266642666526666266672666826669266702667126672266732667426675266762667726678266792668026681266822668326684266852668626687266882668926690266912669226693266942669526696266972669826699267002670126702267032670426705267062670726708267092671026711267122671326714267152671626717267182671926720267212672226723267242672526726267272672826729267302673126732267332673426735267362673726738267392674026741267422674326744267452674626747267482674926750267512675226753267542675526756267572675826759267602676126762267632676426765267662676726768267692677026771267722677326774267752677626777267782677926780267812678226783267842678526786267872678826789267902679126792267932679426795267962679726798267992680026801268022680326804268052680626807268082680926810268112681226813268142681526816268172681826819268202682126822268232682426825268262682726828268292683026831268322683326834268352683626837268382683926840268412684226843268442684526846268472684826849268502685126852268532685426855268562685726858268592686026861268622686326864268652686626867268682686926870268712687226873268742687526876268772687826879268802688126882268832688426885268862688726888268892689026891268922689326894268952689626897268982689926900269012690226903269042690526906269072690826909269102691126912269132691426915269162691726918269192692026921269222692326924269252692626927269282692926930269312693226933269342693526936269372693826939269402694126942269432694426945269462694726948269492695026951269522695326954269552695626957269582695926960269612696226963269642696526966269672696826969269702697126972269732697426975269762697726978269792698026981269822698326984269852698626987269882698926990269912699226993269942699526996269972699826999270002700127002270032700427005270062700727008270092701027011270122701327014270152701627017270182701927020270212702227023270242702527026270272702827029270302703127032270332703427035270362703727038270392704027041270422704327044270452704627047270482704927050270512705227053270542705527056270572705827059270602706127062270632706427065270662706727068270692707027071270722707327074270752707627077270782707927080270812708227083270842708527086270872708827089270902709127092270932709427095270962709727098270992710027101271022710327104271052710627107271082710927110271112711227113271142711527116271172711827119271202712127122271232712427125271262712727128271292713027131271322713327134271352713627137271382713927140271412714227143271442714527146271472714827149271502715127152271532715427155271562715727158271592716027161271622716327164271652716627167271682716927170271712717227173271742717527176271772717827179271802718127182271832718427185271862718727188271892719027191271922719327194271952719627197271982719927200272012720227203272042720527206272072720827209272102721127212272132721427215272162721727218272192722027221272222722327224272252722627227272282722927230272312723227233272342723527236272372723827239272402724127242272432724427245272462724727248272492725027251272522725327254272552725627257272582725927260272612726227263272642726527266272672726827269272702727127272272732727427275272762727727278272792728027281272822728327284272852728627287272882728927290272912729227293272942729527296272972729827299273002730127302273032730427305273062730727308273092731027311273122731327314273152731627317273182731927320273212732227323273242732527326273272732827329273302733127332273332733427335273362733727338273392734027341273422734327344273452734627347273482734927350273512735227353273542735527356273572735827359273602736127362273632736427365273662736727368273692737027371273722737327374273752737627377273782737927380273812738227383273842738527386273872738827389273902739127392273932739427395273962739727398273992740027401274022740327404274052740627407274082740927410274112741227413274142741527416274172741827419274202742127422274232742427425274262742727428274292743027431274322743327434274352743627437274382743927440274412744227443274442744527446274472744827449274502745127452274532745427455274562745727458274592746027461274622746327464274652746627467274682746927470274712747227473274742747527476274772747827479274802748127482274832748427485274862748727488274892749027491274922749327494274952749627497274982749927500275012750227503275042750527506275072750827509275102751127512275132751427515275162751727518275192752027521275222752327524275252752627527275282752927530275312753227533275342753527536275372753827539275402754127542275432754427545275462754727548275492755027551275522755327554275552755627557275582755927560275612756227563275642756527566275672756827569275702757127572275732757427575275762757727578275792758027581275822758327584275852758627587275882758927590275912759227593275942759527596275972759827599276002760127602276032760427605276062760727608276092761027611276122761327614276152761627617276182761927620276212762227623276242762527626276272762827629276302763127632276332763427635276362763727638276392764027641276422764327644276452764627647276482764927650276512765227653276542765527656276572765827659276602766127662276632766427665276662766727668276692767027671276722767327674276752767627677276782767927680276812768227683276842768527686276872768827689276902769127692276932769427695276962769727698276992770027701277022770327704277052770627707277082770927710277112771227713277142771527716277172771827719277202772127722277232772427725277262772727728277292773027731277322773327734277352773627737277382773927740277412774227743277442774527746277472774827749277502775127752277532775427755277562775727758277592776027761277622776327764277652776627767277682776927770277712777227773277742777527776277772777827779277802778127782277832778427785277862778727788277892779027791277922779327794277952779627797277982779927800278012780227803278042780527806278072780827809278102781127812278132781427815278162781727818278192782027821278222782327824278252782627827278282782927830278312783227833278342783527836278372783827839278402784127842278432784427845278462784727848278492785027851278522785327854278552785627857278582785927860278612786227863278642786527866278672786827869278702787127872278732787427875278762787727878278792788027881278822788327884278852788627887278882788927890278912789227893278942789527896278972789827899279002790127902279032790427905279062790727908279092791027911279122791327914279152791627917279182791927920279212792227923279242792527926279272792827929279302793127932279332793427935279362793727938279392794027941279422794327944279452794627947279482794927950279512795227953279542795527956279572795827959279602796127962279632796427965279662796727968279692797027971279722797327974279752797627977279782797927980279812798227983279842798527986279872798827989279902799127992279932799427995279962799727998279992800028001280022800328004280052800628007280082800928010280112801228013280142801528016280172801828019280202802128022280232802428025280262802728028280292803028031280322803328034280352803628037280382803928040280412804228043280442804528046280472804828049280502805128052280532805428055280562805728058280592806028061280622806328064280652806628067280682806928070280712807228073280742807528076280772807828079280802808128082280832808428085280862808728088280892809028091280922809328094280952809628097280982809928100281012810228103281042810528106281072810828109281102811128112281132811428115281162811728118281192812028121281222812328124281252812628127281282812928130281312813228133281342813528136281372813828139281402814128142281432814428145281462814728148281492815028151281522815328154281552815628157281582815928160281612816228163281642816528166281672816828169281702817128172281732817428175281762817728178281792818028181281822818328184281852818628187281882818928190281912819228193281942819528196281972819828199282002820128202282032820428205282062820728208282092821028211282122821328214282152821628217282182821928220282212822228223282242822528226282272822828229282302823128232282332823428235282362823728238282392824028241282422824328244282452824628247282482824928250282512825228253282542825528256282572825828259282602826128262282632826428265282662826728268282692827028271282722827328274282752827628277282782827928280282812828228283282842828528286282872828828289282902829128292282932829428295282962829728298282992830028301283022830328304283052830628307283082830928310283112831228313283142831528316283172831828319283202832128322283232832428325283262832728328283292833028331283322833328334283352833628337283382833928340283412834228343283442834528346283472834828349283502835128352283532835428355283562835728358283592836028361283622836328364283652836628367283682836928370283712837228373283742837528376283772837828379283802838128382283832838428385283862838728388283892839028391283922839328394283952839628397283982839928400284012840228403284042840528406284072840828409284102841128412284132841428415284162841728418284192842028421284222842328424284252842628427284282842928430284312843228433284342843528436284372843828439284402844128442284432844428445284462844728448284492845028451284522845328454284552845628457284582845928460284612846228463284642846528466284672846828469284702847128472284732847428475284762847728478284792848028481284822848328484284852848628487284882848928490284912849228493284942849528496284972849828499285002850128502285032850428505285062850728508285092851028511285122851328514285152851628517285182851928520285212852228523285242852528526285272852828529285302853128532285332853428535285362853728538285392854028541285422854328544285452854628547285482854928550285512855228553285542855528556285572855828559285602856128562285632856428565285662856728568285692857028571285722857328574285752857628577285782857928580285812858228583285842858528586285872858828589285902859128592285932859428595285962859728598285992860028601286022860328604286052860628607286082860928610286112861228613286142861528616286172861828619286202862128622286232862428625286262862728628286292863028631286322863328634286352863628637286382863928640286412864228643286442864528646286472864828649286502865128652286532865428655286562865728658286592866028661286622866328664286652866628667286682866928670286712867228673286742867528676286772867828679286802868128682286832868428685286862868728688286892869028691286922869328694286952869628697286982869928700287012870228703287042870528706287072870828709287102871128712287132871428715287162871728718287192872028721287222872328724287252872628727287282872928730287312873228733287342873528736287372873828739287402874128742287432874428745287462874728748287492875028751287522875328754287552875628757287582875928760287612876228763287642876528766287672876828769287702877128772287732877428775287762877728778287792878028781287822878328784287852878628787287882878928790287912879228793287942879528796287972879828799288002880128802288032880428805288062880728808288092881028811288122881328814288152881628817288182881928820288212882228823288242882528826288272882828829288302883128832288332883428835288362883728838288392884028841288422884328844288452884628847288482884928850288512885228853288542885528856288572885828859288602886128862288632886428865288662886728868288692887028871288722887328874288752887628877288782887928880288812888228883288842888528886288872888828889288902889128892288932889428895288962889728898288992890028901289022890328904289052890628907289082890928910289112891228913289142891528916289172891828919289202892128922289232892428925289262892728928289292893028931289322893328934289352893628937289382893928940289412894228943289442894528946289472894828949289502895128952289532895428955289562895728958289592896028961289622896328964289652896628967289682896928970289712897228973289742897528976289772897828979289802898128982289832898428985289862898728988289892899028991289922899328994289952899628997289982899929000290012900229003290042900529006290072900829009290102901129012290132901429015290162901729018290192902029021290222902329024290252902629027290282902929030290312903229033290342903529036290372903829039290402904129042290432904429045290462904729048290492905029051290522905329054290552905629057290582905929060290612906229063290642906529066290672906829069290702907129072290732907429075290762907729078290792908029081290822908329084290852908629087290882908929090290912909229093290942909529096290972909829099291002910129102291032910429105291062910729108291092911029111291122911329114291152911629117291182911929120291212912229123291242912529126291272912829129291302913129132291332913429135291362913729138291392914029141291422914329144291452914629147291482914929150291512915229153291542915529156291572915829159291602916129162291632916429165291662916729168291692917029171291722917329174291752917629177291782917929180291812918229183291842918529186291872918829189291902919129192291932919429195291962919729198291992920029201292022920329204292052920629207292082920929210292112921229213292142921529216292172921829219292202922129222292232922429225292262922729228292292923029231292322923329234292352923629237292382923929240292412924229243292442924529246292472924829249292502925129252292532925429255292562925729258292592926029261292622926329264292652926629267292682926929270292712927229273292742927529276292772927829279292802928129282292832928429285292862928729288292892929029291292922929329294292952929629297292982929929300293012930229303293042930529306293072930829309293102931129312293132931429315293162931729318293192932029321293222932329324293252932629327293282932929330293312933229333293342933529336293372933829339293402934129342293432934429345293462934729348293492935029351293522935329354293552935629357293582935929360293612936229363293642936529366293672936829369293702937129372293732937429375293762937729378293792938029381293822938329384293852938629387293882938929390293912939229393293942939529396293972939829399294002940129402294032940429405294062940729408294092941029411294122941329414294152941629417294182941929420294212942229423294242942529426294272942829429294302943129432294332943429435294362943729438294392944029441294422944329444294452944629447294482944929450294512945229453294542945529456294572945829459294602946129462294632946429465294662946729468294692947029471294722947329474294752947629477294782947929480294812948229483294842948529486294872948829489294902949129492294932949429495294962949729498294992950029501295022950329504295052950629507295082950929510295112951229513295142951529516295172951829519295202952129522295232952429525295262952729528295292953029531295322953329534295352953629537295382953929540295412954229543295442954529546295472954829549295502955129552295532955429555295562955729558295592956029561295622956329564295652956629567295682956929570295712957229573295742957529576295772957829579295802958129582295832958429585295862958729588295892959029591295922959329594295952959629597295982959929600296012960229603296042960529606296072960829609296102961129612296132961429615296162961729618296192962029621296222962329624296252962629627296282962929630296312963229633296342963529636296372963829639296402964129642296432964429645296462964729648296492965029651296522965329654296552965629657296582965929660296612966229663296642966529666296672966829669296702967129672296732967429675296762967729678296792968029681296822968329684296852968629687296882968929690296912969229693296942969529696296972969829699297002970129702297032970429705297062970729708297092971029711297122971329714297152971629717297182971929720297212972229723297242972529726297272972829729297302973129732297332973429735297362973729738297392974029741297422974329744297452974629747297482974929750297512975229753297542975529756297572975829759297602976129762297632976429765297662976729768297692977029771297722977329774297752977629777297782977929780297812978229783297842978529786297872978829789297902979129792297932979429795297962979729798297992980029801298022980329804298052980629807298082980929810298112981229813298142981529816298172981829819298202982129822298232982429825298262982729828298292983029831298322983329834298352983629837298382983929840298412984229843298442984529846298472984829849298502985129852298532985429855298562985729858298592986029861298622986329864298652986629867298682986929870298712987229873298742987529876298772987829879298802988129882298832988429885298862988729888298892989029891298922989329894298952989629897298982989929900299012990229903299042990529906299072990829909299102991129912299132991429915299162991729918299192992029921299222992329924299252992629927299282992929930299312993229933299342993529936299372993829939299402994129942299432994429945299462994729948299492995029951299522995329954299552995629957299582995929960299612996229963299642996529966299672996829969299702997129972299732997429975299762997729978299792998029981299822998329984299852998629987299882998929990299912999229993299942999529996299972999829999300003000130002300033000430005300063000730008300093001030011300123001330014300153001630017300183001930020300213002230023300243002530026300273002830029300303003130032300333003430035300363003730038300393004030041300423004330044300453004630047300483004930050300513005230053300543005530056300573005830059300603006130062300633006430065300663006730068300693007030071300723007330074300753007630077300783007930080300813008230083300843008530086300873008830089300903009130092300933009430095300963009730098300993010030101301023010330104301053010630107301083010930110301113011230113301143011530116301173011830119301203012130122301233012430125301263012730128301293013030131301323013330134301353013630137301383013930140301413014230143301443014530146301473014830149301503015130152301533015430155301563015730158301593016030161301623016330164301653016630167301683016930170301713017230173301743017530176301773017830179301803018130182301833018430185301863018730188301893019030191301923019330194301953019630197301983019930200302013020230203302043020530206302073020830209302103021130212302133021430215302163021730218302193022030221302223022330224302253022630227302283022930230302313023230233302343023530236302373023830239302403024130242302433024430245302463024730248302493025030251302523025330254302553025630257302583025930260302613026230263302643026530266302673026830269302703027130272302733027430275302763027730278302793028030281302823028330284302853028630287302883028930290302913029230293302943029530296302973029830299303003030130302303033030430305303063030730308303093031030311303123031330314303153031630317303183031930320303213032230323303243032530326303273032830329303303033130332303333033430335303363033730338303393034030341303423034330344303453034630347303483034930350303513035230353303543035530356303573035830359303603036130362303633036430365303663036730368303693037030371303723037330374303753037630377303783037930380303813038230383303843038530386303873038830389303903039130392303933039430395303963039730398303993040030401304023040330404304053040630407304083040930410304113041230413304143041530416304173041830419304203042130422304233042430425304263042730428304293043030431304323043330434304353043630437304383043930440304413044230443304443044530446304473044830449304503045130452304533045430455304563045730458304593046030461304623046330464304653046630467304683046930470304713047230473304743047530476304773047830479304803048130482304833048430485304863048730488304893049030491304923049330494304953049630497304983049930500305013050230503305043050530506305073050830509305103051130512305133051430515305163051730518305193052030521305223052330524305253052630527305283052930530305313053230533305343053530536305373053830539305403054130542305433054430545305463054730548305493055030551305523055330554305553055630557305583055930560305613056230563305643056530566305673056830569305703057130572305733057430575305763057730578305793058030581305823058330584305853058630587305883058930590305913059230593305943059530596305973059830599306003060130602306033060430605306063060730608306093061030611306123061330614306153061630617306183061930620306213062230623306243062530626306273062830629306303063130632306333063430635306363063730638306393064030641306423064330644306453064630647306483064930650306513065230653306543065530656306573065830659306603066130662306633066430665306663066730668306693067030671306723067330674306753067630677306783067930680306813068230683306843068530686306873068830689306903069130692306933069430695306963069730698306993070030701307023070330704307053070630707307083070930710307113071230713307143071530716307173071830719307203072130722307233072430725307263072730728307293073030731307323073330734307353073630737307383073930740307413074230743307443074530746307473074830749307503075130752307533075430755307563075730758307593076030761307623076330764307653076630767307683076930770307713077230773307743077530776307773077830779307803078130782307833078430785307863078730788307893079030791307923079330794307953079630797307983079930800308013080230803308043080530806308073080830809308103081130812308133081430815308163081730818308193082030821308223082330824308253082630827308283082930830308313083230833308343083530836308373083830839308403084130842308433084430845308463084730848308493085030851308523085330854308553085630857308583085930860308613086230863308643086530866308673086830869308703087130872308733087430875308763087730878308793088030881308823088330884308853088630887308883088930890308913089230893308943089530896308973089830899309003090130902309033090430905309063090730908309093091030911309123091330914309153091630917309183091930920309213092230923309243092530926309273092830929309303093130932309333093430935309363093730938309393094030941309423094330944309453094630947309483094930950309513095230953309543095530956309573095830959309603096130962309633096430965309663096730968309693097030971309723097330974309753097630977309783097930980309813098230983309843098530986309873098830989309903099130992309933099430995309963099730998309993100031001310023100331004310053100631007310083100931010310113101231013310143101531016310173101831019310203102131022310233102431025310263102731028310293103031031310323103331034310353103631037310383103931040310413104231043310443104531046310473104831049310503105131052310533105431055310563105731058310593106031061310623106331064310653106631067310683106931070310713107231073310743107531076310773107831079310803108131082310833108431085310863108731088310893109031091310923109331094310953109631097310983109931100311013110231103311043110531106311073110831109311103111131112311133111431115311163111731118311193112031121311223112331124311253112631127311283112931130311313113231133311343113531136311373113831139311403114131142311433114431145311463114731148311493115031151311523115331154311553115631157311583115931160311613116231163311643116531166311673116831169311703117131172311733117431175311763117731178311793118031181311823118331184311853118631187311883118931190311913119231193311943119531196311973119831199312003120131202312033120431205312063120731208312093121031211312123121331214312153121631217312183121931220312213122231223312243122531226312273122831229312303123131232312333123431235312363123731238312393124031241312423124331244312453124631247312483124931250312513125231253312543125531256312573125831259312603126131262312633126431265312663126731268312693127031271312723127331274312753127631277312783127931280312813128231283312843128531286312873128831289312903129131292312933129431295312963129731298312993130031301313023130331304313053130631307313083130931310313113131231313313143131531316313173131831319313203132131322313233132431325313263132731328313293133031331313323133331334313353133631337313383133931340313413134231343313443134531346313473134831349313503135131352313533135431355313563135731358313593136031361313623136331364313653136631367313683136931370313713137231373313743137531376313773137831379313803138131382313833138431385313863138731388313893139031391313923139331394313953139631397313983139931400314013140231403314043140531406314073140831409314103141131412314133141431415314163141731418314193142031421314223142331424314253142631427314283142931430314313143231433314343143531436314373143831439314403144131442314433144431445314463144731448314493145031451314523145331454314553145631457314583145931460314613146231463314643146531466314673146831469314703147131472314733147431475314763147731478314793148031481314823148331484314853148631487314883148931490314913149231493314943149531496314973149831499315003150131502315033150431505315063150731508315093151031511315123151331514315153151631517315183151931520315213152231523315243152531526315273152831529315303153131532315333153431535315363153731538315393154031541315423154331544315453154631547315483154931550315513155231553315543155531556315573155831559315603156131562315633156431565315663156731568315693157031571315723157331574315753157631577315783157931580315813158231583315843158531586315873158831589315903159131592315933159431595315963159731598315993160031601316023160331604316053160631607316083160931610316113161231613316143161531616316173161831619316203162131622316233162431625316263162731628316293163031631316323163331634316353163631637316383163931640316413164231643316443164531646316473164831649316503165131652316533165431655316563165731658316593166031661316623166331664316653166631667316683166931670316713167231673316743167531676316773167831679316803168131682316833168431685316863168731688316893169031691316923169331694316953169631697316983169931700317013170231703317043170531706317073170831709317103171131712317133171431715317163171731718317193172031721317223172331724317253172631727317283172931730317313173231733317343173531736317373173831739317403174131742317433174431745317463174731748317493175031751317523175331754317553175631757317583175931760317613176231763317643176531766317673176831769317703177131772317733177431775317763177731778317793178031781317823178331784317853178631787317883178931790317913179231793317943179531796317973179831799318003180131802318033180431805318063180731808318093181031811318123181331814318153181631817318183181931820318213182231823318243182531826318273182831829318303183131832318333183431835318363183731838318393184031841318423184331844318453184631847318483184931850318513185231853318543185531856318573185831859318603186131862318633186431865318663186731868318693187031871318723187331874318753187631877318783187931880318813188231883318843188531886318873188831889318903189131892318933189431895318963189731898318993190031901319023190331904319053190631907319083190931910319113191231913319143191531916319173191831919319203192131922319233192431925319263192731928319293193031931319323193331934319353193631937319383193931940319413194231943319443194531946319473194831949319503195131952319533195431955319563195731958319593196031961319623196331964319653196631967319683196931970319713197231973319743197531976319773197831979319803198131982319833198431985319863198731988319893199031991319923199331994319953199631997319983199932000320013200232003320043200532006320073200832009320103201132012320133201432015320163201732018320193202032021320223202332024320253202632027320283202932030320313203232033320343203532036320373203832039320403204132042320433204432045320463204732048320493205032051320523205332054320553205632057320583205932060320613206232063320643206532066320673206832069320703207132072320733207432075320763207732078320793208032081320823208332084320853208632087320883208932090320913209232093320943209532096320973209832099321003210132102321033210432105321063210732108321093211032111321123211332114321153211632117321183211932120321213212232123321243212532126321273212832129321303213132132321333213432135321363213732138321393214032141321423214332144321453214632147321483214932150321513215232153321543215532156321573215832159321603216132162321633216432165321663216732168321693217032171321723217332174321753217632177321783217932180321813218232183321843218532186321873218832189321903219132192321933219432195321963219732198321993220032201322023220332204322053220632207322083220932210322113221232213322143221532216322173221832219322203222132222322233222432225322263222732228322293223032231322323223332234322353223632237322383223932240322413224232243322443224532246322473224832249322503225132252322533225432255322563225732258322593226032261322623226332264322653226632267322683226932270322713227232273322743227532276322773227832279322803228132282322833228432285322863228732288322893229032291322923229332294322953229632297322983229932300323013230232303323043230532306323073230832309323103231132312323133231432315323163231732318323193232032321323223232332324323253232632327323283232932330323313233232333323343233532336323373233832339323403234132342323433234432345323463234732348323493235032351323523235332354323553235632357323583235932360323613236232363323643236532366323673236832369323703237132372323733237432375323763237732378323793238032381323823238332384323853238632387323883238932390323913239232393323943239532396323973239832399324003240132402324033240432405324063240732408324093241032411324123241332414324153241632417324183241932420324213242232423324243242532426324273242832429324303243132432324333243432435324363243732438324393244032441324423244332444324453244632447324483244932450324513245232453324543245532456324573245832459324603246132462324633246432465324663246732468324693247032471324723247332474324753247632477324783247932480324813248232483324843248532486324873248832489324903249132492324933249432495324963249732498324993250032501325023250332504325053250632507325083250932510325113251232513325143251532516325173251832519325203252132522325233252432525325263252732528325293253032531325323253332534325353253632537325383253932540325413254232543325443254532546325473254832549325503255132552325533255432555325563255732558325593256032561325623256332564325653256632567325683256932570325713257232573325743257532576325773257832579325803258132582325833258432585325863258732588325893259032591325923259332594325953259632597325983259932600326013260232603326043260532606326073260832609326103261132612326133261432615326163261732618326193262032621326223262332624326253262632627326283262932630326313263232633326343263532636326373263832639326403264132642326433264432645326463264732648326493265032651326523265332654326553265632657326583265932660326613266232663326643266532666326673266832669326703267132672326733267432675326763267732678326793268032681326823268332684326853268632687326883268932690326913269232693326943269532696326973269832699327003270132702327033270432705327063270732708327093271032711327123271332714327153271632717327183271932720327213272232723327243272532726327273272832729327303273132732327333273432735327363273732738327393274032741327423274332744327453274632747327483274932750327513275232753327543275532756327573275832759327603276132762327633276432765327663276732768327693277032771327723277332774327753277632777327783277932780327813278232783327843278532786327873278832789327903279132792327933279432795327963279732798327993280032801328023280332804328053280632807328083280932810328113281232813328143281532816328173281832819328203282132822328233282432825328263282732828328293283032831328323283332834328353283632837328383283932840328413284232843328443284532846328473284832849328503285132852328533285432855328563285732858328593286032861328623286332864328653286632867328683286932870328713287232873328743287532876328773287832879328803288132882328833288432885328863288732888328893289032891328923289332894328953289632897328983289932900329013290232903329043290532906329073290832909329103291132912329133291432915329163291732918329193292032921329223292332924329253292632927329283292932930329313293232933329343293532936329373293832939329403294132942329433294432945329463294732948329493295032951329523295332954329553295632957329583295932960329613296232963329643296532966329673296832969329703297132972329733297432975329763297732978329793298032981329823298332984329853298632987329883298932990329913299232993329943299532996329973299832999330003300133002330033300433005330063300733008330093301033011330123301333014330153301633017330183301933020330213302233023330243302533026330273302833029330303303133032330333303433035330363303733038330393304033041330423304333044330453304633047330483304933050330513305233053330543305533056330573305833059330603306133062330633306433065330663306733068330693307033071330723307333074330753307633077330783307933080330813308233083330843308533086330873308833089330903309133092330933309433095330963309733098330993310033101331023310333104331053310633107331083310933110331113311233113331143311533116331173311833119331203312133122331233312433125331263312733128331293313033131331323313333134331353313633137331383313933140331413314233143331443314533146331473314833149331503315133152331533315433155331563315733158331593316033161331623316333164331653316633167331683316933170331713317233173331743317533176331773317833179331803318133182331833318433185331863318733188331893319033191331923319333194331953319633197331983319933200332013320233203332043320533206332073320833209332103321133212332133321433215332163321733218332193322033221332223322333224332253322633227332283322933230332313323233233332343323533236332373323833239332403324133242332433324433245332463324733248332493325033251332523325333254332553325633257332583325933260332613326233263332643326533266332673326833269332703327133272332733327433275332763327733278332793328033281332823328333284332853328633287332883328933290332913329233293332943329533296332973329833299333003330133302333033330433305333063330733308333093331033311333123331333314333153331633317333183331933320333213332233323333243332533326333273332833329333303333133332333333333433335333363333733338333393334033341333423334333344333453334633347333483334933350333513335233353333543335533356333573335833359333603336133362333633336433365333663336733368333693337033371333723337333374333753337633377333783337933380333813338233383333843338533386333873338833389333903339133392333933339433395333963339733398333993340033401334023340333404334053340633407334083340933410334113341233413334143341533416334173341833419334203342133422334233342433425334263342733428334293343033431334323343333434334353343633437334383343933440334413344233443334443344533446334473344833449334503345133452334533345433455334563345733458334593346033461334623346333464334653346633467334683346933470334713347233473334743347533476334773347833479334803348133482334833348433485334863348733488334893349033491334923349333494334953349633497334983349933500335013350233503335043350533506335073350833509335103351133512335133351433515335163351733518335193352033521335223352333524335253352633527335283352933530335313353233533335343353533536335373353833539335403354133542335433354433545335463354733548335493355033551335523355333554335553355633557335583355933560335613356233563335643356533566335673356833569335703357133572335733357433575335763357733578335793358033581335823358333584335853358633587335883358933590335913359233593335943359533596335973359833599336003360133602336033360433605336063360733608336093361033611336123361333614336153361633617336183361933620336213362233623336243362533626336273362833629336303363133632336333363433635336363363733638336393364033641336423364333644336453364633647336483364933650336513365233653336543365533656336573365833659336603366133662336633366433665336663366733668336693367033671336723367333674336753367633677336783367933680336813368233683336843368533686336873368833689336903369133692336933369433695336963369733698336993370033701337023370333704337053370633707337083370933710337113371233713337143371533716337173371833719337203372133722337233372433725337263372733728337293373033731337323373333734337353373633737337383373933740337413374233743337443374533746337473374833749337503375133752337533375433755337563375733758337593376033761337623376333764337653376633767337683376933770337713377233773337743377533776337773377833779337803378133782337833378433785337863378733788337893379033791337923379333794337953379633797337983379933800338013380233803338043380533806338073380833809338103381133812338133381433815338163381733818338193382033821338223382333824338253382633827338283382933830338313383233833338343383533836338373383833839338403384133842338433384433845338463384733848338493385033851338523385333854338553385633857338583385933860338613386233863338643386533866338673386833869338703387133872338733387433875338763387733878338793388033881338823388333884338853388633887338883388933890338913389233893338943389533896338973389833899339003390133902339033390433905339063390733908339093391033911339123391333914339153391633917339183391933920339213392233923339243392533926339273392833929339303393133932339333393433935339363393733938339393394033941339423394333944339453394633947339483394933950339513395233953339543395533956339573395833959339603396133962339633396433965339663396733968339693397033971339723397333974339753397633977339783397933980339813398233983339843398533986339873398833989339903399133992339933399433995339963399733998339993400034001340023400334004340053400634007340083400934010340113401234013340143401534016340173401834019340203402134022340233402434025340263402734028340293403034031340323403334034340353403634037340383403934040340413404234043340443404534046340473404834049340503405134052340533405434055340563405734058340593406034061340623406334064340653406634067340683406934070340713407234073340743407534076340773407834079340803408134082340833408434085340863408734088340893409034091340923409334094340953409634097340983409934100341013410234103341043410534106341073410834109341103411134112341133411434115341163411734118341193412034121341223412334124341253412634127341283412934130341313413234133341343413534136341373413834139341403414134142341433414434145341463414734148341493415034151341523415334154341553415634157341583415934160341613416234163341643416534166341673416834169341703417134172341733417434175341763417734178341793418034181341823418334184341853418634187341883418934190341913419234193341943419534196341973419834199342003420134202342033420434205342063420734208342093421034211342123421334214342153421634217342183421934220342213422234223342243422534226342273422834229342303423134232342333423434235342363423734238342393424034241342423424334244342453424634247342483424934250342513425234253342543425534256342573425834259342603426134262342633426434265342663426734268342693427034271342723427334274342753427634277342783427934280342813428234283342843428534286342873428834289342903429134292342933429434295342963429734298342993430034301343023430334304343053430634307343083430934310343113431234313343143431534316343173431834319343203432134322343233432434325343263432734328343293433034331343323433334334343353433634337343383433934340343413434234343343443434534346343473434834349343503435134352343533435434355343563435734358343593436034361343623436334364343653436634367343683436934370343713437234373343743437534376343773437834379343803438134382343833438434385343863438734388343893439034391343923439334394343953439634397343983439934400344013440234403344043440534406344073440834409344103441134412344133441434415344163441734418344193442034421344223442334424344253442634427344283442934430344313443234433344343443534436344373443834439344403444134442344433444434445344463444734448344493445034451344523445334454344553445634457344583445934460344613446234463344643446534466344673446834469344703447134472344733447434475344763447734478344793448034481344823448334484344853448634487344883448934490344913449234493344943449534496344973449834499345003450134502345033450434505345063450734508345093451034511345123451334514345153451634517345183451934520345213452234523345243452534526345273452834529345303453134532345333453434535345363453734538345393454034541345423454334544345453454634547345483454934550345513455234553345543455534556345573455834559345603456134562345633456434565345663456734568345693457034571345723457334574345753457634577345783457934580345813458234583345843458534586345873458834589345903459134592345933459434595345963459734598345993460034601346023460334604346053460634607346083460934610346113461234613346143461534616346173461834619346203462134622346233462434625346263462734628346293463034631346323463334634346353463634637346383463934640346413464234643346443464534646346473464834649346503465134652346533465434655346563465734658346593466034661346623466334664346653466634667346683466934670346713467234673346743467534676346773467834679346803468134682346833468434685346863468734688346893469034691346923469334694346953469634697346983469934700347013470234703347043470534706347073470834709347103471134712347133471434715347163471734718347193472034721347223472334724347253472634727347283472934730347313473234733347343473534736347373473834739347403474134742347433474434745347463474734748347493475034751347523475334754347553475634757347583475934760347613476234763347643476534766347673476834769347703477134772347733477434775347763477734778347793478034781347823478334784347853478634787347883478934790347913479234793347943479534796347973479834799348003480134802348033480434805348063480734808348093481034811348123481334814348153481634817348183481934820348213482234823348243482534826348273482834829348303483134832348333483434835348363483734838348393484034841348423484334844348453484634847348483484934850348513485234853348543485534856348573485834859348603486134862348633486434865348663486734868348693487034871348723487334874348753487634877348783487934880348813488234883348843488534886348873488834889348903489134892348933489434895348963489734898348993490034901349023490334904349053490634907349083490934910349113491234913349143491534916349173491834919349203492134922349233492434925349263492734928349293493034931349323493334934349353493634937349383493934940349413494234943349443494534946349473494834949349503495134952349533495434955349563495734958349593496034961349623496334964349653496634967349683496934970349713497234973349743497534976349773497834979349803498134982349833498434985349863498734988349893499034991349923499334994349953499634997349983499935000350013500235003350043500535006350073500835009350103501135012350133501435015350163501735018350193502035021350223502335024350253502635027350283502935030350313503235033350343503535036350373503835039350403504135042350433504435045350463504735048350493505035051350523505335054350553505635057350583505935060350613506235063350643506535066350673506835069350703507135072350733507435075350763507735078350793508035081350823508335084350853508635087350883508935090350913509235093350943509535096350973509835099351003510135102351033510435105351063510735108351093511035111351123511335114351153511635117351183511935120351213512235123351243512535126351273512835129351303513135132351333513435135351363513735138351393514035141351423514335144351453514635147351483514935150351513515235153351543515535156351573515835159351603516135162351633516435165351663516735168351693517035171351723517335174351753517635177351783517935180351813518235183351843518535186351873518835189351903519135192351933519435195351963519735198351993520035201352023520335204352053520635207352083520935210352113521235213352143521535216352173521835219352203522135222352233522435225352263522735228352293523035231352323523335234352353523635237352383523935240352413524235243352443524535246352473524835249352503525135252352533525435255352563525735258352593526035261352623526335264352653526635267352683526935270352713527235273352743527535276352773527835279352803528135282352833528435285352863528735288352893529035291352923529335294352953529635297352983529935300353013530235303353043530535306353073530835309353103531135312353133531435315353163531735318353193532035321353223532335324353253532635327353283532935330353313533235333353343533535336353373533835339353403534135342353433534435345353463534735348353493535035351353523535335354353553535635357353583535935360353613536235363353643536535366353673536835369353703537135372353733537435375353763537735378353793538035381353823538335384353853538635387353883538935390353913539235393353943539535396353973539835399354003540135402354033540435405354063540735408354093541035411354123541335414354153541635417354183541935420354213542235423354243542535426354273542835429354303543135432354333543435435354363543735438354393544035441354423544335444354453544635447354483544935450354513545235453354543545535456354573545835459354603546135462354633546435465354663546735468354693547035471354723547335474354753547635477354783547935480354813548235483354843548535486354873548835489354903549135492354933549435495354963549735498354993550035501355023550335504355053550635507355083550935510355113551235513355143551535516355173551835519355203552135522355233552435525355263552735528355293553035531355323553335534355353553635537355383553935540355413554235543355443554535546355473554835549355503555135552355533555435555355563555735558355593556035561355623556335564355653556635567355683556935570355713557235573355743557535576355773557835579355803558135582355833558435585355863558735588355893559035591355923559335594355953559635597355983559935600356013560235603356043560535606356073560835609356103561135612356133561435615356163561735618356193562035621356223562335624356253562635627356283562935630356313563235633356343563535636356373563835639356403564135642356433564435645356463564735648356493565035651356523565335654356553565635657356583565935660356613566235663356643566535666356673566835669356703567135672356733567435675356763567735678356793568035681356823568335684356853568635687356883568935690356913569235693356943569535696356973569835699357003570135702357033570435705357063570735708357093571035711357123571335714357153571635717357183571935720357213572235723357243572535726357273572835729357303573135732357333573435735357363573735738357393574035741357423574335744357453574635747357483574935750357513575235753357543575535756357573575835759357603576135762357633576435765357663576735768357693577035771357723577335774357753577635777357783577935780357813578235783357843578535786357873578835789357903579135792357933579435795357963579735798357993580035801358023580335804358053580635807358083580935810358113581235813358143581535816358173581835819358203582135822358233582435825358263582735828358293583035831358323583335834358353583635837358383583935840358413584235843358443584535846358473584835849358503585135852358533585435855358563585735858358593586035861358623586335864358653586635867358683586935870358713587235873358743587535876358773587835879358803588135882358833588435885358863588735888358893589035891358923589335894358953589635897358983589935900359013590235903359043590535906359073590835909359103591135912359133591435915359163591735918359193592035921359223592335924359253592635927359283592935930359313593235933359343593535936359373593835939359403594135942359433594435945359463594735948359493595035951359523595335954359553595635957359583595935960359613596235963359643596535966359673596835969359703597135972359733597435975359763597735978359793598035981359823598335984359853598635987359883598935990359913599235993359943599535996359973599835999360003600136002360033600436005360063600736008360093601036011360123601336014360153601636017360183601936020360213602236023360243602536026360273602836029360303603136032360333603436035360363603736038360393604036041360423604336044360453604636047360483604936050360513605236053360543605536056360573605836059360603606136062360633606436065360663606736068360693607036071360723607336074360753607636077360783607936080360813608236083360843608536086360873608836089360903609136092360933609436095360963609736098360993610036101361023610336104361053610636107361083610936110361113611236113361143611536116361173611836119361203612136122361233612436125361263612736128361293613036131361323613336134361353613636137361383613936140361413614236143361443614536146361473614836149361503615136152361533615436155361563615736158361593616036161361623616336164361653616636167361683616936170361713617236173361743617536176361773617836179361803618136182361833618436185361863618736188361893619036191361923619336194361953619636197361983619936200362013620236203362043620536206362073620836209362103621136212362133621436215362163621736218362193622036221362223622336224362253622636227362283622936230362313623236233362343623536236362373623836239362403624136242362433624436245362463624736248362493625036251362523625336254362553625636257362583625936260362613626236263362643626536266362673626836269362703627136272362733627436275362763627736278362793628036281362823628336284362853628636287362883628936290362913629236293362943629536296362973629836299363003630136302363033630436305363063630736308363093631036311363123631336314363153631636317363183631936320363213632236323363243632536326363273632836329363303633136332363333633436335363363633736338363393634036341363423634336344363453634636347363483634936350363513635236353363543635536356363573635836359363603636136362363633636436365363663636736368363693637036371363723637336374363753637636377363783637936380363813638236383363843638536386363873638836389363903639136392363933639436395363963639736398363993640036401364023640336404364053640636407364083640936410364113641236413364143641536416364173641836419364203642136422364233642436425364263642736428364293643036431364323643336434364353643636437364383643936440364413644236443364443644536446364473644836449364503645136452364533645436455364563645736458364593646036461364623646336464364653646636467364683646936470364713647236473364743647536476364773647836479364803648136482364833648436485364863648736488364893649036491364923649336494364953649636497364983649936500365013650236503365043650536506365073650836509365103651136512365133651436515365163651736518365193652036521365223652336524365253652636527365283652936530365313653236533365343653536536365373653836539365403654136542365433654436545365463654736548365493655036551365523655336554365553655636557365583655936560365613656236563365643656536566365673656836569365703657136572365733657436575365763657736578365793658036581365823658336584365853658636587365883658936590365913659236593365943659536596365973659836599366003660136602366033660436605366063660736608366093661036611366123661336614366153661636617366183661936620366213662236623366243662536626366273662836629366303663136632366333663436635366363663736638366393664036641366423664336644366453664636647366483664936650366513665236653366543665536656366573665836659366603666136662366633666436665366663666736668366693667036671366723667336674366753667636677366783667936680366813668236683366843668536686366873668836689366903669136692366933669436695366963669736698366993670036701367023670336704367053670636707367083670936710367113671236713367143671536716367173671836719367203672136722367233672436725367263672736728367293673036731367323673336734367353673636737367383673936740367413674236743367443674536746367473674836749367503675136752367533675436755367563675736758367593676036761367623676336764367653676636767367683676936770367713677236773367743677536776367773677836779367803678136782367833678436785367863678736788367893679036791367923679336794367953679636797367983679936800368013680236803368043680536806368073680836809368103681136812368133681436815368163681736818368193682036821368223682336824368253682636827368283682936830368313683236833368343683536836368373683836839368403684136842368433684436845368463684736848368493685036851368523685336854368553685636857368583685936860368613686236863368643686536866368673686836869368703687136872368733687436875368763687736878368793688036881368823688336884368853688636887368883688936890368913689236893368943689536896368973689836899369003690136902369033690436905369063690736908369093691036911369123691336914369153691636917369183691936920369213692236923369243692536926369273692836929369303693136932369333693436935369363693736938369393694036941369423694336944369453694636947369483694936950369513695236953369543695536956369573695836959369603696136962369633696436965369663696736968369693697036971369723697336974369753697636977369783697936980369813698236983369843698536986369873698836989369903699136992369933699436995369963699736998369993700037001370023700337004370053700637007370083700937010370113701237013370143701537016370173701837019370203702137022370233702437025370263702737028370293703037031370323703337034370353703637037370383703937040370413704237043370443704537046370473704837049370503705137052370533705437055370563705737058370593706037061370623706337064370653706637067370683706937070370713707237073370743707537076370773707837079370803708137082370833708437085370863708737088370893709037091370923709337094370953709637097370983709937100371013710237103371043710537106371073710837109371103711137112371133711437115371163711737118371193712037121371223712337124371253712637127371283712937130371313713237133371343713537136371373713837139371403714137142371433714437145371463714737148371493715037151371523715337154371553715637157371583715937160371613716237163371643716537166371673716837169371703717137172371733717437175371763717737178371793718037181371823718337184371853718637187371883718937190371913719237193371943719537196371973719837199372003720137202372033720437205372063720737208372093721037211372123721337214372153721637217372183721937220372213722237223372243722537226372273722837229372303723137232372333723437235372363723737238372393724037241372423724337244372453724637247372483724937250372513725237253372543725537256372573725837259372603726137262372633726437265372663726737268372693727037271372723727337274372753727637277372783727937280372813728237283372843728537286372873728837289372903729137292372933729437295372963729737298372993730037301373023730337304373053730637307373083730937310373113731237313373143731537316373173731837319373203732137322373233732437325373263732737328373293733037331373323733337334373353733637337373383733937340373413734237343373443734537346373473734837349373503735137352373533735437355373563735737358373593736037361373623736337364373653736637367373683736937370373713737237373373743737537376373773737837379373803738137382373833738437385373863738737388373893739037391373923739337394373953739637397373983739937400374013740237403374043740537406374073740837409374103741137412374133741437415374163741737418374193742037421374223742337424374253742637427374283742937430374313743237433374343743537436374373743837439374403744137442374433744437445374463744737448374493745037451374523745337454374553745637457374583745937460374613746237463374643746537466374673746837469374703747137472374733747437475374763747737478374793748037481374823748337484374853748637487374883748937490374913749237493374943749537496374973749837499375003750137502375033750437505375063750737508375093751037511375123751337514375153751637517375183751937520375213752237523375243752537526375273752837529375303753137532375333753437535375363753737538375393754037541375423754337544375453754637547375483754937550375513755237553375543755537556375573755837559375603756137562375633756437565375663756737568375693757037571375723757337574375753757637577375783757937580375813758237583375843758537586375873758837589375903759137592375933759437595375963759737598375993760037601376023760337604376053760637607376083760937610376113761237613376143761537616376173761837619376203762137622376233762437625376263762737628376293763037631376323763337634376353763637637376383763937640376413764237643376443764537646376473764837649376503765137652376533765437655376563765737658376593766037661376623766337664376653766637667376683766937670376713767237673376743767537676376773767837679376803768137682376833768437685376863768737688376893769037691376923769337694376953769637697376983769937700377013770237703377043770537706377073770837709377103771137712377133771437715377163771737718377193772037721377223772337724377253772637727377283772937730377313773237733377343773537736377373773837739377403774137742377433774437745377463774737748377493775037751377523775337754377553775637757377583775937760377613776237763377643776537766377673776837769377703777137772377733777437775377763777737778377793778037781377823778337784377853778637787377883778937790377913779237793377943779537796377973779837799378003780137802378033780437805378063780737808378093781037811378123781337814378153781637817378183781937820378213782237823378243782537826378273782837829378303783137832378333783437835378363783737838378393784037841378423784337844378453784637847378483784937850378513785237853378543785537856378573785837859378603786137862378633786437865378663786737868378693787037871378723787337874378753787637877378783787937880378813788237883378843788537886378873788837889378903789137892378933789437895378963789737898378993790037901379023790337904379053790637907379083790937910379113791237913379143791537916379173791837919379203792137922379233792437925379263792737928379293793037931379323793337934379353793637937379383793937940379413794237943379443794537946379473794837949379503795137952379533795437955379563795737958379593796037961379623796337964379653796637967379683796937970379713797237973379743797537976379773797837979379803798137982379833798437985379863798737988379893799037991379923799337994379953799637997379983799938000380013800238003380043800538006380073800838009380103801138012380133801438015380163801738018380193802038021380223802338024380253802638027380283802938030380313803238033380343803538036380373803838039380403804138042380433804438045380463804738048380493805038051380523805338054380553805638057380583805938060380613806238063380643806538066380673806838069380703807138072380733807438075380763807738078380793808038081380823808338084380853808638087380883808938090380913809238093380943809538096380973809838099381003810138102381033810438105381063810738108381093811038111381123811338114381153811638117381183811938120381213812238123381243812538126381273812838129381303813138132381333813438135381363813738138381393814038141381423814338144381453814638147381483814938150381513815238153381543815538156381573815838159381603816138162381633816438165381663816738168381693817038171381723817338174381753817638177381783817938180381813818238183381843818538186381873818838189381903819138192381933819438195381963819738198381993820038201382023820338204382053820638207382083820938210382113821238213382143821538216382173821838219382203822138222382233822438225382263822738228382293823038231382323823338234382353823638237382383823938240382413824238243382443824538246382473824838249382503825138252382533825438255382563825738258382593826038261382623826338264382653826638267382683826938270382713827238273382743827538276382773827838279382803828138282382833828438285382863828738288382893829038291382923829338294382953829638297382983829938300383013830238303383043830538306383073830838309383103831138312383133831438315383163831738318383193832038321383223832338324383253832638327383283832938330383313833238333383343833538336383373833838339383403834138342383433834438345383463834738348383493835038351383523835338354383553835638357383583835938360383613836238363383643836538366383673836838369383703837138372383733837438375383763837738378383793838038381383823838338384383853838638387383883838938390383913839238393383943839538396383973839838399384003840138402384033840438405384063840738408384093841038411384123841338414384153841638417384183841938420384213842238423384243842538426384273842838429384303843138432384333843438435384363843738438384393844038441384423844338444384453844638447384483844938450384513845238453384543845538456384573845838459384603846138462384633846438465384663846738468384693847038471384723847338474384753847638477384783847938480384813848238483384843848538486384873848838489384903849138492384933849438495384963849738498384993850038501385023850338504385053850638507385083850938510385113851238513385143851538516385173851838519385203852138522385233852438525385263852738528385293853038531385323853338534385353853638537385383853938540385413854238543385443854538546385473854838549385503855138552385533855438555385563855738558385593856038561385623856338564385653856638567385683856938570385713857238573385743857538576385773857838579385803858138582385833858438585385863858738588385893859038591385923859338594385953859638597385983859938600386013860238603386043860538606386073860838609386103861138612386133861438615386163861738618386193862038621386223862338624386253862638627386283862938630386313863238633386343863538636386373863838639386403864138642386433864438645386463864738648386493865038651386523865338654386553865638657386583865938660386613866238663386643866538666386673866838669386703867138672386733867438675386763867738678386793868038681386823868338684386853868638687386883868938690386913869238693386943869538696386973869838699387003870138702387033870438705387063870738708387093871038711387123871338714387153871638717387183871938720387213872238723387243872538726387273872838729387303873138732387333873438735387363873738738387393874038741387423874338744387453874638747387483874938750387513875238753387543875538756387573875838759387603876138762387633876438765387663876738768387693877038771387723877338774387753877638777387783877938780387813878238783387843878538786387873878838789387903879138792387933879438795387963879738798387993880038801388023880338804388053880638807388083880938810388113881238813388143881538816388173881838819388203882138822388233882438825388263882738828388293883038831388323883338834388353883638837388383883938840388413884238843388443884538846388473884838849388503885138852388533885438855388563885738858388593886038861388623886338864388653886638867388683886938870388713887238873388743887538876388773887838879388803888138882388833888438885388863888738888388893889038891388923889338894388953889638897388983889938900389013890238903389043890538906389073890838909389103891138912389133891438915389163891738918389193892038921389223892338924389253892638927389283892938930389313893238933389343893538936389373893838939389403894138942389433894438945389463894738948389493895038951389523895338954389553895638957389583895938960389613896238963389643896538966389673896838969389703897138972389733897438975389763897738978389793898038981389823898338984389853898638987389883898938990389913899238993389943899538996389973899838999390003900139002390033900439005390063900739008390093901039011390123901339014390153901639017390183901939020390213902239023390243902539026390273902839029390303903139032390333903439035390363903739038390393904039041390423904339044390453904639047390483904939050390513905239053390543905539056390573905839059390603906139062390633906439065390663906739068390693907039071390723907339074390753907639077390783907939080390813908239083390843908539086390873908839089390903909139092390933909439095390963909739098390993910039101391023910339104391053910639107391083910939110391113911239113391143911539116391173911839119391203912139122391233912439125391263912739128391293913039131391323913339134391353913639137391383913939140391413914239143391443914539146391473914839149391503915139152391533915439155391563915739158391593916039161391623916339164391653916639167391683916939170391713917239173391743917539176391773917839179391803918139182391833918439185391863918739188391893919039191391923919339194391953919639197391983919939200392013920239203392043920539206392073920839209392103921139212392133921439215392163921739218392193922039221392223922339224392253922639227392283922939230392313923239233392343923539236392373923839239392403924139242392433924439245392463924739248392493925039251392523925339254392553925639257392583925939260392613926239263392643926539266392673926839269392703927139272392733927439275392763927739278392793928039281392823928339284392853928639287392883928939290392913929239293392943929539296392973929839299393003930139302393033930439305393063930739308393093931039311393123931339314393153931639317393183931939320393213932239323393243932539326393273932839329393303933139332393333933439335393363933739338393393934039341393423934339344393453934639347393483934939350393513935239353393543935539356393573935839359393603936139362393633936439365393663936739368393693937039371393723937339374393753937639377393783937939380393813938239383393843938539386393873938839389393903939139392393933939439395393963939739398393993940039401394023940339404394053940639407394083940939410394113941239413394143941539416394173941839419394203942139422394233942439425394263942739428394293943039431394323943339434394353943639437394383943939440394413944239443394443944539446394473944839449394503945139452394533945439455394563945739458394593946039461394623946339464394653946639467394683946939470394713947239473394743947539476394773947839479394803948139482394833948439485394863948739488394893949039491394923949339494394953949639497394983949939500395013950239503395043950539506395073950839509395103951139512395133951439515395163951739518395193952039521395223952339524395253952639527395283952939530395313953239533395343953539536395373953839539395403954139542395433954439545395463954739548395493955039551395523955339554395553955639557395583955939560395613956239563395643956539566395673956839569395703957139572395733957439575395763957739578395793958039581395823958339584395853958639587395883958939590395913959239593395943959539596395973959839599396003960139602396033960439605396063960739608396093961039611396123961339614396153961639617396183961939620396213962239623396243962539626396273962839629396303963139632396333963439635396363963739638396393964039641396423964339644396453964639647396483964939650396513965239653396543965539656396573965839659396603966139662396633966439665396663966739668396693967039671396723967339674396753967639677396783967939680396813968239683396843968539686396873968839689396903969139692396933969439695396963969739698396993970039701397023970339704397053970639707397083970939710397113971239713397143971539716397173971839719397203972139722397233972439725397263972739728397293973039731397323973339734397353973639737397383973939740397413974239743397443974539746397473974839749397503975139752397533975439755397563975739758397593976039761397623976339764397653976639767397683976939770397713977239773397743977539776397773977839779397803978139782397833978439785397863978739788397893979039791397923979339794397953979639797397983979939800398013980239803398043980539806398073980839809398103981139812398133981439815398163981739818398193982039821398223982339824398253982639827398283982939830398313983239833398343983539836398373983839839398403984139842398433984439845398463984739848398493985039851398523985339854398553985639857398583985939860398613986239863398643986539866398673986839869398703987139872398733987439875398763987739878398793988039881398823988339884398853988639887398883988939890398913989239893398943989539896398973989839899399003990139902399033990439905399063990739908399093991039911399123991339914399153991639917399183991939920399213992239923399243992539926399273992839929399303993139932399333993439935399363993739938399393994039941399423994339944399453994639947399483994939950399513995239953399543995539956399573995839959399603996139962399633996439965399663996739968399693997039971399723997339974399753997639977399783997939980399813998239983399843998539986399873998839989399903999139992399933999439995399963999739998399994000040001400024000340004400054000640007400084000940010400114001240013400144001540016400174001840019400204002140022400234002440025400264002740028400294003040031400324003340034400354003640037400384003940040400414004240043400444004540046400474004840049400504005140052400534005440055400564005740058400594006040061400624006340064400654006640067400684006940070400714007240073400744007540076400774007840079400804008140082400834008440085400864008740088400894009040091400924009340094400954009640097400984009940100401014010240103401044010540106401074010840109401104011140112401134011440115401164011740118401194012040121401224012340124401254012640127401284012940130401314013240133401344013540136401374013840139401404014140142401434014440145401464014740148401494015040151401524015340154401554015640157401584015940160401614016240163401644016540166401674016840169401704017140172401734017440175401764017740178401794018040181401824018340184401854018640187401884018940190401914019240193401944019540196401974019840199402004020140202402034020440205402064020740208402094021040211402124021340214402154021640217402184021940220402214022240223402244022540226402274022840229402304023140232402334023440235402364023740238402394024040241402424024340244402454024640247402484024940250402514025240253402544025540256402574025840259402604026140262402634026440265402664026740268402694027040271402724027340274402754027640277402784027940280402814028240283402844028540286402874028840289402904029140292402934029440295402964029740298402994030040301403024030340304403054030640307403084030940310403114031240313403144031540316403174031840319403204032140322403234032440325403264032740328403294033040331403324033340334403354033640337403384033940340403414034240343403444034540346403474034840349403504035140352403534035440355403564035740358403594036040361403624036340364403654036640367403684036940370403714037240373403744037540376403774037840379403804038140382403834038440385403864038740388403894039040391403924039340394403954039640397403984039940400404014040240403404044040540406404074040840409404104041140412404134041440415404164041740418404194042040421404224042340424404254042640427404284042940430404314043240433404344043540436404374043840439404404044140442404434044440445404464044740448404494045040451404524045340454404554045640457404584045940460404614046240463404644046540466404674046840469404704047140472404734047440475404764047740478404794048040481404824048340484404854048640487404884048940490404914049240493404944049540496404974049840499405004050140502405034050440505405064050740508405094051040511405124051340514405154051640517405184051940520405214052240523405244052540526405274052840529405304053140532405334053440535405364053740538405394054040541405424054340544405454054640547405484054940550405514055240553405544055540556405574055840559405604056140562405634056440565405664056740568405694057040571405724057340574405754057640577405784057940580405814058240583405844058540586405874058840589405904059140592405934059440595405964059740598405994060040601406024060340604406054060640607406084060940610406114061240613406144061540616406174061840619406204062140622406234062440625406264062740628406294063040631406324063340634406354063640637406384063940640406414064240643406444064540646406474064840649406504065140652406534065440655406564065740658406594066040661406624066340664406654066640667406684066940670406714067240673406744067540676406774067840679406804068140682406834068440685406864068740688406894069040691406924069340694406954069640697406984069940700407014070240703407044070540706407074070840709407104071140712407134071440715407164071740718407194072040721407224072340724407254072640727407284072940730407314073240733407344073540736407374073840739407404074140742407434074440745407464074740748407494075040751407524075340754407554075640757407584075940760407614076240763407644076540766407674076840769407704077140772407734077440775407764077740778407794078040781407824078340784407854078640787407884078940790407914079240793407944079540796407974079840799408004080140802408034080440805408064080740808408094081040811408124081340814408154081640817408184081940820408214082240823408244082540826408274082840829408304083140832408334083440835408364083740838408394084040841408424084340844408454084640847408484084940850408514085240853408544085540856408574085840859408604086140862408634086440865408664086740868408694087040871408724087340874408754087640877408784087940880408814088240883408844088540886408874088840889408904089140892408934089440895408964089740898408994090040901409024090340904409054090640907409084090940910409114091240913409144091540916409174091840919409204092140922409234092440925409264092740928409294093040931409324093340934409354093640937409384093940940409414094240943409444094540946409474094840949409504095140952409534095440955409564095740958409594096040961409624096340964409654096640967409684096940970409714097240973409744097540976409774097840979409804098140982409834098440985409864098740988409894099040991409924099340994409954099640997409984099941000410014100241003410044100541006410074100841009410104101141012410134101441015410164101741018410194102041021410224102341024410254102641027410284102941030410314103241033410344103541036410374103841039410404104141042410434104441045410464104741048410494105041051410524105341054410554105641057410584105941060410614106241063410644106541066410674106841069410704107141072410734107441075410764107741078410794108041081410824108341084410854108641087410884108941090410914109241093410944109541096410974109841099411004110141102411034110441105411064110741108411094111041111411124111341114411154111641117411184111941120411214112241123411244112541126411274112841129411304113141132411334113441135411364113741138411394114041141411424114341144411454114641147411484114941150411514115241153411544115541156411574115841159411604116141162411634116441165411664116741168411694117041171411724117341174411754117641177411784117941180411814118241183411844118541186411874118841189411904119141192411934119441195411964119741198411994120041201412024120341204412054120641207412084120941210412114121241213412144121541216412174121841219412204122141222412234122441225412264122741228412294123041231412324123341234412354123641237412384123941240412414124241243412444124541246412474124841249412504125141252412534125441255412564125741258412594126041261412624126341264412654126641267412684126941270412714127241273412744127541276412774127841279412804128141282412834128441285412864128741288412894129041291412924129341294412954129641297412984129941300413014130241303413044130541306413074130841309413104131141312413134131441315413164131741318413194132041321413224132341324413254132641327413284132941330413314133241333413344133541336413374133841339413404134141342413434134441345413464134741348413494135041351413524135341354413554135641357413584135941360413614136241363413644136541366413674136841369413704137141372413734137441375413764137741378413794138041381413824138341384413854138641387413884138941390413914139241393413944139541396413974139841399414004140141402414034140441405414064140741408414094141041411414124141341414414154141641417414184141941420414214142241423414244142541426414274142841429414304143141432414334143441435414364143741438414394144041441414424144341444414454144641447414484144941450414514145241453414544145541456414574145841459414604146141462414634146441465414664146741468414694147041471414724147341474414754147641477414784147941480414814148241483414844148541486414874148841489414904149141492414934149441495414964149741498414994150041501415024150341504415054150641507415084150941510415114151241513415144151541516415174151841519415204152141522415234152441525415264152741528415294153041531415324153341534415354153641537415384153941540415414154241543415444154541546415474154841549415504155141552415534155441555415564155741558415594156041561415624156341564415654156641567415684156941570415714157241573415744157541576415774157841579415804158141582415834158441585415864158741588415894159041591415924159341594415954159641597415984159941600416014160241603416044160541606416074160841609416104161141612416134161441615416164161741618416194162041621416224162341624416254162641627416284162941630416314163241633416344163541636416374163841639416404164141642416434164441645416464164741648416494165041651416524165341654416554165641657416584165941660416614166241663416644166541666416674166841669416704167141672416734167441675416764167741678416794168041681416824168341684416854168641687416884168941690416914169241693416944169541696416974169841699417004170141702417034170441705417064170741708417094171041711417124171341714417154171641717417184171941720417214172241723417244172541726417274172841729417304173141732417334173441735417364173741738417394174041741417424174341744417454174641747417484174941750417514175241753417544175541756417574175841759417604176141762417634176441765417664176741768417694177041771417724177341774417754177641777417784177941780417814178241783417844178541786417874178841789417904179141792417934179441795417964179741798417994180041801418024180341804418054180641807418084180941810418114181241813418144181541816418174181841819418204182141822418234182441825418264182741828418294183041831418324183341834418354183641837418384183941840418414184241843418444184541846418474184841849418504185141852418534185441855418564185741858418594186041861418624186341864418654186641867418684186941870418714187241873418744187541876418774187841879418804188141882418834188441885418864188741888418894189041891418924189341894418954189641897418984189941900419014190241903419044190541906419074190841909419104191141912419134191441915419164191741918419194192041921419224192341924419254192641927419284192941930419314193241933419344193541936419374193841939419404194141942419434194441945419464194741948419494195041951419524195341954419554195641957419584195941960419614196241963419644196541966419674196841969419704197141972419734197441975419764197741978419794198041981419824198341984419854198641987419884198941990419914199241993419944199541996419974199841999420004200142002420034200442005420064200742008420094201042011420124201342014420154201642017420184201942020420214202242023420244202542026420274202842029420304203142032420334203442035420364203742038420394204042041420424204342044420454204642047420484204942050420514205242053420544205542056420574205842059420604206142062420634206442065420664206742068420694207042071420724207342074420754207642077420784207942080420814208242083420844208542086420874208842089420904209142092420934209442095420964209742098420994210042101421024210342104421054210642107421084210942110421114211242113421144211542116421174211842119421204212142122421234212442125421264212742128421294213042131421324213342134421354213642137421384213942140421414214242143421444214542146421474214842149421504215142152421534215442155421564215742158421594216042161421624216342164421654216642167421684216942170421714217242173421744217542176421774217842179421804218142182421834218442185421864218742188421894219042191421924219342194421954219642197421984219942200422014220242203422044220542206422074220842209422104221142212422134221442215422164221742218422194222042221422224222342224422254222642227422284222942230422314223242233422344223542236422374223842239422404224142242422434224442245422464224742248422494225042251422524225342254422554225642257422584225942260422614226242263422644226542266422674226842269422704227142272422734227442275422764227742278422794228042281422824228342284422854228642287422884228942290422914229242293422944229542296422974229842299423004230142302423034230442305423064230742308423094231042311423124231342314423154231642317423184231942320423214232242323423244232542326423274232842329423304233142332423334233442335423364233742338423394234042341423424234342344423454234642347423484234942350423514235242353423544235542356423574235842359423604236142362423634236442365423664236742368423694237042371423724237342374423754237642377423784237942380423814238242383423844238542386423874238842389423904239142392423934239442395423964239742398423994240042401424024240342404424054240642407424084240942410424114241242413424144241542416424174241842419424204242142422424234242442425424264242742428424294243042431424324243342434424354243642437424384243942440424414244242443424444244542446424474244842449424504245142452424534245442455424564245742458424594246042461424624246342464424654246642467424684246942470424714247242473424744247542476424774247842479424804248142482424834248442485424864248742488424894249042491424924249342494424954249642497424984249942500425014250242503425044250542506425074250842509425104251142512425134251442515425164251742518425194252042521425224252342524425254252642527425284252942530425314253242533425344253542536425374253842539425404254142542425434254442545425464254742548425494255042551425524255342554425554255642557425584255942560425614256242563425644256542566425674256842569425704257142572425734257442575425764257742578425794258042581425824258342584425854258642587425884258942590425914259242593425944259542596425974259842599426004260142602426034260442605426064260742608426094261042611426124261342614426154261642617426184261942620426214262242623426244262542626426274262842629426304263142632426334263442635426364263742638426394264042641426424264342644426454264642647426484264942650426514265242653426544265542656426574265842659426604266142662426634266442665426664266742668426694267042671426724267342674426754267642677426784267942680426814268242683426844268542686426874268842689426904269142692426934269442695426964269742698426994270042701427024270342704427054270642707427084270942710427114271242713427144271542716427174271842719427204272142722427234272442725427264272742728427294273042731427324273342734427354273642737427384273942740427414274242743427444274542746427474274842749427504275142752427534275442755427564275742758427594276042761427624276342764427654276642767427684276942770427714277242773427744277542776427774277842779427804278142782427834278442785427864278742788427894279042791427924279342794427954279642797427984279942800428014280242803428044280542806428074280842809428104281142812428134281442815428164281742818428194282042821428224282342824428254282642827428284282942830428314283242833428344283542836428374283842839428404284142842428434284442845428464284742848428494285042851428524285342854428554285642857428584285942860428614286242863428644286542866428674286842869428704287142872428734287442875428764287742878428794288042881428824288342884428854288642887428884288942890428914289242893428944289542896428974289842899429004290142902429034290442905429064290742908429094291042911429124291342914429154291642917429184291942920429214292242923429244292542926429274292842929429304293142932429334293442935429364293742938429394294042941429424294342944429454294642947429484294942950429514295242953429544295542956429574295842959429604296142962429634296442965429664296742968429694297042971429724297342974429754297642977429784297942980429814298242983429844298542986429874298842989429904299142992429934299442995429964299742998429994300043001430024300343004430054300643007430084300943010430114301243013430144301543016430174301843019430204302143022430234302443025430264302743028430294303043031430324303343034430354303643037430384303943040430414304243043430444304543046430474304843049430504305143052430534305443055430564305743058430594306043061430624306343064430654306643067430684306943070430714307243073430744307543076430774307843079430804308143082430834308443085430864308743088430894309043091430924309343094430954309643097430984309943100431014310243103431044310543106431074310843109431104311143112431134311443115431164311743118431194312043121431224312343124431254312643127431284312943130431314313243133431344313543136431374313843139431404314143142431434314443145431464314743148431494315043151431524315343154431554315643157431584315943160431614316243163431644316543166431674316843169431704317143172431734317443175431764317743178431794318043181431824318343184431854318643187431884318943190431914319243193431944319543196431974319843199432004320143202432034320443205432064320743208432094321043211432124321343214432154321643217432184321943220432214322243223432244322543226432274322843229432304323143232432334323443235432364323743238432394324043241432424324343244432454324643247432484324943250432514325243253432544325543256432574325843259432604326143262432634326443265432664326743268432694327043271432724327343274432754327643277432784327943280432814328243283432844328543286432874328843289432904329143292432934329443295432964329743298432994330043301433024330343304433054330643307433084330943310433114331243313433144331543316433174331843319433204332143322433234332443325433264332743328433294333043331433324333343334433354333643337433384333943340433414334243343433444334543346433474334843349433504335143352433534335443355433564335743358433594336043361433624336343364433654336643367433684336943370433714337243373433744337543376433774337843379433804338143382433834338443385433864338743388433894339043391433924339343394433954339643397433984339943400434014340243403434044340543406434074340843409434104341143412434134341443415434164341743418434194342043421434224342343424434254342643427434284342943430434314343243433434344343543436434374343843439434404344143442434434344443445434464344743448434494345043451434524345343454434554345643457434584345943460434614346243463434644346543466434674346843469434704347143472434734347443475434764347743478434794348043481434824348343484434854348643487434884348943490434914349243493434944349543496434974349843499435004350143502435034350443505435064350743508435094351043511435124351343514435154351643517435184351943520435214352243523435244352543526435274352843529435304353143532435334353443535435364353743538435394354043541435424354343544435454354643547435484354943550435514355243553435544355543556435574355843559435604356143562435634356443565435664356743568435694357043571435724357343574435754357643577435784357943580435814358243583435844358543586435874358843589435904359143592435934359443595435964359743598435994360043601436024360343604436054360643607436084360943610436114361243613436144361543616436174361843619436204362143622436234362443625436264362743628436294363043631436324363343634436354363643637436384363943640436414364243643436444364543646436474364843649436504365143652436534365443655436564365743658436594366043661436624366343664436654366643667436684366943670436714367243673436744367543676436774367843679436804368143682436834368443685436864368743688436894369043691436924369343694436954369643697436984369943700437014370243703437044370543706437074370843709437104371143712437134371443715437164371743718437194372043721437224372343724437254372643727437284372943730437314373243733437344373543736437374373843739437404374143742437434374443745437464374743748437494375043751437524375343754437554375643757437584375943760437614376243763437644376543766437674376843769437704377143772437734377443775437764377743778437794378043781437824378343784437854378643787437884378943790437914379243793437944379543796437974379843799438004380143802438034380443805438064380743808438094381043811438124381343814438154381643817438184381943820438214382243823438244382543826438274382843829438304383143832438334383443835438364383743838438394384043841438424384343844438454384643847438484384943850438514385243853438544385543856438574385843859438604386143862438634386443865438664386743868438694387043871438724387343874438754387643877438784387943880438814388243883438844388543886438874388843889438904389143892438934389443895438964389743898438994390043901439024390343904439054390643907439084390943910439114391243913439144391543916439174391843919439204392143922439234392443925439264392743928439294393043931439324393343934439354393643937439384393943940439414394243943439444394543946439474394843949439504395143952439534395443955439564395743958439594396043961439624396343964439654396643967439684396943970439714397243973439744397543976439774397843979439804398143982439834398443985439864398743988439894399043991439924399343994439954399643997439984399944000440014400244003440044400544006440074400844009440104401144012440134401444015440164401744018440194402044021440224402344024440254402644027440284402944030440314403244033440344403544036440374403844039440404404144042440434404444045440464404744048440494405044051440524405344054440554405644057440584405944060440614406244063440644406544066440674406844069440704407144072440734407444075440764407744078440794408044081440824408344084440854408644087440884408944090440914409244093440944409544096440974409844099441004410144102441034410444105441064410744108441094411044111441124411344114441154411644117441184411944120441214412244123441244412544126441274412844129441304413144132441334413444135441364413744138441394414044141441424414344144441454414644147441484414944150441514415244153441544415544156441574415844159441604416144162441634416444165441664416744168441694417044171441724417344174441754417644177441784417944180441814418244183441844418544186441874418844189441904419144192441934419444195441964419744198441994420044201442024420344204442054420644207442084420944210442114421244213442144421544216442174421844219442204422144222442234422444225442264422744228442294423044231442324423344234442354423644237442384423944240442414424244243442444424544246442474424844249442504425144252442534425444255442564425744258442594426044261442624426344264442654426644267442684426944270442714427244273442744427544276442774427844279442804428144282442834428444285442864428744288442894429044291442924429344294442954429644297442984429944300443014430244303443044430544306443074430844309443104431144312443134431444315443164431744318443194432044321443224432344324443254432644327443284432944330443314433244333443344433544336443374433844339443404434144342443434434444345443464434744348443494435044351443524435344354443554435644357443584435944360443614436244363443644436544366443674436844369443704437144372443734437444375443764437744378443794438044381443824438344384443854438644387443884438944390443914439244393443944439544396443974439844399444004440144402444034440444405444064440744408444094441044411444124441344414444154441644417444184441944420444214442244423444244442544426444274442844429444304443144432444334443444435444364443744438444394444044441444424444344444444454444644447444484444944450444514445244453444544445544456444574445844459444604446144462444634446444465444664446744468444694447044471444724447344474444754447644477444784447944480444814448244483444844448544486444874448844489444904449144492444934449444495444964449744498444994450044501445024450344504445054450644507445084450944510445114451244513445144451544516445174451844519445204452144522445234452444525445264452744528445294453044531445324453344534445354453644537445384453944540445414454244543445444454544546445474454844549445504455144552445534455444555445564455744558445594456044561445624456344564445654456644567445684456944570445714457244573445744457544576445774457844579445804458144582445834458444585445864458744588445894459044591445924459344594445954459644597445984459944600446014460244603446044460544606446074460844609446104461144612446134461444615446164461744618446194462044621446224462344624446254462644627446284462944630446314463244633446344463544636446374463844639446404464144642446434464444645446464464744648446494465044651446524465344654446554465644657446584465944660446614466244663446644466544666446674466844669446704467144672446734467444675446764467744678446794468044681446824468344684446854468644687446884468944690446914469244693446944469544696446974469844699447004470144702447034470444705447064470744708447094471044711447124471344714447154471644717447184471944720447214472244723447244472544726447274472844729447304473144732447334473444735447364473744738447394474044741447424474344744447454474644747447484474944750447514475244753447544475544756447574475844759447604476144762447634476444765447664476744768447694477044771447724477344774447754477644777447784477944780447814478244783447844478544786447874478844789447904479144792447934479444795447964479744798447994480044801448024480344804448054480644807448084480944810448114481244813448144481544816448174481844819448204482144822448234482444825448264482744828448294483044831448324483344834448354483644837448384483944840448414484244843448444484544846448474484844849448504485144852448534485444855448564485744858448594486044861448624486344864448654486644867448684486944870448714487244873448744487544876448774487844879448804488144882448834488444885448864488744888448894489044891448924489344894448954489644897448984489944900449014490244903449044490544906449074490844909449104491144912449134491444915449164491744918449194492044921449224492344924449254492644927449284492944930449314493244933449344493544936449374493844939449404494144942449434494444945449464494744948449494495044951449524495344954449554495644957449584495944960449614496244963449644496544966449674496844969449704497144972449734497444975449764497744978449794498044981449824498344984449854498644987449884498944990449914499244993449944499544996449974499844999450004500145002450034500445005450064500745008450094501045011450124501345014450154501645017450184501945020450214502245023450244502545026450274502845029450304503145032450334503445035450364503745038450394504045041450424504345044450454504645047450484504945050450514505245053450544505545056450574505845059450604506145062450634506445065450664506745068450694507045071450724507345074450754507645077450784507945080450814508245083450844508545086450874508845089450904509145092450934509445095450964509745098450994510045101451024510345104451054510645107451084510945110451114511245113451144511545116451174511845119451204512145122451234512445125451264512745128451294513045131451324513345134451354513645137451384513945140451414514245143451444514545146451474514845149451504515145152451534515445155451564515745158451594516045161451624516345164451654516645167451684516945170451714517245173451744517545176451774517845179451804518145182451834518445185451864518745188451894519045191451924519345194451954519645197451984519945200452014520245203452044520545206452074520845209452104521145212452134521445215452164521745218452194522045221452224522345224452254522645227452284522945230452314523245233452344523545236452374523845239452404524145242452434524445245452464524745248452494525045251452524525345254452554525645257452584525945260452614526245263452644526545266452674526845269452704527145272452734527445275452764527745278452794528045281452824528345284452854528645287452884528945290452914529245293452944529545296452974529845299453004530145302453034530445305453064530745308453094531045311453124531345314453154531645317453184531945320453214532245323453244532545326453274532845329453304533145332453334533445335453364533745338453394534045341453424534345344453454534645347453484534945350453514535245353453544535545356453574535845359453604536145362453634536445365453664536745368453694537045371453724537345374453754537645377453784537945380453814538245383453844538545386453874538845389453904539145392453934539445395453964539745398453994540045401454024540345404454054540645407454084540945410454114541245413454144541545416454174541845419454204542145422454234542445425454264542745428454294543045431454324543345434454354543645437454384543945440454414544245443454444544545446454474544845449454504545145452454534545445455454564545745458454594546045461454624546345464454654546645467454684546945470454714547245473454744547545476454774547845479454804548145482454834548445485454864548745488454894549045491454924549345494454954549645497454984549945500455014550245503455044550545506455074550845509455104551145512455134551445515455164551745518455194552045521455224552345524455254552645527455284552945530455314553245533455344553545536455374553845539455404554145542455434554445545455464554745548455494555045551455524555345554455554555645557455584555945560455614556245563455644556545566455674556845569455704557145572455734557445575455764557745578455794558045581455824558345584455854558645587455884558945590455914559245593455944559545596455974559845599456004560145602456034560445605456064560745608456094561045611456124561345614456154561645617456184561945620456214562245623456244562545626456274562845629456304563145632456334563445635456364563745638456394564045641456424564345644456454564645647456484564945650456514565245653456544565545656456574565845659456604566145662456634566445665456664566745668456694567045671456724567345674456754567645677456784567945680456814568245683456844568545686456874568845689456904569145692456934569445695456964569745698456994570045701457024570345704457054570645707457084570945710457114571245713457144571545716457174571845719457204572145722457234572445725457264572745728457294573045731457324573345734457354573645737457384573945740457414574245743457444574545746457474574845749457504575145752457534575445755457564575745758457594576045761457624576345764457654576645767457684576945770457714577245773457744577545776457774577845779457804578145782457834578445785457864578745788457894579045791457924579345794457954579645797457984579945800458014580245803458044580545806458074580845809458104581145812458134581445815458164581745818458194582045821458224582345824458254582645827458284582945830458314583245833458344583545836458374583845839458404584145842458434584445845458464584745848458494585045851458524585345854458554585645857458584585945860458614586245863458644586545866458674586845869458704587145872458734587445875458764587745878458794588045881458824588345884458854588645887458884588945890458914589245893458944589545896458974589845899459004590145902459034590445905459064590745908459094591045911459124591345914459154591645917459184591945920459214592245923459244592545926459274592845929459304593145932459334593445935459364593745938459394594045941459424594345944459454594645947459484594945950459514595245953459544595545956459574595845959459604596145962459634596445965459664596745968459694597045971459724597345974459754597645977459784597945980459814598245983459844598545986459874598845989459904599145992459934599445995459964599745998459994600046001460024600346004460054600646007460084600946010460114601246013460144601546016460174601846019460204602146022460234602446025460264602746028460294603046031460324603346034460354603646037460384603946040460414604246043460444604546046460474604846049460504605146052460534605446055460564605746058460594606046061460624606346064460654606646067460684606946070460714607246073460744607546076460774607846079460804608146082460834608446085460864608746088460894609046091460924609346094460954609646097460984609946100461014610246103461044610546106461074610846109461104611146112461134611446115461164611746118461194612046121461224612346124461254612646127461284612946130461314613246133461344613546136461374613846139461404614146142461434614446145461464614746148461494615046151461524615346154461554615646157461584615946160461614616246163461644616546166461674616846169461704617146172461734617446175461764617746178461794618046181461824618346184461854618646187461884618946190461914619246193461944619546196461974619846199462004620146202462034620446205462064620746208462094621046211462124621346214462154621646217462184621946220462214622246223462244622546226462274622846229462304623146232462334623446235462364623746238462394624046241462424624346244462454624646247462484624946250462514625246253462544625546256462574625846259462604626146262462634626446265462664626746268462694627046271462724627346274462754627646277462784627946280462814628246283462844628546286462874628846289462904629146292462934629446295462964629746298462994630046301463024630346304463054630646307463084630946310463114631246313463144631546316463174631846319463204632146322463234632446325463264632746328463294633046331463324633346334463354633646337463384633946340463414634246343463444634546346463474634846349463504635146352463534635446355463564635746358463594636046361463624636346364463654636646367463684636946370463714637246373463744637546376463774637846379463804638146382463834638446385463864638746388463894639046391463924639346394463954639646397463984639946400464014640246403464044640546406464074640846409464104641146412464134641446415464164641746418464194642046421464224642346424464254642646427464284642946430464314643246433464344643546436464374643846439464404644146442464434644446445464464644746448464494645046451464524645346454464554645646457464584645946460464614646246463464644646546466464674646846469464704647146472464734647446475464764647746478464794648046481464824648346484464854648646487464884648946490464914649246493464944649546496464974649846499465004650146502465034650446505465064650746508465094651046511465124651346514465154651646517465184651946520465214652246523465244652546526465274652846529465304653146532465334653446535465364653746538465394654046541465424654346544465454654646547465484654946550465514655246553465544655546556465574655846559465604656146562465634656446565465664656746568465694657046571465724657346574465754657646577465784657946580465814658246583465844658546586465874658846589465904659146592465934659446595465964659746598465994660046601466024660346604466054660646607466084660946610466114661246613466144661546616466174661846619466204662146622466234662446625466264662746628466294663046631466324663346634466354663646637466384663946640466414664246643466444664546646466474664846649466504665146652466534665446655466564665746658466594666046661466624666346664466654666646667466684666946670466714667246673466744667546676466774667846679466804668146682466834668446685466864668746688466894669046691466924669346694466954669646697466984669946700467014670246703467044670546706467074670846709467104671146712467134671446715467164671746718467194672046721467224672346724467254672646727467284672946730467314673246733467344673546736467374673846739467404674146742467434674446745467464674746748467494675046751467524675346754467554675646757467584675946760467614676246763467644676546766467674676846769467704677146772467734677446775467764677746778467794678046781467824678346784467854678646787467884678946790467914679246793467944679546796467974679846799468004680146802468034680446805468064680746808468094681046811468124681346814468154681646817468184681946820468214682246823468244682546826468274682846829468304683146832468334683446835468364683746838468394684046841468424684346844468454684646847468484684946850468514685246853468544685546856468574685846859468604686146862468634686446865468664686746868468694687046871468724687346874468754687646877468784687946880468814688246883468844688546886468874688846889468904689146892468934689446895468964689746898468994690046901469024690346904469054690646907469084690946910469114691246913469144691546916469174691846919469204692146922469234692446925469264692746928469294693046931469324693346934469354693646937469384693946940469414694246943469444694546946469474694846949469504695146952469534695446955469564695746958469594696046961469624696346964469654696646967469684696946970469714697246973469744697546976469774697846979469804698146982469834698446985469864698746988469894699046991469924699346994469954699646997469984699947000470014700247003470044700547006470074700847009470104701147012470134701447015470164701747018470194702047021470224702347024470254702647027470284702947030470314703247033470344703547036470374703847039470404704147042470434704447045470464704747048470494705047051470524705347054470554705647057470584705947060470614706247063470644706547066470674706847069470704707147072470734707447075470764707747078470794708047081470824708347084470854708647087470884708947090470914709247093470944709547096470974709847099471004710147102471034710447105471064710747108471094711047111471124711347114471154711647117471184711947120471214712247123471244712547126471274712847129471304713147132471334713447135471364713747138471394714047141471424714347144471454714647147471484714947150471514715247153471544715547156471574715847159471604716147162471634716447165471664716747168471694717047171471724717347174471754717647177471784717947180471814718247183471844718547186471874718847189471904719147192471934719447195471964719747198471994720047201472024720347204472054720647207472084720947210472114721247213472144721547216472174721847219472204722147222472234722447225472264722747228472294723047231472324723347234472354723647237472384723947240472414724247243472444724547246472474724847249472504725147252472534725447255472564725747258472594726047261472624726347264472654726647267472684726947270472714727247273472744727547276472774727847279472804728147282472834728447285472864728747288472894729047291472924729347294472954729647297472984729947300473014730247303473044730547306473074730847309473104731147312473134731447315473164731747318473194732047321473224732347324473254732647327473284732947330473314733247333473344733547336473374733847339473404734147342473434734447345473464734747348473494735047351473524735347354473554735647357473584735947360473614736247363473644736547366473674736847369473704737147372473734737447375473764737747378473794738047381473824738347384473854738647387473884738947390473914739247393473944739547396473974739847399474004740147402474034740447405474064740747408474094741047411474124741347414474154741647417474184741947420474214742247423474244742547426474274742847429474304743147432474334743447435474364743747438474394744047441474424744347444474454744647447474484744947450474514745247453474544745547456474574745847459474604746147462474634746447465474664746747468474694747047471474724747347474474754747647477474784747947480474814748247483474844748547486474874748847489474904749147492474934749447495474964749747498474994750047501475024750347504475054750647507475084750947510475114751247513475144751547516475174751847519475204752147522475234752447525475264752747528475294753047531475324753347534475354753647537475384753947540475414754247543475444754547546475474754847549475504755147552475534755447555475564755747558475594756047561475624756347564475654756647567475684756947570475714757247573475744757547576475774757847579475804758147582475834758447585475864758747588475894759047591475924759347594475954759647597475984759947600476014760247603476044760547606476074760847609476104761147612476134761447615476164761747618476194762047621476224762347624476254762647627476284762947630476314763247633476344763547636476374763847639476404764147642476434764447645476464764747648476494765047651476524765347654476554765647657476584765947660476614766247663476644766547666476674766847669476704767147672476734767447675476764767747678476794768047681476824768347684476854768647687476884768947690476914769247693476944769547696476974769847699477004770147702477034770447705477064770747708477094771047711477124771347714477154771647717477184771947720477214772247723477244772547726477274772847729477304773147732477334773447735477364773747738477394774047741477424774347744477454774647747477484774947750477514775247753477544775547756477574775847759477604776147762477634776447765477664776747768477694777047771477724777347774477754777647777477784777947780477814778247783477844778547786477874778847789477904779147792477934779447795477964779747798477994780047801478024780347804478054780647807478084780947810478114781247813478144781547816478174781847819478204782147822478234782447825478264782747828478294783047831478324783347834478354783647837478384783947840478414784247843478444784547846478474784847849478504785147852478534785447855478564785747858478594786047861478624786347864478654786647867478684786947870478714787247873478744787547876478774787847879478804788147882478834788447885478864788747888478894789047891478924789347894478954789647897478984789947900479014790247903479044790547906479074790847909479104791147912479134791447915479164791747918479194792047921479224792347924479254792647927479284792947930479314793247933479344793547936479374793847939479404794147942479434794447945479464794747948479494795047951479524795347954479554795647957479584795947960479614796247963479644796547966479674796847969479704797147972479734797447975479764797747978479794798047981479824798347984479854798647987479884798947990479914799247993479944799547996479974799847999480004800148002480034800448005480064800748008480094801048011480124801348014480154801648017480184801948020480214802248023480244802548026480274802848029480304803148032480334803448035480364803748038480394804048041480424804348044480454804648047480484804948050480514805248053480544805548056480574805848059480604806148062480634806448065480664806748068480694807048071480724807348074480754807648077480784807948080480814808248083480844808548086480874808848089480904809148092480934809448095480964809748098480994810048101481024810348104481054810648107481084810948110481114811248113481144811548116481174811848119481204812148122481234812448125481264812748128481294813048131481324813348134481354813648137481384813948140481414814248143481444814548146481474814848149481504815148152481534815448155481564815748158481594816048161481624816348164481654816648167481684816948170481714817248173481744817548176481774817848179481804818148182481834818448185481864818748188481894819048191481924819348194481954819648197481984819948200482014820248203482044820548206482074820848209482104821148212482134821448215482164821748218482194822048221482224822348224482254822648227482284822948230482314823248233482344823548236482374823848239482404824148242482434824448245482464824748248482494825048251482524825348254482554825648257482584825948260482614826248263482644826548266482674826848269482704827148272482734827448275482764827748278482794828048281482824828348284482854828648287482884828948290482914829248293482944829548296482974829848299483004830148302483034830448305483064830748308483094831048311483124831348314483154831648317483184831948320483214832248323483244832548326483274832848329483304833148332483334833448335483364833748338483394834048341483424834348344483454834648347483484834948350483514835248353483544835548356483574835848359483604836148362483634836448365483664836748368483694837048371483724837348374483754837648377483784837948380483814838248383483844838548386483874838848389483904839148392483934839448395483964839748398483994840048401484024840348404484054840648407484084840948410484114841248413484144841548416484174841848419484204842148422484234842448425484264842748428484294843048431484324843348434484354843648437484384843948440484414844248443484444844548446484474844848449484504845148452484534845448455484564845748458484594846048461484624846348464484654846648467484684846948470484714847248473484744847548476484774847848479484804848148482484834848448485484864848748488484894849048491484924849348494484954849648497484984849948500485014850248503485044850548506485074850848509485104851148512485134851448515485164851748518485194852048521485224852348524485254852648527485284852948530485314853248533485344853548536485374853848539485404854148542485434854448545485464854748548485494855048551485524855348554485554855648557485584855948560485614856248563485644856548566485674856848569485704857148572485734857448575485764857748578485794858048581485824858348584485854858648587485884858948590485914859248593485944859548596485974859848599486004860148602486034860448605486064860748608486094861048611486124861348614486154861648617486184861948620486214862248623486244862548626486274862848629486304863148632486334863448635486364863748638486394864048641486424864348644486454864648647486484864948650486514865248653486544865548656486574865848659486604866148662486634866448665486664866748668486694867048671486724867348674486754867648677486784867948680486814868248683486844868548686486874868848689486904869148692486934869448695486964869748698486994870048701487024870348704487054870648707487084870948710487114871248713487144871548716487174871848719487204872148722487234872448725487264872748728487294873048731487324873348734487354873648737487384873948740487414874248743487444874548746487474874848749487504875148752487534875448755487564875748758487594876048761487624876348764487654876648767487684876948770487714877248773487744877548776487774877848779487804878148782487834878448785487864878748788487894879048791487924879348794487954879648797487984879948800488014880248803488044880548806488074880848809488104881148812488134881448815488164881748818488194882048821488224882348824488254882648827488284882948830488314883248833488344883548836488374883848839488404884148842488434884448845488464884748848488494885048851488524885348854488554885648857488584885948860488614886248863488644886548866488674886848869488704887148872488734887448875488764887748878488794888048881488824888348884488854888648887488884888948890488914889248893488944889548896488974889848899489004890148902489034890448905489064890748908489094891048911489124891348914489154891648917489184891948920489214892248923489244892548926489274892848929489304893148932489334893448935489364893748938489394894048941489424894348944489454894648947489484894948950489514895248953489544895548956489574895848959489604896148962489634896448965489664896748968489694897048971489724897348974489754897648977489784897948980489814898248983489844898548986489874898848989489904899148992489934899448995489964899748998489994900049001490024900349004490054900649007490084900949010490114901249013490144901549016490174901849019490204902149022490234902449025490264902749028490294903049031490324903349034490354903649037490384903949040490414904249043490444904549046490474904849049490504905149052490534905449055490564905749058490594906049061490624906349064490654906649067490684906949070490714907249073490744907549076490774907849079490804908149082490834908449085490864908749088490894909049091490924909349094490954909649097490984909949100491014910249103491044910549106491074910849109491104911149112491134911449115491164911749118491194912049121491224912349124491254912649127491284912949130491314913249133491344913549136491374913849139491404914149142491434914449145491464914749148491494915049151491524915349154491554915649157491584915949160491614916249163491644916549166491674916849169491704917149172491734917449175491764917749178491794918049181491824918349184491854918649187491884918949190491914919249193491944919549196491974919849199492004920149202492034920449205492064920749208492094921049211492124921349214492154921649217492184921949220492214922249223492244922549226492274922849229492304923149232492334923449235492364923749238492394924049241492424924349244492454924649247492484924949250492514925249253492544925549256492574925849259492604926149262492634926449265492664926749268492694927049271492724927349274492754927649277492784927949280492814928249283492844928549286492874928849289492904929149292492934929449295492964929749298492994930049301493024930349304493054930649307493084930949310493114931249313493144931549316493174931849319493204932149322493234932449325493264932749328493294933049331493324933349334493354933649337493384933949340493414934249343493444934549346493474934849349493504935149352493534935449355493564935749358493594936049361493624936349364493654936649367493684936949370493714937249373493744937549376493774937849379493804938149382493834938449385493864938749388493894939049391493924939349394493954939649397493984939949400494014940249403494044940549406494074940849409494104941149412494134941449415494164941749418494194942049421494224942349424494254942649427494284942949430494314943249433494344943549436494374943849439494404944149442494434944449445494464944749448494494945049451494524945349454494554945649457494584945949460494614946249463494644946549466494674946849469494704947149472494734947449475494764947749478494794948049481494824948349484494854948649487494884948949490494914949249493494944949549496494974949849499495004950149502495034950449505495064950749508495094951049511495124951349514495154951649517495184951949520495214952249523495244952549526495274952849529495304953149532495334953449535495364953749538495394954049541495424954349544495454954649547495484954949550495514955249553495544955549556495574955849559495604956149562495634956449565495664956749568495694957049571495724957349574495754957649577495784957949580495814958249583495844958549586495874958849589495904959149592495934959449595495964959749598495994960049601496024960349604496054960649607496084960949610496114961249613496144961549616496174961849619496204962149622496234962449625496264962749628496294963049631496324963349634496354963649637496384963949640496414964249643496444964549646496474964849649496504965149652496534965449655496564965749658496594966049661496624966349664496654966649667496684966949670496714967249673496744967549676496774967849679496804968149682496834968449685496864968749688496894969049691496924969349694496954969649697496984969949700497014970249703497044970549706497074970849709497104971149712497134971449715497164971749718497194972049721497224972349724497254972649727497284972949730497314973249733497344973549736497374973849739497404974149742497434974449745497464974749748497494975049751497524975349754497554975649757497584975949760497614976249763497644976549766497674976849769497704977149772497734977449775497764977749778497794978049781497824978349784497854978649787497884978949790497914979249793497944979549796497974979849799498004980149802498034980449805498064980749808498094981049811498124981349814498154981649817498184981949820498214982249823498244982549826498274982849829498304983149832498334983449835498364983749838498394984049841498424984349844498454984649847498484984949850498514985249853498544985549856498574985849859498604986149862498634986449865498664986749868498694987049871498724987349874498754987649877498784987949880498814988249883498844988549886498874988849889498904989149892498934989449895498964989749898498994990049901499024990349904499054990649907499084990949910499114991249913499144991549916499174991849919499204992149922499234992449925499264992749928499294993049931499324993349934499354993649937499384993949940499414994249943499444994549946499474994849949499504995149952499534995449955499564995749958499594996049961499624996349964499654996649967499684996949970499714997249973499744997549976499774997849979499804998149982499834998449985499864998749988499894999049991499924999349994499954999649997499984999950000500015000250003500045000550006500075000850009500105001150012500135001450015500165001750018500195002050021500225002350024500255002650027500285002950030500315003250033500345003550036500375003850039500405004150042500435004450045500465004750048500495005050051500525005350054500555005650057500585005950060500615006250063500645006550066500675006850069500705007150072500735007450075500765007750078500795008050081500825008350084500855008650087500885008950090500915009250093500945009550096500975009850099501005010150102501035010450105501065010750108501095011050111501125011350114501155011650117501185011950120501215012250123501245012550126501275012850129501305013150132501335013450135501365013750138501395014050141501425014350144501455014650147501485014950150501515015250153501545015550156501575015850159501605016150162501635016450165501665016750168501695017050171501725017350174501755017650177501785017950180501815018250183501845018550186501875018850189501905019150192501935019450195501965019750198501995020050201502025020350204502055020650207502085020950210502115021250213502145021550216502175021850219502205022150222502235022450225502265022750228502295023050231502325023350234502355023650237502385023950240502415024250243502445024550246502475024850249502505025150252502535025450255502565025750258502595026050261502625026350264502655026650267502685026950270502715027250273502745027550276502775027850279502805028150282502835028450285502865028750288502895029050291502925029350294502955029650297502985029950300503015030250303503045030550306503075030850309503105031150312503135031450315503165031750318503195032050321503225032350324503255032650327503285032950330503315033250333503345033550336503375033850339503405034150342503435034450345503465034750348503495035050351503525035350354503555035650357503585035950360503615036250363503645036550366503675036850369503705037150372503735037450375503765037750378503795038050381503825038350384503855038650387503885038950390503915039250393503945039550396503975039850399504005040150402504035040450405504065040750408504095041050411504125041350414504155041650417504185041950420504215042250423504245042550426504275042850429504305043150432504335043450435504365043750438504395044050441504425044350444504455044650447504485044950450504515045250453504545045550456504575045850459504605046150462504635046450465504665046750468504695047050471504725047350474504755047650477504785047950480504815048250483504845048550486504875048850489504905049150492504935049450495504965049750498504995050050501505025050350504505055050650507505085050950510505115051250513505145051550516505175051850519505205052150522505235052450525505265052750528505295053050531505325053350534505355053650537505385053950540505415054250543505445054550546505475054850549505505055150552505535055450555505565055750558505595056050561505625056350564505655056650567505685056950570505715057250573505745057550576505775057850579505805058150582505835058450585505865058750588505895059050591505925059350594505955059650597505985059950600506015060250603506045060550606506075060850609506105061150612506135061450615506165061750618506195062050621506225062350624506255062650627506285062950630506315063250633506345063550636506375063850639506405064150642506435064450645506465064750648506495065050651506525065350654506555065650657506585065950660506615066250663506645066550666506675066850669506705067150672506735067450675506765067750678506795068050681506825068350684506855068650687506885068950690506915069250693506945069550696506975069850699507005070150702507035070450705507065070750708507095071050711507125071350714507155071650717507185071950720507215072250723507245072550726507275072850729507305073150732507335073450735507365073750738507395074050741507425074350744507455074650747507485074950750507515075250753507545075550756507575075850759507605076150762507635076450765507665076750768507695077050771507725077350774507755077650777507785077950780507815078250783507845078550786507875078850789507905079150792507935079450795507965079750798507995080050801508025080350804508055080650807508085080950810508115081250813508145081550816508175081850819508205082150822508235082450825508265082750828508295083050831508325083350834508355083650837508385083950840508415084250843508445084550846508475084850849508505085150852508535085450855508565085750858508595086050861508625086350864508655086650867508685086950870508715087250873508745087550876508775087850879508805088150882508835088450885508865088750888508895089050891508925089350894508955089650897508985089950900509015090250903509045090550906509075090850909509105091150912509135091450915509165091750918509195092050921509225092350924509255092650927509285092950930509315093250933509345093550936509375093850939509405094150942509435094450945509465094750948509495095050951509525095350954509555095650957509585095950960509615096250963509645096550966509675096850969509705097150972509735097450975509765097750978509795098050981509825098350984509855098650987509885098950990509915099250993509945099550996509975099850999510005100151002510035100451005510065100751008510095101051011510125101351014510155101651017510185101951020510215102251023510245102551026510275102851029510305103151032510335103451035510365103751038510395104051041510425104351044510455104651047510485104951050510515105251053510545105551056510575105851059510605106151062510635106451065510665106751068510695107051071510725107351074510755107651077510785107951080510815108251083510845108551086510875108851089510905109151092510935109451095510965109751098510995110051101511025110351104511055110651107511085110951110511115111251113511145111551116511175111851119511205112151122511235112451125511265112751128511295113051131511325113351134511355113651137511385113951140511415114251143511445114551146511475114851149511505115151152511535115451155511565115751158511595116051161511625116351164511655116651167511685116951170511715117251173511745117551176511775117851179511805118151182511835118451185511865118751188511895119051191511925119351194511955119651197511985119951200512015120251203512045120551206512075120851209512105121151212512135121451215512165121751218512195122051221512225122351224512255122651227512285122951230512315123251233512345123551236512375123851239512405124151242512435124451245512465124751248512495125051251512525125351254512555125651257512585125951260512615126251263512645126551266512675126851269512705127151272512735127451275512765127751278512795128051281512825128351284512855128651287512885128951290512915129251293512945129551296512975129851299513005130151302513035130451305513065130751308513095131051311513125131351314513155131651317513185131951320513215132251323513245132551326513275132851329513305133151332513335133451335513365133751338513395134051341513425134351344513455134651347513485134951350513515135251353513545135551356513575135851359513605136151362513635136451365513665136751368513695137051371513725137351374513755137651377513785137951380513815138251383513845138551386513875138851389513905139151392513935139451395513965139751398513995140051401514025140351404514055140651407514085140951410514115141251413514145141551416514175141851419514205142151422514235142451425514265142751428514295143051431514325143351434514355143651437514385143951440514415144251443514445144551446514475144851449514505145151452514535145451455514565145751458514595146051461514625146351464514655146651467514685146951470514715147251473514745147551476514775147851479514805148151482514835148451485514865148751488514895149051491514925149351494514955149651497514985149951500515015150251503515045150551506515075150851509515105151151512515135151451515515165151751518515195152051521515225152351524515255152651527515285152951530515315153251533515345153551536515375153851539515405154151542515435154451545515465154751548515495155051551515525155351554515555155651557515585155951560515615156251563515645156551566515675156851569515705157151572515735157451575515765157751578515795158051581515825158351584515855158651587515885158951590515915159251593515945159551596515975159851599516005160151602516035160451605516065160751608516095161051611516125161351614516155161651617516185161951620516215162251623516245162551626516275162851629516305163151632516335163451635516365163751638516395164051641516425164351644516455164651647516485164951650516515165251653516545165551656516575165851659516605166151662516635166451665516665166751668516695167051671516725167351674516755167651677516785167951680516815168251683516845168551686516875168851689516905169151692516935169451695516965169751698516995170051701517025170351704517055170651707517085170951710517115171251713517145171551716517175171851719517205172151722517235172451725517265172751728517295173051731517325173351734517355173651737517385173951740517415174251743517445174551746517475174851749517505175151752517535175451755517565175751758517595176051761517625176351764517655176651767517685176951770517715177251773517745177551776517775177851779517805178151782517835178451785517865178751788517895179051791517925179351794517955179651797517985179951800518015180251803518045180551806518075180851809518105181151812518135181451815518165181751818518195182051821518225182351824518255182651827518285182951830518315183251833518345183551836518375183851839518405184151842518435184451845518465184751848518495185051851518525185351854518555185651857518585185951860518615186251863518645186551866518675186851869518705187151872518735187451875518765187751878518795188051881518825188351884518855188651887518885188951890518915189251893518945189551896518975189851899519005190151902519035190451905519065190751908519095191051911519125191351914519155191651917519185191951920519215192251923519245192551926519275192851929519305193151932519335193451935519365193751938519395194051941519425194351944519455194651947519485194951950519515195251953519545195551956519575195851959519605196151962519635196451965519665196751968519695197051971519725197351974519755197651977519785197951980519815198251983519845198551986519875198851989519905199151992519935199451995519965199751998519995200052001520025200352004520055200652007520085200952010520115201252013520145201552016520175201852019520205202152022520235202452025520265202752028520295203052031520325203352034520355203652037520385203952040520415204252043520445204552046520475204852049520505205152052520535205452055520565205752058520595206052061520625206352064520655206652067520685206952070520715207252073520745207552076520775207852079520805208152082520835208452085520865208752088520895209052091520925209352094520955209652097520985209952100521015210252103521045210552106521075210852109521105211152112521135211452115521165211752118521195212052121521225212352124521255212652127521285212952130521315213252133521345213552136521375213852139521405214152142521435214452145521465214752148521495215052151521525215352154521555215652157521585215952160521615216252163521645216552166521675216852169521705217152172521735217452175521765217752178521795218052181521825218352184521855218652187521885218952190521915219252193521945219552196521975219852199522005220152202522035220452205522065220752208522095221052211522125221352214522155221652217522185221952220522215222252223522245222552226522275222852229522305223152232522335223452235522365223752238522395224052241522425224352244522455224652247522485224952250522515225252253522545225552256522575225852259522605226152262522635226452265522665226752268522695227052271522725227352274522755227652277522785227952280522815228252283522845228552286522875228852289522905229152292522935229452295522965229752298522995230052301523025230352304523055230652307523085230952310523115231252313523145231552316523175231852319523205232152322523235232452325523265232752328523295233052331523325233352334523355233652337523385233952340523415234252343523445234552346523475234852349523505235152352523535235452355523565235752358523595236052361523625236352364523655236652367523685236952370523715237252373523745237552376523775237852379523805238152382523835238452385523865238752388523895239052391523925239352394523955239652397523985239952400524015240252403524045240552406524075240852409524105241152412524135241452415524165241752418524195242052421524225242352424524255242652427524285242952430524315243252433524345243552436524375243852439524405244152442524435244452445524465244752448524495245052451524525245352454524555245652457524585245952460524615246252463524645246552466524675246852469524705247152472524735247452475524765247752478524795248052481524825248352484524855248652487524885248952490524915249252493524945249552496524975249852499525005250152502525035250452505525065250752508525095251052511525125251352514525155251652517525185251952520525215252252523525245252552526525275252852529525305253152532525335253452535525365253752538525395254052541525425254352544525455254652547525485254952550525515255252553525545255552556525575255852559525605256152562525635256452565525665256752568525695257052571525725257352574525755257652577525785257952580525815258252583525845258552586525875258852589525905259152592525935259452595525965259752598525995260052601526025260352604526055260652607526085260952610526115261252613526145261552616526175261852619526205262152622526235262452625526265262752628526295263052631526325263352634526355263652637526385263952640526415264252643526445264552646526475264852649526505265152652526535265452655526565265752658526595266052661526625266352664526655266652667526685266952670526715267252673526745267552676526775267852679526805268152682526835268452685526865268752688526895269052691526925269352694526955269652697526985269952700527015270252703527045270552706527075270852709527105271152712527135271452715527165271752718527195272052721527225272352724527255272652727527285272952730527315273252733527345273552736527375273852739527405274152742527435274452745527465274752748527495275052751527525275352754527555275652757527585275952760527615276252763527645276552766527675276852769527705277152772527735277452775527765277752778527795278052781527825278352784527855278652787527885278952790527915279252793527945279552796527975279852799528005280152802528035280452805528065280752808528095281052811528125281352814528155281652817528185281952820528215282252823528245282552826528275282852829528305283152832528335283452835528365283752838528395284052841528425284352844528455284652847528485284952850528515285252853528545285552856528575285852859528605286152862528635286452865528665286752868528695287052871528725287352874528755287652877528785287952880528815288252883528845288552886528875288852889528905289152892528935289452895528965289752898528995290052901529025290352904529055290652907529085290952910529115291252913529145291552916529175291852919529205292152922529235292452925529265292752928529295293052931529325293352934529355293652937529385293952940529415294252943529445294552946529475294852949529505295152952529535295452955529565295752958529595296052961529625296352964529655296652967529685296952970529715297252973529745297552976529775297852979529805298152982529835298452985529865298752988529895299052991529925299352994529955299652997529985299953000530015300253003530045300553006530075300853009530105301153012530135301453015530165301753018530195302053021530225302353024530255302653027530285302953030530315303253033530345303553036530375303853039530405304153042530435304453045530465304753048530495305053051530525305353054530555305653057530585305953060530615306253063530645306553066530675306853069530705307153072530735307453075530765307753078530795308053081530825308353084530855308653087530885308953090530915309253093530945309553096530975309853099531005310153102531035310453105531065310753108531095311053111531125311353114531155311653117531185311953120531215312253123531245312553126531275312853129531305313153132531335313453135531365313753138531395314053141531425314353144531455314653147531485314953150531515315253153531545315553156531575315853159531605316153162531635316453165531665316753168531695317053171531725317353174531755317653177531785317953180531815318253183531845318553186531875318853189531905319153192531935319453195531965319753198531995320053201532025320353204532055320653207532085320953210532115321253213532145321553216532175321853219532205322153222532235322453225532265322753228532295323053231532325323353234532355323653237532385323953240532415324253243532445324553246532475324853249532505325153252532535325453255532565325753258532595326053261532625326353264532655326653267532685326953270532715327253273532745327553276532775327853279532805328153282532835328453285532865328753288532895329053291532925329353294532955329653297532985329953300533015330253303533045330553306533075330853309533105331153312533135331453315533165331753318533195332053321533225332353324533255332653327533285332953330533315333253333533345333553336533375333853339533405334153342533435334453345533465334753348533495335053351533525335353354533555335653357533585335953360533615336253363533645336553366533675336853369533705337153372533735337453375533765337753378533795338053381533825338353384533855338653387533885338953390533915339253393533945339553396533975339853399534005340153402534035340453405534065340753408534095341053411534125341353414534155341653417534185341953420534215342253423534245342553426534275342853429534305343153432534335343453435534365343753438534395344053441534425344353444534455344653447534485344953450534515345253453534545345553456534575345853459534605346153462534635346453465534665346753468534695347053471534725347353474534755347653477534785347953480534815348253483534845348553486534875348853489534905349153492534935349453495534965349753498534995350053501535025350353504535055350653507535085350953510535115351253513535145351553516535175351853519535205352153522535235352453525535265352753528535295353053531535325353353534535355353653537535385353953540535415354253543535445354553546535475354853549535505355153552535535355453555535565355753558535595356053561535625356353564535655356653567535685356953570535715357253573535745357553576535775357853579535805358153582535835358453585535865358753588535895359053591535925359353594535955359653597535985359953600536015360253603536045360553606536075360853609536105361153612536135361453615536165361753618536195362053621536225362353624536255362653627536285362953630536315363253633536345363553636536375363853639536405364153642536435364453645536465364753648536495365053651536525365353654536555365653657536585365953660536615366253663536645366553666536675366853669536705367153672536735367453675536765367753678536795368053681536825368353684536855368653687536885368953690536915369253693536945369553696536975369853699537005370153702537035370453705537065370753708537095371053711537125371353714537155371653717537185371953720537215372253723537245372553726537275372853729537305373153732537335373453735537365373753738537395374053741537425374353744537455374653747537485374953750537515375253753537545375553756537575375853759537605376153762537635376453765537665376753768537695377053771537725377353774537755377653777537785377953780537815378253783537845378553786537875378853789537905379153792537935379453795537965379753798537995380053801538025380353804538055380653807538085380953810538115381253813538145381553816538175381853819538205382153822538235382453825538265382753828538295383053831538325383353834538355383653837538385383953840538415384253843538445384553846538475384853849538505385153852538535385453855538565385753858538595386053861538625386353864538655386653867538685386953870538715387253873538745387553876538775387853879538805388153882538835388453885538865388753888538895389053891538925389353894538955389653897538985389953900539015390253903539045390553906539075390853909539105391153912539135391453915539165391753918539195392053921539225392353924539255392653927539285392953930539315393253933539345393553936539375393853939539405394153942539435394453945539465394753948539495395053951539525395353954539555395653957539585395953960539615396253963539645396553966539675396853969539705397153972539735397453975539765397753978539795398053981539825398353984539855398653987539885398953990539915399253993539945399553996539975399853999540005400154002540035400454005540065400754008540095401054011540125401354014540155401654017540185401954020540215402254023540245402554026540275402854029540305403154032540335403454035540365403754038540395404054041540425404354044540455404654047540485404954050540515405254053540545405554056540575405854059540605406154062540635406454065540665406754068540695407054071540725407354074540755407654077540785407954080540815408254083540845408554086540875408854089540905409154092540935409454095540965409754098540995410054101541025410354104541055410654107541085410954110541115411254113541145411554116541175411854119541205412154122541235412454125541265412754128541295413054131541325413354134541355413654137541385413954140541415414254143541445414554146541475414854149541505415154152541535415454155541565415754158541595416054161541625416354164541655416654167541685416954170541715417254173541745417554176541775417854179541805418154182541835418454185541865418754188541895419054191541925419354194541955419654197541985419954200542015420254203542045420554206542075420854209542105421154212542135421454215542165421754218542195422054221542225422354224542255422654227542285422954230542315423254233542345423554236542375423854239542405424154242542435424454245542465424754248542495425054251542525425354254542555425654257542585425954260542615426254263542645426554266542675426854269542705427154272542735427454275542765427754278542795428054281542825428354284542855428654287542885428954290542915429254293542945429554296542975429854299543005430154302543035430454305543065430754308543095431054311543125431354314543155431654317543185431954320543215432254323543245432554326543275432854329543305433154332543335433454335543365433754338543395434054341543425434354344543455434654347543485434954350543515435254353543545435554356543575435854359543605436154362543635436454365543665436754368543695437054371543725437354374543755437654377543785437954380543815438254383543845438554386543875438854389543905439154392543935439454395543965439754398543995440054401544025440354404544055440654407544085440954410544115441254413544145441554416544175441854419544205442154422544235442454425544265442754428544295443054431544325443354434544355443654437544385443954440544415444254443544445444554446544475444854449544505445154452544535445454455544565445754458544595446054461544625446354464544655446654467544685446954470544715447254473544745447554476544775447854479544805448154482544835448454485544865448754488544895449054491544925449354494544955449654497544985449954500545015450254503545045450554506545075450854509545105451154512545135451454515545165451754518545195452054521545225452354524545255452654527545285452954530545315453254533545345453554536545375453854539545405454154542545435454454545545465454754548545495455054551545525455354554545555455654557545585455954560545615456254563545645456554566545675456854569545705457154572545735457454575545765457754578545795458054581545825458354584545855458654587545885458954590545915459254593545945459554596545975459854599546005460154602546035460454605546065460754608546095461054611546125461354614546155461654617546185461954620546215462254623546245462554626546275462854629546305463154632546335463454635546365463754638546395464054641546425464354644546455464654647546485464954650546515465254653546545465554656546575465854659546605466154662546635466454665546665466754668546695467054671546725467354674546755467654677546785467954680546815468254683546845468554686546875468854689546905469154692546935469454695546965469754698546995470054701547025470354704547055470654707547085470954710547115471254713547145471554716547175471854719547205472154722547235472454725547265472754728547295473054731547325473354734547355473654737547385473954740547415474254743547445474554746547475474854749547505475154752547535475454755547565475754758547595476054761547625476354764547655476654767547685476954770547715477254773547745477554776547775477854779547805478154782547835478454785547865478754788547895479054791547925479354794547955479654797547985479954800548015480254803548045480554806548075480854809548105481154812548135481454815548165481754818548195482054821548225482354824548255482654827548285482954830548315483254833548345483554836548375483854839548405484154842548435484454845548465484754848548495485054851548525485354854548555485654857548585485954860548615486254863548645486554866548675486854869548705487154872548735487454875548765487754878548795488054881548825488354884548855488654887548885488954890548915489254893548945489554896548975489854899549005490154902549035490454905549065490754908549095491054911549125491354914549155491654917549185491954920549215492254923549245492554926549275492854929549305493154932549335493454935549365493754938549395494054941549425494354944549455494654947549485494954950549515495254953549545495554956549575495854959549605496154962549635496454965549665496754968549695497054971549725497354974549755497654977549785497954980549815498254983549845498554986549875498854989549905499154992549935499454995549965499754998549995500055001550025500355004550055500655007550085500955010550115501255013550145501555016550175501855019550205502155022550235502455025550265502755028550295503055031550325503355034550355503655037550385503955040550415504255043550445504555046550475504855049550505505155052550535505455055550565505755058550595506055061550625506355064550655506655067550685506955070550715507255073550745507555076550775507855079550805508155082550835508455085550865508755088550895509055091550925509355094550955509655097550985509955100551015510255103551045510555106551075510855109551105511155112551135511455115551165511755118551195512055121551225512355124551255512655127551285512955130551315513255133551345513555136551375513855139551405514155142551435514455145551465514755148551495515055151551525515355154551555515655157551585515955160551615516255163551645516555166551675516855169551705517155172551735517455175551765517755178551795518055181551825518355184551855518655187551885518955190551915519255193551945519555196551975519855199552005520155202552035520455205552065520755208552095521055211552125521355214552155521655217552185521955220552215522255223552245522555226552275522855229552305523155232552335523455235552365523755238552395524055241552425524355244552455524655247552485524955250552515525255253552545525555256552575525855259552605526155262552635526455265552665526755268552695527055271552725527355274552755527655277552785527955280552815528255283552845528555286552875528855289552905529155292552935529455295552965529755298552995530055301553025530355304553055530655307553085530955310553115531255313553145531555316553175531855319553205532155322553235532455325553265532755328553295533055331553325533355334553355533655337553385533955340553415534255343553445534555346553475534855349553505535155352553535535455355553565535755358553595536055361553625536355364553655536655367553685536955370553715537255373553745537555376553775537855379553805538155382553835538455385553865538755388553895539055391553925539355394553955539655397553985539955400554015540255403554045540555406554075540855409554105541155412554135541455415554165541755418554195542055421554225542355424554255542655427554285542955430554315543255433554345543555436554375543855439554405544155442554435544455445554465544755448554495545055451554525545355454554555545655457554585545955460554615546255463554645546555466554675546855469554705547155472554735547455475554765547755478554795548055481554825548355484554855548655487554885548955490554915549255493554945549555496554975549855499555005550155502555035550455505555065550755508555095551055511555125551355514555155551655517555185551955520555215552255523555245552555526555275552855529555305553155532555335553455535555365553755538555395554055541555425554355544555455554655547555485554955550555515555255553555545555555556555575555855559555605556155562555635556455565555665556755568555695557055571555725557355574555755557655577555785557955580555815558255583555845558555586555875558855589555905559155592555935559455595555965559755598555995560055601556025560355604556055560655607556085560955610556115561255613556145561555616556175561855619556205562155622556235562455625556265562755628556295563055631556325563355634556355563655637556385563955640556415564255643556445564555646556475564855649556505565155652556535565455655556565565755658556595566055661556625566355664556655566655667556685566955670556715567255673556745567555676556775567855679556805568155682556835568455685556865568755688556895569055691556925569355694556955569655697556985569955700557015570255703557045570555706557075570855709557105571155712557135571455715557165571755718557195572055721557225572355724557255572655727557285572955730557315573255733557345573555736557375573855739557405574155742557435574455745557465574755748557495575055751557525575355754557555575655757557585575955760557615576255763557645576555766557675576855769557705577155772557735577455775557765577755778557795578055781557825578355784557855578655787557885578955790557915579255793557945579555796557975579855799558005580155802558035580455805558065580755808558095581055811558125581355814558155581655817558185581955820558215582255823558245582555826558275582855829558305583155832558335583455835558365583755838558395584055841558425584355844558455584655847558485584955850558515585255853558545585555856558575585855859558605586155862558635586455865558665586755868558695587055871558725587355874558755587655877558785587955880558815588255883558845588555886558875588855889558905589155892558935589455895558965589755898558995590055901559025590355904559055590655907559085590955910559115591255913559145591555916559175591855919559205592155922559235592455925559265592755928559295593055931559325593355934559355593655937559385593955940559415594255943559445594555946559475594855949559505595155952559535595455955559565595755958559595596055961559625596355964559655596655967559685596955970559715597255973559745597555976559775597855979559805598155982559835598455985559865598755988559895599055991559925599355994559955599655997559985599956000560015600256003560045600556006560075600856009560105601156012560135601456015560165601756018560195602056021560225602356024560255602656027560285602956030560315603256033560345603556036560375603856039560405604156042560435604456045560465604756048560495605056051560525605356054560555605656057560585605956060560615606256063560645606556066560675606856069560705607156072560735607456075560765607756078560795608056081560825608356084560855608656087560885608956090560915609256093560945609556096560975609856099561005610156102561035610456105561065610756108561095611056111561125611356114561155611656117561185611956120561215612256123561245612556126561275612856129561305613156132561335613456135561365613756138561395614056141561425614356144561455614656147561485614956150561515615256153561545615556156561575615856159561605616156162561635616456165561665616756168561695617056171561725617356174561755617656177561785617956180561815618256183561845618556186561875618856189561905619156192561935619456195561965619756198561995620056201562025620356204562055620656207562085620956210562115621256213562145621556216562175621856219562205622156222562235622456225562265622756228562295623056231562325623356234562355623656237562385623956240562415624256243562445624556246562475624856249562505625156252562535625456255562565625756258562595626056261562625626356264562655626656267562685626956270562715627256273562745627556276562775627856279562805628156282562835628456285562865628756288562895629056291562925629356294562955629656297562985629956300563015630256303563045630556306563075630856309563105631156312563135631456315563165631756318563195632056321563225632356324563255632656327563285632956330563315633256333563345633556336563375633856339563405634156342563435634456345563465634756348563495635056351563525635356354563555635656357563585635956360563615636256363563645636556366563675636856369563705637156372563735637456375563765637756378563795638056381563825638356384563855638656387563885638956390563915639256393563945639556396563975639856399564005640156402564035640456405564065640756408564095641056411564125641356414564155641656417564185641956420564215642256423564245642556426564275642856429564305643156432564335643456435564365643756438564395644056441564425644356444564455644656447564485644956450564515645256453564545645556456564575645856459564605646156462564635646456465564665646756468564695647056471564725647356474564755647656477564785647956480564815648256483564845648556486564875648856489564905649156492564935649456495564965649756498564995650056501565025650356504565055650656507565085650956510565115651256513565145651556516565175651856519565205652156522565235652456525565265652756528565295653056531565325653356534565355653656537565385653956540565415654256543565445654556546565475654856549565505655156552565535655456555565565655756558565595656056561565625656356564565655656656567565685656956570565715657256573565745657556576565775657856579565805658156582565835658456585565865658756588565895659056591565925659356594565955659656597565985659956600566015660256603566045660556606566075660856609566105661156612566135661456615566165661756618566195662056621566225662356624566255662656627566285662956630566315663256633566345663556636566375663856639566405664156642566435664456645566465664756648566495665056651566525665356654566555665656657566585665956660566615666256663566645666556666566675666856669566705667156672566735667456675566765667756678566795668056681566825668356684566855668656687566885668956690566915669256693566945669556696566975669856699567005670156702567035670456705567065670756708567095671056711567125671356714567155671656717567185671956720567215672256723567245672556726567275672856729567305673156732567335673456735567365673756738567395674056741567425674356744567455674656747567485674956750567515675256753567545675556756567575675856759567605676156762567635676456765567665676756768567695677056771567725677356774567755677656777567785677956780567815678256783567845678556786567875678856789567905679156792567935679456795567965679756798567995680056801568025680356804568055680656807568085680956810568115681256813568145681556816568175681856819568205682156822568235682456825568265682756828568295683056831568325683356834568355683656837568385683956840568415684256843568445684556846568475684856849568505685156852568535685456855568565685756858568595686056861568625686356864568655686656867568685686956870568715687256873568745687556876568775687856879568805688156882568835688456885568865688756888568895689056891568925689356894568955689656897568985689956900569015690256903569045690556906569075690856909569105691156912569135691456915569165691756918569195692056921569225692356924569255692656927569285692956930569315693256933569345693556936569375693856939569405694156942569435694456945569465694756948569495695056951569525695356954569555695656957569585695956960569615696256963569645696556966569675696856969569705697156972569735697456975569765697756978569795698056981569825698356984569855698656987569885698956990569915699256993569945699556996569975699856999570005700157002570035700457005570065700757008570095701057011570125701357014570155701657017570185701957020570215702257023570245702557026570275702857029570305703157032570335703457035570365703757038570395704057041570425704357044570455704657047570485704957050570515705257053570545705557056570575705857059570605706157062570635706457065570665706757068570695707057071570725707357074570755707657077570785707957080570815708257083570845708557086570875708857089570905709157092570935709457095570965709757098570995710057101571025710357104571055710657107571085710957110571115711257113571145711557116571175711857119571205712157122571235712457125571265712757128571295713057131571325713357134571355713657137571385713957140571415714257143571445714557146571475714857149571505715157152571535715457155571565715757158571595716057161571625716357164571655716657167571685716957170571715717257173571745717557176571775717857179571805718157182571835718457185571865718757188571895719057191571925719357194571955719657197571985719957200572015720257203572045720557206572075720857209572105721157212572135721457215572165721757218572195722057221572225722357224572255722657227572285722957230572315723257233572345723557236572375723857239572405724157242572435724457245572465724757248572495725057251572525725357254572555725657257572585725957260572615726257263572645726557266572675726857269572705727157272572735727457275572765727757278572795728057281572825728357284572855728657287572885728957290572915729257293572945729557296572975729857299573005730157302573035730457305573065730757308573095731057311573125731357314573155731657317573185731957320573215732257323573245732557326573275732857329573305733157332573335733457335573365733757338573395734057341573425734357344573455734657347573485734957350573515735257353573545735557356573575735857359573605736157362573635736457365573665736757368573695737057371573725737357374573755737657377573785737957380573815738257383573845738557386573875738857389573905739157392573935739457395573965739757398573995740057401574025740357404574055740657407574085740957410574115741257413574145741557416574175741857419574205742157422574235742457425574265742757428574295743057431574325743357434574355743657437574385743957440574415744257443574445744557446574475744857449574505745157452574535745457455574565745757458574595746057461574625746357464574655746657467574685746957470574715747257473574745747557476574775747857479574805748157482574835748457485574865748757488574895749057491574925749357494574955749657497574985749957500575015750257503575045750557506575075750857509575105751157512575135751457515575165751757518575195752057521575225752357524575255752657527575285752957530575315753257533575345753557536575375753857539575405754157542575435754457545575465754757548575495755057551575525755357554575555755657557575585755957560575615756257563575645756557566575675756857569575705757157572575735757457575575765757757578575795758057581575825758357584575855758657587575885758957590575915759257593575945759557596575975759857599576005760157602576035760457605576065760757608576095761057611576125761357614576155761657617576185761957620576215762257623576245762557626576275762857629576305763157632576335763457635576365763757638576395764057641576425764357644576455764657647576485764957650576515765257653576545765557656576575765857659576605766157662576635766457665576665766757668576695767057671576725767357674576755767657677576785767957680576815768257683576845768557686576875768857689576905769157692576935769457695576965769757698576995770057701577025770357704577055770657707577085770957710577115771257713577145771557716577175771857719577205772157722577235772457725577265772757728577295773057731577325773357734577355773657737577385773957740577415774257743577445774557746577475774857749577505775157752577535775457755577565775757758577595776057761577625776357764577655776657767577685776957770577715777257773577745777557776577775777857779577805778157782577835778457785577865778757788577895779057791577925779357794577955779657797577985779957800578015780257803578045780557806578075780857809578105781157812578135781457815578165781757818578195782057821578225782357824578255782657827578285782957830578315783257833578345783557836578375783857839578405784157842578435784457845578465784757848578495785057851578525785357854578555785657857578585785957860578615786257863578645786557866578675786857869578705787157872578735787457875578765787757878578795788057881578825788357884578855788657887578885788957890578915789257893578945789557896578975789857899579005790157902579035790457905579065790757908579095791057911579125791357914579155791657917579185791957920579215792257923579245792557926579275792857929579305793157932579335793457935579365793757938579395794057941579425794357944579455794657947579485794957950579515795257953579545795557956579575795857959579605796157962579635796457965579665796757968579695797057971579725797357974579755797657977579785797957980579815798257983579845798557986579875798857989579905799157992579935799457995579965799757998579995800058001580025800358004580055800658007580085800958010580115801258013580145801558016580175801858019580205802158022580235802458025580265802758028580295803058031580325803358034580355803658037580385803958040580415804258043580445804558046580475804858049580505805158052580535805458055580565805758058580595806058061580625806358064580655806658067580685806958070580715807258073580745807558076580775807858079580805808158082580835808458085580865808758088580895809058091580925809358094580955809658097580985809958100581015810258103581045810558106581075810858109581105811158112581135811458115581165811758118581195812058121581225812358124581255812658127581285812958130581315813258133581345813558136581375813858139581405814158142581435814458145581465814758148581495815058151581525815358154581555815658157581585815958160581615816258163581645816558166581675816858169581705817158172581735817458175581765817758178581795818058181581825818358184581855818658187581885818958190581915819258193581945819558196581975819858199582005820158202582035820458205582065820758208582095821058211582125821358214582155821658217582185821958220582215822258223582245822558226582275822858229582305823158232582335823458235582365823758238582395824058241582425824358244582455824658247582485824958250582515825258253582545825558256582575825858259582605826158262582635826458265582665826758268582695827058271582725827358274582755827658277582785827958280582815828258283582845828558286582875828858289582905829158292582935829458295582965829758298582995830058301583025830358304583055830658307583085830958310583115831258313583145831558316583175831858319583205832158322583235832458325583265832758328583295833058331583325833358334583355833658337583385833958340583415834258343583445834558346583475834858349583505835158352583535835458355583565835758358583595836058361583625836358364583655836658367583685836958370583715837258373583745837558376583775837858379583805838158382583835838458385583865838758388583895839058391583925839358394583955839658397583985839958400584015840258403584045840558406584075840858409584105841158412584135841458415584165841758418584195842058421584225842358424584255842658427584285842958430584315843258433584345843558436584375843858439584405844158442584435844458445584465844758448584495845058451584525845358454584555845658457584585845958460584615846258463584645846558466584675846858469584705847158472584735847458475584765847758478584795848058481584825848358484584855848658487584885848958490584915849258493584945849558496584975849858499585005850158502585035850458505585065850758508585095851058511585125851358514585155851658517585185851958520585215852258523585245852558526585275852858529585305853158532585335853458535585365853758538585395854058541585425854358544585455854658547585485854958550585515855258553585545855558556585575855858559585605856158562585635856458565585665856758568585695857058571585725857358574585755857658577585785857958580585815858258583585845858558586585875858858589585905859158592585935859458595585965859758598585995860058601586025860358604586055860658607586085860958610586115861258613586145861558616586175861858619586205862158622586235862458625586265862758628586295863058631586325863358634586355863658637586385863958640586415864258643586445864558646586475864858649586505865158652586535865458655586565865758658586595866058661586625866358664586655866658667586685866958670586715867258673586745867558676586775867858679586805868158682586835868458685586865868758688586895869058691586925869358694586955869658697586985869958700587015870258703587045870558706587075870858709587105871158712587135871458715587165871758718587195872058721587225872358724587255872658727587285872958730587315873258733587345873558736587375873858739587405874158742587435874458745587465874758748587495875058751587525875358754587555875658757587585875958760587615876258763587645876558766587675876858769587705877158772587735877458775587765877758778587795878058781587825878358784587855878658787587885878958790587915879258793587945879558796587975879858799588005880158802588035880458805588065880758808588095881058811588125881358814588155881658817588185881958820588215882258823588245882558826588275882858829588305883158832588335883458835588365883758838588395884058841588425884358844588455884658847588485884958850588515885258853588545885558856588575885858859588605886158862588635886458865588665886758868588695887058871588725887358874588755887658877588785887958880588815888258883588845888558886588875888858889588905889158892588935889458895588965889758898588995890058901589025890358904589055890658907589085890958910589115891258913589145891558916589175891858919589205892158922589235892458925589265892758928589295893058931589325893358934589355893658937589385893958940589415894258943589445894558946589475894858949589505895158952589535895458955589565895758958589595896058961589625896358964589655896658967589685896958970589715897258973589745897558976589775897858979589805898158982589835898458985589865898758988589895899058991589925899358994589955899658997589985899959000590015900259003590045900559006590075900859009590105901159012590135901459015590165901759018590195902059021590225902359024590255902659027590285902959030590315903259033590345903559036590375903859039590405904159042590435904459045590465904759048590495905059051590525905359054590555905659057590585905959060590615906259063590645906559066590675906859069590705907159072590735907459075590765907759078590795908059081590825908359084590855908659087590885908959090590915909259093590945909559096590975909859099591005910159102591035910459105591065910759108591095911059111591125911359114591155911659117591185911959120591215912259123591245912559126591275912859129591305913159132591335913459135591365913759138591395914059141591425914359144591455914659147591485914959150591515915259153591545915559156591575915859159591605916159162591635916459165591665916759168591695917059171591725917359174591755917659177591785917959180591815918259183591845918559186591875918859189591905919159192591935919459195591965919759198591995920059201592025920359204592055920659207592085920959210592115921259213592145921559216592175921859219592205922159222592235922459225592265922759228592295923059231592325923359234592355923659237592385923959240592415924259243592445924559246592475924859249592505925159252592535925459255592565925759258592595926059261592625926359264592655926659267592685926959270592715927259273592745927559276592775927859279592805928159282592835928459285592865928759288592895929059291592925929359294592955929659297592985929959300593015930259303593045930559306593075930859309593105931159312593135931459315593165931759318593195932059321593225932359324593255932659327593285932959330593315933259333593345933559336593375933859339593405934159342593435934459345593465934759348593495935059351593525935359354593555935659357593585935959360593615936259363
  1. /**
  2. * @license
  3. * Video.js 7.5.4 <http://videojs.com/>
  4. * Copyright Brightcove, Inc. <https://www.brightcove.com/>
  5. * Available under Apache License Version 2.0
  6. * <https://github.com/videojs/video.js/blob/master/LICENSE>
  7. *
  8. * Includes vtt.js <https://github.com/mozilla/vtt.js>
  9. * Available under Apache License Version 2.0
  10. * <https://github.com/mozilla/vtt.js/blob/master/LICENSE>
  11. */
  12. (function (global, factory) {
  13. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('global/window'), require('global/document')) :
  14. typeof define === 'function' && define.amd ? define(['global/window', 'global/document'], factory) :
  15. (global = global || self, global.videojs = factory(global.window, global.document));
  16. }(this, function (window$1, document) {
  17. window$1 = window$1 && window$1.hasOwnProperty('default') ? window$1['default'] : window$1;
  18. document = document && document.hasOwnProperty('default') ? document['default'] : document;
  19. var version = "7.5.4";
  20. function _inheritsLoose(subClass, superClass) {
  21. subClass.prototype = Object.create(superClass.prototype);
  22. subClass.prototype.constructor = subClass;
  23. subClass.__proto__ = superClass;
  24. }
  25. function _setPrototypeOf(o, p) {
  26. _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
  27. o.__proto__ = p;
  28. return o;
  29. };
  30. return _setPrototypeOf(o, p);
  31. }
  32. function isNativeReflectConstruct() {
  33. if (typeof Reflect === "undefined" || !Reflect.construct) return false;
  34. if (Reflect.construct.sham) return false;
  35. if (typeof Proxy === "function") return true;
  36. try {
  37. Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
  38. return true;
  39. } catch (e) {
  40. return false;
  41. }
  42. }
  43. function _construct(Parent, args, Class) {
  44. if (isNativeReflectConstruct()) {
  45. _construct = Reflect.construct;
  46. } else {
  47. _construct = function _construct(Parent, args, Class) {
  48. var a = [null];
  49. a.push.apply(a, args);
  50. var Constructor = Function.bind.apply(Parent, a);
  51. var instance = new Constructor();
  52. if (Class) _setPrototypeOf(instance, Class.prototype);
  53. return instance;
  54. };
  55. }
  56. return _construct.apply(null, arguments);
  57. }
  58. function _assertThisInitialized(self) {
  59. if (self === void 0) {
  60. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  61. }
  62. return self;
  63. }
  64. function _taggedTemplateLiteralLoose(strings, raw) {
  65. if (!raw) {
  66. raw = strings.slice(0);
  67. }
  68. strings.raw = raw;
  69. return strings;
  70. }
  71. /**
  72. * @file create-logger.js
  73. * @module create-logger
  74. */
  75. var history = [];
  76. /**
  77. * Log messages to the console and history based on the type of message
  78. *
  79. * @private
  80. * @param {string} type
  81. * The name of the console method to use.
  82. *
  83. * @param {Array} args
  84. * The arguments to be passed to the matching console method.
  85. */
  86. var LogByTypeFactory = function LogByTypeFactory(name, log) {
  87. return function (type, level, args) {
  88. var lvl = log.levels[level];
  89. var lvlRegExp = new RegExp("^(" + lvl + ")$");
  90. if (type !== 'log') {
  91. // Add the type to the front of the message when it's not "log".
  92. args.unshift(type.toUpperCase() + ':');
  93. } // Add console prefix after adding to history.
  94. args.unshift(name + ':'); // Add a clone of the args at this point to history.
  95. if (history) {
  96. history.push([].concat(args));
  97. } // If there's no console then don't try to output messages, but they will
  98. // still be stored in history.
  99. if (!window$1.console) {
  100. return;
  101. } // Was setting these once outside of this function, but containing them
  102. // in the function makes it easier to test cases where console doesn't exist
  103. // when the module is executed.
  104. var fn = window$1.console[type];
  105. if (!fn && type === 'debug') {
  106. // Certain browsers don't have support for console.debug. For those, we
  107. // should default to the closest comparable log.
  108. fn = window$1.console.info || window$1.console.log;
  109. } // Bail out if there's no console or if this type is not allowed by the
  110. // current logging level.
  111. if (!fn || !lvl || !lvlRegExp.test(type)) {
  112. return;
  113. }
  114. fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args);
  115. };
  116. };
  117. function createLogger(name) {
  118. // This is the private tracking variable for logging level.
  119. var level = 'info'; // the curried logByType bound to the specific log and history
  120. var logByType;
  121. /**
  122. * Logs plain debug messages. Similar to `console.log`.
  123. *
  124. * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
  125. * of our JSDoc template, we cannot properly document this as both a function
  126. * and a namespace, so its function signature is documented here.
  127. *
  128. * #### Arguments
  129. * ##### *args
  130. * Mixed[]
  131. *
  132. * Any combination of values that could be passed to `console.log()`.
  133. *
  134. * #### Return Value
  135. *
  136. * `undefined`
  137. *
  138. * @namespace
  139. * @param {Mixed[]} args
  140. * One or more messages or objects that should be logged.
  141. */
  142. var log = function log() {
  143. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  144. args[_key] = arguments[_key];
  145. }
  146. logByType('log', level, args);
  147. }; // This is the logByType helper that the logging methods below use
  148. logByType = LogByTypeFactory(name, log);
  149. /**
  150. * Create a new sublogger which chains the old name to the new name.
  151. *
  152. * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
  153. * ```js
  154. * mylogger('foo');
  155. * // > VIDEOJS: player: foo
  156. * ```
  157. *
  158. * @param {string} name
  159. * The name to add call the new logger
  160. * @return {Object}
  161. */
  162. log.createLogger = function (subname) {
  163. return createLogger(name + ': ' + subname);
  164. };
  165. /**
  166. * Enumeration of available logging levels, where the keys are the level names
  167. * and the values are `|`-separated strings containing logging methods allowed
  168. * in that logging level. These strings are used to create a regular expression
  169. * matching the function name being called.
  170. *
  171. * Levels provided by Video.js are:
  172. *
  173. * - `off`: Matches no calls. Any value that can be cast to `false` will have
  174. * this effect. The most restrictive.
  175. * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
  176. * `log.warn`, and `log.error`).
  177. * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
  178. * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
  179. * - `warn`: Matches `log.warn` and `log.error` calls.
  180. * - `error`: Matches only `log.error` calls.
  181. *
  182. * @type {Object}
  183. */
  184. log.levels = {
  185. all: 'debug|log|warn|error',
  186. off: '',
  187. debug: 'debug|log|warn|error',
  188. info: 'log|warn|error',
  189. warn: 'warn|error',
  190. error: 'error',
  191. DEFAULT: level
  192. };
  193. /**
  194. * Get or set the current logging level.
  195. *
  196. * If a string matching a key from {@link module:log.levels} is provided, acts
  197. * as a setter.
  198. *
  199. * @param {string} [lvl]
  200. * Pass a valid level to set a new logging level.
  201. *
  202. * @return {string}
  203. * The current logging level.
  204. */
  205. log.level = function (lvl) {
  206. if (typeof lvl === 'string') {
  207. if (!log.levels.hasOwnProperty(lvl)) {
  208. throw new Error("\"" + lvl + "\" in not a valid log level");
  209. }
  210. level = lvl;
  211. }
  212. return level;
  213. };
  214. /**
  215. * Returns an array containing everything that has been logged to the history.
  216. *
  217. * This array is a shallow clone of the internal history record. However, its
  218. * contents are _not_ cloned; so, mutating objects inside this array will
  219. * mutate them in history.
  220. *
  221. * @return {Array}
  222. */
  223. log.history = function () {
  224. return history ? [].concat(history) : [];
  225. };
  226. /**
  227. * Allows you to filter the history by the given logger name
  228. *
  229. * @param {string} fname
  230. * The name to filter by
  231. *
  232. * @return {Array}
  233. * The filtered list to return
  234. */
  235. log.history.filter = function (fname) {
  236. return (history || []).filter(function (historyItem) {
  237. // if the first item in each historyItem includes `fname`, then it's a match
  238. return new RegExp(".*" + fname + ".*").test(historyItem[0]);
  239. });
  240. };
  241. /**
  242. * Clears the internal history tracking, but does not prevent further history
  243. * tracking.
  244. */
  245. log.history.clear = function () {
  246. if (history) {
  247. history.length = 0;
  248. }
  249. };
  250. /**
  251. * Disable history tracking if it is currently enabled.
  252. */
  253. log.history.disable = function () {
  254. if (history !== null) {
  255. history.length = 0;
  256. history = null;
  257. }
  258. };
  259. /**
  260. * Enable history tracking if it is currently disabled.
  261. */
  262. log.history.enable = function () {
  263. if (history === null) {
  264. history = [];
  265. }
  266. };
  267. /**
  268. * Logs error messages. Similar to `console.error`.
  269. *
  270. * @param {Mixed[]} args
  271. * One or more messages or objects that should be logged as an error
  272. */
  273. log.error = function () {
  274. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  275. args[_key2] = arguments[_key2];
  276. }
  277. return logByType('error', level, args);
  278. };
  279. /**
  280. * Logs warning messages. Similar to `console.warn`.
  281. *
  282. * @param {Mixed[]} args
  283. * One or more messages or objects that should be logged as a warning.
  284. */
  285. log.warn = function () {
  286. for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  287. args[_key3] = arguments[_key3];
  288. }
  289. return logByType('warn', level, args);
  290. };
  291. /**
  292. * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
  293. * log if `console.debug` is not available
  294. *
  295. * @param {Mixed[]} args
  296. * One or more messages or objects that should be logged as debug.
  297. */
  298. log.debug = function () {
  299. for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
  300. args[_key4] = arguments[_key4];
  301. }
  302. return logByType('debug', level, args);
  303. };
  304. return log;
  305. }
  306. /**
  307. * @file log.js
  308. * @module log
  309. */
  310. var log = createLogger('VIDEOJS');
  311. var createLogger$1 = log.createLogger;
  312. function clean(s) {
  313. return s.replace(/\n\r?\s*/g, '');
  314. }
  315. var tsml = function tsml(sa) {
  316. var s = '',
  317. i = 0;
  318. for (; i < arguments.length; i++) {
  319. s += clean(sa[i]) + (arguments[i + 1] || '');
  320. }
  321. return s;
  322. };
  323. /**
  324. * @file obj.js
  325. * @module obj
  326. */
  327. /**
  328. * @callback obj:EachCallback
  329. *
  330. * @param {Mixed} value
  331. * The current key for the object that is being iterated over.
  332. *
  333. * @param {string} key
  334. * The current key-value for object that is being iterated over
  335. */
  336. /**
  337. * @callback obj:ReduceCallback
  338. *
  339. * @param {Mixed} accum
  340. * The value that is accumulating over the reduce loop.
  341. *
  342. * @param {Mixed} value
  343. * The current key for the object that is being iterated over.
  344. *
  345. * @param {string} key
  346. * The current key-value for object that is being iterated over
  347. *
  348. * @return {Mixed}
  349. * The new accumulated value.
  350. */
  351. var toString = Object.prototype.toString;
  352. /**
  353. * Get the keys of an Object
  354. *
  355. * @param {Object}
  356. * The Object to get the keys from
  357. *
  358. * @return {string[]}
  359. * An array of the keys from the object. Returns an empty array if the
  360. * object passed in was invalid or had no keys.
  361. *
  362. * @private
  363. */
  364. var keys = function keys(object) {
  365. return isObject(object) ? Object.keys(object) : [];
  366. };
  367. /**
  368. * Array-like iteration for objects.
  369. *
  370. * @param {Object} object
  371. * The object to iterate over
  372. *
  373. * @param {obj:EachCallback} fn
  374. * The callback function which is called for each key in the object.
  375. */
  376. function each(object, fn) {
  377. keys(object).forEach(function (key) {
  378. return fn(object[key], key);
  379. });
  380. }
  381. /**
  382. * Array-like reduce for objects.
  383. *
  384. * @param {Object} object
  385. * The Object that you want to reduce.
  386. *
  387. * @param {Function} fn
  388. * A callback function which is called for each key in the object. It
  389. * receives the accumulated value and the per-iteration value and key
  390. * as arguments.
  391. *
  392. * @param {Mixed} [initial = 0]
  393. * Starting value
  394. *
  395. * @return {Mixed}
  396. * The final accumulated value.
  397. */
  398. function reduce(object, fn, initial) {
  399. if (initial === void 0) {
  400. initial = 0;
  401. }
  402. return keys(object).reduce(function (accum, key) {
  403. return fn(accum, object[key], key);
  404. }, initial);
  405. }
  406. /**
  407. * Object.assign-style object shallow merge/extend.
  408. *
  409. * @param {Object} target
  410. * @param {Object} ...sources
  411. * @return {Object}
  412. */
  413. function assign(target) {
  414. for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  415. sources[_key - 1] = arguments[_key];
  416. }
  417. if (Object.assign) {
  418. return Object.assign.apply(Object, [target].concat(sources));
  419. }
  420. sources.forEach(function (source) {
  421. if (!source) {
  422. return;
  423. }
  424. each(source, function (value, key) {
  425. target[key] = value;
  426. });
  427. });
  428. return target;
  429. }
  430. /**
  431. * Returns whether a value is an object of any kind - including DOM nodes,
  432. * arrays, regular expressions, etc. Not functions, though.
  433. *
  434. * This avoids the gotcha where using `typeof` on a `null` value
  435. * results in `'object'`.
  436. *
  437. * @param {Object} value
  438. * @return {boolean}
  439. */
  440. function isObject(value) {
  441. return !!value && typeof value === 'object';
  442. }
  443. /**
  444. * Returns whether an object appears to be a "plain" object - that is, a
  445. * direct instance of `Object`.
  446. *
  447. * @param {Object} value
  448. * @return {boolean}
  449. */
  450. function isPlain(value) {
  451. return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
  452. }
  453. /**
  454. * @file computed-style.js
  455. * @module computed-style
  456. */
  457. /**
  458. * A safe getComputedStyle.
  459. *
  460. * This is needed because in Firefox, if the player is loaded in an iframe with
  461. * `display:none`, then `getComputedStyle` returns `null`, so, we do a
  462. * null-check to make sure that the player doesn't break in these cases.
  463. *
  464. * @function
  465. * @param {Element} el
  466. * The element you want the computed style of
  467. *
  468. * @param {string} prop
  469. * The property name you want
  470. *
  471. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
  472. */
  473. function computedStyle(el, prop) {
  474. if (!el || !prop) {
  475. return '';
  476. }
  477. if (typeof window$1.getComputedStyle === 'function') {
  478. var cs = window$1.getComputedStyle(el);
  479. return cs ? cs[prop] : '';
  480. }
  481. return '';
  482. }
  483. function _templateObject() {
  484. var data = _taggedTemplateLiteralLoose(["Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ", " to ", "."]);
  485. _templateObject = function _templateObject() {
  486. return data;
  487. };
  488. return data;
  489. }
  490. /**
  491. * Detect if a value is a string with any non-whitespace characters.
  492. *
  493. * @private
  494. * @param {string} str
  495. * The string to check
  496. *
  497. * @return {boolean}
  498. * Will be `true` if the string is non-blank, `false` otherwise.
  499. *
  500. */
  501. function isNonBlankString(str) {
  502. return typeof str === 'string' && /\S/.test(str);
  503. }
  504. /**
  505. * Throws an error if the passed string has whitespace. This is used by
  506. * class methods to be relatively consistent with the classList API.
  507. *
  508. * @private
  509. * @param {string} str
  510. * The string to check for whitespace.
  511. *
  512. * @throws {Error}
  513. * Throws an error if there is whitespace in the string.
  514. */
  515. function throwIfWhitespace(str) {
  516. if (/\s/.test(str)) {
  517. throw new Error('class has illegal whitespace characters');
  518. }
  519. }
  520. /**
  521. * Produce a regular expression for matching a className within an elements className.
  522. *
  523. * @private
  524. * @param {string} className
  525. * The className to generate the RegExp for.
  526. *
  527. * @return {RegExp}
  528. * The RegExp that will check for a specific `className` in an elements
  529. * className.
  530. */
  531. function classRegExp(className) {
  532. return new RegExp('(^|\\s)' + className + '($|\\s)');
  533. }
  534. /**
  535. * Whether the current DOM interface appears to be real (i.e. not simulated).
  536. *
  537. * @return {boolean}
  538. * Will be `true` if the DOM appears to be real, `false` otherwise.
  539. */
  540. function isReal() {
  541. // Both document and window will never be undefined thanks to `global`.
  542. return document === window$1.document;
  543. }
  544. /**
  545. * Determines, via duck typing, whether or not a value is a DOM element.
  546. *
  547. * @param {Mixed} value
  548. * The value to check.
  549. *
  550. * @return {boolean}
  551. * Will be `true` if the value is a DOM element, `false` otherwise.
  552. */
  553. function isEl(value) {
  554. return isObject(value) && value.nodeType === 1;
  555. }
  556. /**
  557. * Determines if the current DOM is embedded in an iframe.
  558. *
  559. * @return {boolean}
  560. * Will be `true` if the DOM is embedded in an iframe, `false`
  561. * otherwise.
  562. */
  563. function isInFrame() {
  564. // We need a try/catch here because Safari will throw errors when attempting
  565. // to get either `parent` or `self`
  566. try {
  567. return window$1.parent !== window$1.self;
  568. } catch (x) {
  569. return true;
  570. }
  571. }
  572. /**
  573. * Creates functions to query the DOM using a given method.
  574. *
  575. * @private
  576. * @param {string} method
  577. * The method to create the query with.
  578. *
  579. * @return {Function}
  580. * The query method
  581. */
  582. function createQuerier(method) {
  583. return function (selector, context) {
  584. if (!isNonBlankString(selector)) {
  585. return document[method](null);
  586. }
  587. if (isNonBlankString(context)) {
  588. context = document.querySelector(context);
  589. }
  590. var ctx = isEl(context) ? context : document;
  591. return ctx[method] && ctx[method](selector);
  592. };
  593. }
  594. /**
  595. * Creates an element and applies properties, attributes, and inserts content.
  596. *
  597. * @param {string} [tagName='div']
  598. * Name of tag to be created.
  599. *
  600. * @param {Object} [properties={}]
  601. * Element properties to be applied.
  602. *
  603. * @param {Object} [attributes={}]
  604. * Element attributes to be applied.
  605. *
  606. * @param {module:dom~ContentDescriptor} content
  607. * A content descriptor object.
  608. *
  609. * @return {Element}
  610. * The element that was created.
  611. */
  612. function createEl(tagName, properties, attributes, content) {
  613. if (tagName === void 0) {
  614. tagName = 'div';
  615. }
  616. if (properties === void 0) {
  617. properties = {};
  618. }
  619. if (attributes === void 0) {
  620. attributes = {};
  621. }
  622. var el = document.createElement(tagName);
  623. Object.getOwnPropertyNames(properties).forEach(function (propName) {
  624. var val = properties[propName]; // See #2176
  625. // We originally were accepting both properties and attributes in the
  626. // same object, but that doesn't work so well.
  627. if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
  628. log.warn(tsml(_templateObject(), propName, val));
  629. el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a
  630. // method for it.
  631. } else if (propName === 'textContent') {
  632. textContent(el, val);
  633. } else {
  634. el[propName] = val;
  635. }
  636. });
  637. Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
  638. el.setAttribute(attrName, attributes[attrName]);
  639. });
  640. if (content) {
  641. appendContent(el, content);
  642. }
  643. return el;
  644. }
  645. /**
  646. * Injects text into an element, replacing any existing contents entirely.
  647. *
  648. * @param {Element} el
  649. * The element to add text content into
  650. *
  651. * @param {string} text
  652. * The text content to add.
  653. *
  654. * @return {Element}
  655. * The element with added text content.
  656. */
  657. function textContent(el, text) {
  658. if (typeof el.textContent === 'undefined') {
  659. el.innerText = text;
  660. } else {
  661. el.textContent = text;
  662. }
  663. return el;
  664. }
  665. /**
  666. * Insert an element as the first child node of another
  667. *
  668. * @param {Element} child
  669. * Element to insert
  670. *
  671. * @param {Element} parent
  672. * Element to insert child into
  673. */
  674. function prependTo(child, parent) {
  675. if (parent.firstChild) {
  676. parent.insertBefore(child, parent.firstChild);
  677. } else {
  678. parent.appendChild(child);
  679. }
  680. }
  681. /**
  682. * Check if an element has a class name.
  683. *
  684. * @param {Element} element
  685. * Element to check
  686. *
  687. * @param {string} classToCheck
  688. * Class name to check for
  689. *
  690. * @return {boolean}
  691. * Will be `true` if the element has a class, `false` otherwise.
  692. *
  693. * @throws {Error}
  694. * Throws an error if `classToCheck` has white space.
  695. */
  696. function hasClass(element, classToCheck) {
  697. throwIfWhitespace(classToCheck);
  698. if (element.classList) {
  699. return element.classList.contains(classToCheck);
  700. }
  701. return classRegExp(classToCheck).test(element.className);
  702. }
  703. /**
  704. * Add a class name to an element.
  705. *
  706. * @param {Element} element
  707. * Element to add class name to.
  708. *
  709. * @param {string} classToAdd
  710. * Class name to add.
  711. *
  712. * @return {Element}
  713. * The DOM element with the added class name.
  714. */
  715. function addClass(element, classToAdd) {
  716. if (element.classList) {
  717. element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
  718. // in the case of classList not being supported.
  719. } else if (!hasClass(element, classToAdd)) {
  720. element.className = (element.className + ' ' + classToAdd).trim();
  721. }
  722. return element;
  723. }
  724. /**
  725. * Remove a class name from an element.
  726. *
  727. * @param {Element} element
  728. * Element to remove a class name from.
  729. *
  730. * @param {string} classToRemove
  731. * Class name to remove
  732. *
  733. * @return {Element}
  734. * The DOM element with class name removed.
  735. */
  736. function removeClass(element, classToRemove) {
  737. if (element.classList) {
  738. element.classList.remove(classToRemove);
  739. } else {
  740. throwIfWhitespace(classToRemove);
  741. element.className = element.className.split(/\s+/).filter(function (c) {
  742. return c !== classToRemove;
  743. }).join(' ');
  744. }
  745. return element;
  746. }
  747. /**
  748. * The callback definition for toggleClass.
  749. *
  750. * @callback module:dom~PredicateCallback
  751. * @param {Element} element
  752. * The DOM element of the Component.
  753. *
  754. * @param {string} classToToggle
  755. * The `className` that wants to be toggled
  756. *
  757. * @return {boolean|undefined}
  758. * If `true` is returned, the `classToToggle` will be added to the
  759. * `element`. If `false`, the `classToToggle` will be removed from
  760. * the `element`. If `undefined`, the callback will be ignored.
  761. */
  762. /**
  763. * Adds or removes a class name to/from an element depending on an optional
  764. * condition or the presence/absence of the class name.
  765. *
  766. * @param {Element} element
  767. * The element to toggle a class name on.
  768. *
  769. * @param {string} classToToggle
  770. * The class that should be toggled.
  771. *
  772. * @param {boolean|module:dom~PredicateCallback} [predicate]
  773. * See the return value for {@link module:dom~PredicateCallback}
  774. *
  775. * @return {Element}
  776. * The element with a class that has been toggled.
  777. */
  778. function toggleClass(element, classToToggle, predicate) {
  779. // This CANNOT use `classList` internally because IE11 does not support the
  780. // second parameter to the `classList.toggle()` method! Which is fine because
  781. // `classList` will be used by the add/remove functions.
  782. var has = hasClass(element, classToToggle);
  783. if (typeof predicate === 'function') {
  784. predicate = predicate(element, classToToggle);
  785. }
  786. if (typeof predicate !== 'boolean') {
  787. predicate = !has;
  788. } // If the necessary class operation matches the current state of the
  789. // element, no action is required.
  790. if (predicate === has) {
  791. return;
  792. }
  793. if (predicate) {
  794. addClass(element, classToToggle);
  795. } else {
  796. removeClass(element, classToToggle);
  797. }
  798. return element;
  799. }
  800. /**
  801. * Apply attributes to an HTML element.
  802. *
  803. * @param {Element} el
  804. * Element to add attributes to.
  805. *
  806. * @param {Object} [attributes]
  807. * Attributes to be applied.
  808. */
  809. function setAttributes(el, attributes) {
  810. Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
  811. var attrValue = attributes[attrName];
  812. if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
  813. el.removeAttribute(attrName);
  814. } else {
  815. el.setAttribute(attrName, attrValue === true ? '' : attrValue);
  816. }
  817. });
  818. }
  819. /**
  820. * Get an element's attribute values, as defined on the HTML tag.
  821. *
  822. * Attributes are not the same as properties. They're defined on the tag
  823. * or with setAttribute.
  824. *
  825. * @param {Element} tag
  826. * Element from which to get tag attributes.
  827. *
  828. * @return {Object}
  829. * All attributes of the element. Boolean attributes will be `true` or
  830. * `false`, others will be strings.
  831. */
  832. function getAttributes(tag) {
  833. var obj = {}; // known boolean attributes
  834. // we can check for matching boolean properties, but not all browsers
  835. // and not all tags know about these attributes, so, we still want to check them manually
  836. var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
  837. if (tag && tag.attributes && tag.attributes.length > 0) {
  838. var attrs = tag.attributes;
  839. for (var i = attrs.length - 1; i >= 0; i--) {
  840. var attrName = attrs[i].name;
  841. var attrVal = attrs[i].value; // check for known booleans
  842. // the matching element property will return a value for typeof
  843. if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
  844. // the value of an included boolean attribute is typically an empty
  845. // string ('') which would equal false if we just check for a false value.
  846. // we also don't want support bad code like autoplay='false'
  847. attrVal = attrVal !== null ? true : false;
  848. }
  849. obj[attrName] = attrVal;
  850. }
  851. }
  852. return obj;
  853. }
  854. /**
  855. * Get the value of an element's attribute.
  856. *
  857. * @param {Element} el
  858. * A DOM element.
  859. *
  860. * @param {string} attribute
  861. * Attribute to get the value of.
  862. *
  863. * @return {string}
  864. * The value of the attribute.
  865. */
  866. function getAttribute(el, attribute) {
  867. return el.getAttribute(attribute);
  868. }
  869. /**
  870. * Set the value of an element's attribute.
  871. *
  872. * @param {Element} el
  873. * A DOM element.
  874. *
  875. * @param {string} attribute
  876. * Attribute to set.
  877. *
  878. * @param {string} value
  879. * Value to set the attribute to.
  880. */
  881. function setAttribute(el, attribute, value) {
  882. el.setAttribute(attribute, value);
  883. }
  884. /**
  885. * Remove an element's attribute.
  886. *
  887. * @param {Element} el
  888. * A DOM element.
  889. *
  890. * @param {string} attribute
  891. * Attribute to remove.
  892. */
  893. function removeAttribute(el, attribute) {
  894. el.removeAttribute(attribute);
  895. }
  896. /**
  897. * Attempt to block the ability to select text.
  898. */
  899. function blockTextSelection() {
  900. document.body.focus();
  901. document.onselectstart = function () {
  902. return false;
  903. };
  904. }
  905. /**
  906. * Turn off text selection blocking.
  907. */
  908. function unblockTextSelection() {
  909. document.onselectstart = function () {
  910. return true;
  911. };
  912. }
  913. /**
  914. * Identical to the native `getBoundingClientRect` function, but ensures that
  915. * the method is supported at all (it is in all browsers we claim to support)
  916. * and that the element is in the DOM before continuing.
  917. *
  918. * This wrapper function also shims properties which are not provided by some
  919. * older browsers (namely, IE8).
  920. *
  921. * Additionally, some browsers do not support adding properties to a
  922. * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
  923. * properties (except `x` and `y` which are not widely supported). This helps
  924. * avoid implementations where keys are non-enumerable.
  925. *
  926. * @param {Element} el
  927. * Element whose `ClientRect` we want to calculate.
  928. *
  929. * @return {Object|undefined}
  930. * Always returns a plain object - or `undefined` if it cannot.
  931. */
  932. function getBoundingClientRect(el) {
  933. if (el && el.getBoundingClientRect && el.parentNode) {
  934. var rect = el.getBoundingClientRect();
  935. var result = {};
  936. ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
  937. if (rect[k] !== undefined) {
  938. result[k] = rect[k];
  939. }
  940. });
  941. if (!result.height) {
  942. result.height = parseFloat(computedStyle(el, 'height'));
  943. }
  944. if (!result.width) {
  945. result.width = parseFloat(computedStyle(el, 'width'));
  946. }
  947. return result;
  948. }
  949. }
  950. /**
  951. * Represents the position of a DOM element on the page.
  952. *
  953. * @typedef {Object} module:dom~Position
  954. *
  955. * @property {number} left
  956. * Pixels to the left.
  957. *
  958. * @property {number} top
  959. * Pixels from the top.
  960. */
  961. /**
  962. * Get the position of an element in the DOM.
  963. *
  964. * Uses `getBoundingClientRect` technique from John Resig.
  965. *
  966. * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
  967. *
  968. * @param {Element} el
  969. * Element from which to get offset.
  970. *
  971. * @return {module:dom~Position}
  972. * The position of the element that was passed in.
  973. */
  974. function findPosition(el) {
  975. var box;
  976. if (el.getBoundingClientRect && el.parentNode) {
  977. box = el.getBoundingClientRect();
  978. }
  979. if (!box) {
  980. return {
  981. left: 0,
  982. top: 0
  983. };
  984. }
  985. var docEl = document.documentElement;
  986. var body = document.body;
  987. var clientLeft = docEl.clientLeft || body.clientLeft || 0;
  988. var scrollLeft = window$1.pageXOffset || body.scrollLeft;
  989. var left = box.left + scrollLeft - clientLeft;
  990. var clientTop = docEl.clientTop || body.clientTop || 0;
  991. var scrollTop = window$1.pageYOffset || body.scrollTop;
  992. var top = box.top + scrollTop - clientTop; // Android sometimes returns slightly off decimal values, so need to round
  993. return {
  994. left: Math.round(left),
  995. top: Math.round(top)
  996. };
  997. }
  998. /**
  999. * Represents x and y coordinates for a DOM element or mouse pointer.
  1000. *
  1001. * @typedef {Object} module:dom~Coordinates
  1002. *
  1003. * @property {number} x
  1004. * x coordinate in pixels
  1005. *
  1006. * @property {number} y
  1007. * y coordinate in pixels
  1008. */
  1009. /**
  1010. * Get the pointer position within an element.
  1011. *
  1012. * The base on the coordinates are the bottom left of the element.
  1013. *
  1014. * @param {Element} el
  1015. * Element on which to get the pointer position on.
  1016. *
  1017. * @param {EventTarget~Event} event
  1018. * Event object.
  1019. *
  1020. * @return {module:dom~Coordinates}
  1021. * A coordinates object corresponding to the mouse position.
  1022. *
  1023. */
  1024. function getPointerPosition(el, event) {
  1025. var position = {};
  1026. var box = findPosition(el);
  1027. var boxW = el.offsetWidth;
  1028. var boxH = el.offsetHeight;
  1029. var boxY = box.top;
  1030. var boxX = box.left;
  1031. var pageY = event.pageY;
  1032. var pageX = event.pageX;
  1033. if (event.changedTouches) {
  1034. pageX = event.changedTouches[0].pageX;
  1035. pageY = event.changedTouches[0].pageY;
  1036. }
  1037. position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
  1038. position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
  1039. return position;
  1040. }
  1041. /**
  1042. * Determines, via duck typing, whether or not a value is a text node.
  1043. *
  1044. * @param {Mixed} value
  1045. * Check if this value is a text node.
  1046. *
  1047. * @return {boolean}
  1048. * Will be `true` if the value is a text node, `false` otherwise.
  1049. */
  1050. function isTextNode(value) {
  1051. return isObject(value) && value.nodeType === 3;
  1052. }
  1053. /**
  1054. * Empties the contents of an element.
  1055. *
  1056. * @param {Element} el
  1057. * The element to empty children from
  1058. *
  1059. * @return {Element}
  1060. * The element with no children
  1061. */
  1062. function emptyEl(el) {
  1063. while (el.firstChild) {
  1064. el.removeChild(el.firstChild);
  1065. }
  1066. return el;
  1067. }
  1068. /**
  1069. * This is a mixed value that describes content to be injected into the DOM
  1070. * via some method. It can be of the following types:
  1071. *
  1072. * Type | Description
  1073. * -----------|-------------
  1074. * `string` | The value will be normalized into a text node.
  1075. * `Element` | The value will be accepted as-is.
  1076. * `TextNode` | The value will be accepted as-is.
  1077. * `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).
  1078. * `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.
  1079. *
  1080. * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor
  1081. */
  1082. /**
  1083. * Normalizes content for eventual insertion into the DOM.
  1084. *
  1085. * This allows a wide range of content definition methods, but helps protect
  1086. * from falling into the trap of simply writing to `innerHTML`, which could
  1087. * be an XSS concern.
  1088. *
  1089. * The content for an element can be passed in multiple types and
  1090. * combinations, whose behavior is as follows:
  1091. *
  1092. * @param {module:dom~ContentDescriptor} content
  1093. * A content descriptor value.
  1094. *
  1095. * @return {Array}
  1096. * All of the content that was passed in, normalized to an array of
  1097. * elements or text nodes.
  1098. */
  1099. function normalizeContent(content) {
  1100. // First, invoke content if it is a function. If it produces an array,
  1101. // that needs to happen before normalization.
  1102. if (typeof content === 'function') {
  1103. content = content();
  1104. } // Next up, normalize to an array, so one or many items can be normalized,
  1105. // filtered, and returned.
  1106. return (Array.isArray(content) ? content : [content]).map(function (value) {
  1107. // First, invoke value if it is a function to produce a new value,
  1108. // which will be subsequently normalized to a Node of some kind.
  1109. if (typeof value === 'function') {
  1110. value = value();
  1111. }
  1112. if (isEl(value) || isTextNode(value)) {
  1113. return value;
  1114. }
  1115. if (typeof value === 'string' && /\S/.test(value)) {
  1116. return document.createTextNode(value);
  1117. }
  1118. }).filter(function (value) {
  1119. return value;
  1120. });
  1121. }
  1122. /**
  1123. * Normalizes and appends content to an element.
  1124. *
  1125. * @param {Element} el
  1126. * Element to append normalized content to.
  1127. *
  1128. * @param {module:dom~ContentDescriptor} content
  1129. * A content descriptor value.
  1130. *
  1131. * @return {Element}
  1132. * The element with appended normalized content.
  1133. */
  1134. function appendContent(el, content) {
  1135. normalizeContent(content).forEach(function (node) {
  1136. return el.appendChild(node);
  1137. });
  1138. return el;
  1139. }
  1140. /**
  1141. * Normalizes and inserts content into an element; this is identical to
  1142. * `appendContent()`, except it empties the element first.
  1143. *
  1144. * @param {Element} el
  1145. * Element to insert normalized content into.
  1146. *
  1147. * @param {module:dom~ContentDescriptor} content
  1148. * A content descriptor value.
  1149. *
  1150. * @return {Element}
  1151. * The element with inserted normalized content.
  1152. */
  1153. function insertContent(el, content) {
  1154. return appendContent(emptyEl(el), content);
  1155. }
  1156. /**
  1157. * Check if an event was a single left click.
  1158. *
  1159. * @param {EventTarget~Event} event
  1160. * Event object.
  1161. *
  1162. * @return {boolean}
  1163. * Will be `true` if a single left click, `false` otherwise.
  1164. */
  1165. function isSingleLeftClick(event) {
  1166. // Note: if you create something draggable, be sure to
  1167. // call it on both `mousedown` and `mousemove` event,
  1168. // otherwise `mousedown` should be enough for a button
  1169. if (event.button === undefined && event.buttons === undefined) {
  1170. // Why do we need `buttons` ?
  1171. // Because, middle mouse sometimes have this:
  1172. // e.button === 0 and e.buttons === 4
  1173. // Furthermore, we want to prevent combination click, something like
  1174. // HOLD middlemouse then left click, that would be
  1175. // e.button === 0, e.buttons === 5
  1176. // just `button` is not gonna work
  1177. // Alright, then what this block does ?
  1178. // this is for chrome `simulate mobile devices`
  1179. // I want to support this as well
  1180. return true;
  1181. }
  1182. if (event.button === 0 && event.buttons === undefined) {
  1183. // Touch screen, sometimes on some specific device, `buttons`
  1184. // doesn't have anything (safari on ios, blackberry...)
  1185. return true;
  1186. }
  1187. if (event.button !== 0 || event.buttons !== 1) {
  1188. // This is the reason we have those if else block above
  1189. // if any special case we can catch and let it slide
  1190. // we do it above, when get to here, this definitely
  1191. // is-not-left-click
  1192. return false;
  1193. }
  1194. return true;
  1195. }
  1196. /**
  1197. * Finds a single DOM element matching `selector` within the optional
  1198. * `context` of another DOM element (defaulting to `document`).
  1199. *
  1200. * @param {string} selector
  1201. * A valid CSS selector, which will be passed to `querySelector`.
  1202. *
  1203. * @param {Element|String} [context=document]
  1204. * A DOM element within which to query. Can also be a selector
  1205. * string in which case the first matching element will be used
  1206. * as context. If missing (or no element matches selector), falls
  1207. * back to `document`.
  1208. *
  1209. * @return {Element|null}
  1210. * The element that was found or null.
  1211. */
  1212. var $ = createQuerier('querySelector');
  1213. /**
  1214. * Finds a all DOM elements matching `selector` within the optional
  1215. * `context` of another DOM element (defaulting to `document`).
  1216. *
  1217. * @param {string} selector
  1218. * A valid CSS selector, which will be passed to `querySelectorAll`.
  1219. *
  1220. * @param {Element|String} [context=document]
  1221. * A DOM element within which to query. Can also be a selector
  1222. * string in which case the first matching element will be used
  1223. * as context. If missing (or no element matches selector), falls
  1224. * back to `document`.
  1225. *
  1226. * @return {NodeList}
  1227. * A element list of elements that were found. Will be empty if none
  1228. * were found.
  1229. *
  1230. */
  1231. var $$ = createQuerier('querySelectorAll');
  1232. var Dom = /*#__PURE__*/Object.freeze({
  1233. isReal: isReal,
  1234. isEl: isEl,
  1235. isInFrame: isInFrame,
  1236. createEl: createEl,
  1237. textContent: textContent,
  1238. prependTo: prependTo,
  1239. hasClass: hasClass,
  1240. addClass: addClass,
  1241. removeClass: removeClass,
  1242. toggleClass: toggleClass,
  1243. setAttributes: setAttributes,
  1244. getAttributes: getAttributes,
  1245. getAttribute: getAttribute,
  1246. setAttribute: setAttribute,
  1247. removeAttribute: removeAttribute,
  1248. blockTextSelection: blockTextSelection,
  1249. unblockTextSelection: unblockTextSelection,
  1250. getBoundingClientRect: getBoundingClientRect,
  1251. findPosition: findPosition,
  1252. getPointerPosition: getPointerPosition,
  1253. isTextNode: isTextNode,
  1254. emptyEl: emptyEl,
  1255. normalizeContent: normalizeContent,
  1256. appendContent: appendContent,
  1257. insertContent: insertContent,
  1258. isSingleLeftClick: isSingleLeftClick,
  1259. $: $,
  1260. $$: $$
  1261. });
  1262. /**
  1263. * @file guid.js
  1264. * @module guid
  1265. */
  1266. /**
  1267. * Unique ID for an element or function
  1268. * @type {Number}
  1269. */
  1270. var _guid = 1;
  1271. /**
  1272. * Get a unique auto-incrementing ID by number that has not been returned before.
  1273. *
  1274. * @return {number}
  1275. * A new unique ID.
  1276. */
  1277. function newGUID() {
  1278. return _guid++;
  1279. }
  1280. /**
  1281. * @file dom-data.js
  1282. * @module dom-data
  1283. */
  1284. /**
  1285. * Element Data Store.
  1286. *
  1287. * Allows for binding data to an element without putting it directly on the
  1288. * element. Ex. Event listeners are stored here.
  1289. * (also from jsninja.com, slightly modified and updated for closure compiler)
  1290. *
  1291. * @type {Object}
  1292. * @private
  1293. */
  1294. var elData = {};
  1295. /*
  1296. * Unique attribute name to store an element's guid in
  1297. *
  1298. * @type {String}
  1299. * @constant
  1300. * @private
  1301. */
  1302. var elIdAttr = 'vdata' + Math.floor(window$1.performance && window$1.performance.now() || Date.now());
  1303. /**
  1304. * Returns the cache object where data for an element is stored
  1305. *
  1306. * @param {Element} el
  1307. * Element to store data for.
  1308. *
  1309. * @return {Object}
  1310. * The cache object for that el that was passed in.
  1311. */
  1312. function getData(el) {
  1313. var id = el[elIdAttr];
  1314. if (!id) {
  1315. id = el[elIdAttr] = newGUID();
  1316. }
  1317. if (!elData[id]) {
  1318. elData[id] = {};
  1319. }
  1320. return elData[id];
  1321. }
  1322. /**
  1323. * Returns whether or not an element has cached data
  1324. *
  1325. * @param {Element} el
  1326. * Check if this element has cached data.
  1327. *
  1328. * @return {boolean}
  1329. * - True if the DOM element has cached data.
  1330. * - False otherwise.
  1331. */
  1332. function hasData(el) {
  1333. var id = el[elIdAttr];
  1334. if (!id) {
  1335. return false;
  1336. }
  1337. return !!Object.getOwnPropertyNames(elData[id]).length;
  1338. }
  1339. /**
  1340. * Delete data for the element from the cache and the guid attr from getElementById
  1341. *
  1342. * @param {Element} el
  1343. * Remove cached data for this element.
  1344. */
  1345. function removeData(el) {
  1346. var id = el[elIdAttr];
  1347. if (!id) {
  1348. return;
  1349. } // Remove all stored data
  1350. delete elData[id]; // Remove the elIdAttr property from the DOM node
  1351. try {
  1352. delete el[elIdAttr];
  1353. } catch (e) {
  1354. if (el.removeAttribute) {
  1355. el.removeAttribute(elIdAttr);
  1356. } else {
  1357. // IE doesn't appear to support removeAttribute on the document element
  1358. el[elIdAttr] = null;
  1359. }
  1360. }
  1361. }
  1362. /**
  1363. * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
  1364. * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
  1365. * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
  1366. * robust as jquery's, so there's probably some differences.
  1367. *
  1368. * @file events.js
  1369. * @module events
  1370. */
  1371. /**
  1372. * Clean up the listener cache and dispatchers
  1373. *
  1374. * @param {Element|Object} elem
  1375. * Element to clean up
  1376. *
  1377. * @param {string} type
  1378. * Type of event to clean up
  1379. */
  1380. function _cleanUpEvents(elem, type) {
  1381. var data = getData(elem); // Remove the events of a particular type if there are none left
  1382. if (data.handlers[type].length === 0) {
  1383. delete data.handlers[type]; // data.handlers[type] = null;
  1384. // Setting to null was causing an error with data.handlers
  1385. // Remove the meta-handler from the element
  1386. if (elem.removeEventListener) {
  1387. elem.removeEventListener(type, data.dispatcher, false);
  1388. } else if (elem.detachEvent) {
  1389. elem.detachEvent('on' + type, data.dispatcher);
  1390. }
  1391. } // Remove the events object if there are no types left
  1392. if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
  1393. delete data.handlers;
  1394. delete data.dispatcher;
  1395. delete data.disabled;
  1396. } // Finally remove the element data if there is no data left
  1397. if (Object.getOwnPropertyNames(data).length === 0) {
  1398. removeData(elem);
  1399. }
  1400. }
  1401. /**
  1402. * Loops through an array of event types and calls the requested method for each type.
  1403. *
  1404. * @param {Function} fn
  1405. * The event method we want to use.
  1406. *
  1407. * @param {Element|Object} elem
  1408. * Element or object to bind listeners to
  1409. *
  1410. * @param {string} type
  1411. * Type of event to bind to.
  1412. *
  1413. * @param {EventTarget~EventListener} callback
  1414. * Event listener.
  1415. */
  1416. function _handleMultipleEvents(fn, elem, types, callback) {
  1417. types.forEach(function (type) {
  1418. // Call the event method for each one of the types
  1419. fn(elem, type, callback);
  1420. });
  1421. }
  1422. /**
  1423. * Fix a native event to have standard property values
  1424. *
  1425. * @param {Object} event
  1426. * Event object to fix.
  1427. *
  1428. * @return {Object}
  1429. * Fixed event object.
  1430. */
  1431. function fixEvent(event) {
  1432. function returnTrue() {
  1433. return true;
  1434. }
  1435. function returnFalse() {
  1436. return false;
  1437. } // Test if fixing up is needed
  1438. // Used to check if !event.stopPropagation instead of isPropagationStopped
  1439. // But native events return true for stopPropagation, but don't have
  1440. // other expected methods like isPropagationStopped. Seems to be a problem
  1441. // with the Javascript Ninja code. So we're just overriding all events now.
  1442. if (!event || !event.isPropagationStopped) {
  1443. var old = event || window$1.event;
  1444. event = {}; // Clone the old object so that we can modify the values event = {};
  1445. // IE8 Doesn't like when you mess with native event properties
  1446. // Firefox returns false for event.hasOwnProperty('type') and other props
  1447. // which makes copying more difficult.
  1448. // TODO: Probably best to create a whitelist of event props
  1449. for (var key in old) {
  1450. // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
  1451. // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
  1452. // and webkitMovementX/Y
  1453. if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
  1454. // Chrome 32+ warns if you try to copy deprecated returnValue, but
  1455. // we still want to if preventDefault isn't supported (IE8).
  1456. if (!(key === 'returnValue' && old.preventDefault)) {
  1457. event[key] = old[key];
  1458. }
  1459. }
  1460. } // The event occurred on this element
  1461. if (!event.target) {
  1462. event.target = event.srcElement || document;
  1463. } // Handle which other element the event is related to
  1464. if (!event.relatedTarget) {
  1465. event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
  1466. } // Stop the default browser action
  1467. event.preventDefault = function () {
  1468. if (old.preventDefault) {
  1469. old.preventDefault();
  1470. }
  1471. event.returnValue = false;
  1472. old.returnValue = false;
  1473. event.defaultPrevented = true;
  1474. };
  1475. event.defaultPrevented = false; // Stop the event from bubbling
  1476. event.stopPropagation = function () {
  1477. if (old.stopPropagation) {
  1478. old.stopPropagation();
  1479. }
  1480. event.cancelBubble = true;
  1481. old.cancelBubble = true;
  1482. event.isPropagationStopped = returnTrue;
  1483. };
  1484. event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers
  1485. event.stopImmediatePropagation = function () {
  1486. if (old.stopImmediatePropagation) {
  1487. old.stopImmediatePropagation();
  1488. }
  1489. event.isImmediatePropagationStopped = returnTrue;
  1490. event.stopPropagation();
  1491. };
  1492. event.isImmediatePropagationStopped = returnFalse; // Handle mouse position
  1493. if (event.clientX !== null && event.clientX !== undefined) {
  1494. var doc = document.documentElement;
  1495. var body = document.body;
  1496. event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
  1497. event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
  1498. } // Handle key presses
  1499. event.which = event.charCode || event.keyCode; // Fix button for mouse clicks:
  1500. // 0 == left; 1 == middle; 2 == right
  1501. if (event.button !== null && event.button !== undefined) {
  1502. // The following is disabled because it does not pass videojs-standard
  1503. // and... yikes.
  1504. /* eslint-disable */
  1505. event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
  1506. /* eslint-enable */
  1507. }
  1508. } // Returns fixed-up instance
  1509. return event;
  1510. }
  1511. /**
  1512. * Whether passive event listeners are supported
  1513. */
  1514. var _supportsPassive = false;
  1515. (function () {
  1516. try {
  1517. var opts = Object.defineProperty({}, 'passive', {
  1518. get: function get() {
  1519. _supportsPassive = true;
  1520. }
  1521. });
  1522. window$1.addEventListener('test', null, opts);
  1523. window$1.removeEventListener('test', null, opts);
  1524. } catch (e) {// disregard
  1525. }
  1526. })();
  1527. /**
  1528. * Touch events Chrome expects to be passive
  1529. */
  1530. var passiveEvents = ['touchstart', 'touchmove'];
  1531. /**
  1532. * Add an event listener to element
  1533. * It stores the handler function in a separate cache object
  1534. * and adds a generic handler to the element's event,
  1535. * along with a unique id (guid) to the element.
  1536. *
  1537. * @param {Element|Object} elem
  1538. * Element or object to bind listeners to
  1539. *
  1540. * @param {string|string[]} type
  1541. * Type of event to bind to.
  1542. *
  1543. * @param {EventTarget~EventListener} fn
  1544. * Event listener.
  1545. */
  1546. function on(elem, type, fn) {
  1547. if (Array.isArray(type)) {
  1548. return _handleMultipleEvents(on, elem, type, fn);
  1549. }
  1550. var data = getData(elem); // We need a place to store all our handler data
  1551. if (!data.handlers) {
  1552. data.handlers = {};
  1553. }
  1554. if (!data.handlers[type]) {
  1555. data.handlers[type] = [];
  1556. }
  1557. if (!fn.guid) {
  1558. fn.guid = newGUID();
  1559. }
  1560. data.handlers[type].push(fn);
  1561. if (!data.dispatcher) {
  1562. data.disabled = false;
  1563. data.dispatcher = function (event, hash) {
  1564. if (data.disabled) {
  1565. return;
  1566. }
  1567. event = fixEvent(event);
  1568. var handlers = data.handlers[event.type];
  1569. if (handlers) {
  1570. // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
  1571. var handlersCopy = handlers.slice(0);
  1572. for (var m = 0, n = handlersCopy.length; m < n; m++) {
  1573. if (event.isImmediatePropagationStopped()) {
  1574. break;
  1575. } else {
  1576. try {
  1577. handlersCopy[m].call(elem, event, hash);
  1578. } catch (e) {
  1579. log.error(e);
  1580. }
  1581. }
  1582. }
  1583. }
  1584. };
  1585. }
  1586. if (data.handlers[type].length === 1) {
  1587. if (elem.addEventListener) {
  1588. var options = false;
  1589. if (_supportsPassive && passiveEvents.indexOf(type) > -1) {
  1590. options = {
  1591. passive: true
  1592. };
  1593. }
  1594. elem.addEventListener(type, data.dispatcher, options);
  1595. } else if (elem.attachEvent) {
  1596. elem.attachEvent('on' + type, data.dispatcher);
  1597. }
  1598. }
  1599. }
  1600. /**
  1601. * Removes event listeners from an element
  1602. *
  1603. * @param {Element|Object} elem
  1604. * Object to remove listeners from.
  1605. *
  1606. * @param {string|string[]} [type]
  1607. * Type of listener to remove. Don't include to remove all events from element.
  1608. *
  1609. * @param {EventTarget~EventListener} [fn]
  1610. * Specific listener to remove. Don't include to remove listeners for an event
  1611. * type.
  1612. */
  1613. function off(elem, type, fn) {
  1614. // Don't want to add a cache object through getElData if not needed
  1615. if (!hasData(elem)) {
  1616. return;
  1617. }
  1618. var data = getData(elem); // If no events exist, nothing to unbind
  1619. if (!data.handlers) {
  1620. return;
  1621. }
  1622. if (Array.isArray(type)) {
  1623. return _handleMultipleEvents(off, elem, type, fn);
  1624. } // Utility function
  1625. var removeType = function removeType(el, t) {
  1626. data.handlers[t] = [];
  1627. _cleanUpEvents(el, t);
  1628. }; // Are we removing all bound events?
  1629. if (type === undefined) {
  1630. for (var t in data.handlers) {
  1631. if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
  1632. removeType(elem, t);
  1633. }
  1634. }
  1635. return;
  1636. }
  1637. var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind
  1638. if (!handlers) {
  1639. return;
  1640. } // If no listener was provided, remove all listeners for type
  1641. if (!fn) {
  1642. removeType(elem, type);
  1643. return;
  1644. } // We're only removing a single handler
  1645. if (fn.guid) {
  1646. for (var n = 0; n < handlers.length; n++) {
  1647. if (handlers[n].guid === fn.guid) {
  1648. handlers.splice(n--, 1);
  1649. }
  1650. }
  1651. }
  1652. _cleanUpEvents(elem, type);
  1653. }
  1654. /**
  1655. * Trigger an event for an element
  1656. *
  1657. * @param {Element|Object} elem
  1658. * Element to trigger an event on
  1659. *
  1660. * @param {EventTarget~Event|string} event
  1661. * A string (the type) or an event object with a type attribute
  1662. *
  1663. * @param {Object} [hash]
  1664. * data hash to pass along with the event
  1665. *
  1666. * @return {boolean|undefined}
  1667. * Returns the opposite of `defaultPrevented` if default was
  1668. * prevented. Otherwise, returns `undefined`
  1669. */
  1670. function trigger(elem, event, hash) {
  1671. // Fetches element data and a reference to the parent (for bubbling).
  1672. // Don't want to add a data object to cache for every parent,
  1673. // so checking hasElData first.
  1674. var elemData = hasData(elem) ? getData(elem) : {};
  1675. var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event,
  1676. // handler;
  1677. // If an event name was passed as a string, creates an event out of it
  1678. if (typeof event === 'string') {
  1679. event = {
  1680. type: event,
  1681. target: elem
  1682. };
  1683. } else if (!event.target) {
  1684. event.target = elem;
  1685. } // Normalizes the event properties.
  1686. event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers.
  1687. if (elemData.dispatcher) {
  1688. elemData.dispatcher.call(elem, event, hash);
  1689. } // Unless explicitly stopped or the event does not bubble (e.g. media events)
  1690. // recursively calls this function to bubble the event up the DOM.
  1691. if (parent && !event.isPropagationStopped() && event.bubbles === true) {
  1692. trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled.
  1693. } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
  1694. var targetData = getData(event.target); // Checks if the target has a default action for this event.
  1695. if (event.target[event.type]) {
  1696. // Temporarily disables event dispatching on the target as we have already executed the handler.
  1697. targetData.disabled = true; // Executes the default action.
  1698. if (typeof event.target[event.type] === 'function') {
  1699. event.target[event.type]();
  1700. } // Re-enables event dispatching.
  1701. targetData.disabled = false;
  1702. }
  1703. } // Inform the triggerer if the default was prevented by returning false
  1704. return !event.defaultPrevented;
  1705. }
  1706. /**
  1707. * Trigger a listener only once for an event.
  1708. *
  1709. * @param {Element|Object} elem
  1710. * Element or object to bind to.
  1711. *
  1712. * @param {string|string[]} type
  1713. * Name/type of event
  1714. *
  1715. * @param {Event~EventListener} fn
  1716. * Event listener function
  1717. */
  1718. function one(elem, type, fn) {
  1719. if (Array.isArray(type)) {
  1720. return _handleMultipleEvents(one, elem, type, fn);
  1721. }
  1722. var func = function func() {
  1723. off(elem, type, func);
  1724. fn.apply(this, arguments);
  1725. }; // copy the guid to the new function so it can removed using the original function's ID
  1726. func.guid = fn.guid = fn.guid || newGUID();
  1727. on(elem, type, func);
  1728. }
  1729. var Events = /*#__PURE__*/Object.freeze({
  1730. fixEvent: fixEvent,
  1731. on: on,
  1732. off: off,
  1733. trigger: trigger,
  1734. one: one
  1735. });
  1736. /**
  1737. * @file setup.js - Functions for setting up a player without
  1738. * user interaction based on the data-setup `attribute` of the video tag.
  1739. *
  1740. * @module setup
  1741. */
  1742. var _windowLoaded = false;
  1743. var videojs;
  1744. /**
  1745. * Set up any tags that have a data-setup `attribute` when the player is started.
  1746. */
  1747. var autoSetup = function autoSetup() {
  1748. // Protect against breakage in non-browser environments and check global autoSetup option.
  1749. if (!isReal() || videojs.options.autoSetup === false) {
  1750. return;
  1751. }
  1752. var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
  1753. var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
  1754. var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js'));
  1755. var mediaEls = vids.concat(audios, divs); // Check if any media elements exist
  1756. if (mediaEls && mediaEls.length > 0) {
  1757. for (var i = 0, e = mediaEls.length; i < e; i++) {
  1758. var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func.
  1759. if (mediaEl && mediaEl.getAttribute) {
  1760. // Make sure this player hasn't already been set up.
  1761. if (mediaEl.player === undefined) {
  1762. var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists.
  1763. // We only auto-setup if they've added the data-setup attr.
  1764. if (options !== null) {
  1765. // Create new video.js instance.
  1766. videojs(mediaEl);
  1767. }
  1768. } // If getAttribute isn't defined, we need to wait for the DOM.
  1769. } else {
  1770. autoSetupTimeout(1);
  1771. break;
  1772. }
  1773. } // No videos were found, so keep looping unless page is finished loading.
  1774. } else if (!_windowLoaded) {
  1775. autoSetupTimeout(1);
  1776. }
  1777. };
  1778. /**
  1779. * Wait until the page is loaded before running autoSetup. This will be called in
  1780. * autoSetup if `hasLoaded` returns false.
  1781. *
  1782. * @param {number} wait
  1783. * How long to wait in ms
  1784. *
  1785. * @param {module:videojs} [vjs]
  1786. * The videojs library function
  1787. */
  1788. function autoSetupTimeout(wait, vjs) {
  1789. if (vjs) {
  1790. videojs = vjs;
  1791. }
  1792. window$1.setTimeout(autoSetup, wait);
  1793. }
  1794. if (isReal() && document.readyState === 'complete') {
  1795. _windowLoaded = true;
  1796. } else {
  1797. /**
  1798. * Listen for the load event on window, and set _windowLoaded to true.
  1799. *
  1800. * @listens load
  1801. */
  1802. one(window$1, 'load', function () {
  1803. _windowLoaded = true;
  1804. });
  1805. }
  1806. /**
  1807. * @file stylesheet.js
  1808. * @module stylesheet
  1809. */
  1810. /**
  1811. * Create a DOM syle element given a className for it.
  1812. *
  1813. * @param {string} className
  1814. * The className to add to the created style element.
  1815. *
  1816. * @return {Element}
  1817. * The element that was created.
  1818. */
  1819. var createStyleElement = function createStyleElement(className) {
  1820. var style = document.createElement('style');
  1821. style.className = className;
  1822. return style;
  1823. };
  1824. /**
  1825. * Add text to a DOM element.
  1826. *
  1827. * @param {Element} el
  1828. * The Element to add text content to.
  1829. *
  1830. * @param {string} content
  1831. * The text to add to the element.
  1832. */
  1833. var setTextContent = function setTextContent(el, content) {
  1834. if (el.styleSheet) {
  1835. el.styleSheet.cssText = content;
  1836. } else {
  1837. el.textContent = content;
  1838. }
  1839. };
  1840. /**
  1841. * @file fn.js
  1842. * @module fn
  1843. */
  1844. /**
  1845. * Bind (a.k.a proxy or context). A simple method for changing the context of
  1846. * a function.
  1847. *
  1848. * It also stores a unique id on the function so it can be easily removed from
  1849. * events.
  1850. *
  1851. * @function
  1852. * @param {Mixed} context
  1853. * The object to bind as scope.
  1854. *
  1855. * @param {Function} fn
  1856. * The function to be bound to a scope.
  1857. *
  1858. * @param {number} [uid]
  1859. * An optional unique ID for the function to be set
  1860. *
  1861. * @return {Function}
  1862. * The new function that will be bound into the context given
  1863. */
  1864. var bind = function bind(context, fn, uid) {
  1865. // Make sure the function has a unique ID
  1866. if (!fn.guid) {
  1867. fn.guid = newGUID();
  1868. } // Create the new function that changes the context
  1869. var bound = function bound() {
  1870. return fn.apply(context, arguments);
  1871. }; // Allow for the ability to individualize this function
  1872. // Needed in the case where multiple objects might share the same prototype
  1873. // IF both items add an event listener with the same function, then you try to remove just one
  1874. // it will remove both because they both have the same guid.
  1875. // when using this, you need to use the bind method when you remove the listener as well.
  1876. // currently used in text tracks
  1877. bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
  1878. return bound;
  1879. };
  1880. /**
  1881. * Wraps the given function, `fn`, with a new function that only invokes `fn`
  1882. * at most once per every `wait` milliseconds.
  1883. *
  1884. * @function
  1885. * @param {Function} fn
  1886. * The function to be throttled.
  1887. *
  1888. * @param {number} wait
  1889. * The number of milliseconds by which to throttle.
  1890. *
  1891. * @return {Function}
  1892. */
  1893. var throttle = function throttle(fn, wait) {
  1894. var last = window$1.performance.now();
  1895. var throttled = function throttled() {
  1896. var now = window$1.performance.now();
  1897. if (now - last >= wait) {
  1898. fn.apply(void 0, arguments);
  1899. last = now;
  1900. }
  1901. };
  1902. return throttled;
  1903. };
  1904. /**
  1905. * Creates a debounced function that delays invoking `func` until after `wait`
  1906. * milliseconds have elapsed since the last time the debounced function was
  1907. * invoked.
  1908. *
  1909. * Inspired by lodash and underscore implementations.
  1910. *
  1911. * @function
  1912. * @param {Function} func
  1913. * The function to wrap with debounce behavior.
  1914. *
  1915. * @param {number} wait
  1916. * The number of milliseconds to wait after the last invocation.
  1917. *
  1918. * @param {boolean} [immediate]
  1919. * Whether or not to invoke the function immediately upon creation.
  1920. *
  1921. * @param {Object} [context=window]
  1922. * The "context" in which the debounced function should debounce. For
  1923. * example, if this function should be tied to a Video.js player,
  1924. * the player can be passed here. Alternatively, defaults to the
  1925. * global `window` object.
  1926. *
  1927. * @return {Function}
  1928. * A debounced function.
  1929. */
  1930. var debounce = function debounce(func, wait, immediate, context) {
  1931. if (context === void 0) {
  1932. context = window$1;
  1933. }
  1934. var timeout;
  1935. var cancel = function cancel() {
  1936. context.clearTimeout(timeout);
  1937. timeout = null;
  1938. };
  1939. /* eslint-disable consistent-this */
  1940. var debounced = function debounced() {
  1941. var self = this;
  1942. var args = arguments;
  1943. var _later = function later() {
  1944. timeout = null;
  1945. _later = null;
  1946. if (!immediate) {
  1947. func.apply(self, args);
  1948. }
  1949. };
  1950. if (!timeout && immediate) {
  1951. func.apply(self, args);
  1952. }
  1953. context.clearTimeout(timeout);
  1954. timeout = context.setTimeout(_later, wait);
  1955. };
  1956. /* eslint-enable consistent-this */
  1957. debounced.cancel = cancel;
  1958. return debounced;
  1959. };
  1960. /**
  1961. * @file src/js/event-target.js
  1962. */
  1963. /**
  1964. * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
  1965. * adds shorthand functions that wrap around lengthy functions. For example:
  1966. * the `on` function is a wrapper around `addEventListener`.
  1967. *
  1968. * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
  1969. * @class EventTarget
  1970. */
  1971. var EventTarget = function EventTarget() {};
  1972. /**
  1973. * A Custom DOM event.
  1974. *
  1975. * @typedef {Object} EventTarget~Event
  1976. * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
  1977. */
  1978. /**
  1979. * All event listeners should follow the following format.
  1980. *
  1981. * @callback EventTarget~EventListener
  1982. * @this {EventTarget}
  1983. *
  1984. * @param {EventTarget~Event} event
  1985. * the event that triggered this function
  1986. *
  1987. * @param {Object} [hash]
  1988. * hash of data sent during the event
  1989. */
  1990. /**
  1991. * An object containing event names as keys and booleans as values.
  1992. *
  1993. * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
  1994. * will have extra functionality. See that function for more information.
  1995. *
  1996. * @property EventTarget.prototype.allowedEvents_
  1997. * @private
  1998. */
  1999. EventTarget.prototype.allowedEvents_ = {};
  2000. /**
  2001. * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
  2002. * function that will get called when an event with a certain name gets triggered.
  2003. *
  2004. * @param {string|string[]} type
  2005. * An event name or an array of event names.
  2006. *
  2007. * @param {EventTarget~EventListener} fn
  2008. * The function to call with `EventTarget`s
  2009. */
  2010. EventTarget.prototype.on = function (type, fn) {
  2011. // Remove the addEventListener alias before calling Events.on
  2012. // so we don't get into an infinite type loop
  2013. var ael = this.addEventListener;
  2014. this.addEventListener = function () {};
  2015. on(this, type, fn);
  2016. this.addEventListener = ael;
  2017. };
  2018. /**
  2019. * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
  2020. * the standard DOM API.
  2021. *
  2022. * @function
  2023. * @see {@link EventTarget#on}
  2024. */
  2025. EventTarget.prototype.addEventListener = EventTarget.prototype.on;
  2026. /**
  2027. * Removes an `event listener` for a specific event from an instance of `EventTarget`.
  2028. * This makes it so that the `event listener` will no longer get called when the
  2029. * named event happens.
  2030. *
  2031. * @param {string|string[]} type
  2032. * An event name or an array of event names.
  2033. *
  2034. * @param {EventTarget~EventListener} fn
  2035. * The function to remove.
  2036. */
  2037. EventTarget.prototype.off = function (type, fn) {
  2038. off(this, type, fn);
  2039. };
  2040. /**
  2041. * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
  2042. * the standard DOM API.
  2043. *
  2044. * @function
  2045. * @see {@link EventTarget#off}
  2046. */
  2047. EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
  2048. /**
  2049. * This function will add an `event listener` that gets triggered only once. After the
  2050. * first trigger it will get removed. This is like adding an `event listener`
  2051. * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
  2052. *
  2053. * @param {string|string[]} type
  2054. * An event name or an array of event names.
  2055. *
  2056. * @param {EventTarget~EventListener} fn
  2057. * The function to be called once for each event name.
  2058. */
  2059. EventTarget.prototype.one = function (type, fn) {
  2060. // Remove the addEventListener alialing Events.on
  2061. // so we don't get into an infinite type loop
  2062. var ael = this.addEventListener;
  2063. this.addEventListener = function () {};
  2064. one(this, type, fn);
  2065. this.addEventListener = ael;
  2066. };
  2067. /**
  2068. * This function causes an event to happen. This will then cause any `event listeners`
  2069. * that are waiting for that event, to get called. If there are no `event listeners`
  2070. * for an event then nothing will happen.
  2071. *
  2072. * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
  2073. * Trigger will also call the `on` + `uppercaseEventName` function.
  2074. *
  2075. * Example:
  2076. * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
  2077. * `onClick` if it exists.
  2078. *
  2079. * @param {string|EventTarget~Event|Object} event
  2080. * The name of the event, an `Event`, or an object with a key of type set to
  2081. * an event name.
  2082. */
  2083. EventTarget.prototype.trigger = function (event) {
  2084. var type = event.type || event; // deprecation
  2085. // In a future version we should default target to `this`
  2086. // similar to how we default the target to `elem` in
  2087. // `Events.trigger`. Right now the default `target` will be
  2088. // `document` due to the `Event.fixEvent` call.
  2089. if (typeof event === 'string') {
  2090. event = {
  2091. type: type
  2092. };
  2093. }
  2094. event = fixEvent(event);
  2095. if (this.allowedEvents_[type] && this['on' + type]) {
  2096. this['on' + type](event);
  2097. }
  2098. trigger(this, event);
  2099. };
  2100. /**
  2101. * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
  2102. * the standard DOM API.
  2103. *
  2104. * @function
  2105. * @see {@link EventTarget#trigger}
  2106. */
  2107. EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
  2108. var EVENT_MAP;
  2109. EventTarget.prototype.queueTrigger = function (event) {
  2110. var _this = this;
  2111. // only set up EVENT_MAP if it'll be used
  2112. if (!EVENT_MAP) {
  2113. EVENT_MAP = new Map();
  2114. }
  2115. var type = event.type || event;
  2116. var map = EVENT_MAP.get(this);
  2117. if (!map) {
  2118. map = new Map();
  2119. EVENT_MAP.set(this, map);
  2120. }
  2121. var oldTimeout = map.get(type);
  2122. map.delete(type);
  2123. window$1.clearTimeout(oldTimeout);
  2124. var timeout = window$1.setTimeout(function () {
  2125. // if we cleared out all timeouts for the current target, delete its map
  2126. if (map.size === 0) {
  2127. map = null;
  2128. EVENT_MAP.delete(_this);
  2129. }
  2130. _this.trigger(event);
  2131. }, 0);
  2132. map.set(type, timeout);
  2133. };
  2134. /**
  2135. * @file mixins/evented.js
  2136. * @module evented
  2137. */
  2138. /**
  2139. * Returns whether or not an object has had the evented mixin applied.
  2140. *
  2141. * @param {Object} object
  2142. * An object to test.
  2143. *
  2144. * @return {boolean}
  2145. * Whether or not the object appears to be evented.
  2146. */
  2147. var isEvented = function isEvented(object) {
  2148. return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
  2149. return typeof object[k] === 'function';
  2150. });
  2151. };
  2152. /**
  2153. * Adds a callback to run after the evented mixin applied.
  2154. *
  2155. * @param {Object} object
  2156. * An object to Add
  2157. * @param {Function} callback
  2158. * The callback to run.
  2159. */
  2160. var addEventedCallback = function addEventedCallback(target, callback) {
  2161. if (isEvented(target)) {
  2162. callback();
  2163. } else {
  2164. if (!target.eventedCallbacks) {
  2165. target.eventedCallbacks = [];
  2166. }
  2167. target.eventedCallbacks.push(callback);
  2168. }
  2169. };
  2170. /**
  2171. * Whether a value is a valid event type - non-empty string or array.
  2172. *
  2173. * @private
  2174. * @param {string|Array} type
  2175. * The type value to test.
  2176. *
  2177. * @return {boolean}
  2178. * Whether or not the type is a valid event type.
  2179. */
  2180. var isValidEventType = function isValidEventType(type) {
  2181. return (// The regex here verifies that the `type` contains at least one non-
  2182. // whitespace character.
  2183. typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
  2184. );
  2185. };
  2186. /**
  2187. * Validates a value to determine if it is a valid event target. Throws if not.
  2188. *
  2189. * @private
  2190. * @throws {Error}
  2191. * If the target does not appear to be a valid event target.
  2192. *
  2193. * @param {Object} target
  2194. * The object to test.
  2195. */
  2196. var validateTarget = function validateTarget(target) {
  2197. if (!target.nodeName && !isEvented(target)) {
  2198. throw new Error('Invalid target; must be a DOM node or evented object.');
  2199. }
  2200. };
  2201. /**
  2202. * Validates a value to determine if it is a valid event target. Throws if not.
  2203. *
  2204. * @private
  2205. * @throws {Error}
  2206. * If the type does not appear to be a valid event type.
  2207. *
  2208. * @param {string|Array} type
  2209. * The type to test.
  2210. */
  2211. var validateEventType = function validateEventType(type) {
  2212. if (!isValidEventType(type)) {
  2213. throw new Error('Invalid event type; must be a non-empty string or array.');
  2214. }
  2215. };
  2216. /**
  2217. * Validates a value to determine if it is a valid listener. Throws if not.
  2218. *
  2219. * @private
  2220. * @throws {Error}
  2221. * If the listener is not a function.
  2222. *
  2223. * @param {Function} listener
  2224. * The listener to test.
  2225. */
  2226. var validateListener = function validateListener(listener) {
  2227. if (typeof listener !== 'function') {
  2228. throw new Error('Invalid listener; must be a function.');
  2229. }
  2230. };
  2231. /**
  2232. * Takes an array of arguments given to `on()` or `one()`, validates them, and
  2233. * normalizes them into an object.
  2234. *
  2235. * @private
  2236. * @param {Object} self
  2237. * The evented object on which `on()` or `one()` was called. This
  2238. * object will be bound as the `this` value for the listener.
  2239. *
  2240. * @param {Array} args
  2241. * An array of arguments passed to `on()` or `one()`.
  2242. *
  2243. * @return {Object}
  2244. * An object containing useful values for `on()` or `one()` calls.
  2245. */
  2246. var normalizeListenArgs = function normalizeListenArgs(self, args) {
  2247. // If the number of arguments is less than 3, the target is always the
  2248. // evented object itself.
  2249. var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
  2250. var target;
  2251. var type;
  2252. var listener;
  2253. if (isTargetingSelf) {
  2254. target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to
  2255. // the evented object itself.
  2256. if (args.length >= 3) {
  2257. args.shift();
  2258. }
  2259. type = args[0];
  2260. listener = args[1];
  2261. } else {
  2262. target = args[0];
  2263. type = args[1];
  2264. listener = args[2];
  2265. }
  2266. validateTarget(target);
  2267. validateEventType(type);
  2268. validateListener(listener);
  2269. listener = bind(self, listener);
  2270. return {
  2271. isTargetingSelf: isTargetingSelf,
  2272. target: target,
  2273. type: type,
  2274. listener: listener
  2275. };
  2276. };
  2277. /**
  2278. * Adds the listener to the event type(s) on the target, normalizing for
  2279. * the type of target.
  2280. *
  2281. * @private
  2282. * @param {Element|Object} target
  2283. * A DOM node or evented object.
  2284. *
  2285. * @param {string} method
  2286. * The event binding method to use ("on" or "one").
  2287. *
  2288. * @param {string|Array} type
  2289. * One or more event type(s).
  2290. *
  2291. * @param {Function} listener
  2292. * A listener function.
  2293. */
  2294. var listen = function listen(target, method, type, listener) {
  2295. validateTarget(target);
  2296. if (target.nodeName) {
  2297. Events[method](target, type, listener);
  2298. } else {
  2299. target[method](type, listener);
  2300. }
  2301. };
  2302. /**
  2303. * Contains methods that provide event capabilities to an object which is passed
  2304. * to {@link module:evented|evented}.
  2305. *
  2306. * @mixin EventedMixin
  2307. */
  2308. var EventedMixin = {
  2309. /**
  2310. * Add a listener to an event (or events) on this object or another evented
  2311. * object.
  2312. *
  2313. * @param {string|Array|Element|Object} targetOrType
  2314. * If this is a string or array, it represents the event type(s)
  2315. * that will trigger the listener.
  2316. *
  2317. * Another evented object can be passed here instead, which will
  2318. * cause the listener to listen for events on _that_ object.
  2319. *
  2320. * In either case, the listener's `this` value will be bound to
  2321. * this object.
  2322. *
  2323. * @param {string|Array|Function} typeOrListener
  2324. * If the first argument was a string or array, this should be the
  2325. * listener function. Otherwise, this is a string or array of event
  2326. * type(s).
  2327. *
  2328. * @param {Function} [listener]
  2329. * If the first argument was another evented object, this will be
  2330. * the listener function.
  2331. */
  2332. on: function on$$1() {
  2333. var _this = this;
  2334. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  2335. args[_key] = arguments[_key];
  2336. }
  2337. var _normalizeListenArgs = normalizeListenArgs(this, args),
  2338. isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
  2339. target = _normalizeListenArgs.target,
  2340. type = _normalizeListenArgs.type,
  2341. listener = _normalizeListenArgs.listener;
  2342. listen(target, 'on', type, listener); // If this object is listening to another evented object.
  2343. if (!isTargetingSelf) {
  2344. // If this object is disposed, remove the listener.
  2345. var removeListenerOnDispose = function removeListenerOnDispose() {
  2346. return _this.off(target, type, listener);
  2347. }; // Use the same function ID as the listener so we can remove it later it
  2348. // using the ID of the original listener.
  2349. removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures
  2350. // that if the target is disposed BEFORE this object, we remove the
  2351. // removal listener that was just added. Otherwise, we create a memory leak.
  2352. var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
  2353. return _this.off('dispose', removeListenerOnDispose);
  2354. }; // Use the same function ID as the listener so we can remove it later
  2355. // it using the ID of the original listener.
  2356. removeRemoverOnTargetDispose.guid = listener.guid;
  2357. listen(this, 'on', 'dispose', removeListenerOnDispose);
  2358. listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
  2359. }
  2360. },
  2361. /**
  2362. * Add a listener to an event (or events) on this object or another evented
  2363. * object. The listener will only be called once and then removed.
  2364. *
  2365. * @param {string|Array|Element|Object} targetOrType
  2366. * If this is a string or array, it represents the event type(s)
  2367. * that will trigger the listener.
  2368. *
  2369. * Another evented object can be passed here instead, which will
  2370. * cause the listener to listen for events on _that_ object.
  2371. *
  2372. * In either case, the listener's `this` value will be bound to
  2373. * this object.
  2374. *
  2375. * @param {string|Array|Function} typeOrListener
  2376. * If the first argument was a string or array, this should be the
  2377. * listener function. Otherwise, this is a string or array of event
  2378. * type(s).
  2379. *
  2380. * @param {Function} [listener]
  2381. * If the first argument was another evented object, this will be
  2382. * the listener function.
  2383. */
  2384. one: function one$$1() {
  2385. var _this2 = this;
  2386. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  2387. args[_key2] = arguments[_key2];
  2388. }
  2389. var _normalizeListenArgs2 = normalizeListenArgs(this, args),
  2390. isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
  2391. target = _normalizeListenArgs2.target,
  2392. type = _normalizeListenArgs2.type,
  2393. listener = _normalizeListenArgs2.listener; // Targeting this evented object.
  2394. if (isTargetingSelf) {
  2395. listen(target, 'one', type, listener); // Targeting another evented object.
  2396. } else {
  2397. var wrapper = function wrapper() {
  2398. _this2.off(target, type, wrapper);
  2399. for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  2400. largs[_key3] = arguments[_key3];
  2401. }
  2402. listener.apply(null, largs);
  2403. }; // Use the same function ID as the listener so we can remove it later
  2404. // it using the ID of the original listener.
  2405. wrapper.guid = listener.guid;
  2406. listen(target, 'one', type, wrapper);
  2407. }
  2408. },
  2409. /**
  2410. * Removes listener(s) from event(s) on an evented object.
  2411. *
  2412. * @param {string|Array|Element|Object} [targetOrType]
  2413. * If this is a string or array, it represents the event type(s).
  2414. *
  2415. * Another evented object can be passed here instead, in which case
  2416. * ALL 3 arguments are _required_.
  2417. *
  2418. * @param {string|Array|Function} [typeOrListener]
  2419. * If the first argument was a string or array, this may be the
  2420. * listener function. Otherwise, this is a string or array of event
  2421. * type(s).
  2422. *
  2423. * @param {Function} [listener]
  2424. * If the first argument was another evented object, this will be
  2425. * the listener function; otherwise, _all_ listeners bound to the
  2426. * event type(s) will be removed.
  2427. */
  2428. off: function off$$1(targetOrType, typeOrListener, listener) {
  2429. // Targeting this evented object.
  2430. if (!targetOrType || isValidEventType(targetOrType)) {
  2431. off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object.
  2432. } else {
  2433. var target = targetOrType;
  2434. var type = typeOrListener; // Fail fast and in a meaningful way!
  2435. validateTarget(target);
  2436. validateEventType(type);
  2437. validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used
  2438. listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given
  2439. // the same guid as the event listener in on().
  2440. this.off('dispose', listener);
  2441. if (target.nodeName) {
  2442. off(target, type, listener);
  2443. off(target, 'dispose', listener);
  2444. } else if (isEvented(target)) {
  2445. target.off(type, listener);
  2446. target.off('dispose', listener);
  2447. }
  2448. }
  2449. },
  2450. /**
  2451. * Fire an event on this evented object, causing its listeners to be called.
  2452. *
  2453. * @param {string|Object} event
  2454. * An event type or an object with a type property.
  2455. *
  2456. * @param {Object} [hash]
  2457. * An additional object to pass along to listeners.
  2458. *
  2459. * @return {boolean}
  2460. * Whether or not the default behavior was prevented.
  2461. */
  2462. trigger: function trigger$$1(event, hash) {
  2463. return trigger(this.eventBusEl_, event, hash);
  2464. }
  2465. };
  2466. /**
  2467. * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
  2468. *
  2469. * @param {Object} target
  2470. * The object to which to add event methods.
  2471. *
  2472. * @param {Object} [options={}]
  2473. * Options for customizing the mixin behavior.
  2474. *
  2475. * @param {string} [options.eventBusKey]
  2476. * By default, adds a `eventBusEl_` DOM element to the target object,
  2477. * which is used as an event bus. If the target object already has a
  2478. * DOM element that should be used, pass its key here.
  2479. *
  2480. * @return {Object}
  2481. * The target object.
  2482. */
  2483. function evented(target, options) {
  2484. if (options === void 0) {
  2485. options = {};
  2486. }
  2487. var _options = options,
  2488. eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_.
  2489. if (eventBusKey) {
  2490. if (!target[eventBusKey].nodeName) {
  2491. throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element.");
  2492. }
  2493. target.eventBusEl_ = target[eventBusKey];
  2494. } else {
  2495. target.eventBusEl_ = createEl('span', {
  2496. className: 'vjs-event-bus'
  2497. });
  2498. }
  2499. assign(target, EventedMixin);
  2500. if (target.eventedCallbacks) {
  2501. target.eventedCallbacks.forEach(function (callback) {
  2502. callback();
  2503. });
  2504. } // When any evented object is disposed, it removes all its listeners.
  2505. target.on('dispose', function () {
  2506. target.off();
  2507. window$1.setTimeout(function () {
  2508. target.eventBusEl_ = null;
  2509. }, 0);
  2510. });
  2511. return target;
  2512. }
  2513. /**
  2514. * @file mixins/stateful.js
  2515. * @module stateful
  2516. */
  2517. /**
  2518. * Contains methods that provide statefulness to an object which is passed
  2519. * to {@link module:stateful}.
  2520. *
  2521. * @mixin StatefulMixin
  2522. */
  2523. var StatefulMixin = {
  2524. /**
  2525. * A hash containing arbitrary keys and values representing the state of
  2526. * the object.
  2527. *
  2528. * @type {Object}
  2529. */
  2530. state: {},
  2531. /**
  2532. * Set the state of an object by mutating its
  2533. * {@link module:stateful~StatefulMixin.state|state} object in place.
  2534. *
  2535. * @fires module:stateful~StatefulMixin#statechanged
  2536. * @param {Object|Function} stateUpdates
  2537. * A new set of properties to shallow-merge into the plugin state.
  2538. * Can be a plain object or a function returning a plain object.
  2539. *
  2540. * @return {Object|undefined}
  2541. * An object containing changes that occurred. If no changes
  2542. * occurred, returns `undefined`.
  2543. */
  2544. setState: function setState(stateUpdates) {
  2545. var _this = this;
  2546. // Support providing the `stateUpdates` state as a function.
  2547. if (typeof stateUpdates === 'function') {
  2548. stateUpdates = stateUpdates();
  2549. }
  2550. var changes;
  2551. each(stateUpdates, function (value, key) {
  2552. // Record the change if the value is different from what's in the
  2553. // current state.
  2554. if (_this.state[key] !== value) {
  2555. changes = changes || {};
  2556. changes[key] = {
  2557. from: _this.state[key],
  2558. to: value
  2559. };
  2560. }
  2561. _this.state[key] = value;
  2562. }); // Only trigger "statechange" if there were changes AND we have a trigger
  2563. // function. This allows us to not require that the target object be an
  2564. // evented object.
  2565. if (changes && isEvented(this)) {
  2566. /**
  2567. * An event triggered on an object that is both
  2568. * {@link module:stateful|stateful} and {@link module:evented|evented}
  2569. * indicating that its state has changed.
  2570. *
  2571. * @event module:stateful~StatefulMixin#statechanged
  2572. * @type {Object}
  2573. * @property {Object} changes
  2574. * A hash containing the properties that were changed and
  2575. * the values they were changed `from` and `to`.
  2576. */
  2577. this.trigger({
  2578. changes: changes,
  2579. type: 'statechanged'
  2580. });
  2581. }
  2582. return changes;
  2583. }
  2584. };
  2585. /**
  2586. * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
  2587. * object.
  2588. *
  2589. * If the target object is {@link module:evented|evented} and has a
  2590. * `handleStateChanged` method, that method will be automatically bound to the
  2591. * `statechanged` event on itself.
  2592. *
  2593. * @param {Object} target
  2594. * The object to be made stateful.
  2595. *
  2596. * @param {Object} [defaultState]
  2597. * A default set of properties to populate the newly-stateful object's
  2598. * `state` property.
  2599. *
  2600. * @return {Object}
  2601. * Returns the `target`.
  2602. */
  2603. function stateful(target, defaultState) {
  2604. assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state`
  2605. // added in that step.
  2606. target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists.
  2607. if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
  2608. target.on('statechanged', target.handleStateChanged);
  2609. }
  2610. return target;
  2611. }
  2612. /**
  2613. * @file to-title-case.js
  2614. * @module to-title-case
  2615. */
  2616. /**
  2617. * Uppercase the first letter of a string.
  2618. *
  2619. * @param {string} string
  2620. * String to be uppercased
  2621. *
  2622. * @return {string}
  2623. * The string with an uppercased first letter
  2624. */
  2625. function toTitleCase(string) {
  2626. if (typeof string !== 'string') {
  2627. return string;
  2628. }
  2629. return string.charAt(0).toUpperCase() + string.slice(1);
  2630. }
  2631. /**
  2632. * Compares the TitleCase versions of the two strings for equality.
  2633. *
  2634. * @param {string} str1
  2635. * The first string to compare
  2636. *
  2637. * @param {string} str2
  2638. * The second string to compare
  2639. *
  2640. * @return {boolean}
  2641. * Whether the TitleCase versions of the strings are equal
  2642. */
  2643. function titleCaseEquals(str1, str2) {
  2644. return toTitleCase(str1) === toTitleCase(str2);
  2645. }
  2646. /**
  2647. * @file merge-options.js
  2648. * @module merge-options
  2649. */
  2650. /**
  2651. * Merge two objects recursively.
  2652. *
  2653. * Performs a deep merge like
  2654. * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
  2655. * plain objects (not arrays, elements, or anything else).
  2656. *
  2657. * Non-plain object values will be copied directly from the right-most
  2658. * argument.
  2659. *
  2660. * @static
  2661. * @param {Object[]} sources
  2662. * One or more objects to merge into a new object.
  2663. *
  2664. * @return {Object}
  2665. * A new object that is the merged result of all sources.
  2666. */
  2667. function mergeOptions() {
  2668. var result = {};
  2669. for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) {
  2670. sources[_key] = arguments[_key];
  2671. }
  2672. sources.forEach(function (source) {
  2673. if (!source) {
  2674. return;
  2675. }
  2676. each(source, function (value, key) {
  2677. if (!isPlain(value)) {
  2678. result[key] = value;
  2679. return;
  2680. }
  2681. if (!isPlain(result[key])) {
  2682. result[key] = {};
  2683. }
  2684. result[key] = mergeOptions(result[key], value);
  2685. });
  2686. });
  2687. return result;
  2688. }
  2689. /**
  2690. * Player Component - Base class for all UI objects
  2691. *
  2692. * @file component.js
  2693. */
  2694. /**
  2695. * Base class for all UI Components.
  2696. * Components are UI objects which represent both a javascript object and an element
  2697. * in the DOM. They can be children of other components, and can have
  2698. * children themselves.
  2699. *
  2700. * Components can also use methods from {@link EventTarget}
  2701. */
  2702. var Component =
  2703. /*#__PURE__*/
  2704. function () {
  2705. /**
  2706. * A callback that is called when a component is ready. Does not have any
  2707. * paramters and any callback value will be ignored.
  2708. *
  2709. * @callback Component~ReadyCallback
  2710. * @this Component
  2711. */
  2712. /**
  2713. * Creates an instance of this class.
  2714. *
  2715. * @param {Player} player
  2716. * The `Player` that this class should be attached to.
  2717. *
  2718. * @param {Object} [options]
  2719. * The key/value store of player options.
  2720. *
  2721. * @param {Object[]} [options.children]
  2722. * An array of children objects to intialize this component with. Children objects have
  2723. * a name property that will be used if more than one component of the same type needs to be
  2724. * added.
  2725. *
  2726. * @param {Component~ReadyCallback} [ready]
  2727. * Function that gets called when the `Component` is ready.
  2728. */
  2729. function Component(player, options, ready) {
  2730. // The component might be the player itself and we can't pass `this` to super
  2731. if (!player && this.play) {
  2732. this.player_ = player = this; // eslint-disable-line
  2733. } else {
  2734. this.player_ = player;
  2735. } // Hold the reference to the parent component via `addChild` method
  2736. this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults
  2737. this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options
  2738. options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied
  2739. this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one
  2740. if (!this.id_) {
  2741. // Don't require the player ID function in the case of mock players
  2742. var id = player && player.id && player.id() || 'no_player';
  2743. this.id_ = id + "_component_" + newGUID();
  2744. }
  2745. this.name_ = options.name || null; // Create element if one wasn't provided in options
  2746. if (options.el) {
  2747. this.el_ = options.el;
  2748. } else if (options.createEl !== false) {
  2749. this.el_ = this.createEl();
  2750. } // if evented is anything except false, we want to mixin in evented
  2751. if (options.evented !== false) {
  2752. // Make this an evented object and use `el_`, if available, as its event bus
  2753. evented(this, {
  2754. eventBusKey: this.el_ ? 'el_' : null
  2755. });
  2756. }
  2757. stateful(this, this.constructor.defaultState);
  2758. this.children_ = [];
  2759. this.childIndex_ = {};
  2760. this.childNameIndex_ = {}; // Add any child components in options
  2761. if (options.initChildren !== false) {
  2762. this.initChildren();
  2763. }
  2764. this.ready(ready); // Don't want to trigger ready here or it will before init is actually
  2765. // finished for all children that run this constructor
  2766. if (options.reportTouchActivity !== false) {
  2767. this.enableTouchActivity();
  2768. }
  2769. }
  2770. /**
  2771. * Dispose of the `Component` and all child components.
  2772. *
  2773. * @fires Component#dispose
  2774. */
  2775. var _proto = Component.prototype;
  2776. _proto.dispose = function dispose() {
  2777. /**
  2778. * Triggered when a `Component` is disposed.
  2779. *
  2780. * @event Component#dispose
  2781. * @type {EventTarget~Event}
  2782. *
  2783. * @property {boolean} [bubbles=false]
  2784. * set to false so that the close event does not
  2785. * bubble up
  2786. */
  2787. this.trigger({
  2788. type: 'dispose',
  2789. bubbles: false
  2790. }); // Dispose all children.
  2791. if (this.children_) {
  2792. for (var i = this.children_.length - 1; i >= 0; i--) {
  2793. if (this.children_[i].dispose) {
  2794. this.children_[i].dispose();
  2795. }
  2796. }
  2797. } // Delete child references
  2798. this.children_ = null;
  2799. this.childIndex_ = null;
  2800. this.childNameIndex_ = null;
  2801. this.parentComponent_ = null;
  2802. if (this.el_) {
  2803. // Remove element from DOM
  2804. if (this.el_.parentNode) {
  2805. this.el_.parentNode.removeChild(this.el_);
  2806. }
  2807. removeData(this.el_);
  2808. this.el_ = null;
  2809. } // remove reference to the player after disposing of the element
  2810. this.player_ = null;
  2811. }
  2812. /**
  2813. * Return the {@link Player} that the `Component` has attached to.
  2814. *
  2815. * @return {Player}
  2816. * The player that this `Component` has attached to.
  2817. */
  2818. ;
  2819. _proto.player = function player() {
  2820. return this.player_;
  2821. }
  2822. /**
  2823. * Deep merge of options objects with new options.
  2824. * > Note: When both `obj` and `options` contain properties whose values are objects.
  2825. * The two properties get merged using {@link module:mergeOptions}
  2826. *
  2827. * @param {Object} obj
  2828. * The object that contains new options.
  2829. *
  2830. * @return {Object}
  2831. * A new object of `this.options_` and `obj` merged together.
  2832. *
  2833. * @deprecated since version 5
  2834. */
  2835. ;
  2836. _proto.options = function options(obj) {
  2837. log.warn('this.options() has been deprecated and will be moved to the constructor in 6.0');
  2838. if (!obj) {
  2839. return this.options_;
  2840. }
  2841. this.options_ = mergeOptions(this.options_, obj);
  2842. return this.options_;
  2843. }
  2844. /**
  2845. * Get the `Component`s DOM element
  2846. *
  2847. * @return {Element}
  2848. * The DOM element for this `Component`.
  2849. */
  2850. ;
  2851. _proto.el = function el() {
  2852. return this.el_;
  2853. }
  2854. /**
  2855. * Create the `Component`s DOM element.
  2856. *
  2857. * @param {string} [tagName]
  2858. * Element's DOM node type. e.g. 'div'
  2859. *
  2860. * @param {Object} [properties]
  2861. * An object of properties that should be set.
  2862. *
  2863. * @param {Object} [attributes]
  2864. * An object of attributes that should be set.
  2865. *
  2866. * @return {Element}
  2867. * The element that gets created.
  2868. */
  2869. ;
  2870. _proto.createEl = function createEl$$1(tagName, properties, attributes) {
  2871. return createEl(tagName, properties, attributes);
  2872. }
  2873. /**
  2874. * Localize a string given the string in english.
  2875. *
  2876. * If tokens are provided, it'll try and run a simple token replacement on the provided string.
  2877. * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
  2878. *
  2879. * If a `defaultValue` is provided, it'll use that over `string`,
  2880. * if a value isn't found in provided language files.
  2881. * This is useful if you want to have a descriptive key for token replacement
  2882. * but have a succinct localized string and not require `en.json` to be included.
  2883. *
  2884. * Currently, it is used for the progress bar timing.
  2885. * ```js
  2886. * {
  2887. * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
  2888. * }
  2889. * ```
  2890. * It is then used like so:
  2891. * ```js
  2892. * this.localize('progress bar timing: currentTime={1} duration{2}',
  2893. * [this.player_.currentTime(), this.player_.duration()],
  2894. * '{1} of {2}');
  2895. * ```
  2896. *
  2897. * Which outputs something like: `01:23 of 24:56`.
  2898. *
  2899. *
  2900. * @param {string} string
  2901. * The string to localize and the key to lookup in the language files.
  2902. * @param {string[]} [tokens]
  2903. * If the current item has token replacements, provide the tokens here.
  2904. * @param {string} [defaultValue]
  2905. * Defaults to `string`. Can be a default value to use for token replacement
  2906. * if the lookup key is needed to be separate.
  2907. *
  2908. * @return {string}
  2909. * The localized string or if no localization exists the english string.
  2910. */
  2911. ;
  2912. _proto.localize = function localize(string, tokens, defaultValue) {
  2913. if (defaultValue === void 0) {
  2914. defaultValue = string;
  2915. }
  2916. var code = this.player_.language && this.player_.language();
  2917. var languages = this.player_.languages && this.player_.languages();
  2918. var language = languages && languages[code];
  2919. var primaryCode = code && code.split('-')[0];
  2920. var primaryLang = languages && languages[primaryCode];
  2921. var localizedString = defaultValue;
  2922. if (language && language[string]) {
  2923. localizedString = language[string];
  2924. } else if (primaryLang && primaryLang[string]) {
  2925. localizedString = primaryLang[string];
  2926. }
  2927. if (tokens) {
  2928. localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
  2929. var value = tokens[index - 1];
  2930. var ret = value;
  2931. if (typeof value === 'undefined') {
  2932. ret = match;
  2933. }
  2934. return ret;
  2935. });
  2936. }
  2937. return localizedString;
  2938. }
  2939. /**
  2940. * Return the `Component`s DOM element. This is where children get inserted.
  2941. * This will usually be the the same as the element returned in {@link Component#el}.
  2942. *
  2943. * @return {Element}
  2944. * The content element for this `Component`.
  2945. */
  2946. ;
  2947. _proto.contentEl = function contentEl() {
  2948. return this.contentEl_ || this.el_;
  2949. }
  2950. /**
  2951. * Get this `Component`s ID
  2952. *
  2953. * @return {string}
  2954. * The id of this `Component`
  2955. */
  2956. ;
  2957. _proto.id = function id() {
  2958. return this.id_;
  2959. }
  2960. /**
  2961. * Get the `Component`s name. The name gets used to reference the `Component`
  2962. * and is set during registration.
  2963. *
  2964. * @return {string}
  2965. * The name of this `Component`.
  2966. */
  2967. ;
  2968. _proto.name = function name() {
  2969. return this.name_;
  2970. }
  2971. /**
  2972. * Get an array of all child components
  2973. *
  2974. * @return {Array}
  2975. * The children
  2976. */
  2977. ;
  2978. _proto.children = function children() {
  2979. return this.children_;
  2980. }
  2981. /**
  2982. * Returns the child `Component` with the given `id`.
  2983. *
  2984. * @param {string} id
  2985. * The id of the child `Component` to get.
  2986. *
  2987. * @return {Component|undefined}
  2988. * The child `Component` with the given `id` or undefined.
  2989. */
  2990. ;
  2991. _proto.getChildById = function getChildById(id) {
  2992. return this.childIndex_[id];
  2993. }
  2994. /**
  2995. * Returns the child `Component` with the given `name`.
  2996. *
  2997. * @param {string} name
  2998. * The name of the child `Component` to get.
  2999. *
  3000. * @return {Component|undefined}
  3001. * The child `Component` with the given `name` or undefined.
  3002. */
  3003. ;
  3004. _proto.getChild = function getChild(name) {
  3005. if (!name) {
  3006. return;
  3007. }
  3008. name = toTitleCase(name);
  3009. return this.childNameIndex_[name];
  3010. }
  3011. /**
  3012. * Add a child `Component` inside the current `Component`.
  3013. *
  3014. *
  3015. * @param {string|Component} child
  3016. * The name or instance of a child to add.
  3017. *
  3018. * @param {Object} [options={}]
  3019. * The key/value store of options that will get passed to children of
  3020. * the child.
  3021. *
  3022. * @param {number} [index=this.children_.length]
  3023. * The index to attempt to add a child into.
  3024. *
  3025. * @return {Component}
  3026. * The `Component` that gets added as a child. When using a string the
  3027. * `Component` will get created by this process.
  3028. */
  3029. ;
  3030. _proto.addChild = function addChild(child, options, index) {
  3031. if (options === void 0) {
  3032. options = {};
  3033. }
  3034. if (index === void 0) {
  3035. index = this.children_.length;
  3036. }
  3037. var component;
  3038. var componentName; // If child is a string, create component with options
  3039. if (typeof child === 'string') {
  3040. componentName = toTitleCase(child);
  3041. var componentClassName = options.componentClass || componentName; // Set name through options
  3042. options.name = componentName; // Create a new object & element for this controls set
  3043. // If there's no .player_, this is a player
  3044. var ComponentClass = Component.getComponent(componentClassName);
  3045. if (!ComponentClass) {
  3046. throw new Error("Component " + componentClassName + " does not exist");
  3047. } // data stored directly on the videojs object may be
  3048. // misidentified as a component to retain
  3049. // backwards-compatibility with 4.x. check to make sure the
  3050. // component class can be instantiated.
  3051. if (typeof ComponentClass !== 'function') {
  3052. return null;
  3053. }
  3054. component = new ComponentClass(this.player_ || this, options); // child is a component instance
  3055. } else {
  3056. component = child;
  3057. }
  3058. if (component.parentComponent_) {
  3059. component.parentComponent_.removeChild(component);
  3060. }
  3061. this.children_.splice(index, 0, component);
  3062. component.parentComponent_ = this;
  3063. if (typeof component.id === 'function') {
  3064. this.childIndex_[component.id()] = component;
  3065. } // If a name wasn't used to create the component, check if we can use the
  3066. // name function of the component
  3067. componentName = componentName || component.name && toTitleCase(component.name());
  3068. if (componentName) {
  3069. this.childNameIndex_[componentName] = component;
  3070. } // Add the UI object's element to the container div (box)
  3071. // Having an element is not required
  3072. if (typeof component.el === 'function' && component.el()) {
  3073. var childNodes = this.contentEl().children;
  3074. var refNode = childNodes[index] || null;
  3075. this.contentEl().insertBefore(component.el(), refNode);
  3076. } // Return so it can stored on parent object if desired.
  3077. return component;
  3078. }
  3079. /**
  3080. * Remove a child `Component` from this `Component`s list of children. Also removes
  3081. * the child `Component`s element from this `Component`s element.
  3082. *
  3083. * @param {Component} component
  3084. * The child `Component` to remove.
  3085. */
  3086. ;
  3087. _proto.removeChild = function removeChild(component) {
  3088. if (typeof component === 'string') {
  3089. component = this.getChild(component);
  3090. }
  3091. if (!component || !this.children_) {
  3092. return;
  3093. }
  3094. var childFound = false;
  3095. for (var i = this.children_.length - 1; i >= 0; i--) {
  3096. if (this.children_[i] === component) {
  3097. childFound = true;
  3098. this.children_.splice(i, 1);
  3099. break;
  3100. }
  3101. }
  3102. if (!childFound) {
  3103. return;
  3104. }
  3105. component.parentComponent_ = null;
  3106. this.childIndex_[component.id()] = null;
  3107. this.childNameIndex_[component.name()] = null;
  3108. var compEl = component.el();
  3109. if (compEl && compEl.parentNode === this.contentEl()) {
  3110. this.contentEl().removeChild(component.el());
  3111. }
  3112. }
  3113. /**
  3114. * Add and initialize default child `Component`s based upon options.
  3115. */
  3116. ;
  3117. _proto.initChildren = function initChildren() {
  3118. var _this = this;
  3119. var children = this.options_.children;
  3120. if (children) {
  3121. // `this` is `parent`
  3122. var parentOptions = this.options_;
  3123. var handleAdd = function handleAdd(child) {
  3124. var name = child.name;
  3125. var opts = child.opts; // Allow options for children to be set at the parent options
  3126. // e.g. videojs(id, { controlBar: false });
  3127. // instead of videojs(id, { children: { controlBar: false });
  3128. if (parentOptions[name] !== undefined) {
  3129. opts = parentOptions[name];
  3130. } // Allow for disabling default components
  3131. // e.g. options['children']['posterImage'] = false
  3132. if (opts === false) {
  3133. return;
  3134. } // Allow options to be passed as a simple boolean if no configuration
  3135. // is necessary.
  3136. if (opts === true) {
  3137. opts = {};
  3138. } // We also want to pass the original player options
  3139. // to each component as well so they don't need to
  3140. // reach back into the player for options later.
  3141. opts.playerOptions = _this.options_.playerOptions; // Create and add the child component.
  3142. // Add a direct reference to the child by name on the parent instance.
  3143. // If two of the same component are used, different names should be supplied
  3144. // for each
  3145. var newChild = _this.addChild(name, opts);
  3146. if (newChild) {
  3147. _this[name] = newChild;
  3148. }
  3149. }; // Allow for an array of children details to passed in the options
  3150. var workingChildren;
  3151. var Tech = Component.getComponent('Tech');
  3152. if (Array.isArray(children)) {
  3153. workingChildren = children;
  3154. } else {
  3155. workingChildren = Object.keys(children);
  3156. }
  3157. workingChildren // children that are in this.options_ but also in workingChildren would
  3158. // give us extra children we do not want. So, we want to filter them out.
  3159. .concat(Object.keys(this.options_).filter(function (child) {
  3160. return !workingChildren.some(function (wchild) {
  3161. if (typeof wchild === 'string') {
  3162. return child === wchild;
  3163. }
  3164. return child === wchild.name;
  3165. });
  3166. })).map(function (child) {
  3167. var name;
  3168. var opts;
  3169. if (typeof child === 'string') {
  3170. name = child;
  3171. opts = children[name] || _this.options_[name] || {};
  3172. } else {
  3173. name = child.name;
  3174. opts = child;
  3175. }
  3176. return {
  3177. name: name,
  3178. opts: opts
  3179. };
  3180. }).filter(function (child) {
  3181. // we have to make sure that child.name isn't in the techOrder since
  3182. // techs are registerd as Components but can't aren't compatible
  3183. // See https://github.com/videojs/video.js/issues/2772
  3184. var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name));
  3185. return c && !Tech.isTech(c);
  3186. }).forEach(handleAdd);
  3187. }
  3188. }
  3189. /**
  3190. * Builds the default DOM class name. Should be overriden by sub-components.
  3191. *
  3192. * @return {string}
  3193. * The DOM class name for this object.
  3194. *
  3195. * @abstract
  3196. */
  3197. ;
  3198. _proto.buildCSSClass = function buildCSSClass() {
  3199. // Child classes can include a function that does:
  3200. // return 'CLASS NAME' + this._super();
  3201. return '';
  3202. }
  3203. /**
  3204. * Bind a listener to the component's ready state.
  3205. * Different from event listeners in that if the ready event has already happened
  3206. * it will trigger the function immediately.
  3207. *
  3208. * @return {Component}
  3209. * Returns itself; method can be chained.
  3210. */
  3211. ;
  3212. _proto.ready = function ready(fn, sync) {
  3213. if (sync === void 0) {
  3214. sync = false;
  3215. }
  3216. if (!fn) {
  3217. return;
  3218. }
  3219. if (!this.isReady_) {
  3220. this.readyQueue_ = this.readyQueue_ || [];
  3221. this.readyQueue_.push(fn);
  3222. return;
  3223. }
  3224. if (sync) {
  3225. fn.call(this);
  3226. } else {
  3227. // Call the function asynchronously by default for consistency
  3228. this.setTimeout(fn, 1);
  3229. }
  3230. }
  3231. /**
  3232. * Trigger all the ready listeners for this `Component`.
  3233. *
  3234. * @fires Component#ready
  3235. */
  3236. ;
  3237. _proto.triggerReady = function triggerReady() {
  3238. this.isReady_ = true; // Ensure ready is triggered asynchronously
  3239. this.setTimeout(function () {
  3240. var readyQueue = this.readyQueue_; // Reset Ready Queue
  3241. this.readyQueue_ = [];
  3242. if (readyQueue && readyQueue.length > 0) {
  3243. readyQueue.forEach(function (fn) {
  3244. fn.call(this);
  3245. }, this);
  3246. } // Allow for using event listeners also
  3247. /**
  3248. * Triggered when a `Component` is ready.
  3249. *
  3250. * @event Component#ready
  3251. * @type {EventTarget~Event}
  3252. */
  3253. this.trigger('ready');
  3254. }, 1);
  3255. }
  3256. /**
  3257. * Find a single DOM element matching a `selector`. This can be within the `Component`s
  3258. * `contentEl()` or another custom context.
  3259. *
  3260. * @param {string} selector
  3261. * A valid CSS selector, which will be passed to `querySelector`.
  3262. *
  3263. * @param {Element|string} [context=this.contentEl()]
  3264. * A DOM element within which to query. Can also be a selector string in
  3265. * which case the first matching element will get used as context. If
  3266. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3267. * nothing it falls back to `document`.
  3268. *
  3269. * @return {Element|null}
  3270. * the dom element that was found, or null
  3271. *
  3272. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3273. */
  3274. ;
  3275. _proto.$ = function $$$1(selector, context) {
  3276. return $(selector, context || this.contentEl());
  3277. }
  3278. /**
  3279. * Finds all DOM element matching a `selector`. This can be within the `Component`s
  3280. * `contentEl()` or another custom context.
  3281. *
  3282. * @param {string} selector
  3283. * A valid CSS selector, which will be passed to `querySelectorAll`.
  3284. *
  3285. * @param {Element|string} [context=this.contentEl()]
  3286. * A DOM element within which to query. Can also be a selector string in
  3287. * which case the first matching element will get used as context. If
  3288. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  3289. * nothing it falls back to `document`.
  3290. *
  3291. * @return {NodeList}
  3292. * a list of dom elements that were found
  3293. *
  3294. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  3295. */
  3296. ;
  3297. _proto.$$ = function $$$$1(selector, context) {
  3298. return $$(selector, context || this.contentEl());
  3299. }
  3300. /**
  3301. * Check if a component's element has a CSS class name.
  3302. *
  3303. * @param {string} classToCheck
  3304. * CSS class name to check.
  3305. *
  3306. * @return {boolean}
  3307. * - True if the `Component` has the class.
  3308. * - False if the `Component` does not have the class`
  3309. */
  3310. ;
  3311. _proto.hasClass = function hasClass$$1(classToCheck) {
  3312. return hasClass(this.el_, classToCheck);
  3313. }
  3314. /**
  3315. * Add a CSS class name to the `Component`s element.
  3316. *
  3317. * @param {string} classToAdd
  3318. * CSS class name to add
  3319. */
  3320. ;
  3321. _proto.addClass = function addClass$$1(classToAdd) {
  3322. addClass(this.el_, classToAdd);
  3323. }
  3324. /**
  3325. * Remove a CSS class name from the `Component`s element.
  3326. *
  3327. * @param {string} classToRemove
  3328. * CSS class name to remove
  3329. */
  3330. ;
  3331. _proto.removeClass = function removeClass$$1(classToRemove) {
  3332. removeClass(this.el_, classToRemove);
  3333. }
  3334. /**
  3335. * Add or remove a CSS class name from the component's element.
  3336. * - `classToToggle` gets added when {@link Component#hasClass} would return false.
  3337. * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
  3338. *
  3339. * @param {string} classToToggle
  3340. * The class to add or remove based on (@link Component#hasClass}
  3341. *
  3342. * @param {boolean|Dom~predicate} [predicate]
  3343. * An {@link Dom~predicate} function or a boolean
  3344. */
  3345. ;
  3346. _proto.toggleClass = function toggleClass$$1(classToToggle, predicate) {
  3347. toggleClass(this.el_, classToToggle, predicate);
  3348. }
  3349. /**
  3350. * Show the `Component`s element if it is hidden by removing the
  3351. * 'vjs-hidden' class name from it.
  3352. */
  3353. ;
  3354. _proto.show = function show() {
  3355. this.removeClass('vjs-hidden');
  3356. }
  3357. /**
  3358. * Hide the `Component`s element if it is currently showing by adding the
  3359. * 'vjs-hidden` class name to it.
  3360. */
  3361. ;
  3362. _proto.hide = function hide() {
  3363. this.addClass('vjs-hidden');
  3364. }
  3365. /**
  3366. * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
  3367. * class name to it. Used during fadeIn/fadeOut.
  3368. *
  3369. * @private
  3370. */
  3371. ;
  3372. _proto.lockShowing = function lockShowing() {
  3373. this.addClass('vjs-lock-showing');
  3374. }
  3375. /**
  3376. * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
  3377. * class name from it. Used during fadeIn/fadeOut.
  3378. *
  3379. * @private
  3380. */
  3381. ;
  3382. _proto.unlockShowing = function unlockShowing() {
  3383. this.removeClass('vjs-lock-showing');
  3384. }
  3385. /**
  3386. * Get the value of an attribute on the `Component`s element.
  3387. *
  3388. * @param {string} attribute
  3389. * Name of the attribute to get the value from.
  3390. *
  3391. * @return {string|null}
  3392. * - The value of the attribute that was asked for.
  3393. * - Can be an empty string on some browsers if the attribute does not exist
  3394. * or has no value
  3395. * - Most browsers will return null if the attibute does not exist or has
  3396. * no value.
  3397. *
  3398. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
  3399. */
  3400. ;
  3401. _proto.getAttribute = function getAttribute$$1(attribute) {
  3402. return getAttribute(this.el_, attribute);
  3403. }
  3404. /**
  3405. * Set the value of an attribute on the `Component`'s element
  3406. *
  3407. * @param {string} attribute
  3408. * Name of the attribute to set.
  3409. *
  3410. * @param {string} value
  3411. * Value to set the attribute to.
  3412. *
  3413. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
  3414. */
  3415. ;
  3416. _proto.setAttribute = function setAttribute$$1(attribute, value) {
  3417. setAttribute(this.el_, attribute, value);
  3418. }
  3419. /**
  3420. * Remove an attribute from the `Component`s element.
  3421. *
  3422. * @param {string} attribute
  3423. * Name of the attribute to remove.
  3424. *
  3425. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
  3426. */
  3427. ;
  3428. _proto.removeAttribute = function removeAttribute$$1(attribute) {
  3429. removeAttribute(this.el_, attribute);
  3430. }
  3431. /**
  3432. * Get or set the width of the component based upon the CSS styles.
  3433. * See {@link Component#dimension} for more detailed information.
  3434. *
  3435. * @param {number|string} [num]
  3436. * The width that you want to set postfixed with '%', 'px' or nothing.
  3437. *
  3438. * @param {boolean} [skipListeners]
  3439. * Skip the componentresize event trigger
  3440. *
  3441. * @return {number|string}
  3442. * The width when getting, zero if there is no width. Can be a string
  3443. * postpixed with '%' or 'px'.
  3444. */
  3445. ;
  3446. _proto.width = function width(num, skipListeners) {
  3447. return this.dimension('width', num, skipListeners);
  3448. }
  3449. /**
  3450. * Get or set the height of the component based upon the CSS styles.
  3451. * See {@link Component#dimension} for more detailed information.
  3452. *
  3453. * @param {number|string} [num]
  3454. * The height that you want to set postfixed with '%', 'px' or nothing.
  3455. *
  3456. * @param {boolean} [skipListeners]
  3457. * Skip the componentresize event trigger
  3458. *
  3459. * @return {number|string}
  3460. * The width when getting, zero if there is no width. Can be a string
  3461. * postpixed with '%' or 'px'.
  3462. */
  3463. ;
  3464. _proto.height = function height(num, skipListeners) {
  3465. return this.dimension('height', num, skipListeners);
  3466. }
  3467. /**
  3468. * Set both the width and height of the `Component` element at the same time.
  3469. *
  3470. * @param {number|string} width
  3471. * Width to set the `Component`s element to.
  3472. *
  3473. * @param {number|string} height
  3474. * Height to set the `Component`s element to.
  3475. */
  3476. ;
  3477. _proto.dimensions = function dimensions(width, height) {
  3478. // Skip componentresize listeners on width for optimization
  3479. this.width(width, true);
  3480. this.height(height);
  3481. }
  3482. /**
  3483. * Get or set width or height of the `Component` element. This is the shared code
  3484. * for the {@link Component#width} and {@link Component#height}.
  3485. *
  3486. * Things to know:
  3487. * - If the width or height in an number this will return the number postfixed with 'px'.
  3488. * - If the width/height is a percent this will return the percent postfixed with '%'
  3489. * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
  3490. * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
  3491. * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
  3492. * for more information
  3493. * - If you want the computed style of the component, use {@link Component#currentWidth}
  3494. * and {@link {Component#currentHeight}
  3495. *
  3496. * @fires Component#componentresize
  3497. *
  3498. * @param {string} widthOrHeight
  3499. 8 'width' or 'height'
  3500. *
  3501. * @param {number|string} [num]
  3502. 8 New dimension
  3503. *
  3504. * @param {boolean} [skipListeners]
  3505. * Skip componentresize event trigger
  3506. *
  3507. * @return {number}
  3508. * The dimension when getting or 0 if unset
  3509. */
  3510. ;
  3511. _proto.dimension = function dimension(widthOrHeight, num, skipListeners) {
  3512. if (num !== undefined) {
  3513. // Set to zero if null or literally NaN (NaN !== NaN)
  3514. if (num === null || num !== num) {
  3515. num = 0;
  3516. } // Check if using css width/height (% or px) and adjust
  3517. if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
  3518. this.el_.style[widthOrHeight] = num;
  3519. } else if (num === 'auto') {
  3520. this.el_.style[widthOrHeight] = '';
  3521. } else {
  3522. this.el_.style[widthOrHeight] = num + 'px';
  3523. } // skipListeners allows us to avoid triggering the resize event when setting both width and height
  3524. if (!skipListeners) {
  3525. /**
  3526. * Triggered when a component is resized.
  3527. *
  3528. * @event Component#componentresize
  3529. * @type {EventTarget~Event}
  3530. */
  3531. this.trigger('componentresize');
  3532. }
  3533. return;
  3534. } // Not setting a value, so getting it
  3535. // Make sure element exists
  3536. if (!this.el_) {
  3537. return 0;
  3538. } // Get dimension value from style
  3539. var val = this.el_.style[widthOrHeight];
  3540. var pxIndex = val.indexOf('px');
  3541. if (pxIndex !== -1) {
  3542. // Return the pixel value with no 'px'
  3543. return parseInt(val.slice(0, pxIndex), 10);
  3544. } // No px so using % or no style was set, so falling back to offsetWidth/height
  3545. // If component has display:none, offset will return 0
  3546. // TODO: handle display:none and no dimension style using px
  3547. return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10);
  3548. }
  3549. /**
  3550. * Get the computed width or the height of the component's element.
  3551. *
  3552. * Uses `window.getComputedStyle`.
  3553. *
  3554. * @param {string} widthOrHeight
  3555. * A string containing 'width' or 'height'. Whichever one you want to get.
  3556. *
  3557. * @return {number}
  3558. * The dimension that gets asked for or 0 if nothing was set
  3559. * for that dimension.
  3560. */
  3561. ;
  3562. _proto.currentDimension = function currentDimension(widthOrHeight) {
  3563. var computedWidthOrHeight = 0;
  3564. if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
  3565. throw new Error('currentDimension only accepts width or height value');
  3566. }
  3567. if (typeof window$1.getComputedStyle === 'function') {
  3568. var computedStyle = window$1.getComputedStyle(this.el_);
  3569. computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
  3570. } // remove 'px' from variable and parse as integer
  3571. computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying
  3572. // and we want to check the offset values.
  3573. // This code also runs wherever getComputedStyle doesn't exist.
  3574. if (computedWidthOrHeight === 0) {
  3575. var rule = "offset" + toTitleCase(widthOrHeight);
  3576. computedWidthOrHeight = this.el_[rule];
  3577. }
  3578. return computedWidthOrHeight;
  3579. }
  3580. /**
  3581. * An object that contains width and height values of the `Component`s
  3582. * computed style. Uses `window.getComputedStyle`.
  3583. *
  3584. * @typedef {Object} Component~DimensionObject
  3585. *
  3586. * @property {number} width
  3587. * The width of the `Component`s computed style.
  3588. *
  3589. * @property {number} height
  3590. * The height of the `Component`s computed style.
  3591. */
  3592. /**
  3593. * Get an object that contains computed width and height values of the
  3594. * component's element.
  3595. *
  3596. * Uses `window.getComputedStyle`.
  3597. *
  3598. * @return {Component~DimensionObject}
  3599. * The computed dimensions of the component's element.
  3600. */
  3601. ;
  3602. _proto.currentDimensions = function currentDimensions() {
  3603. return {
  3604. width: this.currentDimension('width'),
  3605. height: this.currentDimension('height')
  3606. };
  3607. }
  3608. /**
  3609. * Get the computed width of the component's element.
  3610. *
  3611. * Uses `window.getComputedStyle`.
  3612. *
  3613. * @return {number}
  3614. * The computed width of the component's element.
  3615. */
  3616. ;
  3617. _proto.currentWidth = function currentWidth() {
  3618. return this.currentDimension('width');
  3619. }
  3620. /**
  3621. * Get the computed height of the component's element.
  3622. *
  3623. * Uses `window.getComputedStyle`.
  3624. *
  3625. * @return {number}
  3626. * The computed height of the component's element.
  3627. */
  3628. ;
  3629. _proto.currentHeight = function currentHeight() {
  3630. return this.currentDimension('height');
  3631. }
  3632. /**
  3633. * Set the focus to this component
  3634. */
  3635. ;
  3636. _proto.focus = function focus() {
  3637. this.el_.focus();
  3638. }
  3639. /**
  3640. * Remove the focus from this component
  3641. */
  3642. ;
  3643. _proto.blur = function blur() {
  3644. this.el_.blur();
  3645. }
  3646. /**
  3647. * When this Component receives a keydown event which it does not process,
  3648. * it passes the event to the Player for handling.
  3649. *
  3650. * @param {EventTarget~Event} event
  3651. * The `keydown` event that caused this function to be called.
  3652. */
  3653. ;
  3654. _proto.handleKeyPress = function handleKeyPress(event) {
  3655. if (this.player_) {
  3656. this.player_.handleKeyPress(event);
  3657. }
  3658. }
  3659. /**
  3660. * Emit a 'tap' events when touch event support gets detected. This gets used to
  3661. * support toggling the controls through a tap on the video. They get enabled
  3662. * because every sub-component would have extra overhead otherwise.
  3663. *
  3664. * @private
  3665. * @fires Component#tap
  3666. * @listens Component#touchstart
  3667. * @listens Component#touchmove
  3668. * @listens Component#touchleave
  3669. * @listens Component#touchcancel
  3670. * @listens Component#touchend
  3671. */
  3672. ;
  3673. _proto.emitTapEvents = function emitTapEvents() {
  3674. // Track the start time so we can determine how long the touch lasted
  3675. var touchStart = 0;
  3676. var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap
  3677. // Other popular libs use anywhere from 2 (hammer.js) to 15,
  3678. // so 10 seems like a nice, round number.
  3679. var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap
  3680. var touchTimeThreshold = 200;
  3681. var couldBeTap;
  3682. this.on('touchstart', function (event) {
  3683. // If more than one finger, don't consider treating this as a click
  3684. if (event.touches.length === 1) {
  3685. // Copy pageX/pageY from the object
  3686. firstTouch = {
  3687. pageX: event.touches[0].pageX,
  3688. pageY: event.touches[0].pageY
  3689. }; // Record start time so we can detect a tap vs. "touch and hold"
  3690. touchStart = window$1.performance.now(); // Reset couldBeTap tracking
  3691. couldBeTap = true;
  3692. }
  3693. });
  3694. this.on('touchmove', function (event) {
  3695. // If more than one finger, don't consider treating this as a click
  3696. if (event.touches.length > 1) {
  3697. couldBeTap = false;
  3698. } else if (firstTouch) {
  3699. // Some devices will throw touchmoves for all but the slightest of taps.
  3700. // So, if we moved only a small distance, this could still be a tap
  3701. var xdiff = event.touches[0].pageX - firstTouch.pageX;
  3702. var ydiff = event.touches[0].pageY - firstTouch.pageY;
  3703. var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
  3704. if (touchDistance > tapMovementThreshold) {
  3705. couldBeTap = false;
  3706. }
  3707. }
  3708. });
  3709. var noTap = function noTap() {
  3710. couldBeTap = false;
  3711. }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
  3712. this.on('touchleave', noTap);
  3713. this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate
  3714. // event
  3715. this.on('touchend', function (event) {
  3716. firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen
  3717. if (couldBeTap === true) {
  3718. // Measure how long the touch lasted
  3719. var touchTime = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap
  3720. if (touchTime < touchTimeThreshold) {
  3721. // Don't let browser turn this into a click
  3722. event.preventDefault();
  3723. /**
  3724. * Triggered when a `Component` is tapped.
  3725. *
  3726. * @event Component#tap
  3727. * @type {EventTarget~Event}
  3728. */
  3729. this.trigger('tap'); // It may be good to copy the touchend event object and change the
  3730. // type to tap, if the other event properties aren't exact after
  3731. // Events.fixEvent runs (e.g. event.target)
  3732. }
  3733. }
  3734. });
  3735. }
  3736. /**
  3737. * This function reports user activity whenever touch events happen. This can get
  3738. * turned off by any sub-components that wants touch events to act another way.
  3739. *
  3740. * Report user touch activity when touch events occur. User activity gets used to
  3741. * determine when controls should show/hide. It is simple when it comes to mouse
  3742. * events, because any mouse event should show the controls. So we capture mouse
  3743. * events that bubble up to the player and report activity when that happens.
  3744. * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
  3745. * controls. So touch events can't help us at the player level either.
  3746. *
  3747. * User activity gets checked asynchronously. So what could happen is a tap event
  3748. * on the video turns the controls off. Then the `touchend` event bubbles up to
  3749. * the player. Which, if it reported user activity, would turn the controls right
  3750. * back on. We also don't want to completely block touch events from bubbling up.
  3751. * Furthermore a `touchmove` event and anything other than a tap, should not turn
  3752. * controls back on.
  3753. *
  3754. * @listens Component#touchstart
  3755. * @listens Component#touchmove
  3756. * @listens Component#touchend
  3757. * @listens Component#touchcancel
  3758. */
  3759. ;
  3760. _proto.enableTouchActivity = function enableTouchActivity() {
  3761. // Don't continue if the root player doesn't support reporting user activity
  3762. if (!this.player() || !this.player().reportUserActivity) {
  3763. return;
  3764. } // listener for reporting that the user is active
  3765. var report = bind(this.player(), this.player().reportUserActivity);
  3766. var touchHolding;
  3767. this.on('touchstart', function () {
  3768. report(); // For as long as the they are touching the device or have their mouse down,
  3769. // we consider them active even if they're not moving their finger or mouse.
  3770. // So we want to continue to update that they are active
  3771. this.clearInterval(touchHolding); // report at the same interval as activityCheck
  3772. touchHolding = this.setInterval(report, 250);
  3773. });
  3774. var touchEnd = function touchEnd(event) {
  3775. report(); // stop the interval that maintains activity if the touch is holding
  3776. this.clearInterval(touchHolding);
  3777. };
  3778. this.on('touchmove', report);
  3779. this.on('touchend', touchEnd);
  3780. this.on('touchcancel', touchEnd);
  3781. }
  3782. /**
  3783. * A callback that has no parameters and is bound into `Component`s context.
  3784. *
  3785. * @callback Component~GenericCallback
  3786. * @this Component
  3787. */
  3788. /**
  3789. * Creates a function that runs after an `x` millisecond timeout. This function is a
  3790. * wrapper around `window.setTimeout`. There are a few reasons to use this one
  3791. * instead though:
  3792. * 1. It gets cleared via {@link Component#clearTimeout} when
  3793. * {@link Component#dispose} gets called.
  3794. * 2. The function callback will gets turned into a {@link Component~GenericCallback}
  3795. *
  3796. * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
  3797. * will cause its dispose listener not to get cleaned up! Please use
  3798. * {@link Component#clearTimeout} or {@link Component#dispose} instead.
  3799. *
  3800. * @param {Component~GenericCallback} fn
  3801. * The function that will be run after `timeout`.
  3802. *
  3803. * @param {number} timeout
  3804. * Timeout in milliseconds to delay before executing the specified function.
  3805. *
  3806. * @return {number}
  3807. * Returns a timeout ID that gets used to identify the timeout. It can also
  3808. * get used in {@link Component#clearTimeout} to clear the timeout that
  3809. * was set.
  3810. *
  3811. * @listens Component#dispose
  3812. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
  3813. */
  3814. ;
  3815. _proto.setTimeout = function setTimeout(fn, timeout) {
  3816. var _this2 = this;
  3817. // declare as variables so they are properly available in timeout function
  3818. // eslint-disable-next-line
  3819. var timeoutId, disposeFn;
  3820. fn = bind(this, fn);
  3821. timeoutId = window$1.setTimeout(function () {
  3822. _this2.off('dispose', disposeFn);
  3823. fn();
  3824. }, timeout);
  3825. disposeFn = function disposeFn() {
  3826. return _this2.clearTimeout(timeoutId);
  3827. };
  3828. disposeFn.guid = "vjs-timeout-" + timeoutId;
  3829. this.on('dispose', disposeFn);
  3830. return timeoutId;
  3831. }
  3832. /**
  3833. * Clears a timeout that gets created via `window.setTimeout` or
  3834. * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
  3835. * use this function instead of `window.clearTimout`. If you don't your dispose
  3836. * listener will not get cleaned up until {@link Component#dispose}!
  3837. *
  3838. * @param {number} timeoutId
  3839. * The id of the timeout to clear. The return value of
  3840. * {@link Component#setTimeout} or `window.setTimeout`.
  3841. *
  3842. * @return {number}
  3843. * Returns the timeout id that was cleared.
  3844. *
  3845. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
  3846. */
  3847. ;
  3848. _proto.clearTimeout = function clearTimeout(timeoutId) {
  3849. window$1.clearTimeout(timeoutId);
  3850. var disposeFn = function disposeFn() {};
  3851. disposeFn.guid = "vjs-timeout-" + timeoutId;
  3852. this.off('dispose', disposeFn);
  3853. return timeoutId;
  3854. }
  3855. /**
  3856. * Creates a function that gets run every `x` milliseconds. This function is a wrapper
  3857. * around `window.setInterval`. There are a few reasons to use this one instead though.
  3858. * 1. It gets cleared via {@link Component#clearInterval} when
  3859. * {@link Component#dispose} gets called.
  3860. * 2. The function callback will be a {@link Component~GenericCallback}
  3861. *
  3862. * @param {Component~GenericCallback} fn
  3863. * The function to run every `x` seconds.
  3864. *
  3865. * @param {number} interval
  3866. * Execute the specified function every `x` milliseconds.
  3867. *
  3868. * @return {number}
  3869. * Returns an id that can be used to identify the interval. It can also be be used in
  3870. * {@link Component#clearInterval} to clear the interval.
  3871. *
  3872. * @listens Component#dispose
  3873. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
  3874. */
  3875. ;
  3876. _proto.setInterval = function setInterval(fn, interval) {
  3877. var _this3 = this;
  3878. fn = bind(this, fn);
  3879. var intervalId = window$1.setInterval(fn, interval);
  3880. var disposeFn = function disposeFn() {
  3881. return _this3.clearInterval(intervalId);
  3882. };
  3883. disposeFn.guid = "vjs-interval-" + intervalId;
  3884. this.on('dispose', disposeFn);
  3885. return intervalId;
  3886. }
  3887. /**
  3888. * Clears an interval that gets created via `window.setInterval` or
  3889. * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
  3890. * use this function instead of `window.clearInterval`. If you don't your dispose
  3891. * listener will not get cleaned up until {@link Component#dispose}!
  3892. *
  3893. * @param {number} intervalId
  3894. * The id of the interval to clear. The return value of
  3895. * {@link Component#setInterval} or `window.setInterval`.
  3896. *
  3897. * @return {number}
  3898. * Returns the interval id that was cleared.
  3899. *
  3900. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
  3901. */
  3902. ;
  3903. _proto.clearInterval = function clearInterval(intervalId) {
  3904. window$1.clearInterval(intervalId);
  3905. var disposeFn = function disposeFn() {};
  3906. disposeFn.guid = "vjs-interval-" + intervalId;
  3907. this.off('dispose', disposeFn);
  3908. return intervalId;
  3909. }
  3910. /**
  3911. * Queues up a callback to be passed to requestAnimationFrame (rAF), but
  3912. * with a few extra bonuses:
  3913. *
  3914. * - Supports browsers that do not support rAF by falling back to
  3915. * {@link Component#setTimeout}.
  3916. *
  3917. * - The callback is turned into a {@link Component~GenericCallback} (i.e.
  3918. * bound to the component).
  3919. *
  3920. * - Automatic cancellation of the rAF callback is handled if the component
  3921. * is disposed before it is called.
  3922. *
  3923. * @param {Component~GenericCallback} fn
  3924. * A function that will be bound to this component and executed just
  3925. * before the browser's next repaint.
  3926. *
  3927. * @return {number}
  3928. * Returns an rAF ID that gets used to identify the timeout. It can
  3929. * also be used in {@link Component#cancelAnimationFrame} to cancel
  3930. * the animation frame callback.
  3931. *
  3932. * @listens Component#dispose
  3933. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
  3934. */
  3935. ;
  3936. _proto.requestAnimationFrame = function requestAnimationFrame(fn) {
  3937. var _this4 = this;
  3938. // declare as variables so they are properly available in rAF function
  3939. // eslint-disable-next-line
  3940. var id, disposeFn;
  3941. if (this.supportsRaf_) {
  3942. fn = bind(this, fn);
  3943. id = window$1.requestAnimationFrame(function () {
  3944. _this4.off('dispose', disposeFn);
  3945. fn();
  3946. });
  3947. disposeFn = function disposeFn() {
  3948. return _this4.cancelAnimationFrame(id);
  3949. };
  3950. disposeFn.guid = "vjs-raf-" + id;
  3951. this.on('dispose', disposeFn);
  3952. return id;
  3953. } // Fall back to using a timer.
  3954. return this.setTimeout(fn, 1000 / 60);
  3955. }
  3956. /**
  3957. * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
  3958. * (rAF).
  3959. *
  3960. * If you queue an rAF callback via {@link Component#requestAnimationFrame},
  3961. * use this function instead of `window.cancelAnimationFrame`. If you don't,
  3962. * your dispose listener will not get cleaned up until {@link Component#dispose}!
  3963. *
  3964. * @param {number} id
  3965. * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
  3966. *
  3967. * @return {number}
  3968. * Returns the rAF ID that was cleared.
  3969. *
  3970. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
  3971. */
  3972. ;
  3973. _proto.cancelAnimationFrame = function cancelAnimationFrame(id) {
  3974. if (this.supportsRaf_) {
  3975. window$1.cancelAnimationFrame(id);
  3976. var disposeFn = function disposeFn() {};
  3977. disposeFn.guid = "vjs-raf-" + id;
  3978. this.off('dispose', disposeFn);
  3979. return id;
  3980. } // Fall back to using a timer.
  3981. return this.clearTimeout(id);
  3982. }
  3983. /**
  3984. * Register a `Component` with `videojs` given the name and the component.
  3985. *
  3986. * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
  3987. * should be registered using {@link Tech.registerTech} or
  3988. * {@link videojs:videojs.registerTech}.
  3989. *
  3990. * > NOTE: This function can also be seen on videojs as
  3991. * {@link videojs:videojs.registerComponent}.
  3992. *
  3993. * @param {string} name
  3994. * The name of the `Component` to register.
  3995. *
  3996. * @param {Component} ComponentToRegister
  3997. * The `Component` class to register.
  3998. *
  3999. * @return {Component}
  4000. * The `Component` that was registered.
  4001. */
  4002. ;
  4003. Component.registerComponent = function registerComponent(name, ComponentToRegister) {
  4004. if (typeof name !== 'string' || !name) {
  4005. throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string.");
  4006. }
  4007. var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered.
  4008. var isTech = Tech && Tech.isTech(ComponentToRegister);
  4009. var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
  4010. if (isTech || !isComp) {
  4011. var reason;
  4012. if (isTech) {
  4013. reason = 'techs must be registered using Tech.registerTech()';
  4014. } else {
  4015. reason = 'must be a Component subclass';
  4016. }
  4017. throw new Error("Illegal component, \"" + name + "\"; " + reason + ".");
  4018. }
  4019. name = toTitleCase(name);
  4020. if (!Component.components_) {
  4021. Component.components_ = {};
  4022. }
  4023. var Player = Component.getComponent('Player');
  4024. if (name === 'Player' && Player && Player.players) {
  4025. var players = Player.players;
  4026. var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be
  4027. // in Players.players. So, we must loop through and verify that the value
  4028. // for each item is not null. This allows registration of the Player component
  4029. // after all players have been disposed or before any were created.
  4030. if (players && playerNames.length > 0 && playerNames.map(function (pname) {
  4031. return players[pname];
  4032. }).every(Boolean)) {
  4033. throw new Error('Can not register Player component after player has been created.');
  4034. }
  4035. }
  4036. Component.components_[name] = ComponentToRegister;
  4037. return ComponentToRegister;
  4038. }
  4039. /**
  4040. * Get a `Component` based on the name it was registered with.
  4041. *
  4042. * @param {string} name
  4043. * The Name of the component to get.
  4044. *
  4045. * @return {Component}
  4046. * The `Component` that got registered under the given name.
  4047. *
  4048. * @deprecated In `videojs` 6 this will not return `Component`s that were not
  4049. * registered using {@link Component.registerComponent}. Currently we
  4050. * check the global `videojs` object for a `Component` name and
  4051. * return that if it exists.
  4052. */
  4053. ;
  4054. Component.getComponent = function getComponent(name) {
  4055. if (!name) {
  4056. return;
  4057. }
  4058. name = toTitleCase(name);
  4059. if (Component.components_ && Component.components_[name]) {
  4060. return Component.components_[name];
  4061. }
  4062. };
  4063. return Component;
  4064. }();
  4065. /**
  4066. * Whether or not this component supports `requestAnimationFrame`.
  4067. *
  4068. * This is exposed primarily for testing purposes.
  4069. *
  4070. * @private
  4071. * @type {Boolean}
  4072. */
  4073. Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function';
  4074. Component.registerComponent('Component', Component);
  4075. /**
  4076. * @file browser.js
  4077. * @module browser
  4078. */
  4079. var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || '';
  4080. var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
  4081. var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
  4082. /**
  4083. * Whether or not this device is an iPad.
  4084. *
  4085. * @static
  4086. * @const
  4087. * @type {Boolean}
  4088. */
  4089. var IS_IPAD = /iPad/i.test(USER_AGENT);
  4090. /**
  4091. * Whether or not this device is an iPhone.
  4092. *
  4093. * @static
  4094. * @const
  4095. * @type {Boolean}
  4096. */
  4097. // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
  4098. // to identify iPhones, we need to exclude iPads.
  4099. // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
  4100. var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
  4101. /**
  4102. * Whether or not this device is an iPod.
  4103. *
  4104. * @static
  4105. * @const
  4106. * @type {Boolean}
  4107. */
  4108. var IS_IPOD = /iPod/i.test(USER_AGENT);
  4109. /**
  4110. * Whether or not this is an iOS device.
  4111. *
  4112. * @static
  4113. * @const
  4114. * @type {Boolean}
  4115. */
  4116. var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
  4117. /**
  4118. * The detected iOS version - or `null`.
  4119. *
  4120. * @static
  4121. * @const
  4122. * @type {string|null}
  4123. */
  4124. var IOS_VERSION = function () {
  4125. var match = USER_AGENT.match(/OS (\d+)_/i);
  4126. if (match && match[1]) {
  4127. return match[1];
  4128. }
  4129. return null;
  4130. }();
  4131. /**
  4132. * Whether or not this is an Android device.
  4133. *
  4134. * @static
  4135. * @const
  4136. * @type {Boolean}
  4137. */
  4138. var IS_ANDROID = /Android/i.test(USER_AGENT);
  4139. /**
  4140. * The detected Android version - or `null`.
  4141. *
  4142. * @static
  4143. * @const
  4144. * @type {number|string|null}
  4145. */
  4146. var ANDROID_VERSION = function () {
  4147. // This matches Android Major.Minor.Patch versions
  4148. // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
  4149. var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
  4150. if (!match) {
  4151. return null;
  4152. }
  4153. var major = match[1] && parseFloat(match[1]);
  4154. var minor = match[2] && parseFloat(match[2]);
  4155. if (major && minor) {
  4156. return parseFloat(match[1] + '.' + match[2]);
  4157. } else if (major) {
  4158. return major;
  4159. }
  4160. return null;
  4161. }();
  4162. /**
  4163. * Whether or not this is a native Android browser.
  4164. *
  4165. * @static
  4166. * @const
  4167. * @type {Boolean}
  4168. */
  4169. var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
  4170. /**
  4171. * Whether or not this is Mozilla Firefox.
  4172. *
  4173. * @static
  4174. * @const
  4175. * @type {Boolean}
  4176. */
  4177. var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
  4178. /**
  4179. * Whether or not this is Microsoft Edge.
  4180. *
  4181. * @static
  4182. * @const
  4183. * @type {Boolean}
  4184. */
  4185. var IS_EDGE = /Edge/i.test(USER_AGENT);
  4186. /**
  4187. * Whether or not this is Google Chrome.
  4188. *
  4189. * This will also be `true` for Chrome on iOS, which will have different support
  4190. * as it is actually Safari under the hood.
  4191. *
  4192. * @static
  4193. * @const
  4194. * @type {Boolean}
  4195. */
  4196. var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
  4197. /**
  4198. * The detected Google Chrome version - or `null`.
  4199. *
  4200. * @static
  4201. * @const
  4202. * @type {number|null}
  4203. */
  4204. var CHROME_VERSION = function () {
  4205. var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
  4206. if (match && match[2]) {
  4207. return parseFloat(match[2]);
  4208. }
  4209. return null;
  4210. }();
  4211. /**
  4212. * The detected Internet Explorer version - or `null`.
  4213. *
  4214. * @static
  4215. * @const
  4216. * @type {number|null}
  4217. */
  4218. var IE_VERSION = function () {
  4219. var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
  4220. var version = result && parseFloat(result[1]);
  4221. if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
  4222. // IE 11 has a different user agent string than other IE versions
  4223. version = 11.0;
  4224. }
  4225. return version;
  4226. }();
  4227. /**
  4228. * Whether or not this is desktop Safari.
  4229. *
  4230. * @static
  4231. * @const
  4232. * @type {Boolean}
  4233. */
  4234. var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
  4235. /**
  4236. * Whether or not this is any flavor of Safari - including iOS.
  4237. *
  4238. * @static
  4239. * @const
  4240. * @type {Boolean}
  4241. */
  4242. var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
  4243. /**
  4244. * Whether or not this is a Windows machine.
  4245. *
  4246. * @static
  4247. * @const
  4248. * @type {Boolean}
  4249. */
  4250. var IS_WINDOWS = /Windows/i.test(USER_AGENT);
  4251. /**
  4252. * Whether or not this device is touch-enabled.
  4253. *
  4254. * @static
  4255. * @const
  4256. * @type {Boolean}
  4257. */
  4258. var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch);
  4259. var browser = /*#__PURE__*/Object.freeze({
  4260. IS_IPAD: IS_IPAD,
  4261. IS_IPHONE: IS_IPHONE,
  4262. IS_IPOD: IS_IPOD,
  4263. IS_IOS: IS_IOS,
  4264. IOS_VERSION: IOS_VERSION,
  4265. IS_ANDROID: IS_ANDROID,
  4266. ANDROID_VERSION: ANDROID_VERSION,
  4267. IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
  4268. IS_FIREFOX: IS_FIREFOX,
  4269. IS_EDGE: IS_EDGE,
  4270. IS_CHROME: IS_CHROME,
  4271. CHROME_VERSION: CHROME_VERSION,
  4272. IE_VERSION: IE_VERSION,
  4273. IS_SAFARI: IS_SAFARI,
  4274. IS_ANY_SAFARI: IS_ANY_SAFARI,
  4275. IS_WINDOWS: IS_WINDOWS,
  4276. TOUCH_ENABLED: TOUCH_ENABLED
  4277. });
  4278. /**
  4279. * @file time-ranges.js
  4280. * @module time-ranges
  4281. */
  4282. /**
  4283. * Returns the time for the specified index at the start or end
  4284. * of a TimeRange object.
  4285. *
  4286. * @typedef {Function} TimeRangeIndex
  4287. *
  4288. * @param {number} [index=0]
  4289. * The range number to return the time for.
  4290. *
  4291. * @return {number}
  4292. * The time offset at the specified index.
  4293. *
  4294. * @deprecated The index argument must be provided.
  4295. * In the future, leaving it out will throw an error.
  4296. */
  4297. /**
  4298. * An object that contains ranges of time.
  4299. *
  4300. * @typedef {Object} TimeRange
  4301. *
  4302. * @property {number} length
  4303. * The number of time ranges represented by this object.
  4304. *
  4305. * @property {module:time-ranges~TimeRangeIndex} start
  4306. * Returns the time offset at which a specified time range begins.
  4307. *
  4308. * @property {module:time-ranges~TimeRangeIndex} end
  4309. * Returns the time offset at which a specified time range ends.
  4310. *
  4311. * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
  4312. */
  4313. /**
  4314. * Check if any of the time ranges are over the maximum index.
  4315. *
  4316. * @private
  4317. * @param {string} fnName
  4318. * The function name to use for logging
  4319. *
  4320. * @param {number} index
  4321. * The index to check
  4322. *
  4323. * @param {number} maxIndex
  4324. * The maximum possible index
  4325. *
  4326. * @throws {Error} if the timeRanges provided are over the maxIndex
  4327. */
  4328. function rangeCheck(fnName, index, maxIndex) {
  4329. if (typeof index !== 'number' || index < 0 || index > maxIndex) {
  4330. throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ").");
  4331. }
  4332. }
  4333. /**
  4334. * Get the time for the specified index at the start or end
  4335. * of a TimeRange object.
  4336. *
  4337. * @private
  4338. * @param {string} fnName
  4339. * The function name to use for logging
  4340. *
  4341. * @param {string} valueIndex
  4342. * The property that should be used to get the time. should be
  4343. * 'start' or 'end'
  4344. *
  4345. * @param {Array} ranges
  4346. * An array of time ranges
  4347. *
  4348. * @param {Array} [rangeIndex=0]
  4349. * The index to start the search at
  4350. *
  4351. * @return {number}
  4352. * The time that offset at the specified index.
  4353. *
  4354. * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
  4355. * @throws {Error} if rangeIndex is more than the length of ranges
  4356. */
  4357. function getRange(fnName, valueIndex, ranges, rangeIndex) {
  4358. rangeCheck(fnName, rangeIndex, ranges.length - 1);
  4359. return ranges[rangeIndex][valueIndex];
  4360. }
  4361. /**
  4362. * Create a time range object given ranges of time.
  4363. *
  4364. * @private
  4365. * @param {Array} [ranges]
  4366. * An array of time ranges.
  4367. */
  4368. function createTimeRangesObj(ranges) {
  4369. if (ranges === undefined || ranges.length === 0) {
  4370. return {
  4371. length: 0,
  4372. start: function start() {
  4373. throw new Error('This TimeRanges object is empty');
  4374. },
  4375. end: function end() {
  4376. throw new Error('This TimeRanges object is empty');
  4377. }
  4378. };
  4379. }
  4380. return {
  4381. length: ranges.length,
  4382. start: getRange.bind(null, 'start', 0, ranges),
  4383. end: getRange.bind(null, 'end', 1, ranges)
  4384. };
  4385. }
  4386. /**
  4387. * Create a `TimeRange` object which mimics an
  4388. * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
  4389. *
  4390. * @param {number|Array[]} start
  4391. * The start of a single range (a number) or an array of ranges (an
  4392. * array of arrays of two numbers each).
  4393. *
  4394. * @param {number} end
  4395. * The end of a single range. Cannot be used with the array form of
  4396. * the `start` argument.
  4397. */
  4398. function createTimeRanges(start, end) {
  4399. if (Array.isArray(start)) {
  4400. return createTimeRangesObj(start);
  4401. } else if (start === undefined || end === undefined) {
  4402. return createTimeRangesObj();
  4403. }
  4404. return createTimeRangesObj([[start, end]]);
  4405. }
  4406. /**
  4407. * @file buffer.js
  4408. * @module buffer
  4409. */
  4410. /**
  4411. * Compute the percentage of the media that has been buffered.
  4412. *
  4413. * @param {TimeRange} buffered
  4414. * The current `TimeRange` object representing buffered time ranges
  4415. *
  4416. * @param {number} duration
  4417. * Total duration of the media
  4418. *
  4419. * @return {number}
  4420. * Percent buffered of the total duration in decimal form.
  4421. */
  4422. function bufferedPercent(buffered, duration) {
  4423. var bufferedDuration = 0;
  4424. var start;
  4425. var end;
  4426. if (!duration) {
  4427. return 0;
  4428. }
  4429. if (!buffered || !buffered.length) {
  4430. buffered = createTimeRanges(0, 0);
  4431. }
  4432. for (var i = 0; i < buffered.length; i++) {
  4433. start = buffered.start(i);
  4434. end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction
  4435. if (end > duration) {
  4436. end = duration;
  4437. }
  4438. bufferedDuration += end - start;
  4439. }
  4440. return bufferedDuration / duration;
  4441. }
  4442. /**
  4443. * @file fullscreen-api.js
  4444. * @module fullscreen-api
  4445. * @private
  4446. */
  4447. /**
  4448. * Store the browser-specific methods for the fullscreen API.
  4449. *
  4450. * @type {Object}
  4451. * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
  4452. * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
  4453. */
  4454. var FullscreenApi = {}; // browser API methods
  4455. var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit
  4456. ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla
  4457. ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft
  4458. ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']];
  4459. var specApi = apiMap[0];
  4460. var browserApi;
  4461. var prefixedAPI = false; // determine the supported set of functions
  4462. for (var i = 0; i < apiMap.length; i++) {
  4463. // check for exitFullscreen function
  4464. if (apiMap[i][1] in document) {
  4465. browserApi = apiMap[i];
  4466. break;
  4467. }
  4468. } // map the browser API names to the spec API names
  4469. if (browserApi) {
  4470. for (var _i = 0; _i < browserApi.length; _i++) {
  4471. FullscreenApi[specApi[_i]] = browserApi[_i];
  4472. }
  4473. prefixedAPI = browserApi[0] === specApi[0];
  4474. }
  4475. /**
  4476. * @file media-error.js
  4477. */
  4478. /**
  4479. * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
  4480. *
  4481. * @param {number|string|Object|MediaError} value
  4482. * This can be of multiple types:
  4483. * - number: should be a standard error code
  4484. * - string: an error message (the code will be 0)
  4485. * - Object: arbitrary properties
  4486. * - `MediaError` (native): used to populate a video.js `MediaError` object
  4487. * - `MediaError` (video.js): will return itself if it's already a
  4488. * video.js `MediaError` object.
  4489. *
  4490. * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
  4491. * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
  4492. *
  4493. * @class MediaError
  4494. */
  4495. function MediaError(value) {
  4496. // Allow redundant calls to this constructor to avoid having `instanceof`
  4497. // checks peppered around the code.
  4498. if (value instanceof MediaError) {
  4499. return value;
  4500. }
  4501. if (typeof value === 'number') {
  4502. this.code = value;
  4503. } else if (typeof value === 'string') {
  4504. // default code is zero, so this is a custom error
  4505. this.message = value;
  4506. } else if (isObject(value)) {
  4507. // We assign the `code` property manually because native `MediaError` objects
  4508. // do not expose it as an own/enumerable property of the object.
  4509. if (typeof value.code === 'number') {
  4510. this.code = value.code;
  4511. }
  4512. assign(this, value);
  4513. }
  4514. if (!this.message) {
  4515. this.message = MediaError.defaultMessages[this.code] || '';
  4516. }
  4517. }
  4518. /**
  4519. * The error code that refers two one of the defined `MediaError` types
  4520. *
  4521. * @type {Number}
  4522. */
  4523. MediaError.prototype.code = 0;
  4524. /**
  4525. * An optional message that to show with the error. Message is not part of the HTML5
  4526. * video spec but allows for more informative custom errors.
  4527. *
  4528. * @type {String}
  4529. */
  4530. MediaError.prototype.message = '';
  4531. /**
  4532. * An optional status code that can be set by plugins to allow even more detail about
  4533. * the error. For example a plugin might provide a specific HTTP status code and an
  4534. * error message for that code. Then when the plugin gets that error this class will
  4535. * know how to display an error message for it. This allows a custom message to show
  4536. * up on the `Player` error overlay.
  4537. *
  4538. * @type {Array}
  4539. */
  4540. MediaError.prototype.status = null;
  4541. /**
  4542. * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
  4543. * specification listed under {@link MediaError} for more information.
  4544. *
  4545. * @enum {array}
  4546. * @readonly
  4547. * @property {string} 0 - MEDIA_ERR_CUSTOM
  4548. * @property {string} 1 - MEDIA_ERR_ABORTED
  4549. * @property {string} 2 - MEDIA_ERR_NETWORK
  4550. * @property {string} 3 - MEDIA_ERR_DECODE
  4551. * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
  4552. * @property {string} 5 - MEDIA_ERR_ENCRYPTED
  4553. */
  4554. MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
  4555. /**
  4556. * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
  4557. *
  4558. * @type {Array}
  4559. * @constant
  4560. */
  4561. MediaError.defaultMessages = {
  4562. 1: 'You aborted the media playback',
  4563. 2: 'A network error caused the media download to fail part-way.',
  4564. 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
  4565. 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
  4566. 5: 'The media is encrypted and we do not have the keys to decrypt it.'
  4567. }; // Add types as properties on MediaError
  4568. // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
  4569. for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
  4570. MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance
  4571. MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
  4572. } // jsdocs for instance/static members added above
  4573. var tuple = SafeParseTuple;
  4574. function SafeParseTuple(obj, reviver) {
  4575. var json;
  4576. var error = null;
  4577. try {
  4578. json = JSON.parse(obj, reviver);
  4579. } catch (err) {
  4580. error = err;
  4581. }
  4582. return [error, json];
  4583. }
  4584. /**
  4585. * Returns whether an object is `Promise`-like (i.e. has a `then` method).
  4586. *
  4587. * @param {Object} value
  4588. * An object that may or may not be `Promise`-like.
  4589. *
  4590. * @return {boolean}
  4591. * Whether or not the object is `Promise`-like.
  4592. */
  4593. function isPromise(value) {
  4594. return value !== undefined && value !== null && typeof value.then === 'function';
  4595. }
  4596. /**
  4597. * Silence a Promise-like object.
  4598. *
  4599. * This is useful for avoiding non-harmful, but potentially confusing "uncaught
  4600. * play promise" rejection error messages.
  4601. *
  4602. * @param {Object} value
  4603. * An object that may or may not be `Promise`-like.
  4604. */
  4605. function silencePromise(value) {
  4606. if (isPromise(value)) {
  4607. value.then(null, function (e) {});
  4608. }
  4609. }
  4610. /**
  4611. * @file text-track-list-converter.js Utilities for capturing text track state and
  4612. * re-creating tracks based on a capture.
  4613. *
  4614. * @module text-track-list-converter
  4615. */
  4616. /**
  4617. * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
  4618. * represents the {@link TextTrack}'s state.
  4619. *
  4620. * @param {TextTrack} track
  4621. * The text track to query.
  4622. *
  4623. * @return {Object}
  4624. * A serializable javascript representation of the TextTrack.
  4625. * @private
  4626. */
  4627. var trackToJson_ = function trackToJson_(track) {
  4628. var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
  4629. if (track[prop]) {
  4630. acc[prop] = track[prop];
  4631. }
  4632. return acc;
  4633. }, {
  4634. cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
  4635. return {
  4636. startTime: cue.startTime,
  4637. endTime: cue.endTime,
  4638. text: cue.text,
  4639. id: cue.id
  4640. };
  4641. })
  4642. });
  4643. return ret;
  4644. };
  4645. /**
  4646. * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
  4647. * state of all {@link TextTrack}s currently configured. The return array is compatible with
  4648. * {@link text-track-list-converter:jsonToTextTracks}.
  4649. *
  4650. * @param {Tech} tech
  4651. * The tech object to query
  4652. *
  4653. * @return {Array}
  4654. * A serializable javascript representation of the {@link Tech}s
  4655. * {@link TextTrackList}.
  4656. */
  4657. var textTracksToJson = function textTracksToJson(tech) {
  4658. var trackEls = tech.$$('track');
  4659. var trackObjs = Array.prototype.map.call(trackEls, function (t) {
  4660. return t.track;
  4661. });
  4662. var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
  4663. var json = trackToJson_(trackEl.track);
  4664. if (trackEl.src) {
  4665. json.src = trackEl.src;
  4666. }
  4667. return json;
  4668. });
  4669. return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
  4670. return trackObjs.indexOf(track) === -1;
  4671. }).map(trackToJson_));
  4672. };
  4673. /**
  4674. * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
  4675. * object {@link TextTrack} representations.
  4676. *
  4677. * @param {Array} json
  4678. * An array of `TextTrack` representation objects, like those that would be
  4679. * produced by `textTracksToJson`.
  4680. *
  4681. * @param {Tech} tech
  4682. * The `Tech` to create the `TextTrack`s on.
  4683. */
  4684. var jsonToTextTracks = function jsonToTextTracks(json, tech) {
  4685. json.forEach(function (track) {
  4686. var addedTrack = tech.addRemoteTextTrack(track).track;
  4687. if (!track.src && track.cues) {
  4688. track.cues.forEach(function (cue) {
  4689. return addedTrack.addCue(cue);
  4690. });
  4691. }
  4692. });
  4693. return tech.textTracks();
  4694. };
  4695. var textTrackConverter = {
  4696. textTracksToJson: textTracksToJson,
  4697. jsonToTextTracks: jsonToTextTracks,
  4698. trackToJson_: trackToJson_
  4699. };
  4700. var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
  4701. function createCommonjsModule(fn, module) {
  4702. return module = { exports: {} }, fn(module, module.exports), module.exports;
  4703. }
  4704. var keycode = createCommonjsModule(function (module, exports) {
  4705. // Source: http://jsfiddle.net/vWx8V/
  4706. // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
  4707. /**
  4708. * Conenience method returns corresponding value for given keyName or keyCode.
  4709. *
  4710. * @param {Mixed} keyCode {Number} or keyName {String}
  4711. * @return {Mixed}
  4712. * @api public
  4713. */
  4714. function keyCode(searchInput) {
  4715. // Keyboard Events
  4716. if (searchInput && 'object' === typeof searchInput) {
  4717. var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
  4718. if (hasKeyCode) searchInput = hasKeyCode;
  4719. } // Numbers
  4720. if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string)
  4721. var search = String(searchInput); // check codes
  4722. var foundNamedKey = codes[search.toLowerCase()];
  4723. if (foundNamedKey) return foundNamedKey; // check aliases
  4724. var foundNamedKey = aliases[search.toLowerCase()];
  4725. if (foundNamedKey) return foundNamedKey; // weird character?
  4726. if (search.length === 1) return search.charCodeAt(0);
  4727. return undefined;
  4728. }
  4729. /**
  4730. * Compares a keyboard event with a given keyCode or keyName.
  4731. *
  4732. * @param {Event} event Keyboard event that should be tested
  4733. * @param {Mixed} keyCode {Number} or keyName {String}
  4734. * @return {Boolean}
  4735. * @api public
  4736. */
  4737. keyCode.isEventKey = function isEventKey(event, nameOrCode) {
  4738. if (event && 'object' === typeof event) {
  4739. var keyCode = event.which || event.keyCode || event.charCode;
  4740. if (keyCode === null || keyCode === undefined) {
  4741. return false;
  4742. }
  4743. if (typeof nameOrCode === 'string') {
  4744. // check codes
  4745. var foundNamedKey = codes[nameOrCode.toLowerCase()];
  4746. if (foundNamedKey) {
  4747. return foundNamedKey === keyCode;
  4748. } // check aliases
  4749. var foundNamedKey = aliases[nameOrCode.toLowerCase()];
  4750. if (foundNamedKey) {
  4751. return foundNamedKey === keyCode;
  4752. }
  4753. } else if (typeof nameOrCode === 'number') {
  4754. return nameOrCode === keyCode;
  4755. }
  4756. return false;
  4757. }
  4758. };
  4759. exports = module.exports = keyCode;
  4760. /**
  4761. * Get by name
  4762. *
  4763. * exports.code['enter'] // => 13
  4764. */
  4765. var codes = exports.code = exports.codes = {
  4766. 'backspace': 8,
  4767. 'tab': 9,
  4768. 'enter': 13,
  4769. 'shift': 16,
  4770. 'ctrl': 17,
  4771. 'alt': 18,
  4772. 'pause/break': 19,
  4773. 'caps lock': 20,
  4774. 'esc': 27,
  4775. 'space': 32,
  4776. 'page up': 33,
  4777. 'page down': 34,
  4778. 'end': 35,
  4779. 'home': 36,
  4780. 'left': 37,
  4781. 'up': 38,
  4782. 'right': 39,
  4783. 'down': 40,
  4784. 'insert': 45,
  4785. 'delete': 46,
  4786. 'command': 91,
  4787. 'left command': 91,
  4788. 'right command': 93,
  4789. 'numpad *': 106,
  4790. 'numpad +': 107,
  4791. 'numpad -': 109,
  4792. 'numpad .': 110,
  4793. 'numpad /': 111,
  4794. 'num lock': 144,
  4795. 'scroll lock': 145,
  4796. 'my computer': 182,
  4797. 'my calculator': 183,
  4798. ';': 186,
  4799. '=': 187,
  4800. ',': 188,
  4801. '-': 189,
  4802. '.': 190,
  4803. '/': 191,
  4804. '`': 192,
  4805. '[': 219,
  4806. '\\': 220,
  4807. ']': 221,
  4808. "'": 222 // Helper aliases
  4809. };
  4810. var aliases = exports.aliases = {
  4811. 'windows': 91,
  4812. '⇧': 16,
  4813. '⌥': 18,
  4814. '⌃': 17,
  4815. '⌘': 91,
  4816. 'ctl': 17,
  4817. 'control': 17,
  4818. 'option': 18,
  4819. 'pause': 19,
  4820. 'break': 19,
  4821. 'caps': 20,
  4822. 'return': 13,
  4823. 'escape': 27,
  4824. 'spc': 32,
  4825. 'spacebar': 32,
  4826. 'pgup': 33,
  4827. 'pgdn': 34,
  4828. 'ins': 45,
  4829. 'del': 46,
  4830. 'cmd': 91
  4831. /*!
  4832. * Programatically add the following
  4833. */
  4834. // lower case chars
  4835. };
  4836. for (i = 97; i < 123; i++) {
  4837. codes[String.fromCharCode(i)] = i - 32;
  4838. } // numbers
  4839. for (var i = 48; i < 58; i++) {
  4840. codes[i - 48] = i;
  4841. } // function keys
  4842. for (i = 1; i < 13; i++) {
  4843. codes['f' + i] = i + 111;
  4844. } // numpad keys
  4845. for (i = 0; i < 10; i++) {
  4846. codes['numpad ' + i] = i + 96;
  4847. }
  4848. /**
  4849. * Get by code
  4850. *
  4851. * exports.name[13] // => 'Enter'
  4852. */
  4853. var names = exports.names = exports.title = {}; // title for backward compat
  4854. // Create reverse mapping
  4855. for (i in codes) {
  4856. names[codes[i]] = i;
  4857. } // Add aliases
  4858. for (var alias in aliases) {
  4859. codes[alias] = aliases[alias];
  4860. }
  4861. });
  4862. var keycode_1 = keycode.code;
  4863. var keycode_2 = keycode.codes;
  4864. var keycode_3 = keycode.aliases;
  4865. var keycode_4 = keycode.names;
  4866. var keycode_5 = keycode.title;
  4867. var MODAL_CLASS_NAME = 'vjs-modal-dialog';
  4868. /**
  4869. * The `ModalDialog` displays over the video and its controls, which blocks
  4870. * interaction with the player until it is closed.
  4871. *
  4872. * Modal dialogs include a "Close" button and will close when that button
  4873. * is activated - or when ESC is pressed anywhere.
  4874. *
  4875. * @extends Component
  4876. */
  4877. var ModalDialog =
  4878. /*#__PURE__*/
  4879. function (_Component) {
  4880. _inheritsLoose(ModalDialog, _Component);
  4881. /**
  4882. * Create an instance of this class.
  4883. *
  4884. * @param {Player} player
  4885. * The `Player` that this class should be attached to.
  4886. *
  4887. * @param {Object} [options]
  4888. * The key/value store of player options.
  4889. *
  4890. * @param {Mixed} [options.content=undefined]
  4891. * Provide customized content for this modal.
  4892. *
  4893. * @param {string} [options.description]
  4894. * A text description for the modal, primarily for accessibility.
  4895. *
  4896. * @param {boolean} [options.fillAlways=false]
  4897. * Normally, modals are automatically filled only the first time
  4898. * they open. This tells the modal to refresh its content
  4899. * every time it opens.
  4900. *
  4901. * @param {string} [options.label]
  4902. * A text label for the modal, primarily for accessibility.
  4903. *
  4904. * @param {boolean} [options.pauseOnOpen=true]
  4905. * If `true`, playback will will be paused if playing when
  4906. * the modal opens, and resumed when it closes.
  4907. *
  4908. * @param {boolean} [options.temporary=true]
  4909. * If `true`, the modal can only be opened once; it will be
  4910. * disposed as soon as it's closed.
  4911. *
  4912. * @param {boolean} [options.uncloseable=false]
  4913. * If `true`, the user will not be able to close the modal
  4914. * through the UI in the normal ways. Programmatic closing is
  4915. * still possible.
  4916. */
  4917. function ModalDialog(player, options) {
  4918. var _this;
  4919. _this = _Component.call(this, player, options) || this;
  4920. _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
  4921. _this.closeable(!_this.options_.uncloseable);
  4922. _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized
  4923. // because we only want the contents of the modal in the contentEl
  4924. // (not the UI elements like the close button).
  4925. _this.contentEl_ = createEl('div', {
  4926. className: MODAL_CLASS_NAME + "-content"
  4927. }, {
  4928. role: 'document'
  4929. });
  4930. _this.descEl_ = createEl('p', {
  4931. className: MODAL_CLASS_NAME + "-description vjs-control-text",
  4932. id: _this.el().getAttribute('aria-describedby')
  4933. });
  4934. textContent(_this.descEl_, _this.description());
  4935. _this.el_.appendChild(_this.descEl_);
  4936. _this.el_.appendChild(_this.contentEl_);
  4937. return _this;
  4938. }
  4939. /**
  4940. * Create the `ModalDialog`'s DOM element
  4941. *
  4942. * @return {Element}
  4943. * The DOM element that gets created.
  4944. */
  4945. var _proto = ModalDialog.prototype;
  4946. _proto.createEl = function createEl$$1() {
  4947. return _Component.prototype.createEl.call(this, 'div', {
  4948. className: this.buildCSSClass(),
  4949. tabIndex: -1
  4950. }, {
  4951. 'aria-describedby': this.id() + "_description",
  4952. 'aria-hidden': 'true',
  4953. 'aria-label': this.label(),
  4954. 'role': 'dialog'
  4955. });
  4956. };
  4957. _proto.dispose = function dispose() {
  4958. this.contentEl_ = null;
  4959. this.descEl_ = null;
  4960. this.previouslyActiveEl_ = null;
  4961. _Component.prototype.dispose.call(this);
  4962. }
  4963. /**
  4964. * Builds the default DOM `className`.
  4965. *
  4966. * @return {string}
  4967. * The DOM `className` for this object.
  4968. */
  4969. ;
  4970. _proto.buildCSSClass = function buildCSSClass() {
  4971. return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this);
  4972. }
  4973. /**
  4974. * Handles `keydown` events on the document, looking for ESC, which closes
  4975. * the modal.
  4976. *
  4977. * @param {EventTarget~Event} event
  4978. * The keypress that triggered this event.
  4979. *
  4980. * @listens keydown
  4981. */
  4982. ;
  4983. _proto.handleKeyPress = function handleKeyPress(event) {
  4984. if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
  4985. this.close();
  4986. }
  4987. }
  4988. /**
  4989. * Returns the label string for this modal. Primarily used for accessibility.
  4990. *
  4991. * @return {string}
  4992. * the localized or raw label of this modal.
  4993. */
  4994. ;
  4995. _proto.label = function label() {
  4996. return this.localize(this.options_.label || 'Modal Window');
  4997. }
  4998. /**
  4999. * Returns the description string for this modal. Primarily used for
  5000. * accessibility.
  5001. *
  5002. * @return {string}
  5003. * The localized or raw description of this modal.
  5004. */
  5005. ;
  5006. _proto.description = function description() {
  5007. var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable.
  5008. if (this.closeable()) {
  5009. desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
  5010. }
  5011. return desc;
  5012. }
  5013. /**
  5014. * Opens the modal.
  5015. *
  5016. * @fires ModalDialog#beforemodalopen
  5017. * @fires ModalDialog#modalopen
  5018. */
  5019. ;
  5020. _proto.open = function open() {
  5021. if (!this.opened_) {
  5022. var player = this.player();
  5023. /**
  5024. * Fired just before a `ModalDialog` is opened.
  5025. *
  5026. * @event ModalDialog#beforemodalopen
  5027. * @type {EventTarget~Event}
  5028. */
  5029. this.trigger('beforemodalopen');
  5030. this.opened_ = true; // Fill content if the modal has never opened before and
  5031. // never been filled.
  5032. if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
  5033. this.fill();
  5034. } // If the player was playing, pause it and take note of its previously
  5035. // playing state.
  5036. this.wasPlaying_ = !player.paused();
  5037. if (this.options_.pauseOnOpen && this.wasPlaying_) {
  5038. player.pause();
  5039. }
  5040. if (this.closeable()) {
  5041. this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
  5042. } // Hide controls and note if they were enabled.
  5043. this.hadControls_ = player.controls();
  5044. player.controls(false);
  5045. this.show();
  5046. this.conditionalFocus_();
  5047. this.el().setAttribute('aria-hidden', 'false');
  5048. /**
  5049. * Fired just after a `ModalDialog` is opened.
  5050. *
  5051. * @event ModalDialog#modalopen
  5052. * @type {EventTarget~Event}
  5053. */
  5054. this.trigger('modalopen');
  5055. this.hasBeenOpened_ = true;
  5056. }
  5057. }
  5058. /**
  5059. * If the `ModalDialog` is currently open or closed.
  5060. *
  5061. * @param {boolean} [value]
  5062. * If given, it will open (`true`) or close (`false`) the modal.
  5063. *
  5064. * @return {boolean}
  5065. * the current open state of the modaldialog
  5066. */
  5067. ;
  5068. _proto.opened = function opened(value) {
  5069. if (typeof value === 'boolean') {
  5070. this[value ? 'open' : 'close']();
  5071. }
  5072. return this.opened_;
  5073. }
  5074. /**
  5075. * Closes the modal, does nothing if the `ModalDialog` is
  5076. * not open.
  5077. *
  5078. * @fires ModalDialog#beforemodalclose
  5079. * @fires ModalDialog#modalclose
  5080. */
  5081. ;
  5082. _proto.close = function close() {
  5083. if (!this.opened_) {
  5084. return;
  5085. }
  5086. var player = this.player();
  5087. /**
  5088. * Fired just before a `ModalDialog` is closed.
  5089. *
  5090. * @event ModalDialog#beforemodalclose
  5091. * @type {EventTarget~Event}
  5092. */
  5093. this.trigger('beforemodalclose');
  5094. this.opened_ = false;
  5095. if (this.wasPlaying_ && this.options_.pauseOnOpen) {
  5096. player.play();
  5097. }
  5098. if (this.closeable()) {
  5099. this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
  5100. }
  5101. if (this.hadControls_) {
  5102. player.controls(true);
  5103. }
  5104. this.hide();
  5105. this.el().setAttribute('aria-hidden', 'true');
  5106. /**
  5107. * Fired just after a `ModalDialog` is closed.
  5108. *
  5109. * @event ModalDialog#modalclose
  5110. * @type {EventTarget~Event}
  5111. */
  5112. this.trigger('modalclose');
  5113. this.conditionalBlur_();
  5114. if (this.options_.temporary) {
  5115. this.dispose();
  5116. }
  5117. }
  5118. /**
  5119. * Check to see if the `ModalDialog` is closeable via the UI.
  5120. *
  5121. * @param {boolean} [value]
  5122. * If given as a boolean, it will set the `closeable` option.
  5123. *
  5124. * @return {boolean}
  5125. * Returns the final value of the closable option.
  5126. */
  5127. ;
  5128. _proto.closeable = function closeable(value) {
  5129. if (typeof value === 'boolean') {
  5130. var closeable = this.closeable_ = !!value;
  5131. var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one.
  5132. if (closeable && !close) {
  5133. // The close button should be a child of the modal - not its
  5134. // content element, so temporarily change the content element.
  5135. var temp = this.contentEl_;
  5136. this.contentEl_ = this.el_;
  5137. close = this.addChild('closeButton', {
  5138. controlText: 'Close Modal Dialog'
  5139. });
  5140. this.contentEl_ = temp;
  5141. this.on(close, 'close', this.close);
  5142. } // If this is being made uncloseable and has a close button, remove it.
  5143. if (!closeable && close) {
  5144. this.off(close, 'close', this.close);
  5145. this.removeChild(close);
  5146. close.dispose();
  5147. }
  5148. }
  5149. return this.closeable_;
  5150. }
  5151. /**
  5152. * Fill the modal's content element with the modal's "content" option.
  5153. * The content element will be emptied before this change takes place.
  5154. */
  5155. ;
  5156. _proto.fill = function fill() {
  5157. this.fillWith(this.content());
  5158. }
  5159. /**
  5160. * Fill the modal's content element with arbitrary content.
  5161. * The content element will be emptied before this change takes place.
  5162. *
  5163. * @fires ModalDialog#beforemodalfill
  5164. * @fires ModalDialog#modalfill
  5165. *
  5166. * @param {Mixed} [content]
  5167. * The same rules apply to this as apply to the `content` option.
  5168. */
  5169. ;
  5170. _proto.fillWith = function fillWith(content) {
  5171. var contentEl = this.contentEl();
  5172. var parentEl = contentEl.parentNode;
  5173. var nextSiblingEl = contentEl.nextSibling;
  5174. /**
  5175. * Fired just before a `ModalDialog` is filled with content.
  5176. *
  5177. * @event ModalDialog#beforemodalfill
  5178. * @type {EventTarget~Event}
  5179. */
  5180. this.trigger('beforemodalfill');
  5181. this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing
  5182. // manipulation to avoid modifying the live DOM multiple times.
  5183. parentEl.removeChild(contentEl);
  5184. this.empty();
  5185. insertContent(contentEl, content);
  5186. /**
  5187. * Fired just after a `ModalDialog` is filled with content.
  5188. *
  5189. * @event ModalDialog#modalfill
  5190. * @type {EventTarget~Event}
  5191. */
  5192. this.trigger('modalfill'); // Re-inject the re-filled content element.
  5193. if (nextSiblingEl) {
  5194. parentEl.insertBefore(contentEl, nextSiblingEl);
  5195. } else {
  5196. parentEl.appendChild(contentEl);
  5197. } // make sure that the close button is last in the dialog DOM
  5198. var closeButton = this.getChild('closeButton');
  5199. if (closeButton) {
  5200. parentEl.appendChild(closeButton.el_);
  5201. }
  5202. }
  5203. /**
  5204. * Empties the content element. This happens anytime the modal is filled.
  5205. *
  5206. * @fires ModalDialog#beforemodalempty
  5207. * @fires ModalDialog#modalempty
  5208. */
  5209. ;
  5210. _proto.empty = function empty() {
  5211. /**
  5212. * Fired just before a `ModalDialog` is emptied.
  5213. *
  5214. * @event ModalDialog#beforemodalempty
  5215. * @type {EventTarget~Event}
  5216. */
  5217. this.trigger('beforemodalempty');
  5218. emptyEl(this.contentEl());
  5219. /**
  5220. * Fired just after a `ModalDialog` is emptied.
  5221. *
  5222. * @event ModalDialog#modalempty
  5223. * @type {EventTarget~Event}
  5224. */
  5225. this.trigger('modalempty');
  5226. }
  5227. /**
  5228. * Gets or sets the modal content, which gets normalized before being
  5229. * rendered into the DOM.
  5230. *
  5231. * This does not update the DOM or fill the modal, but it is called during
  5232. * that process.
  5233. *
  5234. * @param {Mixed} [value]
  5235. * If defined, sets the internal content value to be used on the
  5236. * next call(s) to `fill`. This value is normalized before being
  5237. * inserted. To "clear" the internal content value, pass `null`.
  5238. *
  5239. * @return {Mixed}
  5240. * The current content of the modal dialog
  5241. */
  5242. ;
  5243. _proto.content = function content(value) {
  5244. if (typeof value !== 'undefined') {
  5245. this.content_ = value;
  5246. }
  5247. return this.content_;
  5248. }
  5249. /**
  5250. * conditionally focus the modal dialog if focus was previously on the player.
  5251. *
  5252. * @private
  5253. */
  5254. ;
  5255. _proto.conditionalFocus_ = function conditionalFocus_() {
  5256. var activeEl = document.activeElement;
  5257. var playerEl = this.player_.el_;
  5258. this.previouslyActiveEl_ = null;
  5259. if (playerEl.contains(activeEl) || playerEl === activeEl) {
  5260. this.previouslyActiveEl_ = activeEl;
  5261. this.focus();
  5262. this.on(document, 'keydown', this.handleKeyDown);
  5263. }
  5264. }
  5265. /**
  5266. * conditionally blur the element and refocus the last focused element
  5267. *
  5268. * @private
  5269. */
  5270. ;
  5271. _proto.conditionalBlur_ = function conditionalBlur_() {
  5272. if (this.previouslyActiveEl_) {
  5273. this.previouslyActiveEl_.focus();
  5274. this.previouslyActiveEl_ = null;
  5275. }
  5276. this.off(document, 'keydown', this.handleKeyDown);
  5277. }
  5278. /**
  5279. * Keydown handler. Attached when modal is focused.
  5280. *
  5281. * @listens keydown
  5282. */
  5283. ;
  5284. _proto.handleKeyDown = function handleKeyDown(event) {
  5285. // exit early if it isn't a tab key
  5286. if (!keycode.isEventKey(event, 'Tab')) {
  5287. return;
  5288. }
  5289. var focusableEls = this.focusableEls_();
  5290. var activeEl = this.el_.querySelector(':focus');
  5291. var focusIndex;
  5292. for (var i = 0; i < focusableEls.length; i++) {
  5293. if (activeEl === focusableEls[i]) {
  5294. focusIndex = i;
  5295. break;
  5296. }
  5297. }
  5298. if (document.activeElement === this.el_) {
  5299. focusIndex = 0;
  5300. }
  5301. if (event.shiftKey && focusIndex === 0) {
  5302. focusableEls[focusableEls.length - 1].focus();
  5303. event.preventDefault();
  5304. } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
  5305. focusableEls[0].focus();
  5306. event.preventDefault();
  5307. }
  5308. }
  5309. /**
  5310. * get all focusable elements
  5311. *
  5312. * @private
  5313. */
  5314. ;
  5315. _proto.focusableEls_ = function focusableEls_() {
  5316. var allChildren = this.el_.querySelectorAll('*');
  5317. return Array.prototype.filter.call(allChildren, function (child) {
  5318. 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');
  5319. });
  5320. };
  5321. return ModalDialog;
  5322. }(Component);
  5323. /**
  5324. * Default options for `ModalDialog` default options.
  5325. *
  5326. * @type {Object}
  5327. * @private
  5328. */
  5329. ModalDialog.prototype.options_ = {
  5330. pauseOnOpen: true,
  5331. temporary: true
  5332. };
  5333. Component.registerComponent('ModalDialog', ModalDialog);
  5334. /**
  5335. * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
  5336. * {@link VideoTrackList}
  5337. *
  5338. * @extends EventTarget
  5339. */
  5340. var TrackList =
  5341. /*#__PURE__*/
  5342. function (_EventTarget) {
  5343. _inheritsLoose(TrackList, _EventTarget);
  5344. /**
  5345. * Create an instance of this class
  5346. *
  5347. * @param {Track[]} tracks
  5348. * A list of tracks to initialize the list with.
  5349. *
  5350. * @abstract
  5351. */
  5352. function TrackList(tracks) {
  5353. var _this;
  5354. if (tracks === void 0) {
  5355. tracks = [];
  5356. }
  5357. _this = _EventTarget.call(this) || this;
  5358. _this.tracks_ = [];
  5359. /**
  5360. * @memberof TrackList
  5361. * @member {number} length
  5362. * The current number of `Track`s in the this Trackist.
  5363. * @instance
  5364. */
  5365. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'length', {
  5366. get: function get() {
  5367. return this.tracks_.length;
  5368. }
  5369. });
  5370. for (var i = 0; i < tracks.length; i++) {
  5371. _this.addTrack(tracks[i]);
  5372. }
  5373. return _this;
  5374. }
  5375. /**
  5376. * Add a {@link Track} to the `TrackList`
  5377. *
  5378. * @param {Track} track
  5379. * The audio, video, or text track to add to the list.
  5380. *
  5381. * @fires TrackList#addtrack
  5382. */
  5383. var _proto = TrackList.prototype;
  5384. _proto.addTrack = function addTrack(track) {
  5385. var index = this.tracks_.length;
  5386. if (!('' + index in this)) {
  5387. Object.defineProperty(this, index, {
  5388. get: function get() {
  5389. return this.tracks_[index];
  5390. }
  5391. });
  5392. } // Do not add duplicate tracks
  5393. if (this.tracks_.indexOf(track) === -1) {
  5394. this.tracks_.push(track);
  5395. /**
  5396. * Triggered when a track is added to a track list.
  5397. *
  5398. * @event TrackList#addtrack
  5399. * @type {EventTarget~Event}
  5400. * @property {Track} track
  5401. * A reference to track that was added.
  5402. */
  5403. this.trigger({
  5404. track: track,
  5405. type: 'addtrack',
  5406. target: this
  5407. });
  5408. }
  5409. }
  5410. /**
  5411. * Remove a {@link Track} from the `TrackList`
  5412. *
  5413. * @param {Track} rtrack
  5414. * The audio, video, or text track to remove from the list.
  5415. *
  5416. * @fires TrackList#removetrack
  5417. */
  5418. ;
  5419. _proto.removeTrack = function removeTrack(rtrack) {
  5420. var track;
  5421. for (var i = 0, l = this.length; i < l; i++) {
  5422. if (this[i] === rtrack) {
  5423. track = this[i];
  5424. if (track.off) {
  5425. track.off();
  5426. }
  5427. this.tracks_.splice(i, 1);
  5428. break;
  5429. }
  5430. }
  5431. if (!track) {
  5432. return;
  5433. }
  5434. /**
  5435. * Triggered when a track is removed from track list.
  5436. *
  5437. * @event TrackList#removetrack
  5438. * @type {EventTarget~Event}
  5439. * @property {Track} track
  5440. * A reference to track that was removed.
  5441. */
  5442. this.trigger({
  5443. track: track,
  5444. type: 'removetrack',
  5445. target: this
  5446. });
  5447. }
  5448. /**
  5449. * Get a Track from the TrackList by a tracks id
  5450. *
  5451. * @param {string} id - the id of the track to get
  5452. * @method getTrackById
  5453. * @return {Track}
  5454. * @private
  5455. */
  5456. ;
  5457. _proto.getTrackById = function getTrackById(id) {
  5458. var result = null;
  5459. for (var i = 0, l = this.length; i < l; i++) {
  5460. var track = this[i];
  5461. if (track.id === id) {
  5462. result = track;
  5463. break;
  5464. }
  5465. }
  5466. return result;
  5467. };
  5468. return TrackList;
  5469. }(EventTarget);
  5470. /**
  5471. * Triggered when a different track is selected/enabled.
  5472. *
  5473. * @event TrackList#change
  5474. * @type {EventTarget~Event}
  5475. */
  5476. /**
  5477. * Events that can be called with on + eventName. See {@link EventHandler}.
  5478. *
  5479. * @property {Object} TrackList#allowedEvents_
  5480. * @private
  5481. */
  5482. TrackList.prototype.allowedEvents_ = {
  5483. change: 'change',
  5484. addtrack: 'addtrack',
  5485. removetrack: 'removetrack'
  5486. }; // emulate attribute EventHandler support to allow for feature detection
  5487. for (var event in TrackList.prototype.allowedEvents_) {
  5488. TrackList.prototype['on' + event] = null;
  5489. }
  5490. /**
  5491. * Anywhere we call this function we diverge from the spec
  5492. * as we only support one enabled audiotrack at a time
  5493. *
  5494. * @param {AudioTrackList} list
  5495. * list to work on
  5496. *
  5497. * @param {AudioTrack} track
  5498. * The track to skip
  5499. *
  5500. * @private
  5501. */
  5502. var disableOthers = function disableOthers(list, track) {
  5503. for (var i = 0; i < list.length; i++) {
  5504. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5505. continue;
  5506. } // another audio track is enabled, disable it
  5507. list[i].enabled = false;
  5508. }
  5509. };
  5510. /**
  5511. * The current list of {@link AudioTrack} for a media file.
  5512. *
  5513. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
  5514. * @extends TrackList
  5515. */
  5516. var AudioTrackList =
  5517. /*#__PURE__*/
  5518. function (_TrackList) {
  5519. _inheritsLoose(AudioTrackList, _TrackList);
  5520. /**
  5521. * Create an instance of this class.
  5522. *
  5523. * @param {AudioTrack[]} [tracks=[]]
  5524. * A list of `AudioTrack` to instantiate the list with.
  5525. */
  5526. function AudioTrackList(tracks) {
  5527. var _this;
  5528. if (tracks === void 0) {
  5529. tracks = [];
  5530. }
  5531. // make sure only 1 track is enabled
  5532. // sorted from last index to first index
  5533. for (var i = tracks.length - 1; i >= 0; i--) {
  5534. if (tracks[i].enabled) {
  5535. disableOthers(tracks, tracks[i]);
  5536. break;
  5537. }
  5538. }
  5539. _this = _TrackList.call(this, tracks) || this;
  5540. _this.changing_ = false;
  5541. return _this;
  5542. }
  5543. /**
  5544. * Add an {@link AudioTrack} to the `AudioTrackList`.
  5545. *
  5546. * @param {AudioTrack} track
  5547. * The AudioTrack to add to the list
  5548. *
  5549. * @fires TrackList#addtrack
  5550. */
  5551. var _proto = AudioTrackList.prototype;
  5552. _proto.addTrack = function addTrack(track) {
  5553. var _this2 = this;
  5554. if (track.enabled) {
  5555. disableOthers(this, track);
  5556. }
  5557. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5558. if (!track.addEventListener) {
  5559. return;
  5560. }
  5561. track.enabledChange_ = function () {
  5562. // when we are disabling other tracks (since we don't support
  5563. // more than one track at a time) we will set changing_
  5564. // to true so that we don't trigger additional change events
  5565. if (_this2.changing_) {
  5566. return;
  5567. }
  5568. _this2.changing_ = true;
  5569. disableOthers(_this2, track);
  5570. _this2.changing_ = false;
  5571. _this2.trigger('change');
  5572. };
  5573. /**
  5574. * @listens AudioTrack#enabledchange
  5575. * @fires TrackList#change
  5576. */
  5577. track.addEventListener('enabledchange', track.enabledChange_);
  5578. };
  5579. _proto.removeTrack = function removeTrack(rtrack) {
  5580. _TrackList.prototype.removeTrack.call(this, rtrack);
  5581. if (rtrack.removeEventListener && rtrack.enabledChange_) {
  5582. rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
  5583. rtrack.enabledChange_ = null;
  5584. }
  5585. };
  5586. return AudioTrackList;
  5587. }(TrackList);
  5588. /**
  5589. * Un-select all other {@link VideoTrack}s that are selected.
  5590. *
  5591. * @param {VideoTrackList} list
  5592. * list to work on
  5593. *
  5594. * @param {VideoTrack} track
  5595. * The track to skip
  5596. *
  5597. * @private
  5598. */
  5599. var disableOthers$1 = function disableOthers(list, track) {
  5600. for (var i = 0; i < list.length; i++) {
  5601. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5602. continue;
  5603. } // another video track is enabled, disable it
  5604. list[i].selected = false;
  5605. }
  5606. };
  5607. /**
  5608. * The current list of {@link VideoTrack} for a video.
  5609. *
  5610. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
  5611. * @extends TrackList
  5612. */
  5613. var VideoTrackList =
  5614. /*#__PURE__*/
  5615. function (_TrackList) {
  5616. _inheritsLoose(VideoTrackList, _TrackList);
  5617. /**
  5618. * Create an instance of this class.
  5619. *
  5620. * @param {VideoTrack[]} [tracks=[]]
  5621. * A list of `VideoTrack` to instantiate the list with.
  5622. */
  5623. function VideoTrackList(tracks) {
  5624. var _this;
  5625. if (tracks === void 0) {
  5626. tracks = [];
  5627. }
  5628. // make sure only 1 track is enabled
  5629. // sorted from last index to first index
  5630. for (var i = tracks.length - 1; i >= 0; i--) {
  5631. if (tracks[i].selected) {
  5632. disableOthers$1(tracks, tracks[i]);
  5633. break;
  5634. }
  5635. }
  5636. _this = _TrackList.call(this, tracks) || this;
  5637. _this.changing_ = false;
  5638. /**
  5639. * @member {number} VideoTrackList#selectedIndex
  5640. * The current index of the selected {@link VideoTrack`}.
  5641. */
  5642. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selectedIndex', {
  5643. get: function get() {
  5644. for (var _i = 0; _i < this.length; _i++) {
  5645. if (this[_i].selected) {
  5646. return _i;
  5647. }
  5648. }
  5649. return -1;
  5650. },
  5651. set: function set() {}
  5652. });
  5653. return _this;
  5654. }
  5655. /**
  5656. * Add a {@link VideoTrack} to the `VideoTrackList`.
  5657. *
  5658. * @param {VideoTrack} track
  5659. * The VideoTrack to add to the list
  5660. *
  5661. * @fires TrackList#addtrack
  5662. */
  5663. var _proto = VideoTrackList.prototype;
  5664. _proto.addTrack = function addTrack(track) {
  5665. var _this2 = this;
  5666. if (track.selected) {
  5667. disableOthers$1(this, track);
  5668. }
  5669. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5670. if (!track.addEventListener) {
  5671. return;
  5672. }
  5673. track.selectedChange_ = function () {
  5674. if (_this2.changing_) {
  5675. return;
  5676. }
  5677. _this2.changing_ = true;
  5678. disableOthers$1(_this2, track);
  5679. _this2.changing_ = false;
  5680. _this2.trigger('change');
  5681. };
  5682. /**
  5683. * @listens VideoTrack#selectedchange
  5684. * @fires TrackList#change
  5685. */
  5686. track.addEventListener('selectedchange', track.selectedChange_);
  5687. };
  5688. _proto.removeTrack = function removeTrack(rtrack) {
  5689. _TrackList.prototype.removeTrack.call(this, rtrack);
  5690. if (rtrack.removeEventListener && rtrack.selectedChange_) {
  5691. rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
  5692. rtrack.selectedChange_ = null;
  5693. }
  5694. };
  5695. return VideoTrackList;
  5696. }(TrackList);
  5697. /**
  5698. * The current list of {@link TextTrack} for a media file.
  5699. *
  5700. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
  5701. * @extends TrackList
  5702. */
  5703. var TextTrackList =
  5704. /*#__PURE__*/
  5705. function (_TrackList) {
  5706. _inheritsLoose(TextTrackList, _TrackList);
  5707. function TextTrackList() {
  5708. return _TrackList.apply(this, arguments) || this;
  5709. }
  5710. var _proto = TextTrackList.prototype;
  5711. /**
  5712. * Add a {@link TextTrack} to the `TextTrackList`
  5713. *
  5714. * @param {TextTrack} track
  5715. * The text track to add to the list.
  5716. *
  5717. * @fires TrackList#addtrack
  5718. */
  5719. _proto.addTrack = function addTrack(track) {
  5720. var _this = this;
  5721. _TrackList.prototype.addTrack.call(this, track);
  5722. if (!this.queueChange_) {
  5723. this.queueChange_ = function () {
  5724. return _this.queueTrigger('change');
  5725. };
  5726. }
  5727. if (!this.triggerSelectedlanguagechange) {
  5728. this.triggerSelectedlanguagechange_ = function () {
  5729. return _this.trigger('selectedlanguagechange');
  5730. };
  5731. }
  5732. /**
  5733. * @listens TextTrack#modechange
  5734. * @fires TrackList#change
  5735. */
  5736. track.addEventListener('modechange', this.queueChange_);
  5737. var nonLanguageTextTrackKind = ['metadata', 'chapters'];
  5738. if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
  5739. track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
  5740. }
  5741. };
  5742. _proto.removeTrack = function removeTrack(rtrack) {
  5743. _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added
  5744. if (rtrack.removeEventListener) {
  5745. if (this.queueChange_) {
  5746. rtrack.removeEventListener('modechange', this.queueChange_);
  5747. }
  5748. if (this.selectedlanguagechange_) {
  5749. rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
  5750. }
  5751. }
  5752. };
  5753. return TextTrackList;
  5754. }(TrackList);
  5755. /**
  5756. * @file html-track-element-list.js
  5757. */
  5758. /**
  5759. * The current list of {@link HtmlTrackElement}s.
  5760. */
  5761. var HtmlTrackElementList =
  5762. /*#__PURE__*/
  5763. function () {
  5764. /**
  5765. * Create an instance of this class.
  5766. *
  5767. * @param {HtmlTrackElement[]} [tracks=[]]
  5768. * A list of `HtmlTrackElement` to instantiate the list with.
  5769. */
  5770. function HtmlTrackElementList(trackElements) {
  5771. if (trackElements === void 0) {
  5772. trackElements = [];
  5773. }
  5774. this.trackElements_ = [];
  5775. /**
  5776. * @memberof HtmlTrackElementList
  5777. * @member {number} length
  5778. * The current number of `Track`s in the this Trackist.
  5779. * @instance
  5780. */
  5781. Object.defineProperty(this, 'length', {
  5782. get: function get() {
  5783. return this.trackElements_.length;
  5784. }
  5785. });
  5786. for (var i = 0, length = trackElements.length; i < length; i++) {
  5787. this.addTrackElement_(trackElements[i]);
  5788. }
  5789. }
  5790. /**
  5791. * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
  5792. *
  5793. * @param {HtmlTrackElement} trackElement
  5794. * The track element to add to the list.
  5795. *
  5796. * @private
  5797. */
  5798. var _proto = HtmlTrackElementList.prototype;
  5799. _proto.addTrackElement_ = function addTrackElement_(trackElement) {
  5800. var index = this.trackElements_.length;
  5801. if (!('' + index in this)) {
  5802. Object.defineProperty(this, index, {
  5803. get: function get() {
  5804. return this.trackElements_[index];
  5805. }
  5806. });
  5807. } // Do not add duplicate elements
  5808. if (this.trackElements_.indexOf(trackElement) === -1) {
  5809. this.trackElements_.push(trackElement);
  5810. }
  5811. }
  5812. /**
  5813. * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
  5814. * {@link TextTrack}.
  5815. *
  5816. * @param {TextTrack} track
  5817. * The track associated with a track element.
  5818. *
  5819. * @return {HtmlTrackElement|undefined}
  5820. * The track element that was found or undefined.
  5821. *
  5822. * @private
  5823. */
  5824. ;
  5825. _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
  5826. var trackElement_;
  5827. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5828. if (track === this.trackElements_[i].track) {
  5829. trackElement_ = this.trackElements_[i];
  5830. break;
  5831. }
  5832. }
  5833. return trackElement_;
  5834. }
  5835. /**
  5836. * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
  5837. *
  5838. * @param {HtmlTrackElement} trackElement
  5839. * The track element to remove from the list.
  5840. *
  5841. * @private
  5842. */
  5843. ;
  5844. _proto.removeTrackElement_ = function removeTrackElement_(trackElement) {
  5845. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5846. if (trackElement === this.trackElements_[i]) {
  5847. if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
  5848. this.trackElements_[i].track.off();
  5849. }
  5850. if (typeof this.trackElements_[i].off === 'function') {
  5851. this.trackElements_[i].off();
  5852. }
  5853. this.trackElements_.splice(i, 1);
  5854. break;
  5855. }
  5856. }
  5857. };
  5858. return HtmlTrackElementList;
  5859. }();
  5860. /**
  5861. * @file text-track-cue-list.js
  5862. */
  5863. /**
  5864. * @typedef {Object} TextTrackCueList~TextTrackCue
  5865. *
  5866. * @property {string} id
  5867. * The unique id for this text track cue
  5868. *
  5869. * @property {number} startTime
  5870. * The start time for this text track cue
  5871. *
  5872. * @property {number} endTime
  5873. * The end time for this text track cue
  5874. *
  5875. * @property {boolean} pauseOnExit
  5876. * Pause when the end time is reached if true.
  5877. *
  5878. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
  5879. */
  5880. /**
  5881. * A List of TextTrackCues.
  5882. *
  5883. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
  5884. */
  5885. var TextTrackCueList =
  5886. /*#__PURE__*/
  5887. function () {
  5888. /**
  5889. * Create an instance of this class..
  5890. *
  5891. * @param {Array} cues
  5892. * A list of cues to be initialized with
  5893. */
  5894. function TextTrackCueList(cues) {
  5895. TextTrackCueList.prototype.setCues_.call(this, cues);
  5896. /**
  5897. * @memberof TextTrackCueList
  5898. * @member {number} length
  5899. * The current number of `TextTrackCue`s in the TextTrackCueList.
  5900. * @instance
  5901. */
  5902. Object.defineProperty(this, 'length', {
  5903. get: function get() {
  5904. return this.length_;
  5905. }
  5906. });
  5907. }
  5908. /**
  5909. * A setter for cues in this list. Creates getters
  5910. * an an index for the cues.
  5911. *
  5912. * @param {Array} cues
  5913. * An array of cues to set
  5914. *
  5915. * @private
  5916. */
  5917. var _proto = TextTrackCueList.prototype;
  5918. _proto.setCues_ = function setCues_(cues) {
  5919. var oldLength = this.length || 0;
  5920. var i = 0;
  5921. var l = cues.length;
  5922. this.cues_ = cues;
  5923. this.length_ = cues.length;
  5924. var defineProp = function defineProp(index) {
  5925. if (!('' + index in this)) {
  5926. Object.defineProperty(this, '' + index, {
  5927. get: function get() {
  5928. return this.cues_[index];
  5929. }
  5930. });
  5931. }
  5932. };
  5933. if (oldLength < l) {
  5934. i = oldLength;
  5935. for (; i < l; i++) {
  5936. defineProp.call(this, i);
  5937. }
  5938. }
  5939. }
  5940. /**
  5941. * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
  5942. *
  5943. * @param {string} id
  5944. * The id of the cue that should be searched for.
  5945. *
  5946. * @return {TextTrackCueList~TextTrackCue|null}
  5947. * A single cue or null if none was found.
  5948. */
  5949. ;
  5950. _proto.getCueById = function getCueById(id) {
  5951. var result = null;
  5952. for (var i = 0, l = this.length; i < l; i++) {
  5953. var cue = this[i];
  5954. if (cue.id === id) {
  5955. result = cue;
  5956. break;
  5957. }
  5958. }
  5959. return result;
  5960. };
  5961. return TextTrackCueList;
  5962. }();
  5963. /**
  5964. * @file track-kinds.js
  5965. */
  5966. /**
  5967. * All possible `VideoTrackKind`s
  5968. *
  5969. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
  5970. * @typedef VideoTrack~Kind
  5971. * @enum
  5972. */
  5973. var VideoTrackKind = {
  5974. alternative: 'alternative',
  5975. captions: 'captions',
  5976. main: 'main',
  5977. sign: 'sign',
  5978. subtitles: 'subtitles',
  5979. commentary: 'commentary'
  5980. };
  5981. /**
  5982. * All possible `AudioTrackKind`s
  5983. *
  5984. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
  5985. * @typedef AudioTrack~Kind
  5986. * @enum
  5987. */
  5988. var AudioTrackKind = {
  5989. 'alternative': 'alternative',
  5990. 'descriptions': 'descriptions',
  5991. 'main': 'main',
  5992. 'main-desc': 'main-desc',
  5993. 'translation': 'translation',
  5994. 'commentary': 'commentary'
  5995. };
  5996. /**
  5997. * All possible `TextTrackKind`s
  5998. *
  5999. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
  6000. * @typedef TextTrack~Kind
  6001. * @enum
  6002. */
  6003. var TextTrackKind = {
  6004. subtitles: 'subtitles',
  6005. captions: 'captions',
  6006. descriptions: 'descriptions',
  6007. chapters: 'chapters',
  6008. metadata: 'metadata'
  6009. };
  6010. /**
  6011. * All possible `TextTrackMode`s
  6012. *
  6013. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
  6014. * @typedef TextTrack~Mode
  6015. * @enum
  6016. */
  6017. var TextTrackMode = {
  6018. disabled: 'disabled',
  6019. hidden: 'hidden',
  6020. showing: 'showing'
  6021. };
  6022. /**
  6023. * A Track class that contains all of the common functionality for {@link AudioTrack},
  6024. * {@link VideoTrack}, and {@link TextTrack}.
  6025. *
  6026. * > Note: This class should not be used directly
  6027. *
  6028. * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
  6029. * @extends EventTarget
  6030. * @abstract
  6031. */
  6032. var Track =
  6033. /*#__PURE__*/
  6034. function (_EventTarget) {
  6035. _inheritsLoose(Track, _EventTarget);
  6036. /**
  6037. * Create an instance of this class.
  6038. *
  6039. * @param {Object} [options={}]
  6040. * Object of option names and values
  6041. *
  6042. * @param {string} [options.kind='']
  6043. * A valid kind for the track type you are creating.
  6044. *
  6045. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6046. * A unique id for this AudioTrack.
  6047. *
  6048. * @param {string} [options.label='']
  6049. * The menu label for this track.
  6050. *
  6051. * @param {string} [options.language='']
  6052. * A valid two character language code.
  6053. *
  6054. * @abstract
  6055. */
  6056. function Track(options) {
  6057. var _this;
  6058. if (options === void 0) {
  6059. options = {};
  6060. }
  6061. _this = _EventTarget.call(this) || this;
  6062. var trackProps = {
  6063. id: options.id || 'vjs_track_' + newGUID(),
  6064. kind: options.kind || '',
  6065. label: options.label || '',
  6066. language: options.language || ''
  6067. };
  6068. /**
  6069. * @memberof Track
  6070. * @member {string} id
  6071. * The id of this track. Cannot be changed after creation.
  6072. * @instance
  6073. *
  6074. * @readonly
  6075. */
  6076. /**
  6077. * @memberof Track
  6078. * @member {string} kind
  6079. * The kind of track that this is. Cannot be changed after creation.
  6080. * @instance
  6081. *
  6082. * @readonly
  6083. */
  6084. /**
  6085. * @memberof Track
  6086. * @member {string} label
  6087. * The label of this track. Cannot be changed after creation.
  6088. * @instance
  6089. *
  6090. * @readonly
  6091. */
  6092. /**
  6093. * @memberof Track
  6094. * @member {string} language
  6095. * The two letter language code for this track. Cannot be changed after
  6096. * creation.
  6097. * @instance
  6098. *
  6099. * @readonly
  6100. */
  6101. var _loop = function _loop(key) {
  6102. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), key, {
  6103. get: function get() {
  6104. return trackProps[key];
  6105. },
  6106. set: function set() {}
  6107. });
  6108. };
  6109. for (var key in trackProps) {
  6110. _loop(key);
  6111. }
  6112. return _this;
  6113. }
  6114. return Track;
  6115. }(EventTarget);
  6116. /**
  6117. * @file url.js
  6118. * @module url
  6119. */
  6120. /**
  6121. * @typedef {Object} url:URLObject
  6122. *
  6123. * @property {string} protocol
  6124. * The protocol of the url that was parsed.
  6125. *
  6126. * @property {string} hostname
  6127. * The hostname of the url that was parsed.
  6128. *
  6129. * @property {string} port
  6130. * The port of the url that was parsed.
  6131. *
  6132. * @property {string} pathname
  6133. * The pathname of the url that was parsed.
  6134. *
  6135. * @property {string} search
  6136. * The search query of the url that was parsed.
  6137. *
  6138. * @property {string} hash
  6139. * The hash of the url that was parsed.
  6140. *
  6141. * @property {string} host
  6142. * The host of the url that was parsed.
  6143. */
  6144. /**
  6145. * Resolve and parse the elements of a URL.
  6146. *
  6147. * @function
  6148. * @param {String} url
  6149. * The url to parse
  6150. *
  6151. * @return {url:URLObject}
  6152. * An object of url details
  6153. */
  6154. var parseUrl = function parseUrl(url) {
  6155. var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL
  6156. var a = document.createElement('a');
  6157. a.href = url; // IE8 (and 9?) Fix
  6158. // ie8 doesn't parse the URL correctly until the anchor is actually
  6159. // added to the body, and an innerHTML is needed to trigger the parsing
  6160. var addToBody = a.host === '' && a.protocol !== 'file:';
  6161. var div;
  6162. if (addToBody) {
  6163. div = document.createElement('div');
  6164. div.innerHTML = "<a href=\"" + url + "\"></a>";
  6165. a = div.firstChild; // prevent the div from affecting layout
  6166. div.setAttribute('style', 'display:none; position:absolute;');
  6167. document.body.appendChild(div);
  6168. } // Copy the specific URL properties to a new object
  6169. // This is also needed for IE8 because the anchor loses its
  6170. // properties when it's removed from the dom
  6171. var details = {};
  6172. for (var i = 0; i < props.length; i++) {
  6173. details[props[i]] = a[props[i]];
  6174. } // IE9 adds the port to the host property unlike everyone else. If
  6175. // a port identifier is added for standard ports, strip it.
  6176. if (details.protocol === 'http:') {
  6177. details.host = details.host.replace(/:80$/, '');
  6178. }
  6179. if (details.protocol === 'https:') {
  6180. details.host = details.host.replace(/:443$/, '');
  6181. }
  6182. if (!details.protocol) {
  6183. details.protocol = window$1.location.protocol;
  6184. }
  6185. if (addToBody) {
  6186. document.body.removeChild(div);
  6187. }
  6188. return details;
  6189. };
  6190. /**
  6191. * Get absolute version of relative URL. Used to tell Flash the correct URL.
  6192. *
  6193. * @function
  6194. * @param {string} url
  6195. * URL to make absolute
  6196. *
  6197. * @return {string}
  6198. * Absolute URL
  6199. *
  6200. * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
  6201. */
  6202. var getAbsoluteURL = function getAbsoluteURL(url) {
  6203. // Check if absolute URL
  6204. if (!url.match(/^https?:\/\//)) {
  6205. // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
  6206. var div = document.createElement('div');
  6207. div.innerHTML = "<a href=\"" + url + "\">x</a>";
  6208. url = div.firstChild.href;
  6209. }
  6210. return url;
  6211. };
  6212. /**
  6213. * Returns the extension of the passed file name. It will return an empty string
  6214. * if passed an invalid path.
  6215. *
  6216. * @function
  6217. * @param {string} path
  6218. * The fileName path like '/path/to/file.mp4'
  6219. *
  6220. * @return {string}
  6221. * The extension in lower case or an empty string if no
  6222. * extension could be found.
  6223. */
  6224. var getFileExtension = function getFileExtension(path) {
  6225. if (typeof path === 'string') {
  6226. var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
  6227. var pathParts = splitPathRe.exec(path);
  6228. if (pathParts) {
  6229. return pathParts.pop().toLowerCase();
  6230. }
  6231. }
  6232. return '';
  6233. };
  6234. /**
  6235. * Returns whether the url passed is a cross domain request or not.
  6236. *
  6237. * @function
  6238. * @param {string} url
  6239. * The url to check.
  6240. *
  6241. * @return {boolean}
  6242. * Whether it is a cross domain request or not.
  6243. */
  6244. var isCrossOrigin = function isCrossOrigin(url) {
  6245. var winLoc = window$1.location;
  6246. var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol
  6247. var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin
  6248. // IE8 doesn't know location.origin, so we won't rely on it here
  6249. var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
  6250. return crossOrigin;
  6251. };
  6252. var Url = /*#__PURE__*/Object.freeze({
  6253. parseUrl: parseUrl,
  6254. getAbsoluteURL: getAbsoluteURL,
  6255. getFileExtension: getFileExtension,
  6256. isCrossOrigin: isCrossOrigin
  6257. });
  6258. var isFunction_1 = isFunction;
  6259. var toString$1 = Object.prototype.toString;
  6260. function isFunction(fn) {
  6261. var string = toString$1.call(fn);
  6262. return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below
  6263. fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
  6264. }
  6265. var trim_1 = createCommonjsModule(function (module, exports) {
  6266. exports = module.exports = trim;
  6267. function trim(str) {
  6268. return str.replace(/^\s*|\s*$/g, '');
  6269. }
  6270. exports.left = function (str) {
  6271. return str.replace(/^\s*/, '');
  6272. };
  6273. exports.right = function (str) {
  6274. return str.replace(/\s*$/, '');
  6275. };
  6276. });
  6277. var trim_2 = trim_1.left;
  6278. var trim_3 = trim_1.right;
  6279. var fnToStr = Function.prototype.toString;
  6280. var constructorRegex = /^\s*class\b/;
  6281. var isES6ClassFn = function isES6ClassFunction(value) {
  6282. try {
  6283. var fnStr = fnToStr.call(value);
  6284. return constructorRegex.test(fnStr);
  6285. } catch (e) {
  6286. return false; // not a function
  6287. }
  6288. };
  6289. var tryFunctionObject = function tryFunctionToStr(value) {
  6290. try {
  6291. if (isES6ClassFn(value)) {
  6292. return false;
  6293. }
  6294. fnToStr.call(value);
  6295. return true;
  6296. } catch (e) {
  6297. return false;
  6298. }
  6299. };
  6300. var toStr = Object.prototype.toString;
  6301. var fnClass = '[object Function]';
  6302. var genClass = '[object GeneratorFunction]';
  6303. var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
  6304. var isCallable = function isCallable(value) {
  6305. if (!value) {
  6306. return false;
  6307. }
  6308. if (typeof value !== 'function' && typeof value !== 'object') {
  6309. return false;
  6310. }
  6311. if (typeof value === 'function' && !value.prototype) {
  6312. return true;
  6313. }
  6314. if (hasToStringTag) {
  6315. return tryFunctionObject(value);
  6316. }
  6317. if (isES6ClassFn(value)) {
  6318. return false;
  6319. }
  6320. var strClass = toStr.call(value);
  6321. return strClass === fnClass || strClass === genClass;
  6322. };
  6323. var toStr$1 = Object.prototype.toString;
  6324. var hasOwnProperty = Object.prototype.hasOwnProperty;
  6325. var forEachArray = function forEachArray(array, iterator, receiver) {
  6326. for (var i = 0, len = array.length; i < len; i++) {
  6327. if (hasOwnProperty.call(array, i)) {
  6328. if (receiver == null) {
  6329. iterator(array[i], i, array);
  6330. } else {
  6331. iterator.call(receiver, array[i], i, array);
  6332. }
  6333. }
  6334. }
  6335. };
  6336. var forEachString = function forEachString(string, iterator, receiver) {
  6337. for (var i = 0, len = string.length; i < len; i++) {
  6338. // no such thing as a sparse string.
  6339. if (receiver == null) {
  6340. iterator(string.charAt(i), i, string);
  6341. } else {
  6342. iterator.call(receiver, string.charAt(i), i, string);
  6343. }
  6344. }
  6345. };
  6346. var forEachObject = function forEachObject(object, iterator, receiver) {
  6347. for (var k in object) {
  6348. if (hasOwnProperty.call(object, k)) {
  6349. if (receiver == null) {
  6350. iterator(object[k], k, object);
  6351. } else {
  6352. iterator.call(receiver, object[k], k, object);
  6353. }
  6354. }
  6355. }
  6356. };
  6357. var forEach = function forEach(list, iterator, thisArg) {
  6358. if (!isCallable(iterator)) {
  6359. throw new TypeError('iterator must be a function');
  6360. }
  6361. var receiver;
  6362. if (arguments.length >= 3) {
  6363. receiver = thisArg;
  6364. }
  6365. if (toStr$1.call(list) === '[object Array]') {
  6366. forEachArray(list, iterator, receiver);
  6367. } else if (typeof list === 'string') {
  6368. forEachString(list, iterator, receiver);
  6369. } else {
  6370. forEachObject(list, iterator, receiver);
  6371. }
  6372. };
  6373. var forEach_1 = forEach;
  6374. var isArray = function isArray(arg) {
  6375. return Object.prototype.toString.call(arg) === '[object Array]';
  6376. };
  6377. var parseHeaders = function parseHeaders(headers) {
  6378. if (!headers) return {};
  6379. var result = {};
  6380. forEach_1(trim_1(headers).split('\n'), function (row) {
  6381. var index = row.indexOf(':'),
  6382. key = trim_1(row.slice(0, index)).toLowerCase(),
  6383. value = trim_1(row.slice(index + 1));
  6384. if (typeof result[key] === 'undefined') {
  6385. result[key] = value;
  6386. } else if (isArray(result[key])) {
  6387. result[key].push(value);
  6388. } else {
  6389. result[key] = [result[key], value];
  6390. }
  6391. });
  6392. return result;
  6393. };
  6394. var immutable = extend;
  6395. var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
  6396. function extend() {
  6397. var target = {};
  6398. for (var i = 0; i < arguments.length; i++) {
  6399. var source = arguments[i];
  6400. for (var key in source) {
  6401. if (hasOwnProperty$1.call(source, key)) {
  6402. target[key] = source[key];
  6403. }
  6404. }
  6405. }
  6406. return target;
  6407. }
  6408. var xhr = createXHR;
  6409. createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop;
  6410. createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest;
  6411. forEachArray$1(["get", "put", "post", "patch", "head", "delete"], function (method) {
  6412. createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
  6413. options = initParams(uri, options, callback);
  6414. options.method = method.toUpperCase();
  6415. return _createXHR(options);
  6416. };
  6417. });
  6418. function forEachArray$1(array, iterator) {
  6419. for (var i = 0; i < array.length; i++) {
  6420. iterator(array[i]);
  6421. }
  6422. }
  6423. function isEmpty(obj) {
  6424. for (var i in obj) {
  6425. if (obj.hasOwnProperty(i)) return false;
  6426. }
  6427. return true;
  6428. }
  6429. function initParams(uri, options, callback) {
  6430. var params = uri;
  6431. if (isFunction_1(options)) {
  6432. callback = options;
  6433. if (typeof uri === "string") {
  6434. params = {
  6435. uri: uri
  6436. };
  6437. }
  6438. } else {
  6439. params = immutable(options, {
  6440. uri: uri
  6441. });
  6442. }
  6443. params.callback = callback;
  6444. return params;
  6445. }
  6446. function createXHR(uri, options, callback) {
  6447. options = initParams(uri, options, callback);
  6448. return _createXHR(options);
  6449. }
  6450. function _createXHR(options) {
  6451. if (typeof options.callback === "undefined") {
  6452. throw new Error("callback argument missing");
  6453. }
  6454. var called = false;
  6455. var callback = function cbOnce(err, response, body) {
  6456. if (!called) {
  6457. called = true;
  6458. options.callback(err, response, body);
  6459. }
  6460. };
  6461. function readystatechange() {
  6462. if (xhr.readyState === 4) {
  6463. setTimeout(loadFunc, 0);
  6464. }
  6465. }
  6466. function getBody() {
  6467. // Chrome with requestType=blob throws errors arround when even testing access to responseText
  6468. var body = undefined;
  6469. if (xhr.response) {
  6470. body = xhr.response;
  6471. } else {
  6472. body = xhr.responseText || getXml(xhr);
  6473. }
  6474. if (isJson) {
  6475. try {
  6476. body = JSON.parse(body);
  6477. } catch (e) {}
  6478. }
  6479. return body;
  6480. }
  6481. function errorFunc(evt) {
  6482. clearTimeout(timeoutTimer);
  6483. if (!(evt instanceof Error)) {
  6484. evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
  6485. }
  6486. evt.statusCode = 0;
  6487. return callback(evt, failureResponse);
  6488. } // will load the data & process the response in a special response object
  6489. function loadFunc() {
  6490. if (aborted) return;
  6491. var status;
  6492. clearTimeout(timeoutTimer);
  6493. if (options.useXDR && xhr.status === undefined) {
  6494. //IE8 CORS GET successful response doesn't have a status field, but body is fine
  6495. status = 200;
  6496. } else {
  6497. status = xhr.status === 1223 ? 204 : xhr.status;
  6498. }
  6499. var response = failureResponse;
  6500. var err = null;
  6501. if (status !== 0) {
  6502. response = {
  6503. body: getBody(),
  6504. statusCode: status,
  6505. method: method,
  6506. headers: {},
  6507. url: uri,
  6508. rawRequest: xhr
  6509. };
  6510. if (xhr.getAllResponseHeaders) {
  6511. //remember xhr can in fact be XDR for CORS in IE
  6512. response.headers = parseHeaders(xhr.getAllResponseHeaders());
  6513. }
  6514. } else {
  6515. err = new Error("Internal XMLHttpRequest Error");
  6516. }
  6517. return callback(err, response, response.body);
  6518. }
  6519. var xhr = options.xhr || null;
  6520. if (!xhr) {
  6521. if (options.cors || options.useXDR) {
  6522. xhr = new createXHR.XDomainRequest();
  6523. } else {
  6524. xhr = new createXHR.XMLHttpRequest();
  6525. }
  6526. }
  6527. var key;
  6528. var aborted;
  6529. var uri = xhr.url = options.uri || options.url;
  6530. var method = xhr.method = options.method || "GET";
  6531. var body = options.body || options.data;
  6532. var headers = xhr.headers = options.headers || {};
  6533. var sync = !!options.sync;
  6534. var isJson = false;
  6535. var timeoutTimer;
  6536. var failureResponse = {
  6537. body: undefined,
  6538. headers: {},
  6539. statusCode: 0,
  6540. method: method,
  6541. url: uri,
  6542. rawRequest: xhr
  6543. };
  6544. if ("json" in options && options.json !== false) {
  6545. isJson = true;
  6546. headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
  6547. if (method !== "GET" && method !== "HEAD") {
  6548. headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
  6549. body = JSON.stringify(options.json === true ? body : options.json);
  6550. }
  6551. }
  6552. xhr.onreadystatechange = readystatechange;
  6553. xhr.onload = loadFunc;
  6554. xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
  6555. xhr.onprogress = function () {// IE must die
  6556. };
  6557. xhr.onabort = function () {
  6558. aborted = true;
  6559. };
  6560. xhr.ontimeout = errorFunc;
  6561. xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
  6562. if (!sync) {
  6563. xhr.withCredentials = !!options.withCredentials;
  6564. } // Cannot set timeout with sync request
  6565. // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
  6566. // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
  6567. if (!sync && options.timeout > 0) {
  6568. timeoutTimer = setTimeout(function () {
  6569. if (aborted) return;
  6570. aborted = true; //IE9 may still call readystatechange
  6571. xhr.abort("timeout");
  6572. var e = new Error("XMLHttpRequest timeout");
  6573. e.code = "ETIMEDOUT";
  6574. errorFunc(e);
  6575. }, options.timeout);
  6576. }
  6577. if (xhr.setRequestHeader) {
  6578. for (key in headers) {
  6579. if (headers.hasOwnProperty(key)) {
  6580. xhr.setRequestHeader(key, headers[key]);
  6581. }
  6582. }
  6583. } else if (options.headers && !isEmpty(options.headers)) {
  6584. throw new Error("Headers cannot be set on an XDomainRequest object");
  6585. }
  6586. if ("responseType" in options) {
  6587. xhr.responseType = options.responseType;
  6588. }
  6589. if ("beforeSend" in options && typeof options.beforeSend === "function") {
  6590. options.beforeSend(xhr);
  6591. } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
  6592. // XMLHttpRequest spec says to pass null as body to indicate no body
  6593. // See https://github.com/naugtur/xhr/issues/100.
  6594. xhr.send(body || null);
  6595. return xhr;
  6596. }
  6597. function getXml(xhr) {
  6598. if (xhr.responseType === "document") {
  6599. return xhr.responseXML;
  6600. }
  6601. var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
  6602. if (xhr.responseType === "" && !firefoxBugTakenEffect) {
  6603. return xhr.responseXML;
  6604. }
  6605. return null;
  6606. }
  6607. function noop() {}
  6608. /**
  6609. * Takes a webvtt file contents and parses it into cues
  6610. *
  6611. * @param {string} srcContent
  6612. * webVTT file contents
  6613. *
  6614. * @param {TextTrack} track
  6615. * TextTrack to add cues to. Cues come from the srcContent.
  6616. *
  6617. * @private
  6618. */
  6619. var parseCues = function parseCues(srcContent, track) {
  6620. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder());
  6621. var errors = [];
  6622. parser.oncue = function (cue) {
  6623. track.addCue(cue);
  6624. };
  6625. parser.onparsingerror = function (error) {
  6626. errors.push(error);
  6627. };
  6628. parser.onflush = function () {
  6629. track.trigger({
  6630. type: 'loadeddata',
  6631. target: track
  6632. });
  6633. };
  6634. parser.parse(srcContent);
  6635. if (errors.length > 0) {
  6636. if (window$1.console && window$1.console.groupCollapsed) {
  6637. window$1.console.groupCollapsed("Text Track parsing errors for " + track.src);
  6638. }
  6639. errors.forEach(function (error) {
  6640. return log.error(error);
  6641. });
  6642. if (window$1.console && window$1.console.groupEnd) {
  6643. window$1.console.groupEnd();
  6644. }
  6645. }
  6646. parser.flush();
  6647. };
  6648. /**
  6649. * Load a `TextTrack` from a specified url.
  6650. *
  6651. * @param {string} src
  6652. * Url to load track from.
  6653. *
  6654. * @param {TextTrack} track
  6655. * Track to add cues to. Comes from the content at the end of `url`.
  6656. *
  6657. * @private
  6658. */
  6659. var loadTrack = function loadTrack(src, track) {
  6660. var opts = {
  6661. uri: src
  6662. };
  6663. var crossOrigin = isCrossOrigin(src);
  6664. if (crossOrigin) {
  6665. opts.cors = crossOrigin;
  6666. }
  6667. xhr(opts, bind(this, function (err, response, responseBody) {
  6668. if (err) {
  6669. return log.error(err, response);
  6670. }
  6671. track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  6672. // NOTE: this is only used for the alt/video.novtt.js build
  6673. if (typeof window$1.WebVTT !== 'function') {
  6674. if (track.tech_) {
  6675. // to prevent use before define eslint error, we define loadHandler
  6676. // as a let here
  6677. var loadHandler;
  6678. var errorHandler = function errorHandler() {
  6679. log.error("vttjs failed to load, stopping trying to process " + track.src);
  6680. track.tech_.off('vttjsloaded', loadHandler);
  6681. };
  6682. loadHandler = function loadHandler() {
  6683. track.tech_.off('vttjserror', errorHandler);
  6684. return parseCues(responseBody, track);
  6685. };
  6686. track.tech_.one('vttjsloaded', loadHandler);
  6687. track.tech_.one('vttjserror', errorHandler);
  6688. }
  6689. } else {
  6690. parseCues(responseBody, track);
  6691. }
  6692. }));
  6693. };
  6694. /**
  6695. * A representation of a single `TextTrack`.
  6696. *
  6697. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
  6698. * @extends Track
  6699. */
  6700. var TextTrack =
  6701. /*#__PURE__*/
  6702. function (_Track) {
  6703. _inheritsLoose(TextTrack, _Track);
  6704. /**
  6705. * Create an instance of this class.
  6706. *
  6707. * @param {Object} options={}
  6708. * Object of option names and values
  6709. *
  6710. * @param {Tech} options.tech
  6711. * A reference to the tech that owns this TextTrack.
  6712. *
  6713. * @param {TextTrack~Kind} [options.kind='subtitles']
  6714. * A valid text track kind.
  6715. *
  6716. * @param {TextTrack~Mode} [options.mode='disabled']
  6717. * A valid text track mode.
  6718. *
  6719. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6720. * A unique id for this TextTrack.
  6721. *
  6722. * @param {string} [options.label='']
  6723. * The menu label for this track.
  6724. *
  6725. * @param {string} [options.language='']
  6726. * A valid two character language code.
  6727. *
  6728. * @param {string} [options.srclang='']
  6729. * A valid two character language code. An alternative, but deprioritized
  6730. * version of `options.language`
  6731. *
  6732. * @param {string} [options.src]
  6733. * A url to TextTrack cues.
  6734. *
  6735. * @param {boolean} [options.default]
  6736. * If this track should default to on or off.
  6737. */
  6738. function TextTrack(options) {
  6739. var _this;
  6740. if (options === void 0) {
  6741. options = {};
  6742. }
  6743. if (!options.tech) {
  6744. throw new Error('A tech was not provided.');
  6745. }
  6746. var settings = mergeOptions(options, {
  6747. kind: TextTrackKind[options.kind] || 'subtitles',
  6748. language: options.language || options.srclang || ''
  6749. });
  6750. var mode = TextTrackMode[settings.mode] || 'disabled';
  6751. var default_ = settings.default;
  6752. if (settings.kind === 'metadata' || settings.kind === 'chapters') {
  6753. mode = 'hidden';
  6754. }
  6755. _this = _Track.call(this, settings) || this;
  6756. _this.tech_ = settings.tech;
  6757. _this.cues_ = [];
  6758. _this.activeCues_ = [];
  6759. var cues = new TextTrackCueList(_this.cues_);
  6760. var activeCues = new TextTrackCueList(_this.activeCues_);
  6761. var changed = false;
  6762. var timeupdateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), function () {
  6763. // Accessing this.activeCues for the side-effects of updating itself
  6764. // due to its nature as a getter function. Do not remove or cues will
  6765. // stop updating!
  6766. // Use the setter to prevent deletion from uglify (pure_getters rule)
  6767. this.activeCues = this.activeCues;
  6768. if (changed) {
  6769. this.trigger('cuechange');
  6770. changed = false;
  6771. }
  6772. });
  6773. if (mode !== 'disabled') {
  6774. _this.tech_.ready(function () {
  6775. _this.tech_.on('timeupdate', timeupdateHandler);
  6776. }, true);
  6777. }
  6778. Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), {
  6779. /**
  6780. * @memberof TextTrack
  6781. * @member {boolean} default
  6782. * If this track was set to be on or off by default. Cannot be changed after
  6783. * creation.
  6784. * @instance
  6785. *
  6786. * @readonly
  6787. */
  6788. default: {
  6789. get: function get() {
  6790. return default_;
  6791. },
  6792. set: function set() {}
  6793. },
  6794. /**
  6795. * @memberof TextTrack
  6796. * @member {string} mode
  6797. * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
  6798. * not be set if setting to an invalid mode.
  6799. * @instance
  6800. *
  6801. * @fires TextTrack#modechange
  6802. */
  6803. mode: {
  6804. get: function get() {
  6805. return mode;
  6806. },
  6807. set: function set(newMode) {
  6808. var _this2 = this;
  6809. if (!TextTrackMode[newMode]) {
  6810. return;
  6811. }
  6812. mode = newMode;
  6813. if (mode !== 'disabled') {
  6814. this.tech_.ready(function () {
  6815. _this2.tech_.on('timeupdate', timeupdateHandler);
  6816. }, true);
  6817. } else {
  6818. this.tech_.off('timeupdate', timeupdateHandler);
  6819. }
  6820. /**
  6821. * An event that fires when mode changes on this track. This allows
  6822. * the TextTrackList that holds this track to act accordingly.
  6823. *
  6824. * > Note: This is not part of the spec!
  6825. *
  6826. * @event TextTrack#modechange
  6827. * @type {EventTarget~Event}
  6828. */
  6829. this.trigger('modechange');
  6830. }
  6831. },
  6832. /**
  6833. * @memberof TextTrack
  6834. * @member {TextTrackCueList} cues
  6835. * The text track cue list for this TextTrack.
  6836. * @instance
  6837. */
  6838. cues: {
  6839. get: function get() {
  6840. if (!this.loaded_) {
  6841. return null;
  6842. }
  6843. return cues;
  6844. },
  6845. set: function set() {}
  6846. },
  6847. /**
  6848. * @memberof TextTrack
  6849. * @member {TextTrackCueList} activeCues
  6850. * The list text track cues that are currently active for this TextTrack.
  6851. * @instance
  6852. */
  6853. activeCues: {
  6854. get: function get() {
  6855. if (!this.loaded_) {
  6856. return null;
  6857. } // nothing to do
  6858. if (this.cues.length === 0) {
  6859. return activeCues;
  6860. }
  6861. var ct = this.tech_.currentTime();
  6862. var active = [];
  6863. for (var i = 0, l = this.cues.length; i < l; i++) {
  6864. var cue = this.cues[i];
  6865. if (cue.startTime <= ct && cue.endTime >= ct) {
  6866. active.push(cue);
  6867. } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
  6868. active.push(cue);
  6869. }
  6870. }
  6871. changed = false;
  6872. if (active.length !== this.activeCues_.length) {
  6873. changed = true;
  6874. } else {
  6875. for (var _i = 0; _i < active.length; _i++) {
  6876. if (this.activeCues_.indexOf(active[_i]) === -1) {
  6877. changed = true;
  6878. }
  6879. }
  6880. }
  6881. this.activeCues_ = active;
  6882. activeCues.setCues_(this.activeCues_);
  6883. return activeCues;
  6884. },
  6885. // /!\ Keep this setter empty (see the timeupdate handler above)
  6886. set: function set() {}
  6887. }
  6888. });
  6889. if (settings.src) {
  6890. _this.src = settings.src;
  6891. loadTrack(settings.src, _assertThisInitialized(_assertThisInitialized(_this)));
  6892. } else {
  6893. _this.loaded_ = true;
  6894. }
  6895. return _this;
  6896. }
  6897. /**
  6898. * Add a cue to the internal list of cues.
  6899. *
  6900. * @param {TextTrack~Cue} cue
  6901. * The cue to add to our internal list
  6902. */
  6903. var _proto = TextTrack.prototype;
  6904. _proto.addCue = function addCue(originalCue) {
  6905. var cue = originalCue;
  6906. if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) {
  6907. cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
  6908. for (var prop in originalCue) {
  6909. if (!(prop in cue)) {
  6910. cue[prop] = originalCue[prop];
  6911. }
  6912. } // make sure that `id` is copied over
  6913. cue.id = originalCue.id;
  6914. cue.originalCue_ = originalCue;
  6915. }
  6916. var tracks = this.tech_.textTracks();
  6917. for (var i = 0; i < tracks.length; i++) {
  6918. if (tracks[i] !== this) {
  6919. tracks[i].removeCue(cue);
  6920. }
  6921. }
  6922. this.cues_.push(cue);
  6923. this.cues.setCues_(this.cues_);
  6924. }
  6925. /**
  6926. * Remove a cue from our internal list
  6927. *
  6928. * @param {TextTrack~Cue} removeCue
  6929. * The cue to remove from our internal list
  6930. */
  6931. ;
  6932. _proto.removeCue = function removeCue(_removeCue) {
  6933. var i = this.cues_.length;
  6934. while (i--) {
  6935. var cue = this.cues_[i];
  6936. if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
  6937. this.cues_.splice(i, 1);
  6938. this.cues.setCues_(this.cues_);
  6939. break;
  6940. }
  6941. }
  6942. };
  6943. return TextTrack;
  6944. }(Track);
  6945. /**
  6946. * cuechange - One or more cues in the track have become active or stopped being active.
  6947. */
  6948. TextTrack.prototype.allowedEvents_ = {
  6949. cuechange: 'cuechange'
  6950. };
  6951. /**
  6952. * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
  6953. * only one `AudioTrack` in the list will be enabled at a time.
  6954. *
  6955. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
  6956. * @extends Track
  6957. */
  6958. var AudioTrack =
  6959. /*#__PURE__*/
  6960. function (_Track) {
  6961. _inheritsLoose(AudioTrack, _Track);
  6962. /**
  6963. * Create an instance of this class.
  6964. *
  6965. * @param {Object} [options={}]
  6966. * Object of option names and values
  6967. *
  6968. * @param {AudioTrack~Kind} [options.kind='']
  6969. * A valid audio track kind
  6970. *
  6971. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6972. * A unique id for this AudioTrack.
  6973. *
  6974. * @param {string} [options.label='']
  6975. * The menu label for this track.
  6976. *
  6977. * @param {string} [options.language='']
  6978. * A valid two character language code.
  6979. *
  6980. * @param {boolean} [options.enabled]
  6981. * If this track is the one that is currently playing. If this track is part of
  6982. * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
  6983. */
  6984. function AudioTrack(options) {
  6985. var _this;
  6986. if (options === void 0) {
  6987. options = {};
  6988. }
  6989. var settings = mergeOptions(options, {
  6990. kind: AudioTrackKind[options.kind] || ''
  6991. });
  6992. _this = _Track.call(this, settings) || this;
  6993. var enabled = false;
  6994. /**
  6995. * @memberof AudioTrack
  6996. * @member {boolean} enabled
  6997. * If this `AudioTrack` is enabled or not. When setting this will
  6998. * fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
  6999. * @instance
  7000. *
  7001. * @fires VideoTrack#selectedchange
  7002. */
  7003. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'enabled', {
  7004. get: function get() {
  7005. return enabled;
  7006. },
  7007. set: function set(newEnabled) {
  7008. // an invalid or unchanged value
  7009. if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
  7010. return;
  7011. }
  7012. enabled = newEnabled;
  7013. /**
  7014. * An event that fires when enabled changes on this track. This allows
  7015. * the AudioTrackList that holds this track to act accordingly.
  7016. *
  7017. * > Note: This is not part of the spec! Native tracks will do
  7018. * this internally without an event.
  7019. *
  7020. * @event AudioTrack#enabledchange
  7021. * @type {EventTarget~Event}
  7022. */
  7023. this.trigger('enabledchange');
  7024. }
  7025. }); // if the user sets this track to selected then
  7026. // set selected to that true value otherwise
  7027. // we keep it false
  7028. if (settings.enabled) {
  7029. _this.enabled = settings.enabled;
  7030. }
  7031. _this.loaded_ = true;
  7032. return _this;
  7033. }
  7034. return AudioTrack;
  7035. }(Track);
  7036. /**
  7037. * A representation of a single `VideoTrack`.
  7038. *
  7039. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
  7040. * @extends Track
  7041. */
  7042. var VideoTrack =
  7043. /*#__PURE__*/
  7044. function (_Track) {
  7045. _inheritsLoose(VideoTrack, _Track);
  7046. /**
  7047. * Create an instance of this class.
  7048. *
  7049. * @param {Object} [options={}]
  7050. * Object of option names and values
  7051. *
  7052. * @param {string} [options.kind='']
  7053. * A valid {@link VideoTrack~Kind}
  7054. *
  7055. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  7056. * A unique id for this AudioTrack.
  7057. *
  7058. * @param {string} [options.label='']
  7059. * The menu label for this track.
  7060. *
  7061. * @param {string} [options.language='']
  7062. * A valid two character language code.
  7063. *
  7064. * @param {boolean} [options.selected]
  7065. * If this track is the one that is currently playing.
  7066. */
  7067. function VideoTrack(options) {
  7068. var _this;
  7069. if (options === void 0) {
  7070. options = {};
  7071. }
  7072. var settings = mergeOptions(options, {
  7073. kind: VideoTrackKind[options.kind] || ''
  7074. });
  7075. _this = _Track.call(this, settings) || this;
  7076. var selected = false;
  7077. /**
  7078. * @memberof VideoTrack
  7079. * @member {boolean} selected
  7080. * If this `VideoTrack` is selected or not. When setting this will
  7081. * fire {@link VideoTrack#selectedchange} if the state of selected changed.
  7082. * @instance
  7083. *
  7084. * @fires VideoTrack#selectedchange
  7085. */
  7086. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selected', {
  7087. get: function get() {
  7088. return selected;
  7089. },
  7090. set: function set(newSelected) {
  7091. // an invalid or unchanged value
  7092. if (typeof newSelected !== 'boolean' || newSelected === selected) {
  7093. return;
  7094. }
  7095. selected = newSelected;
  7096. /**
  7097. * An event that fires when selected changes on this track. This allows
  7098. * the VideoTrackList that holds this track to act accordingly.
  7099. *
  7100. * > Note: This is not part of the spec! Native tracks will do
  7101. * this internally without an event.
  7102. *
  7103. * @event VideoTrack#selectedchange
  7104. * @type {EventTarget~Event}
  7105. */
  7106. this.trigger('selectedchange');
  7107. }
  7108. }); // if the user sets this track to selected then
  7109. // set selected to that true value otherwise
  7110. // we keep it false
  7111. if (settings.selected) {
  7112. _this.selected = settings.selected;
  7113. }
  7114. return _this;
  7115. }
  7116. return VideoTrack;
  7117. }(Track);
  7118. /**
  7119. * @memberof HTMLTrackElement
  7120. * @typedef {HTMLTrackElement~ReadyState}
  7121. * @enum {number}
  7122. */
  7123. var NONE = 0;
  7124. var LOADING = 1;
  7125. var LOADED = 2;
  7126. var ERROR = 3;
  7127. /**
  7128. * A single track represented in the DOM.
  7129. *
  7130. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
  7131. * @extends EventTarget
  7132. */
  7133. var HTMLTrackElement =
  7134. /*#__PURE__*/
  7135. function (_EventTarget) {
  7136. _inheritsLoose(HTMLTrackElement, _EventTarget);
  7137. /**
  7138. * Create an instance of this class.
  7139. *
  7140. * @param {Object} options={}
  7141. * Object of option names and values
  7142. *
  7143. * @param {Tech} options.tech
  7144. * A reference to the tech that owns this HTMLTrackElement.
  7145. *
  7146. * @param {TextTrack~Kind} [options.kind='subtitles']
  7147. * A valid text track kind.
  7148. *
  7149. * @param {TextTrack~Mode} [options.mode='disabled']
  7150. * A valid text track mode.
  7151. *
  7152. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  7153. * A unique id for this TextTrack.
  7154. *
  7155. * @param {string} [options.label='']
  7156. * The menu label for this track.
  7157. *
  7158. * @param {string} [options.language='']
  7159. * A valid two character language code.
  7160. *
  7161. * @param {string} [options.srclang='']
  7162. * A valid two character language code. An alternative, but deprioritized
  7163. * vesion of `options.language`
  7164. *
  7165. * @param {string} [options.src]
  7166. * A url to TextTrack cues.
  7167. *
  7168. * @param {boolean} [options.default]
  7169. * If this track should default to on or off.
  7170. */
  7171. function HTMLTrackElement(options) {
  7172. var _this;
  7173. if (options === void 0) {
  7174. options = {};
  7175. }
  7176. _this = _EventTarget.call(this) || this;
  7177. var readyState;
  7178. var track = new TextTrack(options);
  7179. _this.kind = track.kind;
  7180. _this.src = track.src;
  7181. _this.srclang = track.language;
  7182. _this.label = track.label;
  7183. _this.default = track.default;
  7184. Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), {
  7185. /**
  7186. * @memberof HTMLTrackElement
  7187. * @member {HTMLTrackElement~ReadyState} readyState
  7188. * The current ready state of the track element.
  7189. * @instance
  7190. */
  7191. readyState: {
  7192. get: function get() {
  7193. return readyState;
  7194. }
  7195. },
  7196. /**
  7197. * @memberof HTMLTrackElement
  7198. * @member {TextTrack} track
  7199. * The underlying TextTrack object.
  7200. * @instance
  7201. *
  7202. */
  7203. track: {
  7204. get: function get() {
  7205. return track;
  7206. }
  7207. }
  7208. });
  7209. readyState = NONE;
  7210. /**
  7211. * @listens TextTrack#loadeddata
  7212. * @fires HTMLTrackElement#load
  7213. */
  7214. track.addEventListener('loadeddata', function () {
  7215. readyState = LOADED;
  7216. _this.trigger({
  7217. type: 'load',
  7218. target: _assertThisInitialized(_assertThisInitialized(_this))
  7219. });
  7220. });
  7221. return _this;
  7222. }
  7223. return HTMLTrackElement;
  7224. }(EventTarget);
  7225. HTMLTrackElement.prototype.allowedEvents_ = {
  7226. load: 'load'
  7227. };
  7228. HTMLTrackElement.NONE = NONE;
  7229. HTMLTrackElement.LOADING = LOADING;
  7230. HTMLTrackElement.LOADED = LOADED;
  7231. HTMLTrackElement.ERROR = ERROR;
  7232. /*
  7233. * This file contains all track properties that are used in
  7234. * player.js, tech.js, html5.js and possibly other techs in the future.
  7235. */
  7236. var NORMAL = {
  7237. audio: {
  7238. ListClass: AudioTrackList,
  7239. TrackClass: AudioTrack,
  7240. capitalName: 'Audio'
  7241. },
  7242. video: {
  7243. ListClass: VideoTrackList,
  7244. TrackClass: VideoTrack,
  7245. capitalName: 'Video'
  7246. },
  7247. text: {
  7248. ListClass: TextTrackList,
  7249. TrackClass: TextTrack,
  7250. capitalName: 'Text'
  7251. }
  7252. };
  7253. Object.keys(NORMAL).forEach(function (type) {
  7254. NORMAL[type].getterName = type + "Tracks";
  7255. NORMAL[type].privateName = type + "Tracks_";
  7256. });
  7257. var REMOTE = {
  7258. remoteText: {
  7259. ListClass: TextTrackList,
  7260. TrackClass: TextTrack,
  7261. capitalName: 'RemoteText',
  7262. getterName: 'remoteTextTracks',
  7263. privateName: 'remoteTextTracks_'
  7264. },
  7265. remoteTextEl: {
  7266. ListClass: HtmlTrackElementList,
  7267. TrackClass: HTMLTrackElement,
  7268. capitalName: 'RemoteTextTrackEls',
  7269. getterName: 'remoteTextTrackEls',
  7270. privateName: 'remoteTextTrackEls_'
  7271. }
  7272. };
  7273. var ALL = mergeOptions(NORMAL, REMOTE);
  7274. REMOTE.names = Object.keys(REMOTE);
  7275. NORMAL.names = Object.keys(NORMAL);
  7276. ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
  7277. /**
  7278. * Copyright 2013 vtt.js Contributors
  7279. *
  7280. * Licensed under the Apache License, Version 2.0 (the "License");
  7281. * you may not use this file except in compliance with the License.
  7282. * You may obtain a copy of the License at
  7283. *
  7284. * http://www.apache.org/licenses/LICENSE-2.0
  7285. *
  7286. * Unless required by applicable law or agreed to in writing, software
  7287. * distributed under the License is distributed on an "AS IS" BASIS,
  7288. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7289. * See the License for the specific language governing permissions and
  7290. * limitations under the License.
  7291. */
  7292. /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  7293. /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
  7294. var _objCreate = Object.create || function () {
  7295. function F() {}
  7296. return function (o) {
  7297. if (arguments.length !== 1) {
  7298. throw new Error('Object.create shim only accepts one parameter.');
  7299. }
  7300. F.prototype = o;
  7301. return new F();
  7302. };
  7303. }(); // Creates a new ParserError object from an errorData object. The errorData
  7304. // object should have default code and message properties. The default message
  7305. // property can be overriden by passing in a message parameter.
  7306. // See ParsingError.Errors below for acceptable errors.
  7307. function ParsingError(errorData, message) {
  7308. this.name = "ParsingError";
  7309. this.code = errorData.code;
  7310. this.message = message || errorData.message;
  7311. }
  7312. ParsingError.prototype = _objCreate(Error.prototype);
  7313. ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors.
  7314. ParsingError.Errors = {
  7315. BadSignature: {
  7316. code: 0,
  7317. message: "Malformed WebVTT signature."
  7318. },
  7319. BadTimeStamp: {
  7320. code: 1,
  7321. message: "Malformed time stamp."
  7322. }
  7323. }; // Try to parse input as a time stamp.
  7324. function parseTimeStamp(input) {
  7325. function computeSeconds(h, m, s, f) {
  7326. return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
  7327. }
  7328. var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
  7329. if (!m) {
  7330. return null;
  7331. }
  7332. if (m[3]) {
  7333. // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
  7334. return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
  7335. } else if (m[1] > 59) {
  7336. // Timestamp takes the form of [hours]:[minutes].[milliseconds]
  7337. // First position is hours as it's over 59.
  7338. return computeSeconds(m[1], m[2], 0, m[4]);
  7339. } else {
  7340. // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
  7341. return computeSeconds(0, m[1], m[2], m[4]);
  7342. }
  7343. } // A settings object holds key/value pairs and will ignore anything but the first
  7344. // assignment to a specific key.
  7345. function Settings() {
  7346. this.values = _objCreate(null);
  7347. }
  7348. Settings.prototype = {
  7349. // Only accept the first assignment to any key.
  7350. set: function set(k, v) {
  7351. if (!this.get(k) && v !== "") {
  7352. this.values[k] = v;
  7353. }
  7354. },
  7355. // Return the value for a key, or a default value.
  7356. // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
  7357. // a number of possible default values as properties where 'defaultKey' is
  7358. // the key of the property that will be chosen; otherwise it's assumed to be
  7359. // a single value.
  7360. get: function get(k, dflt, defaultKey) {
  7361. if (defaultKey) {
  7362. return this.has(k) ? this.values[k] : dflt[defaultKey];
  7363. }
  7364. return this.has(k) ? this.values[k] : dflt;
  7365. },
  7366. // Check whether we have a value for a key.
  7367. has: function has(k) {
  7368. return k in this.values;
  7369. },
  7370. // Accept a setting if its one of the given alternatives.
  7371. alt: function alt(k, v, a) {
  7372. for (var n = 0; n < a.length; ++n) {
  7373. if (v === a[n]) {
  7374. this.set(k, v);
  7375. break;
  7376. }
  7377. }
  7378. },
  7379. // Accept a setting if its a valid (signed) integer.
  7380. integer: function integer(k, v) {
  7381. if (/^-?\d+$/.test(v)) {
  7382. // integer
  7383. this.set(k, parseInt(v, 10));
  7384. }
  7385. },
  7386. // Accept a setting if its a valid percentage.
  7387. percent: function percent(k, v) {
  7388. var m;
  7389. if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
  7390. v = parseFloat(v);
  7391. if (v >= 0 && v <= 100) {
  7392. this.set(k, v);
  7393. return true;
  7394. }
  7395. }
  7396. return false;
  7397. }
  7398. }; // Helper function to parse input into groups separated by 'groupDelim', and
  7399. // interprete each group as a key/value pair separated by 'keyValueDelim'.
  7400. function parseOptions(input, callback, keyValueDelim, groupDelim) {
  7401. var groups = groupDelim ? input.split(groupDelim) : [input];
  7402. for (var i in groups) {
  7403. if (typeof groups[i] !== "string") {
  7404. continue;
  7405. }
  7406. var kv = groups[i].split(keyValueDelim);
  7407. if (kv.length !== 2) {
  7408. continue;
  7409. }
  7410. var k = kv[0];
  7411. var v = kv[1];
  7412. callback(k, v);
  7413. }
  7414. }
  7415. function parseCue(input, cue, regionList) {
  7416. // Remember the original input if we need to throw an error.
  7417. var oInput = input; // 4.1 WebVTT timestamp
  7418. function consumeTimeStamp() {
  7419. var ts = parseTimeStamp(input);
  7420. if (ts === null) {
  7421. throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
  7422. } // Remove time stamp from input.
  7423. input = input.replace(/^[^\sa-zA-Z-]+/, "");
  7424. return ts;
  7425. } // 4.4.2 WebVTT cue settings
  7426. function consumeCueSettings(input, cue) {
  7427. var settings = new Settings();
  7428. parseOptions(input, function (k, v) {
  7429. switch (k) {
  7430. case "region":
  7431. // Find the last region we parsed with the same region id.
  7432. for (var i = regionList.length - 1; i >= 0; i--) {
  7433. if (regionList[i].id === v) {
  7434. settings.set(k, regionList[i].region);
  7435. break;
  7436. }
  7437. }
  7438. break;
  7439. case "vertical":
  7440. settings.alt(k, v, ["rl", "lr"]);
  7441. break;
  7442. case "line":
  7443. var vals = v.split(","),
  7444. vals0 = vals[0];
  7445. settings.integer(k, vals0);
  7446. settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
  7447. settings.alt(k, vals0, ["auto"]);
  7448. if (vals.length === 2) {
  7449. settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
  7450. }
  7451. break;
  7452. case "position":
  7453. vals = v.split(",");
  7454. settings.percent(k, vals[0]);
  7455. if (vals.length === 2) {
  7456. settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
  7457. }
  7458. break;
  7459. case "size":
  7460. settings.percent(k, v);
  7461. break;
  7462. case "align":
  7463. settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
  7464. break;
  7465. }
  7466. }, /:/, /\s/); // Apply default values for any missing fields.
  7467. cue.region = settings.get("region", null);
  7468. cue.vertical = settings.get("vertical", "");
  7469. cue.line = settings.get("line", "auto");
  7470. cue.lineAlign = settings.get("lineAlign", "start");
  7471. cue.snapToLines = settings.get("snapToLines", true);
  7472. cue.size = settings.get("size", 100);
  7473. cue.align = settings.get("align", "middle");
  7474. cue.position = settings.get("position", {
  7475. start: 0,
  7476. left: 0,
  7477. middle: 50,
  7478. end: 100,
  7479. right: 100
  7480. }, cue.align);
  7481. cue.positionAlign = settings.get("positionAlign", {
  7482. start: "start",
  7483. left: "start",
  7484. middle: "middle",
  7485. end: "end",
  7486. right: "end"
  7487. }, cue.align);
  7488. }
  7489. function skipWhitespace() {
  7490. input = input.replace(/^\s+/, "");
  7491. } // 4.1 WebVTT cue timings.
  7492. skipWhitespace();
  7493. cue.startTime = consumeTimeStamp(); // (1) collect cue start time
  7494. skipWhitespace();
  7495. if (input.substr(0, 3) !== "-->") {
  7496. // (3) next characters must match "-->"
  7497. throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
  7498. }
  7499. input = input.substr(3);
  7500. skipWhitespace();
  7501. cue.endTime = consumeTimeStamp(); // (5) collect cue end time
  7502. // 4.1 WebVTT cue settings list.
  7503. skipWhitespace();
  7504. consumeCueSettings(input, cue);
  7505. }
  7506. var ESCAPE = {
  7507. "&amp;": "&",
  7508. "&lt;": "<",
  7509. "&gt;": ">",
  7510. "&lrm;": "\u200E",
  7511. "&rlm;": "\u200F",
  7512. "&nbsp;": "\xA0"
  7513. };
  7514. var TAG_NAME = {
  7515. c: "span",
  7516. i: "i",
  7517. b: "b",
  7518. u: "u",
  7519. ruby: "ruby",
  7520. rt: "rt",
  7521. v: "span",
  7522. lang: "span"
  7523. };
  7524. var TAG_ANNOTATION = {
  7525. v: "title",
  7526. lang: "lang"
  7527. };
  7528. var NEEDS_PARENT = {
  7529. rt: "ruby"
  7530. }; // Parse content into a document fragment.
  7531. function parseContent(window, input) {
  7532. function nextToken() {
  7533. // Check for end-of-string.
  7534. if (!input) {
  7535. return null;
  7536. } // Consume 'n' characters from the input.
  7537. function consume(result) {
  7538. input = input.substr(result.length);
  7539. return result;
  7540. }
  7541. var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return
  7542. // the tag.
  7543. return consume(m[1] ? m[1] : m[2]);
  7544. } // Unescape a string 's'.
  7545. function unescape1(e) {
  7546. return ESCAPE[e];
  7547. }
  7548. function unescape(s) {
  7549. while (m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/)) {
  7550. s = s.replace(m[0], unescape1);
  7551. }
  7552. return s;
  7553. }
  7554. function shouldAdd(current, element) {
  7555. return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
  7556. } // Create an element for this tag.
  7557. function createElement(type, annotation) {
  7558. var tagName = TAG_NAME[type];
  7559. if (!tagName) {
  7560. return null;
  7561. }
  7562. var element = window.document.createElement(tagName);
  7563. element.localName = tagName;
  7564. var name = TAG_ANNOTATION[type];
  7565. if (name && annotation) {
  7566. element[name] = annotation.trim();
  7567. }
  7568. return element;
  7569. }
  7570. var rootDiv = window.document.createElement("div"),
  7571. current = rootDiv,
  7572. t,
  7573. tagStack = [];
  7574. while ((t = nextToken()) !== null) {
  7575. if (t[0] === '<') {
  7576. if (t[1] === "/") {
  7577. // If the closing tag matches, move back up to the parent node.
  7578. if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
  7579. tagStack.pop();
  7580. current = current.parentNode;
  7581. } // Otherwise just ignore the end tag.
  7582. continue;
  7583. }
  7584. var ts = parseTimeStamp(t.substr(1, t.length - 2));
  7585. var node;
  7586. if (ts) {
  7587. // Timestamps are lead nodes as well.
  7588. node = window.document.createProcessingInstruction("timestamp", ts);
  7589. current.appendChild(node);
  7590. continue;
  7591. }
  7592. var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag.
  7593. if (!m) {
  7594. continue;
  7595. } // Try to construct an element, and ignore the tag if we couldn't.
  7596. node = createElement(m[1], m[3]);
  7597. if (!node) {
  7598. continue;
  7599. } // Determine if the tag should be added based on the context of where it
  7600. // is placed in the cuetext.
  7601. if (!shouldAdd(current, node)) {
  7602. continue;
  7603. } // Set the class list (as a list of classes, separated by space).
  7604. if (m[2]) {
  7605. node.className = m[2].substr(1).replace('.', ' ');
  7606. } // Append the node to the current node, and enter the scope of the new
  7607. // node.
  7608. tagStack.push(m[1]);
  7609. current.appendChild(node);
  7610. current = node;
  7611. continue;
  7612. } // Text nodes are leaf nodes.
  7613. current.appendChild(window.document.createTextNode(unescape(t)));
  7614. }
  7615. return rootDiv;
  7616. } // This is a list of all the Unicode characters that have a strong
  7617. // right-to-left category. What this means is that these characters are
  7618. // written right-to-left for sure. It was generated by pulling all the strong
  7619. // right-to-left characters out of the Unicode data table. That table can
  7620. // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
  7621. var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]];
  7622. function isStrongRTLChar(charCode) {
  7623. for (var i = 0; i < strongRTLRanges.length; i++) {
  7624. var currentRange = strongRTLRanges[i];
  7625. if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
  7626. return true;
  7627. }
  7628. }
  7629. return false;
  7630. }
  7631. function determineBidi(cueDiv) {
  7632. var nodeStack = [],
  7633. text = "",
  7634. charCode;
  7635. if (!cueDiv || !cueDiv.childNodes) {
  7636. return "ltr";
  7637. }
  7638. function pushNodes(nodeStack, node) {
  7639. for (var i = node.childNodes.length - 1; i >= 0; i--) {
  7640. nodeStack.push(node.childNodes[i]);
  7641. }
  7642. }
  7643. function nextTextNode(nodeStack) {
  7644. if (!nodeStack || !nodeStack.length) {
  7645. return null;
  7646. }
  7647. var node = nodeStack.pop(),
  7648. text = node.textContent || node.innerText;
  7649. if (text) {
  7650. // TODO: This should match all unicode type B characters (paragraph
  7651. // separator characters). See issue #115.
  7652. var m = text.match(/^.*(\n|\r)/);
  7653. if (m) {
  7654. nodeStack.length = 0;
  7655. return m[0];
  7656. }
  7657. return text;
  7658. }
  7659. if (node.tagName === "ruby") {
  7660. return nextTextNode(nodeStack);
  7661. }
  7662. if (node.childNodes) {
  7663. pushNodes(nodeStack, node);
  7664. return nextTextNode(nodeStack);
  7665. }
  7666. }
  7667. pushNodes(nodeStack, cueDiv);
  7668. while (text = nextTextNode(nodeStack)) {
  7669. for (var i = 0; i < text.length; i++) {
  7670. charCode = text.charCodeAt(i);
  7671. if (isStrongRTLChar(charCode)) {
  7672. return "rtl";
  7673. }
  7674. }
  7675. }
  7676. return "ltr";
  7677. }
  7678. function computeLinePos(cue) {
  7679. if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
  7680. return cue.line;
  7681. }
  7682. if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
  7683. return -1;
  7684. }
  7685. var track = cue.track,
  7686. trackList = track.textTrackList,
  7687. count = 0;
  7688. for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
  7689. if (trackList[i].mode === "showing") {
  7690. count++;
  7691. }
  7692. }
  7693. return ++count * -1;
  7694. }
  7695. function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the
  7696. // div on 'this'.
  7697. StyleBox.prototype.applyStyles = function (styles, div) {
  7698. div = div || this.div;
  7699. for (var prop in styles) {
  7700. if (styles.hasOwnProperty(prop)) {
  7701. div.style[prop] = styles[prop];
  7702. }
  7703. }
  7704. };
  7705. StyleBox.prototype.formatStyle = function (val, unit) {
  7706. return val === 0 ? 0 : val + unit;
  7707. }; // Constructs the computed display state of the cue (a div). Places the div
  7708. // into the overlay which should be a block level element (usually a div).
  7709. function CueStyleBox(window, cue, styleOptions) {
  7710. StyleBox.call(this);
  7711. this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
  7712. // have inline positioning and will function as the cue background box.
  7713. this.cueDiv = parseContent(window, cue.text);
  7714. var styles = {
  7715. color: "rgba(255, 255, 255, 1)",
  7716. backgroundColor: "rgba(0, 0, 0, 0.8)",
  7717. position: "relative",
  7718. left: 0,
  7719. right: 0,
  7720. top: 0,
  7721. bottom: 0,
  7722. display: "inline",
  7723. writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
  7724. unicodeBidi: "plaintext"
  7725. };
  7726. this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue
  7727. // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
  7728. // mirrors of them except "middle" which is "center" in CSS.
  7729. this.div = window.document.createElement("div");
  7730. styles = {
  7731. direction: determineBidi(this.cueDiv),
  7732. writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
  7733. unicodeBidi: "plaintext",
  7734. textAlign: cue.align === "middle" ? "center" : cue.align,
  7735. font: styleOptions.font,
  7736. whiteSpace: "pre-line",
  7737. position: "absolute"
  7738. };
  7739. this.applyStyles(styles);
  7740. this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text
  7741. // position of the cue box. The reference edge will be resolved later when
  7742. // the box orientation styles are applied.
  7743. var textPos = 0;
  7744. switch (cue.positionAlign) {
  7745. case "start":
  7746. textPos = cue.position;
  7747. break;
  7748. case "middle":
  7749. textPos = cue.position - cue.size / 2;
  7750. break;
  7751. case "end":
  7752. textPos = cue.position - cue.size;
  7753. break;
  7754. } // Horizontal box orientation; textPos is the distance from the left edge of the
  7755. // area to the left edge of the box and cue.size is the distance extending to
  7756. // the right from there.
  7757. if (cue.vertical === "") {
  7758. this.applyStyles({
  7759. left: this.formatStyle(textPos, "%"),
  7760. width: this.formatStyle(cue.size, "%")
  7761. }); // Vertical box orientation; textPos is the distance from the top edge of the
  7762. // area to the top edge of the box and cue.size is the height extending
  7763. // downwards from there.
  7764. } else {
  7765. this.applyStyles({
  7766. top: this.formatStyle(textPos, "%"),
  7767. height: this.formatStyle(cue.size, "%")
  7768. });
  7769. }
  7770. this.move = function (box) {
  7771. this.applyStyles({
  7772. top: this.formatStyle(box.top, "px"),
  7773. bottom: this.formatStyle(box.bottom, "px"),
  7774. left: this.formatStyle(box.left, "px"),
  7775. right: this.formatStyle(box.right, "px"),
  7776. height: this.formatStyle(box.height, "px"),
  7777. width: this.formatStyle(box.width, "px")
  7778. });
  7779. };
  7780. }
  7781. CueStyleBox.prototype = _objCreate(StyleBox.prototype);
  7782. CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily
  7783. // compute things with such as if it overlaps or intersects with another Element.
  7784. // Can initialize it with either a StyleBox or another BoxPosition.
  7785. function BoxPosition(obj) {
  7786. // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
  7787. // was passed in and we need to copy the results of 'getBoundingClientRect'
  7788. // as the object returned is readonly. All co-ordinate values are in reference
  7789. // to the viewport origin (top left).
  7790. var lh, height, width, top;
  7791. if (obj.div) {
  7792. height = obj.div.offsetHeight;
  7793. width = obj.div.offsetWidth;
  7794. top = obj.div.offsetTop;
  7795. var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
  7796. obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of
  7797. // the inner div's lines. This could be due to bold text, etc, on some platforms.
  7798. // In this case we should get the average line height and use that. This will
  7799. // result in the desired behaviour.
  7800. lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
  7801. }
  7802. this.left = obj.left;
  7803. this.right = obj.right;
  7804. this.top = obj.top || top;
  7805. this.height = obj.height || height;
  7806. this.bottom = obj.bottom || top + (obj.height || height);
  7807. this.width = obj.width || width;
  7808. this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
  7809. } // Move the box along a particular axis. Optionally pass in an amount to move
  7810. // the box. If no amount is passed then the default is the line height of the
  7811. // box.
  7812. BoxPosition.prototype.move = function (axis, toMove) {
  7813. toMove = toMove !== undefined ? toMove : this.lineHeight;
  7814. switch (axis) {
  7815. case "+x":
  7816. this.left += toMove;
  7817. this.right += toMove;
  7818. break;
  7819. case "-x":
  7820. this.left -= toMove;
  7821. this.right -= toMove;
  7822. break;
  7823. case "+y":
  7824. this.top += toMove;
  7825. this.bottom += toMove;
  7826. break;
  7827. case "-y":
  7828. this.top -= toMove;
  7829. this.bottom -= toMove;
  7830. break;
  7831. }
  7832. }; // Check if this box overlaps another box, b2.
  7833. BoxPosition.prototype.overlaps = function (b2) {
  7834. return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
  7835. }; // Check if this box overlaps any other boxes in boxes.
  7836. BoxPosition.prototype.overlapsAny = function (boxes) {
  7837. for (var i = 0; i < boxes.length; i++) {
  7838. if (this.overlaps(boxes[i])) {
  7839. return true;
  7840. }
  7841. }
  7842. return false;
  7843. }; // Check if this box is within another box.
  7844. BoxPosition.prototype.within = function (container) {
  7845. return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
  7846. }; // Check if this box is entirely within the container or it is overlapping
  7847. // on the edge opposite of the axis direction passed. For example, if "+x" is
  7848. // passed and the box is overlapping on the left edge of the container, then
  7849. // return true.
  7850. BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
  7851. switch (axis) {
  7852. case "+x":
  7853. return this.left < container.left;
  7854. case "-x":
  7855. return this.right > container.right;
  7856. case "+y":
  7857. return this.top < container.top;
  7858. case "-y":
  7859. return this.bottom > container.bottom;
  7860. }
  7861. }; // Find the percentage of the area that this box is overlapping with another
  7862. // box.
  7863. BoxPosition.prototype.intersectPercentage = function (b2) {
  7864. var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
  7865. y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
  7866. intersectArea = x * y;
  7867. return intersectArea / (this.height * this.width);
  7868. }; // Convert the positions from this box to CSS compatible positions using
  7869. // the reference container's positions. This has to be done because this
  7870. // box's positions are in reference to the viewport origin, whereas, CSS
  7871. // values are in referecne to their respective edges.
  7872. BoxPosition.prototype.toCSSCompatValues = function (reference) {
  7873. return {
  7874. top: this.top - reference.top,
  7875. bottom: reference.bottom - this.bottom,
  7876. left: this.left - reference.left,
  7877. right: reference.right - this.right,
  7878. height: this.height,
  7879. width: this.width
  7880. };
  7881. }; // Get an object that represents the box's position without anything extra.
  7882. // Can pass a StyleBox, HTMLElement, or another BoxPositon.
  7883. BoxPosition.getSimpleBoxPosition = function (obj) {
  7884. var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
  7885. var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
  7886. var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
  7887. obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
  7888. var ret = {
  7889. left: obj.left,
  7890. right: obj.right,
  7891. top: obj.top || top,
  7892. height: obj.height || height,
  7893. bottom: obj.bottom || top + (obj.height || height),
  7894. width: obj.width || width
  7895. };
  7896. return ret;
  7897. }; // Move a StyleBox to its specified, or next best, position. The containerBox
  7898. // is the box that contains the StyleBox, such as a div. boxPositions are
  7899. // a list of other boxes that the styleBox can't overlap with.
  7900. function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
  7901. // Find the best position for a cue box, b, on the video. The axis parameter
  7902. // is a list of axis, the order of which, it will move the box along. For example:
  7903. // Passing ["+x", "-x"] will move the box first along the x axis in the positive
  7904. // direction. If it doesn't find a good position for it there it will then move
  7905. // it along the x axis in the negative direction.
  7906. function findBestPosition(b, axis) {
  7907. var bestPosition,
  7908. specifiedPosition = new BoxPosition(b),
  7909. percentage = 1; // Highest possible so the first thing we get is better.
  7910. for (var i = 0; i < axis.length; i++) {
  7911. while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
  7912. b.move(axis[i]);
  7913. } // We found a spot where we aren't overlapping anything. This is our
  7914. // best position.
  7915. if (b.within(containerBox)) {
  7916. return b;
  7917. }
  7918. var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try
  7919. // then remember this position as the best position.
  7920. if (percentage > p) {
  7921. bestPosition = new BoxPosition(b);
  7922. percentage = p;
  7923. } // Reset the box position to the specified position.
  7924. b = new BoxPosition(specifiedPosition);
  7925. }
  7926. return bestPosition || specifiedPosition;
  7927. }
  7928. var boxPosition = new BoxPosition(styleBox),
  7929. cue = styleBox.cue,
  7930. linePos = computeLinePos(cue),
  7931. axis = []; // If we have a line number to align the cue to.
  7932. if (cue.snapToLines) {
  7933. var size;
  7934. switch (cue.vertical) {
  7935. case "":
  7936. axis = ["+y", "-y"];
  7937. size = "height";
  7938. break;
  7939. case "rl":
  7940. axis = ["+x", "-x"];
  7941. size = "width";
  7942. break;
  7943. case "lr":
  7944. axis = ["-x", "+x"];
  7945. size = "width";
  7946. break;
  7947. }
  7948. var step = boxPosition.lineHeight,
  7949. position = step * Math.round(linePos),
  7950. maxPosition = containerBox[size] + step,
  7951. initialAxis = axis[0]; // If the specified intial position is greater then the max position then
  7952. // clamp the box to the amount of steps it would take for the box to
  7953. // reach the max position.
  7954. if (Math.abs(position) > maxPosition) {
  7955. position = position < 0 ? -1 : 1;
  7956. position *= Math.ceil(maxPosition / step) * step;
  7957. } // If computed line position returns negative then line numbers are
  7958. // relative to the bottom of the video instead of the top. Therefore, we
  7959. // need to increase our initial position by the length or width of the
  7960. // video, depending on the writing direction, and reverse our axis directions.
  7961. if (linePos < 0) {
  7962. position += cue.vertical === "" ? containerBox.height : containerBox.width;
  7963. axis = axis.reverse();
  7964. } // Move the box to the specified position. This may not be its best
  7965. // position.
  7966. boxPosition.move(initialAxis, position);
  7967. } else {
  7968. // If we have a percentage line value for the cue.
  7969. var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
  7970. switch (cue.lineAlign) {
  7971. case "middle":
  7972. linePos -= calculatedPercentage / 2;
  7973. break;
  7974. case "end":
  7975. linePos -= calculatedPercentage;
  7976. break;
  7977. } // Apply initial line position to the cue box.
  7978. switch (cue.vertical) {
  7979. case "":
  7980. styleBox.applyStyles({
  7981. top: styleBox.formatStyle(linePos, "%")
  7982. });
  7983. break;
  7984. case "rl":
  7985. styleBox.applyStyles({
  7986. left: styleBox.formatStyle(linePos, "%")
  7987. });
  7988. break;
  7989. case "lr":
  7990. styleBox.applyStyles({
  7991. right: styleBox.formatStyle(linePos, "%")
  7992. });
  7993. break;
  7994. }
  7995. axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning
  7996. // to it.
  7997. boxPosition = new BoxPosition(styleBox);
  7998. }
  7999. var bestPosition = findBestPosition(boxPosition, axis);
  8000. styleBox.move(bestPosition.toCSSCompatValues(containerBox));
  8001. }
  8002. function WebVTT$1() {} // Nothing
  8003. // Helper to allow strings to be decoded instead of the default binary utf8 data.
  8004. WebVTT$1.StringDecoder = function () {
  8005. return {
  8006. decode: function decode(data) {
  8007. if (!data) {
  8008. return "";
  8009. }
  8010. if (typeof data !== "string") {
  8011. throw new Error("Error - expected string data.");
  8012. }
  8013. return decodeURIComponent(encodeURIComponent(data));
  8014. }
  8015. };
  8016. };
  8017. WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
  8018. if (!window || !cuetext) {
  8019. return null;
  8020. }
  8021. return parseContent(window, cuetext);
  8022. };
  8023. var FONT_SIZE_PERCENT = 0.05;
  8024. var FONT_STYLE = "sans-serif";
  8025. var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it.
  8026. // @param overlay A block level element (usually a div) that the computed cues
  8027. // and regions will be placed into.
  8028. WebVTT$1.processCues = function (window, cues, overlay) {
  8029. if (!window || !cues || !overlay) {
  8030. return null;
  8031. } // Remove all previous children.
  8032. while (overlay.firstChild) {
  8033. overlay.removeChild(overlay.firstChild);
  8034. }
  8035. var paddedOverlay = window.document.createElement("div");
  8036. paddedOverlay.style.position = "absolute";
  8037. paddedOverlay.style.left = "0";
  8038. paddedOverlay.style.right = "0";
  8039. paddedOverlay.style.top = "0";
  8040. paddedOverlay.style.bottom = "0";
  8041. paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
  8042. overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could
  8043. // be the case if a cue's state has been changed since the last computation or
  8044. // if it has not been computed yet.
  8045. function shouldCompute(cues) {
  8046. for (var i = 0; i < cues.length; i++) {
  8047. if (cues[i].hasBeenReset || !cues[i].displayState) {
  8048. return true;
  8049. }
  8050. }
  8051. return false;
  8052. } // We don't need to recompute the cues' display states. Just reuse them.
  8053. if (!shouldCompute(cues)) {
  8054. for (var i = 0; i < cues.length; i++) {
  8055. paddedOverlay.appendChild(cues[i].displayState);
  8056. }
  8057. return;
  8058. }
  8059. var boxPositions = [],
  8060. containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
  8061. fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
  8062. var styleOptions = {
  8063. font: fontSize + "px " + FONT_STYLE
  8064. };
  8065. (function () {
  8066. var styleBox, cue;
  8067. for (var i = 0; i < cues.length; i++) {
  8068. cue = cues[i]; // Compute the intial position and styles of the cue div.
  8069. styleBox = new CueStyleBox(window, cue, styleOptions);
  8070. paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position.
  8071. moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later
  8072. // if we don't have too.
  8073. cue.displayState = styleBox.div;
  8074. boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
  8075. }
  8076. })();
  8077. };
  8078. WebVTT$1.Parser = function (window, vttjs, decoder) {
  8079. if (!decoder) {
  8080. decoder = vttjs;
  8081. vttjs = {};
  8082. }
  8083. if (!vttjs) {
  8084. vttjs = {};
  8085. }
  8086. this.window = window;
  8087. this.vttjs = vttjs;
  8088. this.state = "INITIAL";
  8089. this.buffer = "";
  8090. this.decoder = decoder || new TextDecoder("utf8");
  8091. this.regionList = [];
  8092. };
  8093. WebVTT$1.Parser.prototype = {
  8094. // If the error is a ParsingError then report it to the consumer if
  8095. // possible. If it's not a ParsingError then throw it like normal.
  8096. reportOrThrowError: function reportOrThrowError(e) {
  8097. if (e instanceof ParsingError) {
  8098. this.onparsingerror && this.onparsingerror(e);
  8099. } else {
  8100. throw e;
  8101. }
  8102. },
  8103. parse: function parse(data) {
  8104. var self = this; // If there is no data then we won't decode it, but will just try to parse
  8105. // whatever is in buffer already. This may occur in circumstances, for
  8106. // example when flush() is called.
  8107. if (data) {
  8108. // Try to decode the data that we received.
  8109. self.buffer += self.decoder.decode(data, {
  8110. stream: true
  8111. });
  8112. }
  8113. function collectNextLine() {
  8114. var buffer = self.buffer;
  8115. var pos = 0;
  8116. while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
  8117. ++pos;
  8118. }
  8119. var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below.
  8120. if (buffer[pos] === '\r') {
  8121. ++pos;
  8122. }
  8123. if (buffer[pos] === '\n') {
  8124. ++pos;
  8125. }
  8126. self.buffer = buffer.substr(pos);
  8127. return line;
  8128. } // 3.4 WebVTT region and WebVTT region settings syntax
  8129. function parseRegion(input) {
  8130. var settings = new Settings();
  8131. parseOptions(input, function (k, v) {
  8132. switch (k) {
  8133. case "id":
  8134. settings.set(k, v);
  8135. break;
  8136. case "width":
  8137. settings.percent(k, v);
  8138. break;
  8139. case "lines":
  8140. settings.integer(k, v);
  8141. break;
  8142. case "regionanchor":
  8143. case "viewportanchor":
  8144. var xy = v.split(',');
  8145. if (xy.length !== 2) {
  8146. break;
  8147. } // We have to make sure both x and y parse, so use a temporary
  8148. // settings object here.
  8149. var anchor = new Settings();
  8150. anchor.percent("x", xy[0]);
  8151. anchor.percent("y", xy[1]);
  8152. if (!anchor.has("x") || !anchor.has("y")) {
  8153. break;
  8154. }
  8155. settings.set(k + "X", anchor.get("x"));
  8156. settings.set(k + "Y", anchor.get("y"));
  8157. break;
  8158. case "scroll":
  8159. settings.alt(k, v, ["up"]);
  8160. break;
  8161. }
  8162. }, /=/, /\s/); // Create the region, using default values for any values that were not
  8163. // specified.
  8164. if (settings.has("id")) {
  8165. var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
  8166. region.width = settings.get("width", 100);
  8167. region.lines = settings.get("lines", 3);
  8168. region.regionAnchorX = settings.get("regionanchorX", 0);
  8169. region.regionAnchorY = settings.get("regionanchorY", 100);
  8170. region.viewportAnchorX = settings.get("viewportanchorX", 0);
  8171. region.viewportAnchorY = settings.get("viewportanchorY", 100);
  8172. region.scroll = settings.get("scroll", ""); // Register the region.
  8173. self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that
  8174. // reference it.
  8175. self.regionList.push({
  8176. id: settings.get("id"),
  8177. region: region
  8178. });
  8179. }
  8180. } // draft-pantos-http-live-streaming-20
  8181. // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
  8182. // 3.5 WebVTT
  8183. function parseTimestampMap(input) {
  8184. var settings = new Settings();
  8185. parseOptions(input, function (k, v) {
  8186. switch (k) {
  8187. case "MPEGT":
  8188. settings.integer(k + 'S', v);
  8189. break;
  8190. case "LOCA":
  8191. settings.set(k + 'L', parseTimeStamp(v));
  8192. break;
  8193. }
  8194. }, /[^\d]:/, /,/);
  8195. self.ontimestampmap && self.ontimestampmap({
  8196. "MPEGTS": settings.get("MPEGTS"),
  8197. "LOCAL": settings.get("LOCAL")
  8198. });
  8199. } // 3.2 WebVTT metadata header syntax
  8200. function parseHeader(input) {
  8201. if (input.match(/X-TIMESTAMP-MAP/)) {
  8202. // This line contains HLS X-TIMESTAMP-MAP metadata
  8203. parseOptions(input, function (k, v) {
  8204. switch (k) {
  8205. case "X-TIMESTAMP-MAP":
  8206. parseTimestampMap(v);
  8207. break;
  8208. }
  8209. }, /=/);
  8210. } else {
  8211. parseOptions(input, function (k, v) {
  8212. switch (k) {
  8213. case "Region":
  8214. // 3.3 WebVTT region metadata header syntax
  8215. parseRegion(v);
  8216. break;
  8217. }
  8218. }, /:/);
  8219. }
  8220. } // 5.1 WebVTT file parsing.
  8221. try {
  8222. var line;
  8223. if (self.state === "INITIAL") {
  8224. // We can't start parsing until we have the first line.
  8225. if (!/\r\n|\n/.test(self.buffer)) {
  8226. return this;
  8227. }
  8228. line = collectNextLine();
  8229. var m = line.match(/^WEBVTT([ \t].*)?$/);
  8230. if (!m || !m[0]) {
  8231. throw new ParsingError(ParsingError.Errors.BadSignature);
  8232. }
  8233. self.state = "HEADER";
  8234. }
  8235. var alreadyCollectedLine = false;
  8236. while (self.buffer) {
  8237. // We can't parse a line until we have the full line.
  8238. if (!/\r\n|\n/.test(self.buffer)) {
  8239. return this;
  8240. }
  8241. if (!alreadyCollectedLine) {
  8242. line = collectNextLine();
  8243. } else {
  8244. alreadyCollectedLine = false;
  8245. }
  8246. switch (self.state) {
  8247. case "HEADER":
  8248. // 13-18 - Allow a header (metadata) under the WEBVTT line.
  8249. if (/:/.test(line)) {
  8250. parseHeader(line);
  8251. } else if (!line) {
  8252. // An empty line terminates the header and starts the body (cues).
  8253. self.state = "ID";
  8254. }
  8255. continue;
  8256. case "NOTE":
  8257. // Ignore NOTE blocks.
  8258. if (!line) {
  8259. self.state = "ID";
  8260. }
  8261. continue;
  8262. case "ID":
  8263. // Check for the start of NOTE blocks.
  8264. if (/^NOTE($|[ \t])/.test(line)) {
  8265. self.state = "NOTE";
  8266. break;
  8267. } // 19-29 - Allow any number of line terminators, then initialize new cue values.
  8268. if (!line) {
  8269. continue;
  8270. }
  8271. self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
  8272. self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data.
  8273. if (line.indexOf("-->") === -1) {
  8274. self.cue.id = line;
  8275. continue;
  8276. }
  8277. // Process line as start of a cue.
  8278. /*falls through*/
  8279. case "CUE":
  8280. // 40 - Collect cue timings and settings.
  8281. try {
  8282. parseCue(line, self.cue, self.regionList);
  8283. } catch (e) {
  8284. self.reportOrThrowError(e); // In case of an error ignore rest of the cue.
  8285. self.cue = null;
  8286. self.state = "BADCUE";
  8287. continue;
  8288. }
  8289. self.state = "CUETEXT";
  8290. continue;
  8291. case "CUETEXT":
  8292. var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue.
  8293. // 35 - If we have the special substring '-->' then report the cue,
  8294. // but do not collect the line as we need to process the current
  8295. // one as a new cue.
  8296. if (!line || hasSubstring && (alreadyCollectedLine = true)) {
  8297. // We are done parsing self cue.
  8298. self.oncue && self.oncue(self.cue);
  8299. self.cue = null;
  8300. self.state = "ID";
  8301. continue;
  8302. }
  8303. if (self.cue.text) {
  8304. self.cue.text += "\n";
  8305. }
  8306. self.cue.text += line;
  8307. continue;
  8308. case "BADCUE":
  8309. // BADCUE
  8310. // 54-62 - Collect and discard the remaining cue.
  8311. if (!line) {
  8312. self.state = "ID";
  8313. }
  8314. continue;
  8315. }
  8316. }
  8317. } catch (e) {
  8318. self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have.
  8319. if (self.state === "CUETEXT" && self.cue && self.oncue) {
  8320. self.oncue(self.cue);
  8321. }
  8322. self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise
  8323. // another exception occurred so enter BADCUE state.
  8324. self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
  8325. }
  8326. return this;
  8327. },
  8328. flush: function flush() {
  8329. var self = this;
  8330. try {
  8331. // Finish decoding the stream.
  8332. self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region.
  8333. if (self.cue || self.state === "HEADER") {
  8334. self.buffer += "\n\n";
  8335. self.parse();
  8336. } // If we've flushed, parsed, and we're still on the INITIAL state then
  8337. // that means we don't have enough of the stream to parse the first
  8338. // line.
  8339. if (self.state === "INITIAL") {
  8340. throw new ParsingError(ParsingError.Errors.BadSignature);
  8341. }
  8342. } catch (e) {
  8343. self.reportOrThrowError(e);
  8344. }
  8345. self.onflush && self.onflush();
  8346. return this;
  8347. }
  8348. };
  8349. var vtt = WebVTT$1;
  8350. /**
  8351. * Copyright 2013 vtt.js Contributors
  8352. *
  8353. * Licensed under the Apache License, Version 2.0 (the "License");
  8354. * you may not use this file except in compliance with the License.
  8355. * You may obtain a copy of the License at
  8356. *
  8357. * http://www.apache.org/licenses/LICENSE-2.0
  8358. *
  8359. * Unless required by applicable law or agreed to in writing, software
  8360. * distributed under the License is distributed on an "AS IS" BASIS,
  8361. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8362. * See the License for the specific language governing permissions and
  8363. * limitations under the License.
  8364. */
  8365. var autoKeyword = "auto";
  8366. var directionSetting = {
  8367. "": 1,
  8368. "lr": 1,
  8369. "rl": 1
  8370. };
  8371. var alignSetting = {
  8372. "start": 1,
  8373. "middle": 1,
  8374. "end": 1,
  8375. "left": 1,
  8376. "right": 1
  8377. };
  8378. function findDirectionSetting(value) {
  8379. if (typeof value !== "string") {
  8380. return false;
  8381. }
  8382. var dir = directionSetting[value.toLowerCase()];
  8383. return dir ? value.toLowerCase() : false;
  8384. }
  8385. function findAlignSetting(value) {
  8386. if (typeof value !== "string") {
  8387. return false;
  8388. }
  8389. var align = alignSetting[value.toLowerCase()];
  8390. return align ? value.toLowerCase() : false;
  8391. }
  8392. function VTTCue(startTime, endTime, text) {
  8393. /**
  8394. * Shim implementation specific properties. These properties are not in
  8395. * the spec.
  8396. */
  8397. // Lets us know when the VTTCue's data has changed in such a way that we need
  8398. // to recompute its display state. This lets us compute its display state
  8399. // lazily.
  8400. this.hasBeenReset = false;
  8401. /**
  8402. * VTTCue and TextTrackCue properties
  8403. * http://dev.w3.org/html5/webvtt/#vttcue-interface
  8404. */
  8405. var _id = "";
  8406. var _pauseOnExit = false;
  8407. var _startTime = startTime;
  8408. var _endTime = endTime;
  8409. var _text = text;
  8410. var _region = null;
  8411. var _vertical = "";
  8412. var _snapToLines = true;
  8413. var _line = "auto";
  8414. var _lineAlign = "start";
  8415. var _position = 50;
  8416. var _positionAlign = "middle";
  8417. var _size = 50;
  8418. var _align = "middle";
  8419. Object.defineProperties(this, {
  8420. "id": {
  8421. enumerable: true,
  8422. get: function get() {
  8423. return _id;
  8424. },
  8425. set: function set(value) {
  8426. _id = "" + value;
  8427. }
  8428. },
  8429. "pauseOnExit": {
  8430. enumerable: true,
  8431. get: function get() {
  8432. return _pauseOnExit;
  8433. },
  8434. set: function set(value) {
  8435. _pauseOnExit = !!value;
  8436. }
  8437. },
  8438. "startTime": {
  8439. enumerable: true,
  8440. get: function get() {
  8441. return _startTime;
  8442. },
  8443. set: function set(value) {
  8444. if (typeof value !== "number") {
  8445. throw new TypeError("Start time must be set to a number.");
  8446. }
  8447. _startTime = value;
  8448. this.hasBeenReset = true;
  8449. }
  8450. },
  8451. "endTime": {
  8452. enumerable: true,
  8453. get: function get() {
  8454. return _endTime;
  8455. },
  8456. set: function set(value) {
  8457. if (typeof value !== "number") {
  8458. throw new TypeError("End time must be set to a number.");
  8459. }
  8460. _endTime = value;
  8461. this.hasBeenReset = true;
  8462. }
  8463. },
  8464. "text": {
  8465. enumerable: true,
  8466. get: function get() {
  8467. return _text;
  8468. },
  8469. set: function set(value) {
  8470. _text = "" + value;
  8471. this.hasBeenReset = true;
  8472. }
  8473. },
  8474. "region": {
  8475. enumerable: true,
  8476. get: function get() {
  8477. return _region;
  8478. },
  8479. set: function set(value) {
  8480. _region = value;
  8481. this.hasBeenReset = true;
  8482. }
  8483. },
  8484. "vertical": {
  8485. enumerable: true,
  8486. get: function get() {
  8487. return _vertical;
  8488. },
  8489. set: function set(value) {
  8490. var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string.
  8491. if (setting === false) {
  8492. throw new SyntaxError("An invalid or illegal string was specified.");
  8493. }
  8494. _vertical = setting;
  8495. this.hasBeenReset = true;
  8496. }
  8497. },
  8498. "snapToLines": {
  8499. enumerable: true,
  8500. get: function get() {
  8501. return _snapToLines;
  8502. },
  8503. set: function set(value) {
  8504. _snapToLines = !!value;
  8505. this.hasBeenReset = true;
  8506. }
  8507. },
  8508. "line": {
  8509. enumerable: true,
  8510. get: function get() {
  8511. return _line;
  8512. },
  8513. set: function set(value) {
  8514. if (typeof value !== "number" && value !== autoKeyword) {
  8515. throw new SyntaxError("An invalid number or illegal string was specified.");
  8516. }
  8517. _line = value;
  8518. this.hasBeenReset = true;
  8519. }
  8520. },
  8521. "lineAlign": {
  8522. enumerable: true,
  8523. get: function get() {
  8524. return _lineAlign;
  8525. },
  8526. set: function set(value) {
  8527. var setting = findAlignSetting(value);
  8528. if (!setting) {
  8529. throw new SyntaxError("An invalid or illegal string was specified.");
  8530. }
  8531. _lineAlign = setting;
  8532. this.hasBeenReset = true;
  8533. }
  8534. },
  8535. "position": {
  8536. enumerable: true,
  8537. get: function get() {
  8538. return _position;
  8539. },
  8540. set: function set(value) {
  8541. if (value < 0 || value > 100) {
  8542. throw new Error("Position must be between 0 and 100.");
  8543. }
  8544. _position = value;
  8545. this.hasBeenReset = true;
  8546. }
  8547. },
  8548. "positionAlign": {
  8549. enumerable: true,
  8550. get: function get() {
  8551. return _positionAlign;
  8552. },
  8553. set: function set(value) {
  8554. var setting = findAlignSetting(value);
  8555. if (!setting) {
  8556. throw new SyntaxError("An invalid or illegal string was specified.");
  8557. }
  8558. _positionAlign = setting;
  8559. this.hasBeenReset = true;
  8560. }
  8561. },
  8562. "size": {
  8563. enumerable: true,
  8564. get: function get() {
  8565. return _size;
  8566. },
  8567. set: function set(value) {
  8568. if (value < 0 || value > 100) {
  8569. throw new Error("Size must be between 0 and 100.");
  8570. }
  8571. _size = value;
  8572. this.hasBeenReset = true;
  8573. }
  8574. },
  8575. "align": {
  8576. enumerable: true,
  8577. get: function get() {
  8578. return _align;
  8579. },
  8580. set: function set(value) {
  8581. var setting = findAlignSetting(value);
  8582. if (!setting) {
  8583. throw new SyntaxError("An invalid or illegal string was specified.");
  8584. }
  8585. _align = setting;
  8586. this.hasBeenReset = true;
  8587. }
  8588. }
  8589. });
  8590. /**
  8591. * Other <track> spec defined properties
  8592. */
  8593. // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
  8594. this.displayState = undefined;
  8595. }
  8596. /**
  8597. * VTTCue methods
  8598. */
  8599. VTTCue.prototype.getCueAsHTML = function () {
  8600. // Assume WebVTT.convertCueToDOMTree is on the global.
  8601. return WebVTT.convertCueToDOMTree(window, this.text);
  8602. };
  8603. var vttcue = VTTCue;
  8604. /**
  8605. * Copyright 2013 vtt.js Contributors
  8606. *
  8607. * Licensed under the Apache License, Version 2.0 (the "License");
  8608. * you may not use this file except in compliance with the License.
  8609. * You may obtain a copy of the License at
  8610. *
  8611. * http://www.apache.org/licenses/LICENSE-2.0
  8612. *
  8613. * Unless required by applicable law or agreed to in writing, software
  8614. * distributed under the License is distributed on an "AS IS" BASIS,
  8615. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8616. * See the License for the specific language governing permissions and
  8617. * limitations under the License.
  8618. */
  8619. var scrollSetting = {
  8620. "": true,
  8621. "up": true
  8622. };
  8623. function findScrollSetting(value) {
  8624. if (typeof value !== "string") {
  8625. return false;
  8626. }
  8627. var scroll = scrollSetting[value.toLowerCase()];
  8628. return scroll ? value.toLowerCase() : false;
  8629. }
  8630. function isValidPercentValue(value) {
  8631. return typeof value === "number" && value >= 0 && value <= 100;
  8632. } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
  8633. function VTTRegion() {
  8634. var _width = 100;
  8635. var _lines = 3;
  8636. var _regionAnchorX = 0;
  8637. var _regionAnchorY = 100;
  8638. var _viewportAnchorX = 0;
  8639. var _viewportAnchorY = 100;
  8640. var _scroll = "";
  8641. Object.defineProperties(this, {
  8642. "width": {
  8643. enumerable: true,
  8644. get: function get() {
  8645. return _width;
  8646. },
  8647. set: function set(value) {
  8648. if (!isValidPercentValue(value)) {
  8649. throw new Error("Width must be between 0 and 100.");
  8650. }
  8651. _width = value;
  8652. }
  8653. },
  8654. "lines": {
  8655. enumerable: true,
  8656. get: function get() {
  8657. return _lines;
  8658. },
  8659. set: function set(value) {
  8660. if (typeof value !== "number") {
  8661. throw new TypeError("Lines must be set to a number.");
  8662. }
  8663. _lines = value;
  8664. }
  8665. },
  8666. "regionAnchorY": {
  8667. enumerable: true,
  8668. get: function get() {
  8669. return _regionAnchorY;
  8670. },
  8671. set: function set(value) {
  8672. if (!isValidPercentValue(value)) {
  8673. throw new Error("RegionAnchorX must be between 0 and 100.");
  8674. }
  8675. _regionAnchorY = value;
  8676. }
  8677. },
  8678. "regionAnchorX": {
  8679. enumerable: true,
  8680. get: function get() {
  8681. return _regionAnchorX;
  8682. },
  8683. set: function set(value) {
  8684. if (!isValidPercentValue(value)) {
  8685. throw new Error("RegionAnchorY must be between 0 and 100.");
  8686. }
  8687. _regionAnchorX = value;
  8688. }
  8689. },
  8690. "viewportAnchorY": {
  8691. enumerable: true,
  8692. get: function get() {
  8693. return _viewportAnchorY;
  8694. },
  8695. set: function set(value) {
  8696. if (!isValidPercentValue(value)) {
  8697. throw new Error("ViewportAnchorY must be between 0 and 100.");
  8698. }
  8699. _viewportAnchorY = value;
  8700. }
  8701. },
  8702. "viewportAnchorX": {
  8703. enumerable: true,
  8704. get: function get() {
  8705. return _viewportAnchorX;
  8706. },
  8707. set: function set(value) {
  8708. if (!isValidPercentValue(value)) {
  8709. throw new Error("ViewportAnchorX must be between 0 and 100.");
  8710. }
  8711. _viewportAnchorX = value;
  8712. }
  8713. },
  8714. "scroll": {
  8715. enumerable: true,
  8716. get: function get() {
  8717. return _scroll;
  8718. },
  8719. set: function set(value) {
  8720. var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value.
  8721. if (setting === false) {
  8722. throw new SyntaxError("An invalid or illegal string was specified.");
  8723. }
  8724. _scroll = setting;
  8725. }
  8726. }
  8727. });
  8728. }
  8729. var vttregion = VTTRegion;
  8730. var browserIndex = createCommonjsModule(function (module) {
  8731. /**
  8732. * Copyright 2013 vtt.js Contributors
  8733. *
  8734. * Licensed under the Apache License, Version 2.0 (the "License");
  8735. * you may not use this file except in compliance with the License.
  8736. * You may obtain a copy of the License at
  8737. *
  8738. * http://www.apache.org/licenses/LICENSE-2.0
  8739. *
  8740. * Unless required by applicable law or agreed to in writing, software
  8741. * distributed under the License is distributed on an "AS IS" BASIS,
  8742. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8743. * See the License for the specific language governing permissions and
  8744. * limitations under the License.
  8745. */
  8746. // Default exports for Node. Export the extended versions of VTTCue and
  8747. // VTTRegion in Node since we likely want the capability to convert back and
  8748. // forth between JSON. If we don't then it's not that big of a deal since we're
  8749. // off browser.
  8750. var vttjs = module.exports = {
  8751. WebVTT: vtt,
  8752. VTTCue: vttcue,
  8753. VTTRegion: vttregion
  8754. };
  8755. window$1.vttjs = vttjs;
  8756. window$1.WebVTT = vttjs.WebVTT;
  8757. var cueShim = vttjs.VTTCue;
  8758. var regionShim = vttjs.VTTRegion;
  8759. var nativeVTTCue = window$1.VTTCue;
  8760. var nativeVTTRegion = window$1.VTTRegion;
  8761. vttjs.shim = function () {
  8762. window$1.VTTCue = cueShim;
  8763. window$1.VTTRegion = regionShim;
  8764. };
  8765. vttjs.restore = function () {
  8766. window$1.VTTCue = nativeVTTCue;
  8767. window$1.VTTRegion = nativeVTTRegion;
  8768. };
  8769. if (!window$1.VTTCue) {
  8770. vttjs.shim();
  8771. }
  8772. });
  8773. var browserIndex_1 = browserIndex.WebVTT;
  8774. var browserIndex_2 = browserIndex.VTTCue;
  8775. var browserIndex_3 = browserIndex.VTTRegion;
  8776. /**
  8777. * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
  8778. * that just contains the src url alone.
  8779. * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
  8780. * `var SourceString = 'http://example.com/some-video.mp4';`
  8781. *
  8782. * @typedef {Object|string} Tech~SourceObject
  8783. *
  8784. * @property {string} src
  8785. * The url to the source
  8786. *
  8787. * @property {string} type
  8788. * The mime type of the source
  8789. */
  8790. /**
  8791. * A function used by {@link Tech} to create a new {@link TextTrack}.
  8792. *
  8793. * @private
  8794. *
  8795. * @param {Tech} self
  8796. * An instance of the Tech class.
  8797. *
  8798. * @param {string} kind
  8799. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  8800. *
  8801. * @param {string} [label]
  8802. * Label to identify the text track
  8803. *
  8804. * @param {string} [language]
  8805. * Two letter language abbreviation
  8806. *
  8807. * @param {Object} [options={}]
  8808. * An object with additional text track options
  8809. *
  8810. * @return {TextTrack}
  8811. * The text track that was created.
  8812. */
  8813. function createTrackHelper(self, kind, label, language, options) {
  8814. if (options === void 0) {
  8815. options = {};
  8816. }
  8817. var tracks = self.textTracks();
  8818. options.kind = kind;
  8819. if (label) {
  8820. options.label = label;
  8821. }
  8822. if (language) {
  8823. options.language = language;
  8824. }
  8825. options.tech = self;
  8826. var track = new ALL.text.TrackClass(options);
  8827. tracks.addTrack(track);
  8828. return track;
  8829. }
  8830. /**
  8831. * This is the base class for media playback technology controllers, such as
  8832. * {@link Flash} and {@link HTML5}
  8833. *
  8834. * @extends Component
  8835. */
  8836. var Tech =
  8837. /*#__PURE__*/
  8838. function (_Component) {
  8839. _inheritsLoose(Tech, _Component);
  8840. /**
  8841. * Create an instance of this Tech.
  8842. *
  8843. * @param {Object} [options]
  8844. * The key/value store of player options.
  8845. *
  8846. * @param {Component~ReadyCallback} ready
  8847. * Callback function to call when the `HTML5` Tech is ready.
  8848. */
  8849. function Tech(options, ready) {
  8850. var _this;
  8851. if (options === void 0) {
  8852. options = {};
  8853. }
  8854. if (ready === void 0) {
  8855. ready = function ready() {};
  8856. }
  8857. // we don't want the tech to report user activity automatically.
  8858. // This is done manually in addControlsListeners
  8859. options.reportTouchActivity = false;
  8860. _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to
  8861. // implement a very limited played()
  8862. _this.hasStarted_ = false;
  8863. _this.on('playing', function () {
  8864. this.hasStarted_ = true;
  8865. });
  8866. _this.on('loadstart', function () {
  8867. this.hasStarted_ = false;
  8868. });
  8869. ALL.names.forEach(function (name) {
  8870. var props = ALL[name];
  8871. if (options && options[props.getterName]) {
  8872. _this[props.privateName] = options[props.getterName];
  8873. }
  8874. }); // Manually track progress in cases where the browser/flash player doesn't report it.
  8875. if (!_this.featuresProgressEvents) {
  8876. _this.manualProgressOn();
  8877. } // Manually track timeupdates in cases where the browser/flash player doesn't report it.
  8878. if (!_this.featuresTimeupdateEvents) {
  8879. _this.manualTimeUpdatesOn();
  8880. }
  8881. ['Text', 'Audio', 'Video'].forEach(function (track) {
  8882. if (options["native" + track + "Tracks"] === false) {
  8883. _this["featuresNative" + track + "Tracks"] = false;
  8884. }
  8885. });
  8886. if (options.nativeCaptions === false || options.nativeTextTracks === false) {
  8887. _this.featuresNativeTextTracks = false;
  8888. } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
  8889. _this.featuresNativeTextTracks = true;
  8890. }
  8891. if (!_this.featuresNativeTextTracks) {
  8892. _this.emulateTextTracks();
  8893. }
  8894. _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
  8895. _this.initTrackListeners(); // Turn on component tap events only if not using native controls
  8896. if (!options.nativeControlsForTouch) {
  8897. _this.emitTapEvents();
  8898. }
  8899. if (_this.constructor) {
  8900. _this.name_ = _this.constructor.name || 'Unknown Tech';
  8901. }
  8902. return _this;
  8903. }
  8904. /**
  8905. * A special function to trigger source set in a way that will allow player
  8906. * to re-trigger if the player or tech are not ready yet.
  8907. *
  8908. * @fires Tech#sourceset
  8909. * @param {string} src The source string at the time of the source changing.
  8910. */
  8911. var _proto = Tech.prototype;
  8912. _proto.triggerSourceset = function triggerSourceset(src) {
  8913. var _this2 = this;
  8914. if (!this.isReady_) {
  8915. // on initial ready we have to trigger source set
  8916. // 1ms after ready so that player can watch for it.
  8917. this.one('ready', function () {
  8918. return _this2.setTimeout(function () {
  8919. return _this2.triggerSourceset(src);
  8920. }, 1);
  8921. });
  8922. }
  8923. /**
  8924. * Fired when the source is set on the tech causing the media element
  8925. * to reload.
  8926. *
  8927. * @see {@link Player#event:sourceset}
  8928. * @event Tech#sourceset
  8929. * @type {EventTarget~Event}
  8930. */
  8931. this.trigger({
  8932. src: src,
  8933. type: 'sourceset'
  8934. });
  8935. }
  8936. /* Fallbacks for unsupported event types
  8937. ================================================================================ */
  8938. /**
  8939. * Polyfill the `progress` event for browsers that don't support it natively.
  8940. *
  8941. * @see {@link Tech#trackProgress}
  8942. */
  8943. ;
  8944. _proto.manualProgressOn = function manualProgressOn() {
  8945. this.on('durationchange', this.onDurationChange);
  8946. this.manualProgress = true; // Trigger progress watching when a source begins loading
  8947. this.one('ready', this.trackProgress);
  8948. }
  8949. /**
  8950. * Turn off the polyfill for `progress` events that was created in
  8951. * {@link Tech#manualProgressOn}
  8952. */
  8953. ;
  8954. _proto.manualProgressOff = function manualProgressOff() {
  8955. this.manualProgress = false;
  8956. this.stopTrackingProgress();
  8957. this.off('durationchange', this.onDurationChange);
  8958. }
  8959. /**
  8960. * This is used to trigger a `progress` event when the buffered percent changes. It
  8961. * sets an interval function that will be called every 500 milliseconds to check if the
  8962. * buffer end percent has changed.
  8963. *
  8964. * > This function is called by {@link Tech#manualProgressOn}
  8965. *
  8966. * @param {EventTarget~Event} event
  8967. * The `ready` event that caused this to run.
  8968. *
  8969. * @listens Tech#ready
  8970. * @fires Tech#progress
  8971. */
  8972. ;
  8973. _proto.trackProgress = function trackProgress(event) {
  8974. this.stopTrackingProgress();
  8975. this.progressInterval = this.setInterval(bind(this, function () {
  8976. // Don't trigger unless buffered amount is greater than last time
  8977. var numBufferedPercent = this.bufferedPercent();
  8978. if (this.bufferedPercent_ !== numBufferedPercent) {
  8979. /**
  8980. * See {@link Player#progress}
  8981. *
  8982. * @event Tech#progress
  8983. * @type {EventTarget~Event}
  8984. */
  8985. this.trigger('progress');
  8986. }
  8987. this.bufferedPercent_ = numBufferedPercent;
  8988. if (numBufferedPercent === 1) {
  8989. this.stopTrackingProgress();
  8990. }
  8991. }), 500);
  8992. }
  8993. /**
  8994. * Update our internal duration on a `durationchange` event by calling
  8995. * {@link Tech#duration}.
  8996. *
  8997. * @param {EventTarget~Event} event
  8998. * The `durationchange` event that caused this to run.
  8999. *
  9000. * @listens Tech#durationchange
  9001. */
  9002. ;
  9003. _proto.onDurationChange = function onDurationChange(event) {
  9004. this.duration_ = this.duration();
  9005. }
  9006. /**
  9007. * Get and create a `TimeRange` object for buffering.
  9008. *
  9009. * @return {TimeRange}
  9010. * The time range object that was created.
  9011. */
  9012. ;
  9013. _proto.buffered = function buffered() {
  9014. return createTimeRanges(0, 0);
  9015. }
  9016. /**
  9017. * Get the percentage of the current video that is currently buffered.
  9018. *
  9019. * @return {number}
  9020. * A number from 0 to 1 that represents the decimal percentage of the
  9021. * video that is buffered.
  9022. *
  9023. */
  9024. ;
  9025. _proto.bufferedPercent = function bufferedPercent$$1() {
  9026. return bufferedPercent(this.buffered(), this.duration_);
  9027. }
  9028. /**
  9029. * Turn off the polyfill for `progress` events that was created in
  9030. * {@link Tech#manualProgressOn}
  9031. * Stop manually tracking progress events by clearing the interval that was set in
  9032. * {@link Tech#trackProgress}.
  9033. */
  9034. ;
  9035. _proto.stopTrackingProgress = function stopTrackingProgress() {
  9036. this.clearInterval(this.progressInterval);
  9037. }
  9038. /**
  9039. * Polyfill the `timeupdate` event for browsers that don't support it.
  9040. *
  9041. * @see {@link Tech#trackCurrentTime}
  9042. */
  9043. ;
  9044. _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
  9045. this.manualTimeUpdates = true;
  9046. this.on('play', this.trackCurrentTime);
  9047. this.on('pause', this.stopTrackingCurrentTime);
  9048. }
  9049. /**
  9050. * Turn off the polyfill for `timeupdate` events that was created in
  9051. * {@link Tech#manualTimeUpdatesOn}
  9052. */
  9053. ;
  9054. _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
  9055. this.manualTimeUpdates = false;
  9056. this.stopTrackingCurrentTime();
  9057. this.off('play', this.trackCurrentTime);
  9058. this.off('pause', this.stopTrackingCurrentTime);
  9059. }
  9060. /**
  9061. * Sets up an interval function to track current time and trigger `timeupdate` every
  9062. * 250 milliseconds.
  9063. *
  9064. * @listens Tech#play
  9065. * @triggers Tech#timeupdate
  9066. */
  9067. ;
  9068. _proto.trackCurrentTime = function trackCurrentTime() {
  9069. if (this.currentTimeInterval) {
  9070. this.stopTrackingCurrentTime();
  9071. }
  9072. this.currentTimeInterval = this.setInterval(function () {
  9073. /**
  9074. * Triggered at an interval of 250ms to indicated that time is passing in the video.
  9075. *
  9076. * @event Tech#timeupdate
  9077. * @type {EventTarget~Event}
  9078. */
  9079. this.trigger({
  9080. type: 'timeupdate',
  9081. target: this,
  9082. manuallyTriggered: true
  9083. }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  9084. }, 250);
  9085. }
  9086. /**
  9087. * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
  9088. * `timeupdate` event is no longer triggered.
  9089. *
  9090. * @listens {Tech#pause}
  9091. */
  9092. ;
  9093. _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
  9094. this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen,
  9095. // the progress bar won't make it all the way to the end
  9096. this.trigger({
  9097. type: 'timeupdate',
  9098. target: this,
  9099. manuallyTriggered: true
  9100. });
  9101. }
  9102. /**
  9103. * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
  9104. * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
  9105. *
  9106. * @fires Component#dispose
  9107. */
  9108. ;
  9109. _proto.dispose = function dispose() {
  9110. // clear out all tracks because we can't reuse them between techs
  9111. this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking
  9112. if (this.manualProgress) {
  9113. this.manualProgressOff();
  9114. }
  9115. if (this.manualTimeUpdates) {
  9116. this.manualTimeUpdatesOff();
  9117. }
  9118. _Component.prototype.dispose.call(this);
  9119. }
  9120. /**
  9121. * Clear out a single `TrackList` or an array of `TrackLists` given their names.
  9122. *
  9123. * > Note: Techs without source handlers should call this between sources for `video`
  9124. * & `audio` tracks. You don't want to use them between tracks!
  9125. *
  9126. * @param {string[]|string} types
  9127. * TrackList names to clear, valid names are `video`, `audio`, and
  9128. * `text`.
  9129. */
  9130. ;
  9131. _proto.clearTracks = function clearTracks(types) {
  9132. var _this3 = this;
  9133. types = [].concat(types); // clear out all tracks because we can't reuse them between techs
  9134. types.forEach(function (type) {
  9135. var list = _this3[type + "Tracks"]() || [];
  9136. var i = list.length;
  9137. while (i--) {
  9138. var track = list[i];
  9139. if (type === 'text') {
  9140. _this3.removeRemoteTextTrack(track);
  9141. }
  9142. list.removeTrack(track);
  9143. }
  9144. });
  9145. }
  9146. /**
  9147. * Remove any TextTracks added via addRemoteTextTrack that are
  9148. * flagged for automatic garbage collection
  9149. */
  9150. ;
  9151. _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
  9152. var list = this.autoRemoteTextTracks_ || [];
  9153. var i = list.length;
  9154. while (i--) {
  9155. var track = list[i];
  9156. this.removeRemoteTextTrack(track);
  9157. }
  9158. }
  9159. /**
  9160. * Reset the tech, which will removes all sources and reset the internal readyState.
  9161. *
  9162. * @abstract
  9163. */
  9164. ;
  9165. _proto.reset = function reset() {}
  9166. /**
  9167. * Get or set an error on the Tech.
  9168. *
  9169. * @param {MediaError} [err]
  9170. * Error to set on the Tech
  9171. *
  9172. * @return {MediaError|null}
  9173. * The current error object on the tech, or null if there isn't one.
  9174. */
  9175. ;
  9176. _proto.error = function error(err) {
  9177. if (err !== undefined) {
  9178. this.error_ = new MediaError(err);
  9179. this.trigger('error');
  9180. }
  9181. return this.error_;
  9182. }
  9183. /**
  9184. * Returns the `TimeRange`s that have been played through for the current source.
  9185. *
  9186. * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
  9187. * It only checks whether the source has played at all or not.
  9188. *
  9189. * @return {TimeRange}
  9190. * - A single time range if this video has played
  9191. * - An empty set of ranges if not.
  9192. */
  9193. ;
  9194. _proto.played = function played() {
  9195. if (this.hasStarted_) {
  9196. return createTimeRanges(0, 0);
  9197. }
  9198. return createTimeRanges();
  9199. }
  9200. /**
  9201. * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
  9202. * previously called.
  9203. *
  9204. * @fires Tech#timeupdate
  9205. */
  9206. ;
  9207. _proto.setCurrentTime = function setCurrentTime() {
  9208. // improve the accuracy of manual timeupdates
  9209. if (this.manualTimeUpdates) {
  9210. /**
  9211. * A manual `timeupdate` event.
  9212. *
  9213. * @event Tech#timeupdate
  9214. * @type {EventTarget~Event}
  9215. */
  9216. this.trigger({
  9217. type: 'timeupdate',
  9218. target: this,
  9219. manuallyTriggered: true
  9220. });
  9221. }
  9222. }
  9223. /**
  9224. * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
  9225. * {@link TextTrackList} events.
  9226. *
  9227. * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`.
  9228. *
  9229. * @fires Tech#audiotrackchange
  9230. * @fires Tech#videotrackchange
  9231. * @fires Tech#texttrackchange
  9232. */
  9233. ;
  9234. _proto.initTrackListeners = function initTrackListeners() {
  9235. var _this4 = this;
  9236. /**
  9237. * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
  9238. *
  9239. * @event Tech#audiotrackchange
  9240. * @type {EventTarget~Event}
  9241. */
  9242. /**
  9243. * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
  9244. *
  9245. * @event Tech#videotrackchange
  9246. * @type {EventTarget~Event}
  9247. */
  9248. /**
  9249. * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
  9250. *
  9251. * @event Tech#texttrackchange
  9252. * @type {EventTarget~Event}
  9253. */
  9254. NORMAL.names.forEach(function (name) {
  9255. var props = NORMAL[name];
  9256. var trackListChanges = function trackListChanges() {
  9257. _this4.trigger(name + "trackchange");
  9258. };
  9259. var tracks = _this4[props.getterName]();
  9260. tracks.addEventListener('removetrack', trackListChanges);
  9261. tracks.addEventListener('addtrack', trackListChanges);
  9262. _this4.on('dispose', function () {
  9263. tracks.removeEventListener('removetrack', trackListChanges);
  9264. tracks.removeEventListener('addtrack', trackListChanges);
  9265. });
  9266. });
  9267. }
  9268. /**
  9269. * Emulate TextTracks using vtt.js if necessary
  9270. *
  9271. * @fires Tech#vttjsloaded
  9272. * @fires Tech#vttjserror
  9273. */
  9274. ;
  9275. _proto.addWebVttScript_ = function addWebVttScript_() {
  9276. var _this5 = this;
  9277. if (window$1.WebVTT) {
  9278. return;
  9279. } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
  9280. // signals that the Tech is ready at which point Tech.el_ is part of the DOM
  9281. // before inserting the WebVTT script
  9282. if (document.body.contains(this.el())) {
  9283. // load via require if available and vtt.js script location was not passed in
  9284. // as an option. novtt builds will turn the above require call into an empty object
  9285. // which will cause this if check to always fail.
  9286. if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
  9287. this.trigger('vttjsloaded');
  9288. return;
  9289. } // load vtt.js via the script location option or the cdn of no location was
  9290. // passed in
  9291. var script = document.createElement('script');
  9292. script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
  9293. script.onload = function () {
  9294. /**
  9295. * Fired when vtt.js is loaded.
  9296. *
  9297. * @event Tech#vttjsloaded
  9298. * @type {EventTarget~Event}
  9299. */
  9300. _this5.trigger('vttjsloaded');
  9301. };
  9302. script.onerror = function () {
  9303. /**
  9304. * Fired when vtt.js was not loaded due to an error
  9305. *
  9306. * @event Tech#vttjsloaded
  9307. * @type {EventTarget~Event}
  9308. */
  9309. _this5.trigger('vttjserror');
  9310. };
  9311. this.on('dispose', function () {
  9312. script.onload = null;
  9313. script.onerror = null;
  9314. }); // but have not loaded yet and we set it to true before the inject so that
  9315. // we don't overwrite the injected window.WebVTT if it loads right away
  9316. window$1.WebVTT = true;
  9317. this.el().parentNode.appendChild(script);
  9318. } else {
  9319. this.ready(this.addWebVttScript_);
  9320. }
  9321. }
  9322. /**
  9323. * Emulate texttracks
  9324. *
  9325. */
  9326. ;
  9327. _proto.emulateTextTracks = function emulateTextTracks() {
  9328. var _this6 = this;
  9329. var tracks = this.textTracks();
  9330. var remoteTracks = this.remoteTextTracks();
  9331. var handleAddTrack = function handleAddTrack(e) {
  9332. return tracks.addTrack(e.track);
  9333. };
  9334. var handleRemoveTrack = function handleRemoveTrack(e) {
  9335. return tracks.removeTrack(e.track);
  9336. };
  9337. remoteTracks.on('addtrack', handleAddTrack);
  9338. remoteTracks.on('removetrack', handleRemoveTrack);
  9339. this.addWebVttScript_();
  9340. var updateDisplay = function updateDisplay() {
  9341. return _this6.trigger('texttrackchange');
  9342. };
  9343. var textTracksChanges = function textTracksChanges() {
  9344. updateDisplay();
  9345. for (var i = 0; i < tracks.length; i++) {
  9346. var track = tracks[i];
  9347. track.removeEventListener('cuechange', updateDisplay);
  9348. if (track.mode === 'showing') {
  9349. track.addEventListener('cuechange', updateDisplay);
  9350. }
  9351. }
  9352. };
  9353. textTracksChanges();
  9354. tracks.addEventListener('change', textTracksChanges);
  9355. tracks.addEventListener('addtrack', textTracksChanges);
  9356. tracks.addEventListener('removetrack', textTracksChanges);
  9357. this.on('dispose', function () {
  9358. remoteTracks.off('addtrack', handleAddTrack);
  9359. remoteTracks.off('removetrack', handleRemoveTrack);
  9360. tracks.removeEventListener('change', textTracksChanges);
  9361. tracks.removeEventListener('addtrack', textTracksChanges);
  9362. tracks.removeEventListener('removetrack', textTracksChanges);
  9363. for (var i = 0; i < tracks.length; i++) {
  9364. var track = tracks[i];
  9365. track.removeEventListener('cuechange', updateDisplay);
  9366. }
  9367. });
  9368. }
  9369. /**
  9370. * Create and returns a remote {@link TextTrack} object.
  9371. *
  9372. * @param {string} kind
  9373. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  9374. *
  9375. * @param {string} [label]
  9376. * Label to identify the text track
  9377. *
  9378. * @param {string} [language]
  9379. * Two letter language abbreviation
  9380. *
  9381. * @return {TextTrack}
  9382. * The TextTrack that gets created.
  9383. */
  9384. ;
  9385. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  9386. if (!kind) {
  9387. throw new Error('TextTrack kind is required but was not provided');
  9388. }
  9389. return createTrackHelper(this, kind, label, language);
  9390. }
  9391. /**
  9392. * Create an emulated TextTrack for use by addRemoteTextTrack
  9393. *
  9394. * This is intended to be overridden by classes that inherit from
  9395. * Tech in order to create native or custom TextTracks.
  9396. *
  9397. * @param {Object} options
  9398. * The object should contain the options to initialize the TextTrack with.
  9399. *
  9400. * @param {string} [options.kind]
  9401. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  9402. *
  9403. * @param {string} [options.label].
  9404. * Label to identify the text track
  9405. *
  9406. * @param {string} [options.language]
  9407. * Two letter language abbreviation.
  9408. *
  9409. * @return {HTMLTrackElement}
  9410. * The track element that gets created.
  9411. */
  9412. ;
  9413. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  9414. var track = mergeOptions(options, {
  9415. tech: this
  9416. });
  9417. return new REMOTE.remoteTextEl.TrackClass(track);
  9418. }
  9419. /**
  9420. * Creates a remote text track object and returns an html track element.
  9421. *
  9422. * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
  9423. *
  9424. * @param {Object} options
  9425. * See {@link Tech#createRemoteTextTrack} for more detailed properties.
  9426. *
  9427. * @param {boolean} [manualCleanup=true]
  9428. * - When false: the TextTrack will be automatically removed from the video
  9429. * element whenever the source changes
  9430. * - When True: The TextTrack will have to be cleaned up manually
  9431. *
  9432. * @return {HTMLTrackElement}
  9433. * An Html Track Element.
  9434. *
  9435. * @deprecated The default functionality for this function will be equivalent
  9436. * to "manualCleanup=false" in the future. The manualCleanup parameter will
  9437. * also be removed.
  9438. */
  9439. ;
  9440. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  9441. var _this7 = this;
  9442. if (options === void 0) {
  9443. options = {};
  9444. }
  9445. var htmlTrackElement = this.createRemoteTextTrack(options);
  9446. if (manualCleanup !== true && manualCleanup !== false) {
  9447. // deprecation warning
  9448. log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
  9449. manualCleanup = true;
  9450. } // store HTMLTrackElement and TextTrack to remote list
  9451. this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
  9452. this.remoteTextTracks().addTrack(htmlTrackElement.track);
  9453. if (manualCleanup !== true) {
  9454. // create the TextTrackList if it doesn't exist
  9455. this.ready(function () {
  9456. return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
  9457. });
  9458. }
  9459. return htmlTrackElement;
  9460. }
  9461. /**
  9462. * Remove a remote text track from the remote `TextTrackList`.
  9463. *
  9464. * @param {TextTrack} track
  9465. * `TextTrack` to remove from the `TextTrackList`
  9466. */
  9467. ;
  9468. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  9469. var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list
  9470. this.remoteTextTrackEls().removeTrackElement_(trackElement);
  9471. this.remoteTextTracks().removeTrack(track);
  9472. this.autoRemoteTextTracks_.removeTrack(track);
  9473. }
  9474. /**
  9475. * Gets available media playback quality metrics as specified by the W3C's Media
  9476. * Playback Quality API.
  9477. *
  9478. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  9479. *
  9480. * @return {Object}
  9481. * An object with supported media playback quality metrics
  9482. *
  9483. * @abstract
  9484. */
  9485. ;
  9486. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  9487. return {};
  9488. }
  9489. /**
  9490. * A method to set a poster from a `Tech`.
  9491. *
  9492. * @abstract
  9493. */
  9494. ;
  9495. _proto.setPoster = function setPoster() {}
  9496. /**
  9497. * A method to check for the presence of the 'playsinline' <video> attribute.
  9498. *
  9499. * @abstract
  9500. */
  9501. ;
  9502. _proto.playsinline = function playsinline() {}
  9503. /**
  9504. * A method to set or unset the 'playsinline' <video> attribute.
  9505. *
  9506. * @abstract
  9507. */
  9508. ;
  9509. _proto.setPlaysinline = function setPlaysinline() {}
  9510. /**
  9511. * Attempt to force override of native audio tracks.
  9512. *
  9513. * @param {boolean} override - If set to true native audio will be overridden,
  9514. * otherwise native audio will potentially be used.
  9515. *
  9516. * @abstract
  9517. */
  9518. ;
  9519. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {}
  9520. /**
  9521. * Attempt to force override of native video tracks.
  9522. *
  9523. * @param {boolean} override - If set to true native video will be overridden,
  9524. * otherwise native video will potentially be used.
  9525. *
  9526. * @abstract
  9527. */
  9528. ;
  9529. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {}
  9530. /*
  9531. * Check if the tech can support the given mime-type.
  9532. *
  9533. * The base tech does not support any type, but source handlers might
  9534. * overwrite this.
  9535. *
  9536. * @param {string} type
  9537. * The mimetype to check for support
  9538. *
  9539. * @return {string}
  9540. * 'probably', 'maybe', or empty string
  9541. *
  9542. * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
  9543. *
  9544. * @abstract
  9545. */
  9546. ;
  9547. _proto.canPlayType = function canPlayType() {
  9548. return '';
  9549. }
  9550. /**
  9551. * Check if the type is supported by this tech.
  9552. *
  9553. * The base tech does not support any type, but source handlers might
  9554. * overwrite this.
  9555. *
  9556. * @param {string} type
  9557. * The media type to check
  9558. * @return {string} Returns the native video element's response
  9559. */
  9560. ;
  9561. Tech.canPlayType = function canPlayType() {
  9562. return '';
  9563. }
  9564. /**
  9565. * Check if the tech can support the given source
  9566. *
  9567. * @param {Object} srcObj
  9568. * The source object
  9569. * @param {Object} options
  9570. * The options passed to the tech
  9571. * @return {string} 'probably', 'maybe', or '' (empty string)
  9572. */
  9573. ;
  9574. Tech.canPlaySource = function canPlaySource(srcObj, options) {
  9575. return Tech.canPlayType(srcObj.type);
  9576. }
  9577. /*
  9578. * Return whether the argument is a Tech or not.
  9579. * Can be passed either a Class like `Html5` or a instance like `player.tech_`
  9580. *
  9581. * @param {Object} component
  9582. * The item to check
  9583. *
  9584. * @return {boolean}
  9585. * Whether it is a tech or not
  9586. * - True if it is a tech
  9587. * - False if it is not
  9588. */
  9589. ;
  9590. Tech.isTech = function isTech(component) {
  9591. return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
  9592. }
  9593. /**
  9594. * Registers a `Tech` into a shared list for videojs.
  9595. *
  9596. * @param {string} name
  9597. * Name of the `Tech` to register.
  9598. *
  9599. * @param {Object} tech
  9600. * The `Tech` class to register.
  9601. */
  9602. ;
  9603. Tech.registerTech = function registerTech(name, tech) {
  9604. if (!Tech.techs_) {
  9605. Tech.techs_ = {};
  9606. }
  9607. if (!Tech.isTech(tech)) {
  9608. throw new Error("Tech " + name + " must be a Tech");
  9609. }
  9610. if (!Tech.canPlayType) {
  9611. throw new Error('Techs must have a static canPlayType method on them');
  9612. }
  9613. if (!Tech.canPlaySource) {
  9614. throw new Error('Techs must have a static canPlaySource method on them');
  9615. }
  9616. name = toTitleCase(name);
  9617. Tech.techs_[name] = tech;
  9618. if (name !== 'Tech') {
  9619. // camel case the techName for use in techOrder
  9620. Tech.defaultTechOrder_.push(name);
  9621. }
  9622. return tech;
  9623. }
  9624. /**
  9625. * Get a `Tech` from the shared list by name.
  9626. *
  9627. * @param {string} name
  9628. * `camelCase` or `TitleCase` name of the Tech to get
  9629. *
  9630. * @return {Tech|undefined}
  9631. * The `Tech` or undefined if there was no tech with the name requested.
  9632. */
  9633. ;
  9634. Tech.getTech = function getTech(name) {
  9635. if (!name) {
  9636. return;
  9637. }
  9638. name = toTitleCase(name);
  9639. if (Tech.techs_ && Tech.techs_[name]) {
  9640. return Tech.techs_[name];
  9641. }
  9642. if (window$1 && window$1.videojs && window$1.videojs[name]) {
  9643. log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)");
  9644. return window$1.videojs[name];
  9645. }
  9646. };
  9647. return Tech;
  9648. }(Component);
  9649. /**
  9650. * Get the {@link VideoTrackList}
  9651. *
  9652. * @returns {VideoTrackList}
  9653. * @method Tech.prototype.videoTracks
  9654. */
  9655. /**
  9656. * Get the {@link AudioTrackList}
  9657. *
  9658. * @returns {AudioTrackList}
  9659. * @method Tech.prototype.audioTracks
  9660. */
  9661. /**
  9662. * Get the {@link TextTrackList}
  9663. *
  9664. * @returns {TextTrackList}
  9665. * @method Tech.prototype.textTracks
  9666. */
  9667. /**
  9668. * Get the remote element {@link TextTrackList}
  9669. *
  9670. * @returns {TextTrackList}
  9671. * @method Tech.prototype.remoteTextTracks
  9672. */
  9673. /**
  9674. * Get the remote element {@link HtmlTrackElementList}
  9675. *
  9676. * @returns {HtmlTrackElementList}
  9677. * @method Tech.prototype.remoteTextTrackEls
  9678. */
  9679. ALL.names.forEach(function (name) {
  9680. var props = ALL[name];
  9681. Tech.prototype[props.getterName] = function () {
  9682. this[props.privateName] = this[props.privateName] || new props.ListClass();
  9683. return this[props.privateName];
  9684. };
  9685. });
  9686. /**
  9687. * List of associated text tracks
  9688. *
  9689. * @type {TextTrackList}
  9690. * @private
  9691. * @property Tech#textTracks_
  9692. */
  9693. /**
  9694. * List of associated audio tracks.
  9695. *
  9696. * @type {AudioTrackList}
  9697. * @private
  9698. * @property Tech#audioTracks_
  9699. */
  9700. /**
  9701. * List of associated video tracks.
  9702. *
  9703. * @type {VideoTrackList}
  9704. * @private
  9705. * @property Tech#videoTracks_
  9706. */
  9707. /**
  9708. * Boolean indicating whether the `Tech` supports volume control.
  9709. *
  9710. * @type {boolean}
  9711. * @default
  9712. */
  9713. Tech.prototype.featuresVolumeControl = true;
  9714. /**
  9715. * Boolean indicating whether the `Tech` supports muting volume.
  9716. *
  9717. * @type {bolean}
  9718. * @default
  9719. */
  9720. Tech.prototype.featuresMuteControl = true;
  9721. /**
  9722. * Boolean indicating whether the `Tech` supports fullscreen resize control.
  9723. * Resizing plugins using request fullscreen reloads the plugin
  9724. *
  9725. * @type {boolean}
  9726. * @default
  9727. */
  9728. Tech.prototype.featuresFullscreenResize = false;
  9729. /**
  9730. * Boolean indicating whether the `Tech` supports changing the speed at which the video
  9731. * plays. Examples:
  9732. * - Set player to play 2x (twice) as fast
  9733. * - Set player to play 0.5x (half) as fast
  9734. *
  9735. * @type {boolean}
  9736. * @default
  9737. */
  9738. Tech.prototype.featuresPlaybackRate = false;
  9739. /**
  9740. * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
  9741. * not triggered by video-js-swf. This will be used to determine if
  9742. * {@link Tech#manualProgressOn} should be called.
  9743. *
  9744. * @type {boolean}
  9745. * @default
  9746. */
  9747. Tech.prototype.featuresProgressEvents = false;
  9748. /**
  9749. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  9750. *
  9751. * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
  9752. * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
  9753. * a new source.
  9754. *
  9755. * @type {boolean}
  9756. * @default
  9757. */
  9758. Tech.prototype.featuresSourceset = false;
  9759. /**
  9760. * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
  9761. * not triggered by video-js-swf. This will be used to determine if
  9762. * {@link Tech#manualTimeUpdates} should be called.
  9763. *
  9764. * @type {boolean}
  9765. * @default
  9766. */
  9767. Tech.prototype.featuresTimeupdateEvents = false;
  9768. /**
  9769. * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
  9770. * This will help us integrate with native `TextTrack`s if the browser supports them.
  9771. *
  9772. * @type {boolean}
  9773. * @default
  9774. */
  9775. Tech.prototype.featuresNativeTextTracks = false;
  9776. /**
  9777. * A functional mixin for techs that want to use the Source Handler pattern.
  9778. * Source handlers are scripts for handling specific formats.
  9779. * The source handler pattern is used for adaptive formats (HLS, DASH) that
  9780. * manually load video data and feed it into a Source Buffer (Media Source Extensions)
  9781. * Example: `Tech.withSourceHandlers.call(MyTech);`
  9782. *
  9783. * @param {Tech} _Tech
  9784. * The tech to add source handler functions to.
  9785. *
  9786. * @mixes Tech~SourceHandlerAdditions
  9787. */
  9788. Tech.withSourceHandlers = function (_Tech) {
  9789. /**
  9790. * Register a source handler
  9791. *
  9792. * @param {Function} handler
  9793. * The source handler class
  9794. *
  9795. * @param {number} [index]
  9796. * Register it at the following index
  9797. */
  9798. _Tech.registerSourceHandler = function (handler, index) {
  9799. var handlers = _Tech.sourceHandlers;
  9800. if (!handlers) {
  9801. handlers = _Tech.sourceHandlers = [];
  9802. }
  9803. if (index === undefined) {
  9804. // add to the end of the list
  9805. index = handlers.length;
  9806. }
  9807. handlers.splice(index, 0, handler);
  9808. };
  9809. /**
  9810. * Check if the tech can support the given type. Also checks the
  9811. * Techs sourceHandlers.
  9812. *
  9813. * @param {string} type
  9814. * The mimetype to check.
  9815. *
  9816. * @return {string}
  9817. * 'probably', 'maybe', or '' (empty string)
  9818. */
  9819. _Tech.canPlayType = function (type) {
  9820. var handlers = _Tech.sourceHandlers || [];
  9821. var can;
  9822. for (var i = 0; i < handlers.length; i++) {
  9823. can = handlers[i].canPlayType(type);
  9824. if (can) {
  9825. return can;
  9826. }
  9827. }
  9828. return '';
  9829. };
  9830. /**
  9831. * Returns the first source handler that supports the source.
  9832. *
  9833. * TODO: Answer question: should 'probably' be prioritized over 'maybe'
  9834. *
  9835. * @param {Tech~SourceObject} source
  9836. * The source object
  9837. *
  9838. * @param {Object} options
  9839. * The options passed to the tech
  9840. *
  9841. * @return {SourceHandler|null}
  9842. * The first source handler that supports the source or null if
  9843. * no SourceHandler supports the source
  9844. */
  9845. _Tech.selectSourceHandler = function (source, options) {
  9846. var handlers = _Tech.sourceHandlers || [];
  9847. var can;
  9848. for (var i = 0; i < handlers.length; i++) {
  9849. can = handlers[i].canHandleSource(source, options);
  9850. if (can) {
  9851. return handlers[i];
  9852. }
  9853. }
  9854. return null;
  9855. };
  9856. /**
  9857. * Check if the tech can support the given source.
  9858. *
  9859. * @param {Tech~SourceObject} srcObj
  9860. * The source object
  9861. *
  9862. * @param {Object} options
  9863. * The options passed to the tech
  9864. *
  9865. * @return {string}
  9866. * 'probably', 'maybe', or '' (empty string)
  9867. */
  9868. _Tech.canPlaySource = function (srcObj, options) {
  9869. var sh = _Tech.selectSourceHandler(srcObj, options);
  9870. if (sh) {
  9871. return sh.canHandleSource(srcObj, options);
  9872. }
  9873. return '';
  9874. };
  9875. /**
  9876. * When using a source handler, prefer its implementation of
  9877. * any function normally provided by the tech.
  9878. */
  9879. var deferrable = ['seekable', 'seeking', 'duration'];
  9880. /**
  9881. * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
  9882. * function if it exists, with a fallback to the Techs seekable function.
  9883. *
  9884. * @method _Tech.seekable
  9885. */
  9886. /**
  9887. * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
  9888. * function if it exists, otherwise it will fallback to the techs duration function.
  9889. *
  9890. * @method _Tech.duration
  9891. */
  9892. deferrable.forEach(function (fnName) {
  9893. var originalFn = this[fnName];
  9894. if (typeof originalFn !== 'function') {
  9895. return;
  9896. }
  9897. this[fnName] = function () {
  9898. if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
  9899. return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
  9900. }
  9901. return originalFn.apply(this, arguments);
  9902. };
  9903. }, _Tech.prototype);
  9904. /**
  9905. * Create a function for setting the source using a source object
  9906. * and source handlers.
  9907. * Should never be called unless a source handler was found.
  9908. *
  9909. * @param {Tech~SourceObject} source
  9910. * A source object with src and type keys
  9911. */
  9912. _Tech.prototype.setSource = function (source) {
  9913. var sh = _Tech.selectSourceHandler(source, this.options_);
  9914. if (!sh) {
  9915. // Fall back to a native source hander when unsupported sources are
  9916. // deliberately set
  9917. if (_Tech.nativeSourceHandler) {
  9918. sh = _Tech.nativeSourceHandler;
  9919. } else {
  9920. log.error('No source handler found for the current source.');
  9921. }
  9922. } // Dispose any existing source handler
  9923. this.disposeSourceHandler();
  9924. this.off('dispose', this.disposeSourceHandler);
  9925. if (sh !== _Tech.nativeSourceHandler) {
  9926. this.currentSource_ = source;
  9927. }
  9928. this.sourceHandler_ = sh.handleSource(source, this, this.options_);
  9929. this.one('dispose', this.disposeSourceHandler);
  9930. };
  9931. /**
  9932. * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
  9933. *
  9934. * @listens Tech#dispose
  9935. */
  9936. _Tech.prototype.disposeSourceHandler = function () {
  9937. // if we have a source and get another one
  9938. // then we are loading something new
  9939. // than clear all of our current tracks
  9940. if (this.currentSource_) {
  9941. this.clearTracks(['audio', 'video']);
  9942. this.currentSource_ = null;
  9943. } // always clean up auto-text tracks
  9944. this.cleanupAutoTextTracks();
  9945. if (this.sourceHandler_) {
  9946. if (this.sourceHandler_.dispose) {
  9947. this.sourceHandler_.dispose();
  9948. }
  9949. this.sourceHandler_ = null;
  9950. }
  9951. };
  9952. }; // The base Tech class needs to be registered as a Component. It is the only
  9953. // Tech that can be registered as a Component.
  9954. Component.registerComponent('Tech', Tech);
  9955. Tech.registerTech('Tech', Tech);
  9956. /**
  9957. * A list of techs that should be added to techOrder on Players
  9958. *
  9959. * @private
  9960. */
  9961. Tech.defaultTechOrder_ = [];
  9962. /**
  9963. * @file middleware.js
  9964. * @module middleware
  9965. */
  9966. var middlewares = {};
  9967. var middlewareInstances = {};
  9968. var TERMINATOR = {};
  9969. /**
  9970. * A middleware object is a plain JavaScript object that has methods that
  9971. * match the {@link Tech} methods found in the lists of allowed
  9972. * {@link module:middleware.allowedGetters|getters},
  9973. * {@link module:middleware.allowedSetters|setters}, and
  9974. * {@link module:middleware.allowedMediators|mediators}.
  9975. *
  9976. * @typedef {Object} MiddlewareObject
  9977. */
  9978. /**
  9979. * A middleware factory function that should return a
  9980. * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
  9981. *
  9982. * This factory will be called for each player when needed, with the player
  9983. * passed in as an argument.
  9984. *
  9985. * @callback MiddlewareFactory
  9986. * @param {Player} player
  9987. * A Video.js player.
  9988. */
  9989. /**
  9990. * Define a middleware that the player should use by way of a factory function
  9991. * that returns a middleware object.
  9992. *
  9993. * @param {string} type
  9994. * The MIME type to match or `"*"` for all MIME types.
  9995. *
  9996. * @param {MiddlewareFactory} middleware
  9997. * A middleware factory function that will be executed for
  9998. * matching types.
  9999. */
  10000. function use(type, middleware) {
  10001. middlewares[type] = middlewares[type] || [];
  10002. middlewares[type].push(middleware);
  10003. }
  10004. /**
  10005. * Asynchronously sets a source using middleware by recursing through any
  10006. * matching middlewares and calling `setSource` on each, passing along the
  10007. * previous returned value each time.
  10008. *
  10009. * @param {Player} player
  10010. * A {@link Player} instance.
  10011. *
  10012. * @param {Tech~SourceObject} src
  10013. * A source object.
  10014. *
  10015. * @param {Function}
  10016. * The next middleware to run.
  10017. */
  10018. function setSource(player, src, next) {
  10019. player.setTimeout(function () {
  10020. return setSourceHelper(src, middlewares[src.type], next, player);
  10021. }, 1);
  10022. }
  10023. /**
  10024. * When the tech is set, passes the tech to each middleware's `setTech` method.
  10025. *
  10026. * @param {Object[]} middleware
  10027. * An array of middleware instances.
  10028. *
  10029. * @param {Tech} tech
  10030. * A Video.js tech.
  10031. */
  10032. function setTech(middleware, tech) {
  10033. middleware.forEach(function (mw) {
  10034. return mw.setTech && mw.setTech(tech);
  10035. });
  10036. }
  10037. /**
  10038. * Calls a getter on the tech first, through each middleware
  10039. * from right to left to the player.
  10040. *
  10041. * @param {Object[]} middleware
  10042. * An array of middleware instances.
  10043. *
  10044. * @param {Tech} tech
  10045. * The current tech.
  10046. *
  10047. * @param {string} method
  10048. * A method name.
  10049. *
  10050. * @return {Mixed}
  10051. * The final value from the tech after middleware has intercepted it.
  10052. */
  10053. function get(middleware, tech, method) {
  10054. return middleware.reduceRight(middlewareIterator(method), tech[method]());
  10055. }
  10056. /**
  10057. * Takes the argument given to the player and calls the setter method on each
  10058. * middleware from left to right to the tech.
  10059. *
  10060. * @param {Object[]} middleware
  10061. * An array of middleware instances.
  10062. *
  10063. * @param {Tech} tech
  10064. * The current tech.
  10065. *
  10066. * @param {string} method
  10067. * A method name.
  10068. *
  10069. * @param {Mixed} arg
  10070. * The value to set on the tech.
  10071. *
  10072. * @return {Mixed}
  10073. * The return value of the `method` of the `tech`.
  10074. */
  10075. function set$1(middleware, tech, method, arg) {
  10076. return tech[method](middleware.reduce(middlewareIterator(method), arg));
  10077. }
  10078. /**
  10079. * Takes the argument given to the player and calls the `call` version of the
  10080. * method on each middleware from left to right.
  10081. *
  10082. * Then, call the passed in method on the tech and return the result unchanged
  10083. * back to the player, through middleware, this time from right to left.
  10084. *
  10085. * @param {Object[]} middleware
  10086. * An array of middleware instances.
  10087. *
  10088. * @param {Tech} tech
  10089. * The current tech.
  10090. *
  10091. * @param {string} method
  10092. * A method name.
  10093. *
  10094. * @param {Mixed} arg
  10095. * The value to set on the tech.
  10096. *
  10097. * @return {Mixed}
  10098. * The return value of the `method` of the `tech`, regardless of the
  10099. * return values of middlewares.
  10100. */
  10101. function mediate(middleware, tech, method, arg) {
  10102. if (arg === void 0) {
  10103. arg = null;
  10104. }
  10105. var callMethod = 'call' + toTitleCase(method);
  10106. var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
  10107. var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to
  10108. // prevent confusion if a techs method actually returns null.
  10109. var returnValue = terminated ? null : tech[method](middlewareValue);
  10110. executeRight(middleware, method, returnValue, terminated);
  10111. return returnValue;
  10112. }
  10113. /**
  10114. * Enumeration of allowed getters where the keys are method names.
  10115. *
  10116. * @type {Object}
  10117. */
  10118. var allowedGetters = {
  10119. buffered: 1,
  10120. currentTime: 1,
  10121. duration: 1,
  10122. seekable: 1,
  10123. played: 1,
  10124. paused: 1,
  10125. volume: 1
  10126. };
  10127. /**
  10128. * Enumeration of allowed setters where the keys are method names.
  10129. *
  10130. * @type {Object}
  10131. */
  10132. var allowedSetters = {
  10133. setCurrentTime: 1,
  10134. setVolume: 1
  10135. };
  10136. /**
  10137. * Enumeration of allowed mediators where the keys are method names.
  10138. *
  10139. * @type {Object}
  10140. */
  10141. var allowedMediators = {
  10142. play: 1,
  10143. pause: 1
  10144. };
  10145. function middlewareIterator(method) {
  10146. return function (value, mw) {
  10147. // if the previous middleware terminated, pass along the termination
  10148. if (value === TERMINATOR) {
  10149. return TERMINATOR;
  10150. }
  10151. if (mw[method]) {
  10152. return mw[method](value);
  10153. }
  10154. return value;
  10155. };
  10156. }
  10157. function executeRight(mws, method, value, terminated) {
  10158. for (var i = mws.length - 1; i >= 0; i--) {
  10159. var mw = mws[i];
  10160. if (mw[method]) {
  10161. mw[method](terminated, value);
  10162. }
  10163. }
  10164. }
  10165. /**
  10166. * Clear the middleware cache for a player.
  10167. *
  10168. * @param {Player} player
  10169. * A {@link Player} instance.
  10170. */
  10171. function clearCacheForPlayer(player) {
  10172. middlewareInstances[player.id()] = null;
  10173. }
  10174. /**
  10175. * {
  10176. * [playerId]: [[mwFactory, mwInstance], ...]
  10177. * }
  10178. *
  10179. * @private
  10180. */
  10181. function getOrCreateFactory(player, mwFactory) {
  10182. var mws = middlewareInstances[player.id()];
  10183. var mw = null;
  10184. if (mws === undefined || mws === null) {
  10185. mw = mwFactory(player);
  10186. middlewareInstances[player.id()] = [[mwFactory, mw]];
  10187. return mw;
  10188. }
  10189. for (var i = 0; i < mws.length; i++) {
  10190. var _mws$i = mws[i],
  10191. mwf = _mws$i[0],
  10192. mwi = _mws$i[1];
  10193. if (mwf !== mwFactory) {
  10194. continue;
  10195. }
  10196. mw = mwi;
  10197. }
  10198. if (mw === null) {
  10199. mw = mwFactory(player);
  10200. mws.push([mwFactory, mw]);
  10201. }
  10202. return mw;
  10203. }
  10204. function setSourceHelper(src, middleware, next, player, acc, lastRun) {
  10205. if (src === void 0) {
  10206. src = {};
  10207. }
  10208. if (middleware === void 0) {
  10209. middleware = [];
  10210. }
  10211. if (acc === void 0) {
  10212. acc = [];
  10213. }
  10214. if (lastRun === void 0) {
  10215. lastRun = false;
  10216. }
  10217. var _middleware = middleware,
  10218. mwFactory = _middleware[0],
  10219. mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road
  10220. if (typeof mwFactory === 'string') {
  10221. setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw,
  10222. // then call the mw's setSource method
  10223. } else if (mwFactory) {
  10224. var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware
  10225. if (!mw.setSource) {
  10226. acc.push(mw);
  10227. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  10228. }
  10229. mw.setSource(assign({}, src), function (err, _src) {
  10230. // something happened, try the next middleware on the current level
  10231. // make sure to use the old src
  10232. if (err) {
  10233. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  10234. } // we've succeeded, now we need to go deeper
  10235. acc.push(mw); // if it's the same type, continue down the current chain
  10236. // otherwise, we want to go down the new chain
  10237. setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
  10238. });
  10239. } else if (mwrest.length) {
  10240. setSourceHelper(src, mwrest, next, player, acc, lastRun);
  10241. } else if (lastRun) {
  10242. next(src, acc);
  10243. } else {
  10244. setSourceHelper(src, middlewares['*'], next, player, acc, true);
  10245. }
  10246. }
  10247. /**
  10248. * Mimetypes
  10249. *
  10250. * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
  10251. * @typedef Mimetypes~Kind
  10252. * @enum
  10253. */
  10254. var MimetypesKind = {
  10255. opus: 'video/ogg',
  10256. ogv: 'video/ogg',
  10257. mp4: 'video/mp4',
  10258. mov: 'video/mp4',
  10259. m4v: 'video/mp4',
  10260. mkv: 'video/x-matroska',
  10261. mp3: 'audio/mpeg',
  10262. aac: 'audio/aac',
  10263. oga: 'audio/ogg',
  10264. m3u8: 'application/x-mpegURL',
  10265. jpg: 'image/jpeg',
  10266. jpeg: 'image/jpeg',
  10267. gif: 'image/gif',
  10268. png: 'image/png',
  10269. svg: 'image/svg+xml',
  10270. webp: 'image/webp'
  10271. };
  10272. /**
  10273. * Get the mimetype of a given src url if possible
  10274. *
  10275. * @param {string} src
  10276. * The url to the src
  10277. *
  10278. * @return {string}
  10279. * return the mimetype if it was known or empty string otherwise
  10280. */
  10281. var getMimetype = function getMimetype(src) {
  10282. if (src === void 0) {
  10283. src = '';
  10284. }
  10285. var ext = getFileExtension(src);
  10286. var mimetype = MimetypesKind[ext.toLowerCase()];
  10287. return mimetype || '';
  10288. };
  10289. /**
  10290. * Find the mime type of a given source string if possible. Uses the player
  10291. * source cache.
  10292. *
  10293. * @param {Player} player
  10294. * The player object
  10295. *
  10296. * @param {string} src
  10297. * The source string
  10298. *
  10299. * @return {string}
  10300. * The type that was found
  10301. */
  10302. var findMimetype = function findMimetype(player, src) {
  10303. if (!src) {
  10304. return '';
  10305. } // 1. check for the type in the `source` cache
  10306. if (player.cache_.source.src === src && player.cache_.source.type) {
  10307. return player.cache_.source.type;
  10308. } // 2. see if we have this source in our `currentSources` cache
  10309. var matchingSources = player.cache_.sources.filter(function (s) {
  10310. return s.src === src;
  10311. });
  10312. if (matchingSources.length) {
  10313. return matchingSources[0].type;
  10314. } // 3. look for the src url in source elements and use the type there
  10315. var sources = player.$$('source');
  10316. for (var i = 0; i < sources.length; i++) {
  10317. var s = sources[i];
  10318. if (s.type && s.src && s.src === src) {
  10319. return s.type;
  10320. }
  10321. } // 4. finally fallback to our list of mime types based on src url extension
  10322. return getMimetype(src);
  10323. };
  10324. /**
  10325. * @module filter-source
  10326. */
  10327. /**
  10328. * Filter out single bad source objects or multiple source objects in an
  10329. * array. Also flattens nested source object arrays into a 1 dimensional
  10330. * array of source objects.
  10331. *
  10332. * @param {Tech~SourceObject|Tech~SourceObject[]} src
  10333. * The src object to filter
  10334. *
  10335. * @return {Tech~SourceObject[]}
  10336. * An array of sourceobjects containing only valid sources
  10337. *
  10338. * @private
  10339. */
  10340. var filterSource = function filterSource(src) {
  10341. // traverse array
  10342. if (Array.isArray(src)) {
  10343. var newsrc = [];
  10344. src.forEach(function (srcobj) {
  10345. srcobj = filterSource(srcobj);
  10346. if (Array.isArray(srcobj)) {
  10347. newsrc = newsrc.concat(srcobj);
  10348. } else if (isObject(srcobj)) {
  10349. newsrc.push(srcobj);
  10350. }
  10351. });
  10352. src = newsrc;
  10353. } else if (typeof src === 'string' && src.trim()) {
  10354. // convert string into object
  10355. src = [fixSource({
  10356. src: src
  10357. })];
  10358. } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
  10359. // src is already valid
  10360. src = [fixSource(src)];
  10361. } else {
  10362. // invalid source, turn it into an empty array
  10363. src = [];
  10364. }
  10365. return src;
  10366. };
  10367. /**
  10368. * Checks src mimetype, adding it when possible
  10369. *
  10370. * @param {Tech~SourceObject} src
  10371. * The src object to check
  10372. * @return {Tech~SourceObject}
  10373. * src Object with known type
  10374. */
  10375. function fixSource(src) {
  10376. var mimetype = getMimetype(src.src);
  10377. if (!src.type && mimetype) {
  10378. src.type = mimetype;
  10379. }
  10380. return src;
  10381. }
  10382. /**
  10383. * The `MediaLoader` is the `Component` that decides which playback technology to load
  10384. * when a player is initialized.
  10385. *
  10386. * @extends Component
  10387. */
  10388. var MediaLoader =
  10389. /*#__PURE__*/
  10390. function (_Component) {
  10391. _inheritsLoose(MediaLoader, _Component);
  10392. /**
  10393. * Create an instance of this class.
  10394. *
  10395. * @param {Player} player
  10396. * The `Player` that this class should attach to.
  10397. *
  10398. * @param {Object} [options]
  10399. * The key/value store of player options.
  10400. *
  10401. * @param {Component~ReadyCallback} [ready]
  10402. * The function that is run when this component is ready.
  10403. */
  10404. function MediaLoader(player, options, ready) {
  10405. var _this;
  10406. // MediaLoader has no element
  10407. var options_ = mergeOptions({
  10408. createEl: false
  10409. }, options);
  10410. _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized,
  10411. // load the first supported playback technology.
  10412. if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
  10413. for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
  10414. var techName = toTitleCase(j[i]);
  10415. var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
  10416. // Remove once that deprecated behavior is removed.
  10417. if (!techName) {
  10418. tech = Component.getComponent(techName);
  10419. } // Check if the browser supports this technology
  10420. if (tech && tech.isSupported()) {
  10421. player.loadTech_(techName);
  10422. break;
  10423. }
  10424. }
  10425. } else {
  10426. // Loop through playback technologies (HTML5, Flash) and check for support.
  10427. // Then load the best source.
  10428. // A few assumptions here:
  10429. // All playback technologies respect preload false.
  10430. player.src(options.playerOptions.sources);
  10431. }
  10432. return _this;
  10433. }
  10434. return MediaLoader;
  10435. }(Component);
  10436. Component.registerComponent('MediaLoader', MediaLoader);
  10437. /**
  10438. * Clickable Component which is clickable or keyboard actionable,
  10439. * but is not a native HTML button.
  10440. *
  10441. * @extends Component
  10442. */
  10443. var ClickableComponent =
  10444. /*#__PURE__*/
  10445. function (_Component) {
  10446. _inheritsLoose(ClickableComponent, _Component);
  10447. /**
  10448. * Creates an instance of this class.
  10449. *
  10450. * @param {Player} player
  10451. * The `Player` that this class should be attached to.
  10452. *
  10453. * @param {Object} [options]
  10454. * The key/value store of player options.
  10455. */
  10456. function ClickableComponent(player, options) {
  10457. var _this;
  10458. _this = _Component.call(this, player, options) || this;
  10459. _this.emitTapEvents();
  10460. _this.enable();
  10461. return _this;
  10462. }
  10463. /**
  10464. * Create the `Component`s DOM element.
  10465. *
  10466. * @param {string} [tag=div]
  10467. * The element's node type.
  10468. *
  10469. * @param {Object} [props={}]
  10470. * An object of properties that should be set on the element.
  10471. *
  10472. * @param {Object} [attributes={}]
  10473. * An object of attributes that should be set on the element.
  10474. *
  10475. * @return {Element}
  10476. * The element that gets created.
  10477. */
  10478. var _proto = ClickableComponent.prototype;
  10479. _proto.createEl = function createEl$$1(tag, props, attributes) {
  10480. if (tag === void 0) {
  10481. tag = 'div';
  10482. }
  10483. if (props === void 0) {
  10484. props = {};
  10485. }
  10486. if (attributes === void 0) {
  10487. attributes = {};
  10488. }
  10489. props = assign({
  10490. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  10491. className: this.buildCSSClass(),
  10492. tabIndex: 0
  10493. }, props);
  10494. if (tag === 'button') {
  10495. log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead.");
  10496. } // Add ARIA attributes for clickable element which is not a native HTML button
  10497. attributes = assign({
  10498. role: 'button'
  10499. }, attributes);
  10500. this.tabIndex_ = props.tabIndex;
  10501. var el = _Component.prototype.createEl.call(this, tag, props, attributes);
  10502. this.createControlTextEl(el);
  10503. return el;
  10504. };
  10505. _proto.dispose = function dispose() {
  10506. // remove controlTextEl_ on dispose
  10507. this.controlTextEl_ = null;
  10508. _Component.prototype.dispose.call(this);
  10509. }
  10510. /**
  10511. * Create a control text element on this `Component`
  10512. *
  10513. * @param {Element} [el]
  10514. * Parent element for the control text.
  10515. *
  10516. * @return {Element}
  10517. * The control text element that gets created.
  10518. */
  10519. ;
  10520. _proto.createControlTextEl = function createControlTextEl(el) {
  10521. this.controlTextEl_ = createEl('span', {
  10522. className: 'vjs-control-text'
  10523. }, {
  10524. // let the screen reader user know that the text of the element may change
  10525. 'aria-live': 'polite'
  10526. });
  10527. if (el) {
  10528. el.appendChild(this.controlTextEl_);
  10529. }
  10530. this.controlText(this.controlText_, el);
  10531. return this.controlTextEl_;
  10532. }
  10533. /**
  10534. * Get or set the localize text to use for the controls on the `Component`.
  10535. *
  10536. * @param {string} [text]
  10537. * Control text for element.
  10538. *
  10539. * @param {Element} [el=this.el()]
  10540. * Element to set the title on.
  10541. *
  10542. * @return {string}
  10543. * - The control text when getting
  10544. */
  10545. ;
  10546. _proto.controlText = function controlText(text, el) {
  10547. if (el === void 0) {
  10548. el = this.el();
  10549. }
  10550. if (text === undefined) {
  10551. return this.controlText_ || 'Need Text';
  10552. }
  10553. var localizedText = this.localize(text);
  10554. this.controlText_ = text;
  10555. textContent(this.controlTextEl_, localizedText);
  10556. if (!this.nonIconControl) {
  10557. // Set title attribute if only an icon is shown
  10558. el.setAttribute('title', localizedText);
  10559. }
  10560. }
  10561. /**
  10562. * Builds the default DOM `className`.
  10563. *
  10564. * @return {string}
  10565. * The DOM `className` for this object.
  10566. */
  10567. ;
  10568. _proto.buildCSSClass = function buildCSSClass() {
  10569. return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this);
  10570. }
  10571. /**
  10572. * Enable this `Component`s element.
  10573. */
  10574. ;
  10575. _proto.enable = function enable() {
  10576. if (!this.enabled_) {
  10577. this.enabled_ = true;
  10578. this.removeClass('vjs-disabled');
  10579. this.el_.setAttribute('aria-disabled', 'false');
  10580. if (typeof this.tabIndex_ !== 'undefined') {
  10581. this.el_.setAttribute('tabIndex', this.tabIndex_);
  10582. }
  10583. this.on(['tap', 'click'], this.handleClick);
  10584. this.on('focus', this.handleFocus);
  10585. this.on('blur', this.handleBlur);
  10586. }
  10587. }
  10588. /**
  10589. * Disable this `Component`s element.
  10590. */
  10591. ;
  10592. _proto.disable = function disable() {
  10593. this.enabled_ = false;
  10594. this.addClass('vjs-disabled');
  10595. this.el_.setAttribute('aria-disabled', 'true');
  10596. if (typeof this.tabIndex_ !== 'undefined') {
  10597. this.el_.removeAttribute('tabIndex');
  10598. }
  10599. this.off(['tap', 'click'], this.handleClick);
  10600. this.off('focus', this.handleFocus);
  10601. this.off('blur', this.handleBlur);
  10602. }
  10603. /**
  10604. * This gets called when a `ClickableComponent` gets:
  10605. * - Clicked (via the `click` event, listening starts in the constructor)
  10606. * - Tapped (via the `tap` event, listening starts in the constructor)
  10607. * - The following things happen in order:
  10608. * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the
  10609. * `ClickableComponent`.
  10610. * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using
  10611. * {@link ClickableComponent#handleKeyPress}.
  10612. * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses
  10613. * the space or enter key.
  10614. * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown`
  10615. * event as a parameter.
  10616. *
  10617. * @param {EventTarget~Event} event
  10618. * The `keydown`, `tap`, or `click` event that caused this function to be
  10619. * called.
  10620. *
  10621. * @listens tap
  10622. * @listens click
  10623. * @abstract
  10624. */
  10625. ;
  10626. _proto.handleClick = function handleClick(event) {}
  10627. /**
  10628. * This gets called when a `ClickableComponent` gains focus via a `focus` event.
  10629. * Turns on listening for `keydown` events. When they happen it
  10630. * calls `this.handleKeyPress`.
  10631. *
  10632. * @param {EventTarget~Event} event
  10633. * The `focus` event that caused this function to be called.
  10634. *
  10635. * @listens focus
  10636. */
  10637. ;
  10638. _proto.handleFocus = function handleFocus(event) {
  10639. on(document, 'keydown', bind(this, this.handleKeyPress));
  10640. }
  10641. /**
  10642. * Called when this ClickableComponent has focus and a key gets pressed down. By
  10643. * default it will call `this.handleClick` when the key is space or enter.
  10644. *
  10645. * @param {EventTarget~Event} event
  10646. * The `keydown` event that caused this function to be called.
  10647. *
  10648. * @listens keydown
  10649. */
  10650. ;
  10651. _proto.handleKeyPress = function handleKeyPress(event) {
  10652. // Support Space or Enter key operation to fire a click event
  10653. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  10654. event.preventDefault();
  10655. this.trigger('click');
  10656. } else {
  10657. // Pass keypress handling up for unsupported keys
  10658. _Component.prototype.handleKeyPress.call(this, event);
  10659. }
  10660. }
  10661. /**
  10662. * Called when a `ClickableComponent` loses focus. Turns off the listener for
  10663. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  10664. *
  10665. * @param {EventTarget~Event} event
  10666. * The `blur` event that caused this function to be called.
  10667. *
  10668. * @listens blur
  10669. */
  10670. ;
  10671. _proto.handleBlur = function handleBlur(event) {
  10672. off(document, 'keydown', bind(this, this.handleKeyPress));
  10673. };
  10674. return ClickableComponent;
  10675. }(Component);
  10676. Component.registerComponent('ClickableComponent', ClickableComponent);
  10677. /**
  10678. * A `ClickableComponent` that handles showing the poster image for the player.
  10679. *
  10680. * @extends ClickableComponent
  10681. */
  10682. var PosterImage =
  10683. /*#__PURE__*/
  10684. function (_ClickableComponent) {
  10685. _inheritsLoose(PosterImage, _ClickableComponent);
  10686. /**
  10687. * Create an instance of this class.
  10688. *
  10689. * @param {Player} player
  10690. * The `Player` that this class should attach to.
  10691. *
  10692. * @param {Object} [options]
  10693. * The key/value store of player options.
  10694. */
  10695. function PosterImage(player, options) {
  10696. var _this;
  10697. _this = _ClickableComponent.call(this, player, options) || this;
  10698. _this.update();
  10699. player.on('posterchange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update));
  10700. return _this;
  10701. }
  10702. /**
  10703. * Clean up and dispose of the `PosterImage`.
  10704. */
  10705. var _proto = PosterImage.prototype;
  10706. _proto.dispose = function dispose() {
  10707. this.player().off('posterchange', this.update);
  10708. _ClickableComponent.prototype.dispose.call(this);
  10709. }
  10710. /**
  10711. * Create the `PosterImage`s DOM element.
  10712. *
  10713. * @return {Element}
  10714. * The element that gets created.
  10715. */
  10716. ;
  10717. _proto.createEl = function createEl$$1() {
  10718. var el = createEl('div', {
  10719. className: 'vjs-poster',
  10720. // Don't want poster to be tabbable.
  10721. tabIndex: -1
  10722. });
  10723. return el;
  10724. }
  10725. /**
  10726. * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
  10727. *
  10728. * @listens Player#posterchange
  10729. *
  10730. * @param {EventTarget~Event} [event]
  10731. * The `Player#posterchange` event that triggered this function.
  10732. */
  10733. ;
  10734. _proto.update = function update(event) {
  10735. var url = this.player().poster();
  10736. this.setSrc(url); // If there's no poster source we should display:none on this component
  10737. // so it's not still clickable or right-clickable
  10738. if (url) {
  10739. this.show();
  10740. } else {
  10741. this.hide();
  10742. }
  10743. }
  10744. /**
  10745. * Set the source of the `PosterImage` depending on the display method.
  10746. *
  10747. * @param {string} url
  10748. * The URL to the source for the `PosterImage`.
  10749. */
  10750. ;
  10751. _proto.setSrc = function setSrc(url) {
  10752. var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise
  10753. // this will throw an extra error
  10754. if (url) {
  10755. backgroundImage = "url(\"" + url + "\")";
  10756. }
  10757. this.el_.style.backgroundImage = backgroundImage;
  10758. }
  10759. /**
  10760. * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
  10761. * {@link ClickableComponent#handleClick} for instances where this will be triggered.
  10762. *
  10763. * @listens tap
  10764. * @listens click
  10765. * @listens keydown
  10766. *
  10767. * @param {EventTarget~Event} event
  10768. + The `click`, `tap` or `keydown` event that caused this function to be called.
  10769. */
  10770. ;
  10771. _proto.handleClick = function handleClick(event) {
  10772. // We don't want a click to trigger playback when controls are disabled
  10773. if (!this.player_.controls()) {
  10774. return;
  10775. }
  10776. if (this.player_.paused()) {
  10777. silencePromise(this.player_.play());
  10778. } else {
  10779. this.player_.pause();
  10780. } // call handleFocus manually to get hotkeys working
  10781. this.player_.handleFocus({});
  10782. };
  10783. return PosterImage;
  10784. }(ClickableComponent);
  10785. Component.registerComponent('PosterImage', PosterImage);
  10786. var darkGray = '#222';
  10787. var lightGray = '#ccc';
  10788. var fontMap = {
  10789. monospace: 'monospace',
  10790. sansSerif: 'sans-serif',
  10791. serif: 'serif',
  10792. monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
  10793. monospaceSerif: '"Courier New", monospace',
  10794. proportionalSansSerif: 'sans-serif',
  10795. proportionalSerif: 'serif',
  10796. casual: '"Comic Sans MS", Impact, fantasy',
  10797. script: '"Monotype Corsiva", cursive',
  10798. smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
  10799. };
  10800. /**
  10801. * Construct an rgba color from a given hex color code.
  10802. *
  10803. * @param {number} color
  10804. * Hex number for color, like #f0e or #f604e2.
  10805. *
  10806. * @param {number} opacity
  10807. * Value for opacity, 0.0 - 1.0.
  10808. *
  10809. * @return {string}
  10810. * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
  10811. */
  10812. function constructColor(color, opacity) {
  10813. var hex;
  10814. if (color.length === 4) {
  10815. // color looks like "#f0e"
  10816. hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
  10817. } else if (color.length === 7) {
  10818. // color looks like "#f604e2"
  10819. hex = color.slice(1);
  10820. } else {
  10821. throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
  10822. }
  10823. return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
  10824. }
  10825. /**
  10826. * Try to update the style of a DOM element. Some style changes will throw an error,
  10827. * particularly in IE8. Those should be noops.
  10828. *
  10829. * @param {Element} el
  10830. * The DOM element to be styled.
  10831. *
  10832. * @param {string} style
  10833. * The CSS property on the element that should be styled.
  10834. *
  10835. * @param {string} rule
  10836. * The style rule that should be applied to the property.
  10837. *
  10838. * @private
  10839. */
  10840. function tryUpdateStyle(el, style, rule) {
  10841. try {
  10842. el.style[style] = rule;
  10843. } catch (e) {
  10844. // Satisfies linter.
  10845. return;
  10846. }
  10847. }
  10848. /**
  10849. * The component for displaying text track cues.
  10850. *
  10851. * @extends Component
  10852. */
  10853. var TextTrackDisplay =
  10854. /*#__PURE__*/
  10855. function (_Component) {
  10856. _inheritsLoose(TextTrackDisplay, _Component);
  10857. /**
  10858. * Creates an instance of this class.
  10859. *
  10860. * @param {Player} player
  10861. * The `Player` that this class should be attached to.
  10862. *
  10863. * @param {Object} [options]
  10864. * The key/value store of player options.
  10865. *
  10866. * @param {Component~ReadyCallback} [ready]
  10867. * The function to call when `TextTrackDisplay` is ready.
  10868. */
  10869. function TextTrackDisplay(player, options, ready) {
  10870. var _this;
  10871. _this = _Component.call(this, player, options, ready) || this;
  10872. var updateDisplayHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay);
  10873. player.on('loadstart', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.toggleDisplay));
  10874. player.on('texttrackchange', updateDisplayHandler);
  10875. player.on('loadedmetadata', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.preselectTrack)); // This used to be called during player init, but was causing an error
  10876. // if a track should show by default and the display hadn't loaded yet.
  10877. // Should probably be moved to an external track loader when we support
  10878. // tracks that don't need a display.
  10879. player.ready(bind(_assertThisInitialized(_assertThisInitialized(_this)), function () {
  10880. if (player.tech_ && player.tech_.featuresNativeTextTracks) {
  10881. this.hide();
  10882. return;
  10883. }
  10884. player.on('fullscreenchange', updateDisplayHandler);
  10885. player.on('playerresize', updateDisplayHandler);
  10886. window$1.addEventListener('orientationchange', updateDisplayHandler);
  10887. player.on('dispose', function () {
  10888. return window$1.removeEventListener('orientationchange', updateDisplayHandler);
  10889. });
  10890. var tracks = this.options_.playerOptions.tracks || [];
  10891. for (var i = 0; i < tracks.length; i++) {
  10892. this.player_.addRemoteTextTrack(tracks[i], true);
  10893. }
  10894. this.preselectTrack();
  10895. }));
  10896. return _this;
  10897. }
  10898. /**
  10899. * Preselect a track following this precedence:
  10900. * - matches the previously selected {@link TextTrack}'s language and kind
  10901. * - matches the previously selected {@link TextTrack}'s language only
  10902. * - is the first default captions track
  10903. * - is the first default descriptions track
  10904. *
  10905. * @listens Player#loadstart
  10906. */
  10907. var _proto = TextTrackDisplay.prototype;
  10908. _proto.preselectTrack = function preselectTrack() {
  10909. var modes = {
  10910. captions: 1,
  10911. subtitles: 1
  10912. };
  10913. var trackList = this.player_.textTracks();
  10914. var userPref = this.player_.cache_.selectedLanguage;
  10915. var firstDesc;
  10916. var firstCaptions;
  10917. var preferredTrack;
  10918. for (var i = 0; i < trackList.length; i++) {
  10919. var track = trackList[i];
  10920. if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
  10921. // Always choose the track that matches both language and kind
  10922. if (track.kind === userPref.kind) {
  10923. preferredTrack = track; // or choose the first track that matches language
  10924. } else if (!preferredTrack) {
  10925. preferredTrack = track;
  10926. } // clear everything if offTextTrackMenuItem was clicked
  10927. } else if (userPref && !userPref.enabled) {
  10928. preferredTrack = null;
  10929. firstDesc = null;
  10930. firstCaptions = null;
  10931. } else if (track.default) {
  10932. if (track.kind === 'descriptions' && !firstDesc) {
  10933. firstDesc = track;
  10934. } else if (track.kind in modes && !firstCaptions) {
  10935. firstCaptions = track;
  10936. }
  10937. }
  10938. } // The preferredTrack matches the user preference and takes
  10939. // precedence over all the other tracks.
  10940. // So, display the preferredTrack before the first default track
  10941. // and the subtitles/captions track before the descriptions track
  10942. if (preferredTrack) {
  10943. preferredTrack.mode = 'showing';
  10944. } else if (firstCaptions) {
  10945. firstCaptions.mode = 'showing';
  10946. } else if (firstDesc) {
  10947. firstDesc.mode = 'showing';
  10948. }
  10949. }
  10950. /**
  10951. * Turn display of {@link TextTrack}'s from the current state into the other state.
  10952. * There are only two states:
  10953. * - 'shown'
  10954. * - 'hidden'
  10955. *
  10956. * @listens Player#loadstart
  10957. */
  10958. ;
  10959. _proto.toggleDisplay = function toggleDisplay() {
  10960. if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
  10961. this.hide();
  10962. } else {
  10963. this.show();
  10964. }
  10965. }
  10966. /**
  10967. * Create the {@link Component}'s DOM element.
  10968. *
  10969. * @return {Element}
  10970. * The element that was created.
  10971. */
  10972. ;
  10973. _proto.createEl = function createEl() {
  10974. return _Component.prototype.createEl.call(this, 'div', {
  10975. className: 'vjs-text-track-display'
  10976. }, {
  10977. 'aria-live': 'off',
  10978. 'aria-atomic': 'true'
  10979. });
  10980. }
  10981. /**
  10982. * Clear all displayed {@link TextTrack}s.
  10983. */
  10984. ;
  10985. _proto.clearDisplay = function clearDisplay() {
  10986. if (typeof window$1.WebVTT === 'function') {
  10987. window$1.WebVTT.processCues(window$1, [], this.el_);
  10988. }
  10989. }
  10990. /**
  10991. * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
  10992. * a {@link Player#fullscreenchange} is fired.
  10993. *
  10994. * @listens Player#texttrackchange
  10995. * @listens Player#fullscreenchange
  10996. */
  10997. ;
  10998. _proto.updateDisplay = function updateDisplay() {
  10999. var tracks = this.player_.textTracks();
  11000. this.clearDisplay(); // Track display prioritization model: if multiple tracks are 'showing',
  11001. // display the first 'subtitles' or 'captions' track which is 'showing',
  11002. // otherwise display the first 'descriptions' track which is 'showing'
  11003. var descriptionsTrack = null;
  11004. var captionsSubtitlesTrack = null;
  11005. var i = tracks.length;
  11006. while (i--) {
  11007. var track = tracks[i];
  11008. if (track.mode === 'showing') {
  11009. if (track.kind === 'descriptions') {
  11010. descriptionsTrack = track;
  11011. } else {
  11012. captionsSubtitlesTrack = track;
  11013. }
  11014. }
  11015. }
  11016. if (captionsSubtitlesTrack) {
  11017. if (this.getAttribute('aria-live') !== 'off') {
  11018. this.setAttribute('aria-live', 'off');
  11019. }
  11020. this.updateForTrack(captionsSubtitlesTrack);
  11021. } else if (descriptionsTrack) {
  11022. if (this.getAttribute('aria-live') !== 'assertive') {
  11023. this.setAttribute('aria-live', 'assertive');
  11024. }
  11025. this.updateForTrack(descriptionsTrack);
  11026. }
  11027. }
  11028. /**
  11029. * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
  11030. *
  11031. * @param {TextTrack} track
  11032. * Text track object to be added to the list.
  11033. */
  11034. ;
  11035. _proto.updateForTrack = function updateForTrack(track) {
  11036. if (typeof window$1.WebVTT !== 'function' || !track.activeCues) {
  11037. return;
  11038. }
  11039. var cues = [];
  11040. for (var _i = 0; _i < track.activeCues.length; _i++) {
  11041. cues.push(track.activeCues[_i]);
  11042. }
  11043. window$1.WebVTT.processCues(window$1, cues, this.el_);
  11044. if (!this.player_.textTrackSettings) {
  11045. return;
  11046. }
  11047. var overrides = this.player_.textTrackSettings.getValues();
  11048. var i = cues.length;
  11049. while (i--) {
  11050. var cue = cues[i];
  11051. if (!cue) {
  11052. continue;
  11053. }
  11054. var cueDiv = cue.displayState;
  11055. if (overrides.color) {
  11056. cueDiv.firstChild.style.color = overrides.color;
  11057. }
  11058. if (overrides.textOpacity) {
  11059. tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
  11060. }
  11061. if (overrides.backgroundColor) {
  11062. cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
  11063. }
  11064. if (overrides.backgroundOpacity) {
  11065. tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
  11066. }
  11067. if (overrides.windowColor) {
  11068. if (overrides.windowOpacity) {
  11069. tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
  11070. } else {
  11071. cueDiv.style.backgroundColor = overrides.windowColor;
  11072. }
  11073. }
  11074. if (overrides.edgeStyle) {
  11075. if (overrides.edgeStyle === 'dropshadow') {
  11076. cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray;
  11077. } else if (overrides.edgeStyle === 'raised') {
  11078. cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray;
  11079. } else if (overrides.edgeStyle === 'depressed') {
  11080. cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray;
  11081. } else if (overrides.edgeStyle === 'uniform') {
  11082. cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray;
  11083. }
  11084. }
  11085. if (overrides.fontPercent && overrides.fontPercent !== 1) {
  11086. var fontSize = window$1.parseFloat(cueDiv.style.fontSize);
  11087. cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
  11088. cueDiv.style.height = 'auto';
  11089. cueDiv.style.top = 'auto';
  11090. cueDiv.style.bottom = '2px';
  11091. }
  11092. if (overrides.fontFamily && overrides.fontFamily !== 'default') {
  11093. if (overrides.fontFamily === 'small-caps') {
  11094. cueDiv.firstChild.style.fontVariant = 'small-caps';
  11095. } else {
  11096. cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
  11097. }
  11098. }
  11099. }
  11100. };
  11101. return TextTrackDisplay;
  11102. }(Component);
  11103. Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
  11104. /**
  11105. * A loading spinner for use during waiting/loading events.
  11106. *
  11107. * @extends Component
  11108. */
  11109. var LoadingSpinner =
  11110. /*#__PURE__*/
  11111. function (_Component) {
  11112. _inheritsLoose(LoadingSpinner, _Component);
  11113. function LoadingSpinner() {
  11114. return _Component.apply(this, arguments) || this;
  11115. }
  11116. var _proto = LoadingSpinner.prototype;
  11117. /**
  11118. * Create the `LoadingSpinner`s DOM element.
  11119. *
  11120. * @return {Element}
  11121. * The dom element that gets created.
  11122. */
  11123. _proto.createEl = function createEl$$1() {
  11124. var isAudio = this.player_.isAudio();
  11125. var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
  11126. var controlText = createEl('span', {
  11127. className: 'vjs-control-text',
  11128. innerHTML: this.localize('{1} is loading.', [playerType])
  11129. });
  11130. var el = _Component.prototype.createEl.call(this, 'div', {
  11131. className: 'vjs-loading-spinner',
  11132. dir: 'ltr'
  11133. });
  11134. el.appendChild(controlText);
  11135. return el;
  11136. };
  11137. return LoadingSpinner;
  11138. }(Component);
  11139. Component.registerComponent('LoadingSpinner', LoadingSpinner);
  11140. /**
  11141. * Base class for all buttons.
  11142. *
  11143. * @extends ClickableComponent
  11144. */
  11145. var Button =
  11146. /*#__PURE__*/
  11147. function (_ClickableComponent) {
  11148. _inheritsLoose(Button, _ClickableComponent);
  11149. function Button() {
  11150. return _ClickableComponent.apply(this, arguments) || this;
  11151. }
  11152. var _proto = Button.prototype;
  11153. /**
  11154. * Create the `Button`s DOM element.
  11155. *
  11156. * @param {string} [tag="button"]
  11157. * The element's node type. This argument is IGNORED: no matter what
  11158. * is passed, it will always create a `button` element.
  11159. *
  11160. * @param {Object} [props={}]
  11161. * An object of properties that should be set on the element.
  11162. *
  11163. * @param {Object} [attributes={}]
  11164. * An object of attributes that should be set on the element.
  11165. *
  11166. * @return {Element}
  11167. * The element that gets created.
  11168. */
  11169. _proto.createEl = function createEl(tag, props, attributes) {
  11170. if (props === void 0) {
  11171. props = {};
  11172. }
  11173. if (attributes === void 0) {
  11174. attributes = {};
  11175. }
  11176. tag = 'button';
  11177. props = assign({
  11178. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  11179. className: this.buildCSSClass()
  11180. }, props); // Add attributes for button element
  11181. attributes = assign({
  11182. // Necessary since the default button type is "submit"
  11183. type: 'button'
  11184. }, attributes);
  11185. var el = Component.prototype.createEl.call(this, tag, props, attributes);
  11186. this.createControlTextEl(el);
  11187. return el;
  11188. }
  11189. /**
  11190. * Add a child `Component` inside of this `Button`.
  11191. *
  11192. * @param {string|Component} child
  11193. * The name or instance of a child to add.
  11194. *
  11195. * @param {Object} [options={}]
  11196. * The key/value store of options that will get passed to children of
  11197. * the child.
  11198. *
  11199. * @return {Component}
  11200. * The `Component` that gets added as a child. When using a string the
  11201. * `Component` will get created by this process.
  11202. *
  11203. * @deprecated since version 5
  11204. */
  11205. ;
  11206. _proto.addChild = function addChild(child, options) {
  11207. if (options === void 0) {
  11208. options = {};
  11209. }
  11210. var className = this.constructor.name;
  11211. log.warn("Adding an actionable (user controllable) child to a Button (" + className + ") is not supported; use a ClickableComponent instead."); // Avoid the error message generated by ClickableComponent's addChild method
  11212. return Component.prototype.addChild.call(this, child, options);
  11213. }
  11214. /**
  11215. * Enable the `Button` element so that it can be activated or clicked. Use this with
  11216. * {@link Button#disable}.
  11217. */
  11218. ;
  11219. _proto.enable = function enable() {
  11220. _ClickableComponent.prototype.enable.call(this);
  11221. this.el_.removeAttribute('disabled');
  11222. }
  11223. /**
  11224. * Disable the `Button` element so that it cannot be activated or clicked. Use this with
  11225. * {@link Button#enable}.
  11226. */
  11227. ;
  11228. _proto.disable = function disable() {
  11229. _ClickableComponent.prototype.disable.call(this);
  11230. this.el_.setAttribute('disabled', 'disabled');
  11231. }
  11232. /**
  11233. * This gets called when a `Button` has focus and `keydown` is triggered via a key
  11234. * press.
  11235. *
  11236. * @param {EventTarget~Event} event
  11237. * The event that caused this function to get called.
  11238. *
  11239. * @listens keydown
  11240. */
  11241. ;
  11242. _proto.handleKeyPress = function handleKeyPress(event) {
  11243. // Ignore Space or Enter key operation, which is handled by the browser for a button.
  11244. if (!(keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter'))) {
  11245. // Pass keypress handling up for unsupported keys
  11246. _ClickableComponent.prototype.handleKeyPress.call(this, event);
  11247. }
  11248. };
  11249. return Button;
  11250. }(ClickableComponent);
  11251. Component.registerComponent('Button', Button);
  11252. /**
  11253. * The initial play button that shows before the video has played. The hiding of the
  11254. * `BigPlayButton` get done via CSS and `Player` states.
  11255. *
  11256. * @extends Button
  11257. */
  11258. var BigPlayButton =
  11259. /*#__PURE__*/
  11260. function (_Button) {
  11261. _inheritsLoose(BigPlayButton, _Button);
  11262. function BigPlayButton(player, options) {
  11263. var _this;
  11264. _this = _Button.call(this, player, options) || this;
  11265. _this.mouseused_ = false;
  11266. _this.on('mousedown', _this.handleMouseDown);
  11267. return _this;
  11268. }
  11269. /**
  11270. * Builds the default DOM `className`.
  11271. *
  11272. * @return {string}
  11273. * The DOM `className` for this object. Always returns 'vjs-big-play-button'.
  11274. */
  11275. var _proto = BigPlayButton.prototype;
  11276. _proto.buildCSSClass = function buildCSSClass() {
  11277. return 'vjs-big-play-button';
  11278. }
  11279. /**
  11280. * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
  11281. * for more detailed information on what a click can be.
  11282. *
  11283. * @param {EventTarget~Event} event
  11284. * The `keydown`, `tap`, or `click` event that caused this function to be
  11285. * called.
  11286. *
  11287. * @listens tap
  11288. * @listens click
  11289. */
  11290. ;
  11291. _proto.handleClick = function handleClick(event) {
  11292. var playPromise = this.player_.play(); // exit early if clicked via the mouse
  11293. if (this.mouseused_ && event.clientX && event.clientY) {
  11294. silencePromise(playPromise); // call handleFocus manually to get hotkeys working
  11295. this.player_.handleFocus({});
  11296. return;
  11297. }
  11298. var cb = this.player_.getChild('controlBar');
  11299. var playToggle = cb && cb.getChild('playToggle');
  11300. if (!playToggle) {
  11301. this.player_.focus();
  11302. return;
  11303. }
  11304. var playFocus = function playFocus() {
  11305. return playToggle.focus();
  11306. };
  11307. if (isPromise(playPromise)) {
  11308. playPromise.then(playFocus, function () {});
  11309. } else {
  11310. this.setTimeout(playFocus, 1);
  11311. }
  11312. };
  11313. _proto.handleKeyPress = function handleKeyPress(event) {
  11314. this.mouseused_ = false;
  11315. _Button.prototype.handleKeyPress.call(this, event);
  11316. };
  11317. _proto.handleMouseDown = function handleMouseDown(event) {
  11318. this.mouseused_ = true;
  11319. };
  11320. return BigPlayButton;
  11321. }(Button);
  11322. /**
  11323. * The text that should display over the `BigPlayButton`s controls. Added to for localization.
  11324. *
  11325. * @type {string}
  11326. * @private
  11327. */
  11328. BigPlayButton.prototype.controlText_ = 'Play Video';
  11329. Component.registerComponent('BigPlayButton', BigPlayButton);
  11330. /**
  11331. * The `CloseButton` is a `{@link Button}` that fires a `close` event when
  11332. * it gets clicked.
  11333. *
  11334. * @extends Button
  11335. */
  11336. var CloseButton =
  11337. /*#__PURE__*/
  11338. function (_Button) {
  11339. _inheritsLoose(CloseButton, _Button);
  11340. /**
  11341. * Creates an instance of the this class.
  11342. *
  11343. * @param {Player} player
  11344. * The `Player` that this class should be attached to.
  11345. *
  11346. * @param {Object} [options]
  11347. * The key/value store of player options.
  11348. */
  11349. function CloseButton(player, options) {
  11350. var _this;
  11351. _this = _Button.call(this, player, options) || this;
  11352. _this.controlText(options && options.controlText || _this.localize('Close'));
  11353. return _this;
  11354. }
  11355. /**
  11356. * Builds the default DOM `className`.
  11357. *
  11358. * @return {string}
  11359. * The DOM `className` for this object.
  11360. */
  11361. var _proto = CloseButton.prototype;
  11362. _proto.buildCSSClass = function buildCSSClass() {
  11363. return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this);
  11364. }
  11365. /**
  11366. * This gets called when a `CloseButton` has focus and `keydown` is triggered via a key
  11367. * press.
  11368. *
  11369. * @param {EventTarget~Event} event
  11370. * The event that caused this function to get called.
  11371. *
  11372. * @listens keydown
  11373. */
  11374. ;
  11375. _proto.handleKeyPress = function handleKeyPress(event) {} // Override the default `Button` behavior, and don't pass the keypress event
  11376. // up to the player because this button is part of a `ModalDialog`, which
  11377. // doesn't pass keypresses to the player either.
  11378. /**
  11379. * This gets called when a `CloseButton` gets clicked. See
  11380. * {@link ClickableComponent#handleClick} for more information on when this will be
  11381. * triggered
  11382. *
  11383. * @param {EventTarget~Event} event
  11384. * The `keydown`, `tap`, or `click` event that caused this function to be
  11385. * called.
  11386. *
  11387. * @listens tap
  11388. * @listens click
  11389. * @fires CloseButton#close
  11390. */
  11391. ;
  11392. _proto.handleClick = function handleClick(event) {
  11393. /**
  11394. * Triggered when the a `CloseButton` is clicked.
  11395. *
  11396. * @event CloseButton#close
  11397. * @type {EventTarget~Event}
  11398. *
  11399. * @property {boolean} [bubbles=false]
  11400. * set to false so that the close event does not
  11401. * bubble up to parents if there is no listener
  11402. */
  11403. this.trigger({
  11404. type: 'close',
  11405. bubbles: false
  11406. });
  11407. };
  11408. return CloseButton;
  11409. }(Button);
  11410. Component.registerComponent('CloseButton', CloseButton);
  11411. /**
  11412. * Button to toggle between play and pause.
  11413. *
  11414. * @extends Button
  11415. */
  11416. var PlayToggle =
  11417. /*#__PURE__*/
  11418. function (_Button) {
  11419. _inheritsLoose(PlayToggle, _Button);
  11420. /**
  11421. * Creates an instance of this class.
  11422. *
  11423. * @param {Player} player
  11424. * The `Player` that this class should be attached to.
  11425. *
  11426. * @param {Object} [options={}]
  11427. * The key/value store of player options.
  11428. */
  11429. function PlayToggle(player, options) {
  11430. var _this;
  11431. if (options === void 0) {
  11432. options = {};
  11433. }
  11434. _this = _Button.call(this, player, options) || this; // show or hide replay icon
  11435. options.replay = options.replay === undefined || options.replay;
  11436. _this.on(player, 'play', _this.handlePlay);
  11437. _this.on(player, 'pause', _this.handlePause);
  11438. if (options.replay) {
  11439. _this.on(player, 'ended', _this.handleEnded);
  11440. }
  11441. return _this;
  11442. }
  11443. /**
  11444. * Builds the default DOM `className`.
  11445. *
  11446. * @return {string}
  11447. * The DOM `className` for this object.
  11448. */
  11449. var _proto = PlayToggle.prototype;
  11450. _proto.buildCSSClass = function buildCSSClass() {
  11451. return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this);
  11452. }
  11453. /**
  11454. * This gets called when an `PlayToggle` is "clicked". See
  11455. * {@link ClickableComponent} for more detailed information on what a click can be.
  11456. *
  11457. * @param {EventTarget~Event} [event]
  11458. * The `keydown`, `tap`, or `click` event that caused this function to be
  11459. * called.
  11460. *
  11461. * @listens tap
  11462. * @listens click
  11463. */
  11464. ;
  11465. _proto.handleClick = function handleClick(event) {
  11466. if (this.player_.paused()) {
  11467. this.player_.play();
  11468. } else {
  11469. this.player_.pause();
  11470. }
  11471. }
  11472. /**
  11473. * This gets called once after the video has ended and the user seeks so that
  11474. * we can change the replay button back to a play button.
  11475. *
  11476. * @param {EventTarget~Event} [event]
  11477. * The event that caused this function to run.
  11478. *
  11479. * @listens Player#seeked
  11480. */
  11481. ;
  11482. _proto.handleSeeked = function handleSeeked(event) {
  11483. this.removeClass('vjs-ended');
  11484. if (this.player_.paused()) {
  11485. this.handlePause(event);
  11486. } else {
  11487. this.handlePlay(event);
  11488. }
  11489. }
  11490. /**
  11491. * Add the vjs-playing class to the element so it can change appearance.
  11492. *
  11493. * @param {EventTarget~Event} [event]
  11494. * The event that caused this function to run.
  11495. *
  11496. * @listens Player#play
  11497. */
  11498. ;
  11499. _proto.handlePlay = function handlePlay(event) {
  11500. this.removeClass('vjs-ended');
  11501. this.removeClass('vjs-paused');
  11502. this.addClass('vjs-playing'); // change the button text to "Pause"
  11503. this.controlText('Pause');
  11504. }
  11505. /**
  11506. * Add the vjs-paused class to the element so it can change appearance.
  11507. *
  11508. * @param {EventTarget~Event} [event]
  11509. * The event that caused this function to run.
  11510. *
  11511. * @listens Player#pause
  11512. */
  11513. ;
  11514. _proto.handlePause = function handlePause(event) {
  11515. this.removeClass('vjs-playing');
  11516. this.addClass('vjs-paused'); // change the button text to "Play"
  11517. this.controlText('Play');
  11518. }
  11519. /**
  11520. * Add the vjs-ended class to the element so it can change appearance
  11521. *
  11522. * @param {EventTarget~Event} [event]
  11523. * The event that caused this function to run.
  11524. *
  11525. * @listens Player#ended
  11526. */
  11527. ;
  11528. _proto.handleEnded = function handleEnded(event) {
  11529. this.removeClass('vjs-playing');
  11530. this.addClass('vjs-ended'); // change the button text to "Replay"
  11531. this.controlText('Replay'); // on the next seek remove the replay button
  11532. this.one(this.player_, 'seeked', this.handleSeeked);
  11533. };
  11534. return PlayToggle;
  11535. }(Button);
  11536. /**
  11537. * The text that should display over the `PlayToggle`s controls. Added for localization.
  11538. *
  11539. * @type {string}
  11540. * @private
  11541. */
  11542. PlayToggle.prototype.controlText_ = 'Play';
  11543. Component.registerComponent('PlayToggle', PlayToggle);
  11544. /**
  11545. * @file format-time.js
  11546. * @module format-time
  11547. */
  11548. /**
  11549. * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
  11550. * seconds) will force a number of leading zeros to cover the length of the
  11551. * guide.
  11552. *
  11553. * @private
  11554. * @param {number} seconds
  11555. * Number of seconds to be turned into a string
  11556. *
  11557. * @param {number} guide
  11558. * Number (in seconds) to model the string after
  11559. *
  11560. * @return {string}
  11561. * Time formatted as H:MM:SS or M:SS
  11562. */
  11563. var defaultImplementation = function defaultImplementation(seconds, guide) {
  11564. seconds = seconds < 0 ? 0 : seconds;
  11565. var s = Math.floor(seconds % 60);
  11566. var m = Math.floor(seconds / 60 % 60);
  11567. var h = Math.floor(seconds / 3600);
  11568. var gm = Math.floor(guide / 60 % 60);
  11569. var gh = Math.floor(guide / 3600); // handle invalid times
  11570. if (isNaN(seconds) || seconds === Infinity) {
  11571. // '-' is false for all relational operators (e.g. <, >=) so this setting
  11572. // will add the minimum number of fields specified by the guide
  11573. h = m = s = '-';
  11574. } // Check if we need to show hours
  11575. h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero.
  11576. // Always show at least one digit of minutes.
  11577. m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds
  11578. s = s < 10 ? '0' + s : s;
  11579. return h + m + s;
  11580. }; // Internal pointer to the current implementation.
  11581. var implementation = defaultImplementation;
  11582. /**
  11583. * Replaces the default formatTime implementation with a custom implementation.
  11584. *
  11585. * @param {Function} customImplementation
  11586. * A function which will be used in place of the default formatTime
  11587. * implementation. Will receive the current time in seconds and the
  11588. * guide (in seconds) as arguments.
  11589. */
  11590. function setFormatTime(customImplementation) {
  11591. implementation = customImplementation;
  11592. }
  11593. /**
  11594. * Resets formatTime to the default implementation.
  11595. */
  11596. function resetFormatTime() {
  11597. implementation = defaultImplementation;
  11598. }
  11599. /**
  11600. * Delegates to either the default time formatting function or a custom
  11601. * function supplied via `setFormatTime`.
  11602. *
  11603. * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
  11604. * guide (in seconds) will force a number of leading zeros to cover the
  11605. * length of the guide.
  11606. *
  11607. * @static
  11608. * @example formatTime(125, 600) === "02:05"
  11609. * @param {number} seconds
  11610. * Number of seconds to be turned into a string
  11611. *
  11612. * @param {number} guide
  11613. * Number (in seconds) to model the string after
  11614. *
  11615. * @return {string}
  11616. * Time formatted as H:MM:SS or M:SS
  11617. */
  11618. function formatTime(seconds, guide) {
  11619. if (guide === void 0) {
  11620. guide = seconds;
  11621. }
  11622. return implementation(seconds, guide);
  11623. }
  11624. /**
  11625. * Displays time information about the video
  11626. *
  11627. * @extends Component
  11628. */
  11629. var TimeDisplay =
  11630. /*#__PURE__*/
  11631. function (_Component) {
  11632. _inheritsLoose(TimeDisplay, _Component);
  11633. /**
  11634. * Creates an instance of this class.
  11635. *
  11636. * @param {Player} player
  11637. * The `Player` that this class should be attached to.
  11638. *
  11639. * @param {Object} [options]
  11640. * The key/value store of player options.
  11641. */
  11642. function TimeDisplay(player, options) {
  11643. var _this;
  11644. _this = _Component.call(this, player, options) || this;
  11645. _this.throttledUpdateContent = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateContent), 25);
  11646. _this.on(player, 'timeupdate', _this.throttledUpdateContent);
  11647. return _this;
  11648. }
  11649. /**
  11650. * Create the `Component`'s DOM element
  11651. *
  11652. * @return {Element}
  11653. * The element that was created.
  11654. */
  11655. var _proto = TimeDisplay.prototype;
  11656. _proto.createEl = function createEl$$1() {
  11657. var className = this.buildCSSClass();
  11658. var el = _Component.prototype.createEl.call(this, 'div', {
  11659. className: className + " vjs-time-control vjs-control",
  11660. innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>"
  11661. });
  11662. this.contentEl_ = createEl('span', {
  11663. className: className + "-display"
  11664. }, {
  11665. // tell screen readers not to automatically read the time as it changes
  11666. 'aria-live': 'off',
  11667. // span elements have no implicit role, but some screen readers (notably VoiceOver)
  11668. // treat them as a break between items in the DOM when using arrow keys
  11669. // (or left-to-right swipes on iOS) to read contents of a page. Using
  11670. // role='presentation' causes VoiceOver to NOT treat this span as a break.
  11671. 'role': 'presentation'
  11672. });
  11673. this.updateTextNode_();
  11674. el.appendChild(this.contentEl_);
  11675. return el;
  11676. };
  11677. _proto.dispose = function dispose() {
  11678. this.contentEl_ = null;
  11679. this.textNode_ = null;
  11680. _Component.prototype.dispose.call(this);
  11681. }
  11682. /**
  11683. * Updates the "remaining time" text node with new content using the
  11684. * contents of the `formattedTime_` property.
  11685. *
  11686. * @private
  11687. */
  11688. ;
  11689. _proto.updateTextNode_ = function updateTextNode_() {
  11690. if (!this.contentEl_) {
  11691. return;
  11692. }
  11693. while (this.contentEl_.firstChild) {
  11694. this.contentEl_.removeChild(this.contentEl_.firstChild);
  11695. }
  11696. this.textNode_ = document.createTextNode(this.formattedTime_ || this.formatTime_(0));
  11697. this.contentEl_.appendChild(this.textNode_);
  11698. }
  11699. /**
  11700. * Generates a formatted time for this component to use in display.
  11701. *
  11702. * @param {number} time
  11703. * A numeric time, in seconds.
  11704. *
  11705. * @return {string}
  11706. * A formatted time
  11707. *
  11708. * @private
  11709. */
  11710. ;
  11711. _proto.formatTime_ = function formatTime_(time) {
  11712. return formatTime(time);
  11713. }
  11714. /**
  11715. * Updates the time display text node if it has what was passed in changed
  11716. * the formatted time.
  11717. *
  11718. * @param {number} time
  11719. * The time to update to
  11720. *
  11721. * @private
  11722. */
  11723. ;
  11724. _proto.updateFormattedTime_ = function updateFormattedTime_(time) {
  11725. var formattedTime = this.formatTime_(time);
  11726. if (formattedTime === this.formattedTime_) {
  11727. return;
  11728. }
  11729. this.formattedTime_ = formattedTime;
  11730. this.requestAnimationFrame(this.updateTextNode_);
  11731. }
  11732. /**
  11733. * To be filled out in the child class, should update the displayed time
  11734. * in accordance with the fact that the current time has changed.
  11735. *
  11736. * @param {EventTarget~Event} [event]
  11737. * The `timeupdate` event that caused this to run.
  11738. *
  11739. * @listens Player#timeupdate
  11740. */
  11741. ;
  11742. _proto.updateContent = function updateContent(event) {};
  11743. return TimeDisplay;
  11744. }(Component);
  11745. /**
  11746. * The text that is added to the `TimeDisplay` for screen reader users.
  11747. *
  11748. * @type {string}
  11749. * @private
  11750. */
  11751. TimeDisplay.prototype.labelText_ = 'Time';
  11752. /**
  11753. * The text that should display over the `TimeDisplay`s controls. Added to for localization.
  11754. *
  11755. * @type {string}
  11756. * @private
  11757. *
  11758. * @deprecated in v7; controlText_ is not used in non-active display Components
  11759. */
  11760. TimeDisplay.prototype.controlText_ = 'Time';
  11761. Component.registerComponent('TimeDisplay', TimeDisplay);
  11762. /**
  11763. * Displays the current time
  11764. *
  11765. * @extends Component
  11766. */
  11767. var CurrentTimeDisplay =
  11768. /*#__PURE__*/
  11769. function (_TimeDisplay) {
  11770. _inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
  11771. /**
  11772. * Creates an instance of this class.
  11773. *
  11774. * @param {Player} player
  11775. * The `Player` that this class should be attached to.
  11776. *
  11777. * @param {Object} [options]
  11778. * The key/value store of player options.
  11779. */
  11780. function CurrentTimeDisplay(player, options) {
  11781. var _this;
  11782. _this = _TimeDisplay.call(this, player, options) || this;
  11783. _this.on(player, 'ended', _this.handleEnded);
  11784. return _this;
  11785. }
  11786. /**
  11787. * Builds the default DOM `className`.
  11788. *
  11789. * @return {string}
  11790. * The DOM `className` for this object.
  11791. */
  11792. var _proto = CurrentTimeDisplay.prototype;
  11793. _proto.buildCSSClass = function buildCSSClass() {
  11794. return 'vjs-current-time';
  11795. }
  11796. /**
  11797. * Update current time display
  11798. *
  11799. * @param {EventTarget~Event} [event]
  11800. * The `timeupdate` event that caused this function to run.
  11801. *
  11802. * @listens Player#timeupdate
  11803. */
  11804. ;
  11805. _proto.updateContent = function updateContent(event) {
  11806. // Allows for smooth scrubbing, when player can't keep up.
  11807. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  11808. this.updateFormattedTime_(time);
  11809. }
  11810. /**
  11811. * When the player fires ended there should be no time left. Sadly
  11812. * this is not always the case, lets make it seem like that is the case
  11813. * for users.
  11814. *
  11815. * @param {EventTarget~Event} [event]
  11816. * The `ended` event that caused this to run.
  11817. *
  11818. * @listens Player#ended
  11819. */
  11820. ;
  11821. _proto.handleEnded = function handleEnded(event) {
  11822. if (!this.player_.duration()) {
  11823. return;
  11824. }
  11825. this.updateFormattedTime_(this.player_.duration());
  11826. };
  11827. return CurrentTimeDisplay;
  11828. }(TimeDisplay);
  11829. /**
  11830. * The text that is added to the `CurrentTimeDisplay` for screen reader users.
  11831. *
  11832. * @type {string}
  11833. * @private
  11834. */
  11835. CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
  11836. /**
  11837. * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
  11838. *
  11839. * @type {string}
  11840. * @private
  11841. *
  11842. * @deprecated in v7; controlText_ is not used in non-active display Components
  11843. */
  11844. CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
  11845. Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
  11846. /**
  11847. * Displays the duration
  11848. *
  11849. * @extends Component
  11850. */
  11851. var DurationDisplay =
  11852. /*#__PURE__*/
  11853. function (_TimeDisplay) {
  11854. _inheritsLoose(DurationDisplay, _TimeDisplay);
  11855. /**
  11856. * Creates an instance of this class.
  11857. *
  11858. * @param {Player} player
  11859. * The `Player` that this class should be attached to.
  11860. *
  11861. * @param {Object} [options]
  11862. * The key/value store of player options.
  11863. */
  11864. function DurationDisplay(player, options) {
  11865. var _this;
  11866. _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes,
  11867. // as they should always display the changed duration as
  11868. // it has changed
  11869. _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded,
  11870. // but the durationchange on the user agent will not fire.
  11871. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  11872. _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
  11873. // listeners could have broken dependent applications/libraries. These
  11874. // can likely be removed for 7.0.
  11875. _this.on(player, 'loadedmetadata', _this.throttledUpdateContent);
  11876. return _this;
  11877. }
  11878. /**
  11879. * Builds the default DOM `className`.
  11880. *
  11881. * @return {string}
  11882. * The DOM `className` for this object.
  11883. */
  11884. var _proto = DurationDisplay.prototype;
  11885. _proto.buildCSSClass = function buildCSSClass() {
  11886. return 'vjs-duration';
  11887. }
  11888. /**
  11889. * Update duration time display.
  11890. *
  11891. * @param {EventTarget~Event} [event]
  11892. * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
  11893. * this function to be called.
  11894. *
  11895. * @listens Player#durationchange
  11896. * @listens Player#timeupdate
  11897. * @listens Player#loadedmetadata
  11898. */
  11899. ;
  11900. _proto.updateContent = function updateContent(event) {
  11901. var duration = this.player_.duration();
  11902. if (this.duration_ !== duration) {
  11903. this.duration_ = duration;
  11904. this.updateFormattedTime_(duration);
  11905. }
  11906. };
  11907. return DurationDisplay;
  11908. }(TimeDisplay);
  11909. /**
  11910. * The text that is added to the `DurationDisplay` for screen reader users.
  11911. *
  11912. * @type {string}
  11913. * @private
  11914. */
  11915. DurationDisplay.prototype.labelText_ = 'Duration';
  11916. /**
  11917. * The text that should display over the `DurationDisplay`s controls. Added to for localization.
  11918. *
  11919. * @type {string}
  11920. * @private
  11921. *
  11922. * @deprecated in v7; controlText_ is not used in non-active display Components
  11923. */
  11924. DurationDisplay.prototype.controlText_ = 'Duration';
  11925. Component.registerComponent('DurationDisplay', DurationDisplay);
  11926. /**
  11927. * The separator between the current time and duration.
  11928. * Can be hidden if it's not needed in the design.
  11929. *
  11930. * @extends Component
  11931. */
  11932. var TimeDivider =
  11933. /*#__PURE__*/
  11934. function (_Component) {
  11935. _inheritsLoose(TimeDivider, _Component);
  11936. function TimeDivider() {
  11937. return _Component.apply(this, arguments) || this;
  11938. }
  11939. var _proto = TimeDivider.prototype;
  11940. /**
  11941. * Create the component's DOM element
  11942. *
  11943. * @return {Element}
  11944. * The element that was created.
  11945. */
  11946. _proto.createEl = function createEl() {
  11947. return _Component.prototype.createEl.call(this, 'div', {
  11948. className: 'vjs-time-control vjs-time-divider',
  11949. innerHTML: '<div><span>/</span></div>'
  11950. }, {
  11951. // this element and its contents can be hidden from assistive techs since
  11952. // it is made extraneous by the announcement of the control text
  11953. // for the current time and duration displays
  11954. 'aria-hidden': true
  11955. });
  11956. };
  11957. return TimeDivider;
  11958. }(Component);
  11959. Component.registerComponent('TimeDivider', TimeDivider);
  11960. /**
  11961. * Displays the time left in the video
  11962. *
  11963. * @extends Component
  11964. */
  11965. var RemainingTimeDisplay =
  11966. /*#__PURE__*/
  11967. function (_TimeDisplay) {
  11968. _inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
  11969. /**
  11970. * Creates an instance of this class.
  11971. *
  11972. * @param {Player} player
  11973. * The `Player` that this class should be attached to.
  11974. *
  11975. * @param {Object} [options]
  11976. * The key/value store of player options.
  11977. */
  11978. function RemainingTimeDisplay(player, options) {
  11979. var _this;
  11980. _this = _TimeDisplay.call(this, player, options) || this;
  11981. _this.on(player, 'durationchange', _this.throttledUpdateContent);
  11982. _this.on(player, 'ended', _this.handleEnded);
  11983. return _this;
  11984. }
  11985. /**
  11986. * Builds the default DOM `className`.
  11987. *
  11988. * @return {string}
  11989. * The DOM `className` for this object.
  11990. */
  11991. var _proto = RemainingTimeDisplay.prototype;
  11992. _proto.buildCSSClass = function buildCSSClass() {
  11993. return 'vjs-remaining-time';
  11994. }
  11995. /**
  11996. * Create the `Component`'s DOM element with the "minus" characted prepend to the time
  11997. *
  11998. * @return {Element}
  11999. * The element that was created.
  12000. */
  12001. ;
  12002. _proto.createEl = function createEl$$1() {
  12003. var el = _TimeDisplay.prototype.createEl.call(this);
  12004. el.insertBefore(createEl('span', {}, {
  12005. 'aria-hidden': true
  12006. }, '-'), this.contentEl_);
  12007. return el;
  12008. }
  12009. /**
  12010. * Update remaining time display.
  12011. *
  12012. * @param {EventTarget~Event} [event]
  12013. * The `timeupdate` or `durationchange` event that caused this to run.
  12014. *
  12015. * @listens Player#timeupdate
  12016. * @listens Player#durationchange
  12017. */
  12018. ;
  12019. _proto.updateContent = function updateContent(event) {
  12020. if (typeof this.player_.duration() !== 'number') {
  12021. return;
  12022. } // @deprecated We should only use remainingTimeDisplay
  12023. // as of video.js 7
  12024. if (this.player_.remainingTimeDisplay) {
  12025. this.updateFormattedTime_(this.player_.remainingTimeDisplay());
  12026. } else {
  12027. this.updateFormattedTime_(this.player_.remainingTime());
  12028. }
  12029. }
  12030. /**
  12031. * When the player fires ended there should be no time left. Sadly
  12032. * this is not always the case, lets make it seem like that is the case
  12033. * for users.
  12034. *
  12035. * @param {EventTarget~Event} [event]
  12036. * The `ended` event that caused this to run.
  12037. *
  12038. * @listens Player#ended
  12039. */
  12040. ;
  12041. _proto.handleEnded = function handleEnded(event) {
  12042. if (!this.player_.duration()) {
  12043. return;
  12044. }
  12045. this.updateFormattedTime_(0);
  12046. };
  12047. return RemainingTimeDisplay;
  12048. }(TimeDisplay);
  12049. /**
  12050. * The text that is added to the `RemainingTimeDisplay` for screen reader users.
  12051. *
  12052. * @type {string}
  12053. * @private
  12054. */
  12055. RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
  12056. /**
  12057. * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
  12058. *
  12059. * @type {string}
  12060. * @private
  12061. *
  12062. * @deprecated in v7; controlText_ is not used in non-active display Components
  12063. */
  12064. RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
  12065. Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
  12066. /**
  12067. * Displays the live indicator when duration is Infinity.
  12068. *
  12069. * @extends Component
  12070. */
  12071. var LiveDisplay =
  12072. /*#__PURE__*/
  12073. function (_Component) {
  12074. _inheritsLoose(LiveDisplay, _Component);
  12075. /**
  12076. * Creates an instance of this class.
  12077. *
  12078. * @param {Player} player
  12079. * The `Player` that this class should be attached to.
  12080. *
  12081. * @param {Object} [options]
  12082. * The key/value store of player options.
  12083. */
  12084. function LiveDisplay(player, options) {
  12085. var _this;
  12086. _this = _Component.call(this, player, options) || this;
  12087. _this.updateShowing();
  12088. _this.on(_this.player(), 'durationchange', _this.updateShowing);
  12089. return _this;
  12090. }
  12091. /**
  12092. * Create the `Component`'s DOM element
  12093. *
  12094. * @return {Element}
  12095. * The element that was created.
  12096. */
  12097. var _proto = LiveDisplay.prototype;
  12098. _proto.createEl = function createEl$$1() {
  12099. var el = _Component.prototype.createEl.call(this, 'div', {
  12100. className: 'vjs-live-control vjs-control'
  12101. });
  12102. this.contentEl_ = createEl('div', {
  12103. className: 'vjs-live-display',
  12104. innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE')
  12105. }, {
  12106. 'aria-live': 'off'
  12107. });
  12108. el.appendChild(this.contentEl_);
  12109. return el;
  12110. };
  12111. _proto.dispose = function dispose() {
  12112. this.contentEl_ = null;
  12113. _Component.prototype.dispose.call(this);
  12114. }
  12115. /**
  12116. * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
  12117. * it accordingly
  12118. *
  12119. * @param {EventTarget~Event} [event]
  12120. * The {@link Player#durationchange} event that caused this function to run.
  12121. *
  12122. * @listens Player#durationchange
  12123. */
  12124. ;
  12125. _proto.updateShowing = function updateShowing(event) {
  12126. if (this.player().duration() === Infinity) {
  12127. this.show();
  12128. } else {
  12129. this.hide();
  12130. }
  12131. };
  12132. return LiveDisplay;
  12133. }(Component);
  12134. Component.registerComponent('LiveDisplay', LiveDisplay);
  12135. /**
  12136. * Displays the live indicator when duration is Infinity.
  12137. *
  12138. * @extends Component
  12139. */
  12140. var SeekToLive =
  12141. /*#__PURE__*/
  12142. function (_Button) {
  12143. _inheritsLoose(SeekToLive, _Button);
  12144. /**
  12145. * Creates an instance of this class.
  12146. *
  12147. * @param {Player} player
  12148. * The `Player` that this class should be attached to.
  12149. *
  12150. * @param {Object} [options]
  12151. * The key/value store of player options.
  12152. */
  12153. function SeekToLive(player, options) {
  12154. var _this;
  12155. _this = _Button.call(this, player, options) || this;
  12156. _this.updateLiveEdgeStatus();
  12157. if (_this.player_.liveTracker) {
  12158. _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus);
  12159. }
  12160. return _this;
  12161. }
  12162. /**
  12163. * Create the `Component`'s DOM element
  12164. *
  12165. * @return {Element}
  12166. * The element that was created.
  12167. */
  12168. var _proto = SeekToLive.prototype;
  12169. _proto.createEl = function createEl$$1() {
  12170. var el = _Button.prototype.createEl.call(this, 'button', {
  12171. className: 'vjs-seek-to-live-control vjs-control'
  12172. });
  12173. this.textEl_ = createEl('span', {
  12174. className: 'vjs-seek-to-live-text',
  12175. innerHTML: this.localize('LIVE')
  12176. }, {
  12177. 'aria-hidden': 'true'
  12178. });
  12179. el.appendChild(this.textEl_);
  12180. return el;
  12181. }
  12182. /**
  12183. * Update the state of this button if we are at the live edge
  12184. * or not
  12185. */
  12186. ;
  12187. _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus(e) {
  12188. // default to live edge
  12189. if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
  12190. this.setAttribute('aria-disabled', true);
  12191. this.addClass('vjs-at-live-edge');
  12192. this.controlText('Seek to live, currently playing live');
  12193. } else {
  12194. this.setAttribute('aria-disabled', false);
  12195. this.removeClass('vjs-at-live-edge');
  12196. this.controlText('Seek to live, currently behind live');
  12197. }
  12198. }
  12199. /**
  12200. * On click bring us as near to the live point as possible.
  12201. * This requires that we wait for the next `live-seekable-change`
  12202. * event which will happen every segment length seconds.
  12203. */
  12204. ;
  12205. _proto.handleClick = function handleClick() {
  12206. this.player_.liveTracker.seekToLiveEdge();
  12207. }
  12208. /**
  12209. * Dispose of the element and stop tracking
  12210. */
  12211. ;
  12212. _proto.dispose = function dispose() {
  12213. if (this.player_.liveTracker) {
  12214. this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus);
  12215. }
  12216. this.textEl_ = null;
  12217. _Button.prototype.dispose.call(this);
  12218. };
  12219. return SeekToLive;
  12220. }(Button);
  12221. SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
  12222. Component.registerComponent('SeekToLive', SeekToLive);
  12223. /**
  12224. * The base functionality for a slider. Can be vertical or horizontal.
  12225. * For instance the volume bar or the seek bar on a video is a slider.
  12226. *
  12227. * @extends Component
  12228. */
  12229. var Slider =
  12230. /*#__PURE__*/
  12231. function (_Component) {
  12232. _inheritsLoose(Slider, _Component);
  12233. /**
  12234. * Create an instance of this class
  12235. *
  12236. * @param {Player} player
  12237. * The `Player` that this class should be attached to.
  12238. *
  12239. * @param {Object} [options]
  12240. * The key/value store of player options.
  12241. */
  12242. function Slider(player, options) {
  12243. var _this;
  12244. _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for
  12245. _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type
  12246. _this.vertical(!!_this.options_.vertical);
  12247. _this.enable();
  12248. return _this;
  12249. }
  12250. /**
  12251. * Are controls are currently enabled for this slider or not.
  12252. *
  12253. * @return {boolean}
  12254. * true if controls are enabled, false otherwise
  12255. */
  12256. var _proto = Slider.prototype;
  12257. _proto.enabled = function enabled() {
  12258. return this.enabled_;
  12259. }
  12260. /**
  12261. * Enable controls for this slider if they are disabled
  12262. */
  12263. ;
  12264. _proto.enable = function enable() {
  12265. if (this.enabled()) {
  12266. return;
  12267. }
  12268. this.on('mousedown', this.handleMouseDown);
  12269. this.on('touchstart', this.handleMouseDown);
  12270. this.on('focus', this.handleFocus);
  12271. this.on('blur', this.handleBlur);
  12272. this.on('click', this.handleClick);
  12273. this.on(this.player_, 'controlsvisible', this.update);
  12274. if (this.playerEvent) {
  12275. this.on(this.player_, this.playerEvent, this.update);
  12276. }
  12277. this.removeClass('disabled');
  12278. this.setAttribute('tabindex', 0);
  12279. this.enabled_ = true;
  12280. }
  12281. /**
  12282. * Disable controls for this slider if they are enabled
  12283. */
  12284. ;
  12285. _proto.disable = function disable() {
  12286. if (!this.enabled()) {
  12287. return;
  12288. }
  12289. var doc = this.bar.el_.ownerDocument;
  12290. this.off('mousedown', this.handleMouseDown);
  12291. this.off('touchstart', this.handleMouseDown);
  12292. this.off('focus', this.handleFocus);
  12293. this.off('blur', this.handleBlur);
  12294. this.off('click', this.handleClick);
  12295. this.off(this.player_, 'controlsvisible', this.update);
  12296. this.off(doc, 'mousemove', this.handleMouseMove);
  12297. this.off(doc, 'mouseup', this.handleMouseUp);
  12298. this.off(doc, 'touchmove', this.handleMouseMove);
  12299. this.off(doc, 'touchend', this.handleMouseUp);
  12300. this.removeAttribute('tabindex');
  12301. this.addClass('disabled');
  12302. if (this.playerEvent) {
  12303. this.off(this.player_, this.playerEvent, this.update);
  12304. }
  12305. this.enabled_ = false;
  12306. }
  12307. /**
  12308. * Create the `Slider`s DOM element.
  12309. *
  12310. * @param {string} type
  12311. * Type of element to create.
  12312. *
  12313. * @param {Object} [props={}]
  12314. * List of properties in Object form.
  12315. *
  12316. * @param {Object} [attributes={}]
  12317. * list of attributes in Object form.
  12318. *
  12319. * @return {Element}
  12320. * The element that gets created.
  12321. */
  12322. ;
  12323. _proto.createEl = function createEl$$1(type, props, attributes) {
  12324. if (props === void 0) {
  12325. props = {};
  12326. }
  12327. if (attributes === void 0) {
  12328. attributes = {};
  12329. }
  12330. // Add the slider element class to all sub classes
  12331. props.className = props.className + ' vjs-slider';
  12332. props = assign({
  12333. tabIndex: 0
  12334. }, props);
  12335. attributes = assign({
  12336. 'role': 'slider',
  12337. 'aria-valuenow': 0,
  12338. 'aria-valuemin': 0,
  12339. 'aria-valuemax': 100,
  12340. 'tabIndex': 0
  12341. }, attributes);
  12342. return _Component.prototype.createEl.call(this, type, props, attributes);
  12343. }
  12344. /**
  12345. * Handle `mousedown` or `touchstart` events on the `Slider`.
  12346. *
  12347. * @param {EventTarget~Event} event
  12348. * `mousedown` or `touchstart` event that triggered this function
  12349. *
  12350. * @listens mousedown
  12351. * @listens touchstart
  12352. * @fires Slider#slideractive
  12353. */
  12354. ;
  12355. _proto.handleMouseDown = function handleMouseDown(event) {
  12356. var doc = this.bar.el_.ownerDocument;
  12357. if (event.type === 'mousedown') {
  12358. event.preventDefault();
  12359. } // Do not call preventDefault() on touchstart in Chrome
  12360. // to avoid console warnings. Use a 'touch-action: none' style
  12361. // instead to prevent unintented scrolling.
  12362. // https://developers.google.com/web/updates/2017/01/scrolling-intervention
  12363. if (event.type === 'touchstart' && !IS_CHROME) {
  12364. event.preventDefault();
  12365. }
  12366. blockTextSelection();
  12367. this.addClass('vjs-sliding');
  12368. /**
  12369. * Triggered when the slider is in an active state
  12370. *
  12371. * @event Slider#slideractive
  12372. * @type {EventTarget~Event}
  12373. */
  12374. this.trigger('slideractive');
  12375. this.on(doc, 'mousemove', this.handleMouseMove);
  12376. this.on(doc, 'mouseup', this.handleMouseUp);
  12377. this.on(doc, 'touchmove', this.handleMouseMove);
  12378. this.on(doc, 'touchend', this.handleMouseUp);
  12379. this.handleMouseMove(event);
  12380. }
  12381. /**
  12382. * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
  12383. * The `mousemove` and `touchmove` events will only only trigger this function during
  12384. * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
  12385. * {@link Slider#handleMouseUp}.
  12386. *
  12387. * @param {EventTarget~Event} event
  12388. * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
  12389. * this function
  12390. *
  12391. * @listens mousemove
  12392. * @listens touchmove
  12393. */
  12394. ;
  12395. _proto.handleMouseMove = function handleMouseMove(event) {}
  12396. /**
  12397. * Handle `mouseup` or `touchend` events on the `Slider`.
  12398. *
  12399. * @param {EventTarget~Event} event
  12400. * `mouseup` or `touchend` event that triggered this function.
  12401. *
  12402. * @listens touchend
  12403. * @listens mouseup
  12404. * @fires Slider#sliderinactive
  12405. */
  12406. ;
  12407. _proto.handleMouseUp = function handleMouseUp() {
  12408. var doc = this.bar.el_.ownerDocument;
  12409. unblockTextSelection();
  12410. this.removeClass('vjs-sliding');
  12411. /**
  12412. * Triggered when the slider is no longer in an active state.
  12413. *
  12414. * @event Slider#sliderinactive
  12415. * @type {EventTarget~Event}
  12416. */
  12417. this.trigger('sliderinactive');
  12418. this.off(doc, 'mousemove', this.handleMouseMove);
  12419. this.off(doc, 'mouseup', this.handleMouseUp);
  12420. this.off(doc, 'touchmove', this.handleMouseMove);
  12421. this.off(doc, 'touchend', this.handleMouseUp);
  12422. this.update();
  12423. }
  12424. /**
  12425. * Update the progress bar of the `Slider`.
  12426. *
  12427. * @return {number}
  12428. * The percentage of progress the progress bar represents as a
  12429. * number from 0 to 1.
  12430. */
  12431. ;
  12432. _proto.update = function update() {
  12433. // In VolumeBar init we have a setTimeout for update that pops and update
  12434. // to the end of the execution stack. The player is destroyed before then
  12435. // update will cause an error
  12436. if (!this.el_) {
  12437. return;
  12438. } // If scrubbing, we could use a cached value to make the handle keep up
  12439. // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
  12440. // some flash players are slow, so we might want to utilize this later.
  12441. // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
  12442. var progress = this.getPercent();
  12443. var bar = this.bar; // If there's no bar...
  12444. if (!bar) {
  12445. return;
  12446. } // Protect against no duration and other division issues
  12447. if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
  12448. progress = 0;
  12449. } // Convert to a percentage for setting
  12450. var percentage = (progress * 100).toFixed(2) + '%';
  12451. var style = bar.el().style; // Set the new bar width or height
  12452. if (this.vertical()) {
  12453. style.height = percentage;
  12454. } else {
  12455. style.width = percentage;
  12456. }
  12457. return progress;
  12458. }
  12459. /**
  12460. * Calculate distance for slider
  12461. *
  12462. * @param {EventTarget~Event} event
  12463. * The event that caused this function to run.
  12464. *
  12465. * @return {number}
  12466. * The current position of the Slider.
  12467. * - position.x for vertical `Slider`s
  12468. * - position.y for horizontal `Slider`s
  12469. */
  12470. ;
  12471. _proto.calculateDistance = function calculateDistance(event) {
  12472. var position = getPointerPosition(this.el_, event);
  12473. if (this.vertical()) {
  12474. return position.y;
  12475. }
  12476. return position.x;
  12477. }
  12478. /**
  12479. * Handle a `focus` event on this `Slider`.
  12480. *
  12481. * @param {EventTarget~Event} event
  12482. * The `focus` event that caused this function to run.
  12483. *
  12484. * @listens focus
  12485. */
  12486. ;
  12487. _proto.handleFocus = function handleFocus() {
  12488. this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  12489. }
  12490. /**
  12491. * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
  12492. * arrow keys. This function will only be called when the slider has focus. See
  12493. * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
  12494. *
  12495. * @param {EventTarget~Event} event
  12496. * the `keydown` event that caused this function to run.
  12497. *
  12498. * @listens keydown
  12499. */
  12500. ;
  12501. _proto.handleKeyPress = function handleKeyPress(event) {
  12502. // Left and Down Arrows
  12503. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  12504. event.preventDefault();
  12505. this.stepBack(); // Up and Right Arrows
  12506. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  12507. event.preventDefault();
  12508. this.stepForward();
  12509. } else {
  12510. // Pass keypress handling up for unsupported keys
  12511. _Component.prototype.handleKeyPress.call(this, event);
  12512. }
  12513. }
  12514. /**
  12515. * Handle a `blur` event on this `Slider`.
  12516. *
  12517. * @param {EventTarget~Event} event
  12518. * The `blur` event that caused this function to run.
  12519. *
  12520. * @listens blur
  12521. */
  12522. ;
  12523. _proto.handleBlur = function handleBlur() {
  12524. this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  12525. }
  12526. /**
  12527. * Listener for click events on slider, used to prevent clicks
  12528. * from bubbling up to parent elements like button menus.
  12529. *
  12530. * @param {Object} event
  12531. * Event that caused this object to run
  12532. */
  12533. ;
  12534. _proto.handleClick = function handleClick(event) {
  12535. event.stopImmediatePropagation();
  12536. event.preventDefault();
  12537. }
  12538. /**
  12539. * Get/set if slider is horizontal for vertical
  12540. *
  12541. * @param {boolean} [bool]
  12542. * - true if slider is vertical,
  12543. * - false is horizontal
  12544. *
  12545. * @return {boolean}
  12546. * - true if slider is vertical, and getting
  12547. * - false if the slider is horizontal, and getting
  12548. */
  12549. ;
  12550. _proto.vertical = function vertical(bool) {
  12551. if (bool === undefined) {
  12552. return this.vertical_ || false;
  12553. }
  12554. this.vertical_ = !!bool;
  12555. if (this.vertical_) {
  12556. this.addClass('vjs-slider-vertical');
  12557. } else {
  12558. this.addClass('vjs-slider-horizontal');
  12559. }
  12560. };
  12561. return Slider;
  12562. }(Component);
  12563. Component.registerComponent('Slider', Slider);
  12564. /**
  12565. * Shows loading progress
  12566. *
  12567. * @extends Component
  12568. */
  12569. var LoadProgressBar =
  12570. /*#__PURE__*/
  12571. function (_Component) {
  12572. _inheritsLoose(LoadProgressBar, _Component);
  12573. /**
  12574. * Creates an instance of this class.
  12575. *
  12576. * @param {Player} player
  12577. * The `Player` that this class should be attached to.
  12578. *
  12579. * @param {Object} [options]
  12580. * The key/value store of player options.
  12581. */
  12582. function LoadProgressBar(player, options) {
  12583. var _this;
  12584. _this = _Component.call(this, player, options) || this;
  12585. _this.partEls_ = [];
  12586. _this.on(player, 'progress', _this.update);
  12587. return _this;
  12588. }
  12589. /**
  12590. * Create the `Component`'s DOM element
  12591. *
  12592. * @return {Element}
  12593. * The element that was created.
  12594. */
  12595. var _proto = LoadProgressBar.prototype;
  12596. _proto.createEl = function createEl$$1() {
  12597. return _Component.prototype.createEl.call(this, 'div', {
  12598. className: 'vjs-load-progress',
  12599. innerHTML: "<span class=\"vjs-control-text\"><span>" + this.localize('Loaded') + "</span>: <span class=\"vjs-control-text-loaded-percentage\">0%</span></span>"
  12600. });
  12601. };
  12602. _proto.dispose = function dispose() {
  12603. this.partEls_ = null;
  12604. _Component.prototype.dispose.call(this);
  12605. }
  12606. /**
  12607. * Update progress bar
  12608. *
  12609. * @param {EventTarget~Event} [event]
  12610. * The `progress` event that caused this function to run.
  12611. *
  12612. * @listens Player#progress
  12613. */
  12614. ;
  12615. _proto.update = function update(event) {
  12616. var liveTracker = this.player_.liveTracker;
  12617. var buffered = this.player_.buffered();
  12618. var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
  12619. var bufferedEnd = this.player_.bufferedEnd();
  12620. var children = this.partEls_;
  12621. var controlTextPercentage = this.$('.vjs-control-text-loaded-percentage'); // get the percent width of a time compared to the total end
  12622. var percentify = function percentify(time, end, rounded) {
  12623. // no NaN
  12624. var percent = time / end || 0;
  12625. percent = (percent >= 1 ? 1 : percent) * 100;
  12626. if (rounded) {
  12627. percent = percent.toFixed(2);
  12628. }
  12629. return percent + '%';
  12630. }; // update the width of the progress bar
  12631. this.el_.style.width = percentify(bufferedEnd, duration); // update the control-text
  12632. textContent(controlTextPercentage, percentify(bufferedEnd, duration, true)); // add child elements to represent the individual buffered time ranges
  12633. for (var i = 0; i < buffered.length; i++) {
  12634. var start = buffered.start(i);
  12635. var end = buffered.end(i);
  12636. var part = children[i];
  12637. if (!part) {
  12638. part = this.el_.appendChild(createEl());
  12639. children[i] = part;
  12640. } // set the percent based on the width of the progress bar (bufferedEnd)
  12641. part.style.left = percentify(start, bufferedEnd);
  12642. part.style.width = percentify(end - start, bufferedEnd);
  12643. } // remove unused buffered range elements
  12644. for (var _i = children.length; _i > buffered.length; _i--) {
  12645. this.el_.removeChild(children[_i - 1]);
  12646. }
  12647. children.length = buffered.length;
  12648. };
  12649. return LoadProgressBar;
  12650. }(Component);
  12651. Component.registerComponent('LoadProgressBar', LoadProgressBar);
  12652. /**
  12653. * Time tooltips display a time above the progress bar.
  12654. *
  12655. * @extends Component
  12656. */
  12657. var TimeTooltip =
  12658. /*#__PURE__*/
  12659. function (_Component) {
  12660. _inheritsLoose(TimeTooltip, _Component);
  12661. function TimeTooltip() {
  12662. return _Component.apply(this, arguments) || this;
  12663. }
  12664. var _proto = TimeTooltip.prototype;
  12665. /**
  12666. * Create the time tooltip DOM element
  12667. *
  12668. * @return {Element}
  12669. * The element that was created.
  12670. */
  12671. _proto.createEl = function createEl$$1() {
  12672. return _Component.prototype.createEl.call(this, 'div', {
  12673. className: 'vjs-time-tooltip'
  12674. }, {
  12675. 'aria-hidden': 'true'
  12676. });
  12677. }
  12678. /**
  12679. * Updates the position of the time tooltip relative to the `SeekBar`.
  12680. *
  12681. * @param {Object} seekBarRect
  12682. * The `ClientRect` for the {@link SeekBar} element.
  12683. *
  12684. * @param {number} seekBarPoint
  12685. * A number from 0 to 1, representing a horizontal reference point
  12686. * from the left edge of the {@link SeekBar}
  12687. */
  12688. ;
  12689. _proto.update = function update(seekBarRect, seekBarPoint, content) {
  12690. var tooltipRect = getBoundingClientRect(this.el_);
  12691. var playerRect = getBoundingClientRect(this.player_.el());
  12692. var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available
  12693. // for example, if the player isn't in the DOM for testing
  12694. if (!playerRect || !tooltipRect) {
  12695. return;
  12696. } // This is the space left of the `seekBarPoint` available within the bounds
  12697. // of the player. We calculate any gap between the left edge of the player
  12698. // and the left edge of the `SeekBar` and add the number of pixels in the
  12699. // `SeekBar` before hitting the `seekBarPoint`
  12700. var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds
  12701. // of the player. We calculate the number of pixels from the `seekBarPoint`
  12702. // to the right edge of the `SeekBar` and add to that any gap between the
  12703. // right edge of the `SeekBar` and the player.
  12704. var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled
  12705. // further to the right to center it over the `seekBarPoint`.
  12706. var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on
  12707. // the results of the space calculations above.
  12708. if (spaceLeftOfPoint < pullTooltipBy) {
  12709. pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
  12710. } else if (spaceRightOfPoint < pullTooltipBy) {
  12711. pullTooltipBy = spaceRightOfPoint;
  12712. } // Due to the imprecision of decimal/ratio based calculations and varying
  12713. // rounding behaviors, there are cases where the spacing adjustment is off
  12714. // by a pixel or two. This adds insurance to these calculations.
  12715. if (pullTooltipBy < 0) {
  12716. pullTooltipBy = 0;
  12717. } else if (pullTooltipBy > tooltipRect.width) {
  12718. pullTooltipBy = tooltipRect.width;
  12719. }
  12720. this.el_.style.right = "-" + pullTooltipBy + "px";
  12721. textContent(this.el_, content);
  12722. }
  12723. /**
  12724. * Updates the position of the time tooltip relative to the `SeekBar`.
  12725. *
  12726. * @param {Object} seekBarRect
  12727. * The `ClientRect` for the {@link SeekBar} element.
  12728. *
  12729. * @param {number} seekBarPoint
  12730. * A number from 0 to 1, representing a horizontal reference point
  12731. * from the left edge of the {@link SeekBar}
  12732. *
  12733. * @param {number} time
  12734. * The time to update the tooltip to, not used during live playback
  12735. *
  12736. * @param {Function} cb
  12737. * A function that will be called during the request animation frame
  12738. * for tooltips that need to do additional animations from the default
  12739. */
  12740. ;
  12741. _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) {
  12742. var _this = this;
  12743. // If there is an existing rAF ID, cancel it so we don't over-queue.
  12744. if (this.rafId_) {
  12745. this.cancelAnimationFrame(this.rafId_);
  12746. }
  12747. this.rafId_ = this.requestAnimationFrame(function () {
  12748. var content;
  12749. var duration = _this.player_.duration();
  12750. if (_this.player_.liveTracker && _this.player_.liveTracker.isLive()) {
  12751. var liveWindow = _this.player_.liveTracker.liveWindow();
  12752. var secondsBehind = liveWindow - seekBarPoint * liveWindow;
  12753. content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
  12754. } else {
  12755. content = formatTime(time, duration);
  12756. }
  12757. _this.update(seekBarRect, seekBarPoint, content);
  12758. if (cb) {
  12759. cb();
  12760. }
  12761. });
  12762. };
  12763. return TimeTooltip;
  12764. }(Component);
  12765. Component.registerComponent('TimeTooltip', TimeTooltip);
  12766. /**
  12767. * Used by {@link SeekBar} to display media playback progress as part of the
  12768. * {@link ProgressControl}.
  12769. *
  12770. * @extends Component
  12771. */
  12772. var PlayProgressBar =
  12773. /*#__PURE__*/
  12774. function (_Component) {
  12775. _inheritsLoose(PlayProgressBar, _Component);
  12776. function PlayProgressBar() {
  12777. return _Component.apply(this, arguments) || this;
  12778. }
  12779. var _proto = PlayProgressBar.prototype;
  12780. /**
  12781. * Create the the DOM element for this class.
  12782. *
  12783. * @return {Element}
  12784. * The element that was created.
  12785. */
  12786. _proto.createEl = function createEl() {
  12787. return _Component.prototype.createEl.call(this, 'div', {
  12788. className: 'vjs-play-progress vjs-slider-bar'
  12789. }, {
  12790. 'aria-hidden': 'true'
  12791. });
  12792. }
  12793. /**
  12794. * Enqueues updates to its own DOM as well as the DOM of its
  12795. * {@link TimeTooltip} child.
  12796. *
  12797. * @param {Object} seekBarRect
  12798. * The `ClientRect` for the {@link SeekBar} element.
  12799. *
  12800. * @param {number} seekBarPoint
  12801. * A number from 0 to 1, representing a horizontal reference point
  12802. * from the left edge of the {@link SeekBar}
  12803. */
  12804. ;
  12805. _proto.update = function update(seekBarRect, seekBarPoint) {
  12806. var timeTooltip = this.getChild('timeTooltip');
  12807. if (!timeTooltip) {
  12808. return;
  12809. }
  12810. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  12811. timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
  12812. };
  12813. return PlayProgressBar;
  12814. }(Component);
  12815. /**
  12816. * Default options for {@link PlayProgressBar}.
  12817. *
  12818. * @type {Object}
  12819. * @private
  12820. */
  12821. PlayProgressBar.prototype.options_ = {
  12822. children: []
  12823. }; // Time tooltips should not be added to a player on mobile devices
  12824. if (!IS_IOS && !IS_ANDROID) {
  12825. PlayProgressBar.prototype.options_.children.push('timeTooltip');
  12826. }
  12827. Component.registerComponent('PlayProgressBar', PlayProgressBar);
  12828. /**
  12829. * The {@link MouseTimeDisplay} component tracks mouse movement over the
  12830. * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
  12831. * indicating the time which is represented by a given point in the
  12832. * {@link ProgressControl}.
  12833. *
  12834. * @extends Component
  12835. */
  12836. var MouseTimeDisplay =
  12837. /*#__PURE__*/
  12838. function (_Component) {
  12839. _inheritsLoose(MouseTimeDisplay, _Component);
  12840. /**
  12841. * Creates an instance of this class.
  12842. *
  12843. * @param {Player} player
  12844. * The {@link Player} that this class should be attached to.
  12845. *
  12846. * @param {Object} [options]
  12847. * The key/value store of player options.
  12848. */
  12849. function MouseTimeDisplay(player, options) {
  12850. var _this;
  12851. _this = _Component.call(this, player, options) || this;
  12852. _this.update = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update), 25);
  12853. return _this;
  12854. }
  12855. /**
  12856. * Create the DOM element for this class.
  12857. *
  12858. * @return {Element}
  12859. * The element that was created.
  12860. */
  12861. var _proto = MouseTimeDisplay.prototype;
  12862. _proto.createEl = function createEl() {
  12863. return _Component.prototype.createEl.call(this, 'div', {
  12864. className: 'vjs-mouse-display'
  12865. });
  12866. }
  12867. /**
  12868. * Enqueues updates to its own DOM as well as the DOM of its
  12869. * {@link TimeTooltip} child.
  12870. *
  12871. * @param {Object} seekBarRect
  12872. * The `ClientRect` for the {@link SeekBar} element.
  12873. *
  12874. * @param {number} seekBarPoint
  12875. * A number from 0 to 1, representing a horizontal reference point
  12876. * from the left edge of the {@link SeekBar}
  12877. */
  12878. ;
  12879. _proto.update = function update(seekBarRect, seekBarPoint) {
  12880. var _this2 = this;
  12881. var time = seekBarPoint * this.player_.duration();
  12882. this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () {
  12883. _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px";
  12884. });
  12885. };
  12886. return MouseTimeDisplay;
  12887. }(Component);
  12888. /**
  12889. * Default options for `MouseTimeDisplay`
  12890. *
  12891. * @type {Object}
  12892. * @private
  12893. */
  12894. MouseTimeDisplay.prototype.options_ = {
  12895. children: ['timeTooltip']
  12896. };
  12897. Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
  12898. var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
  12899. var PAGE_KEY_MULTIPLIER = 12; // The interval at which the bar should update as it progresses.
  12900. var UPDATE_REFRESH_INTERVAL = 30;
  12901. /**
  12902. * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
  12903. * as its `bar`.
  12904. *
  12905. * @extends Slider
  12906. */
  12907. var SeekBar =
  12908. /*#__PURE__*/
  12909. function (_Slider) {
  12910. _inheritsLoose(SeekBar, _Slider);
  12911. /**
  12912. * Creates an instance of this class.
  12913. *
  12914. * @param {Player} player
  12915. * The `Player` that this class should be attached to.
  12916. *
  12917. * @param {Object} [options]
  12918. * The key/value store of player options.
  12919. */
  12920. function SeekBar(player, options) {
  12921. var _this;
  12922. _this = _Slider.call(this, player, options) || this;
  12923. _this.setEventHandlers_();
  12924. return _this;
  12925. }
  12926. /**
  12927. * Sets the event handlers
  12928. *
  12929. * @private
  12930. */
  12931. var _proto = SeekBar.prototype;
  12932. _proto.setEventHandlers_ = function setEventHandlers_() {
  12933. this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL);
  12934. this.on(this.player_, 'timeupdate', this.update);
  12935. this.on(this.player_, 'ended', this.handleEnded);
  12936. this.on(this.player_, 'durationchange', this.update);
  12937. if (this.player_.liveTracker) {
  12938. this.on(this.player_.liveTracker, 'liveedgechange', this.update);
  12939. } // when playing, let's ensure we smoothly update the play progress bar
  12940. // via an interval
  12941. this.updateInterval = null;
  12942. this.on(this.player_, ['playing'], this.enableInterval_);
  12943. this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden,
  12944. // also, this causes the CPU to spike and eventually crash the page on IE11.
  12945. if ('hidden' in document && 'visibilityState' in document) {
  12946. this.on(document, 'visibilitychange', this.toggleVisibility_);
  12947. }
  12948. };
  12949. _proto.toggleVisibility_ = function toggleVisibility_(e) {
  12950. if (document.hidden) {
  12951. this.disableInterval_(e);
  12952. } else {
  12953. this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP
  12954. this.requestAnimationFrame(this.update);
  12955. }
  12956. };
  12957. _proto.enableInterval_ = function enableInterval_() {
  12958. var _this2 = this;
  12959. this.clearInterval(this.updateInterval);
  12960. this.updateInterval = this.setInterval(function () {
  12961. _this2.requestAnimationFrame(_this2.update);
  12962. }, UPDATE_REFRESH_INTERVAL);
  12963. };
  12964. _proto.disableInterval_ = function disableInterval_(e) {
  12965. if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e.type !== 'ended') {
  12966. return;
  12967. }
  12968. this.clearInterval(this.updateInterval);
  12969. }
  12970. /**
  12971. * Create the `Component`'s DOM element
  12972. *
  12973. * @return {Element}
  12974. * The element that was created.
  12975. */
  12976. ;
  12977. _proto.createEl = function createEl$$1() {
  12978. return _Slider.prototype.createEl.call(this, 'div', {
  12979. className: 'vjs-progress-holder'
  12980. }, {
  12981. 'aria-label': this.localize('Progress Bar')
  12982. });
  12983. }
  12984. /**
  12985. * This function updates the play progress bar and accessibility
  12986. * attributes to whatever is passed in.
  12987. *
  12988. * @param {number} currentTime
  12989. * The currentTime value that should be used for accessibility
  12990. *
  12991. * @param {number} percent
  12992. * The percentage as a decimal that the bar should be filled from 0-1.
  12993. *
  12994. * @private
  12995. */
  12996. ;
  12997. _proto.update_ = function update_(currentTime, percent) {
  12998. var liveTracker = this.player_.liveTracker;
  12999. var duration = this.player_.duration();
  13000. if (liveTracker && liveTracker.isLive()) {
  13001. duration = this.player_.liveTracker.liveCurrentTime();
  13002. } // machine readable value of progress bar (percentage complete)
  13003. this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); // human readable value of progress bar (time complete)
  13004. this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}')); // Update the `PlayProgressBar`.
  13005. if (this.bar) {
  13006. this.bar.update(getBoundingClientRect(this.el_), percent);
  13007. }
  13008. }
  13009. /**
  13010. * Update the seek bar's UI.
  13011. *
  13012. * @param {EventTarget~Event} [event]
  13013. * The `timeupdate` or `ended` event that caused this to run.
  13014. *
  13015. * @listens Player#timeupdate
  13016. *
  13017. * @return {number}
  13018. * The current percent at a number from 0-1
  13019. */
  13020. ;
  13021. _proto.update = function update(event) {
  13022. // if the offsetParent is null, then this element is hidden, in which case
  13023. // we don't need to update it.
  13024. if (this.el().offsetParent === null) {
  13025. return;
  13026. }
  13027. var percent = _Slider.prototype.update.call(this);
  13028. this.update_(this.getCurrentTime_(), percent);
  13029. return percent;
  13030. }
  13031. /**
  13032. * Get the value of current time but allows for smooth scrubbing,
  13033. * when player can't keep up.
  13034. *
  13035. * @return {number}
  13036. * The current time value to display
  13037. *
  13038. * @private
  13039. */
  13040. ;
  13041. _proto.getCurrentTime_ = function getCurrentTime_() {
  13042. return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  13043. }
  13044. /**
  13045. * We want the seek bar to be full on ended
  13046. * no matter what the actual internal values are. so we force it.
  13047. *
  13048. * @param {EventTarget~Event} [event]
  13049. * The `timeupdate` or `ended` event that caused this to run.
  13050. *
  13051. * @listens Player#ended
  13052. */
  13053. ;
  13054. _proto.handleEnded = function handleEnded(event) {
  13055. this.update_(this.player_.duration(), 1);
  13056. }
  13057. /**
  13058. * Get the percentage of media played so far.
  13059. *
  13060. * @return {number}
  13061. * The percentage of media played so far (0 to 1).
  13062. */
  13063. ;
  13064. _proto.getPercent = function getPercent() {
  13065. var currentTime = this.getCurrentTime_();
  13066. var percent;
  13067. var liveTracker = this.player_.liveTracker;
  13068. if (liveTracker && liveTracker.isLive()) {
  13069. percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge
  13070. if (liveTracker.atLiveEdge()) {
  13071. percent = 1;
  13072. }
  13073. } else {
  13074. percent = currentTime / this.player_.duration();
  13075. }
  13076. return percent >= 1 ? 1 : percent || 0;
  13077. }
  13078. /**
  13079. * Handle mouse down on seek bar
  13080. *
  13081. * @param {EventTarget~Event} event
  13082. * The `mousedown` event that caused this to run.
  13083. *
  13084. * @listens mousedown
  13085. */
  13086. ;
  13087. _proto.handleMouseDown = function handleMouseDown(event) {
  13088. if (!isSingleLeftClick(event)) {
  13089. return;
  13090. } // Stop event propagation to prevent double fire in progress-control.js
  13091. event.stopPropagation();
  13092. this.player_.scrubbing(true);
  13093. this.videoWasPlaying = !this.player_.paused();
  13094. this.player_.pause();
  13095. _Slider.prototype.handleMouseDown.call(this, event);
  13096. }
  13097. /**
  13098. * Handle mouse move on seek bar
  13099. *
  13100. * @param {EventTarget~Event} event
  13101. * The `mousemove` event that caused this to run.
  13102. *
  13103. * @listens mousemove
  13104. */
  13105. ;
  13106. _proto.handleMouseMove = function handleMouseMove(event) {
  13107. if (!isSingleLeftClick(event)) {
  13108. return;
  13109. }
  13110. var newTime;
  13111. var distance = this.calculateDistance(event);
  13112. var liveTracker = this.player_.liveTracker;
  13113. if (!liveTracker || !liveTracker.isLive()) {
  13114. newTime = distance * this.player_.duration(); // Don't let video end while scrubbing.
  13115. if (newTime === this.player_.duration()) {
  13116. newTime = newTime - 0.1;
  13117. }
  13118. } else {
  13119. var seekableStart = liveTracker.seekableStart();
  13120. var seekableEnd = liveTracker.liveCurrentTime();
  13121. newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing.
  13122. if (newTime >= seekableEnd) {
  13123. newTime = seekableEnd;
  13124. } // Compensate for precision differences so that currentTime is not less
  13125. // than seekable start
  13126. if (newTime <= seekableStart) {
  13127. newTime = seekableStart + 0.1;
  13128. } // On android seekableEnd can be Infinity sometimes,
  13129. // this will cause newTime to be Infinity, which is
  13130. // not a valid currentTime.
  13131. if (newTime === Infinity) {
  13132. return;
  13133. }
  13134. } // Set new time (tell player to seek to new time)
  13135. this.player_.currentTime(newTime);
  13136. };
  13137. _proto.enable = function enable() {
  13138. _Slider.prototype.enable.call(this);
  13139. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  13140. if (!mouseTimeDisplay) {
  13141. return;
  13142. }
  13143. mouseTimeDisplay.show();
  13144. };
  13145. _proto.disable = function disable() {
  13146. _Slider.prototype.disable.call(this);
  13147. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  13148. if (!mouseTimeDisplay) {
  13149. return;
  13150. }
  13151. mouseTimeDisplay.hide();
  13152. }
  13153. /**
  13154. * Handle mouse up on seek bar
  13155. *
  13156. * @param {EventTarget~Event} event
  13157. * The `mouseup` event that caused this to run.
  13158. *
  13159. * @listens mouseup
  13160. */
  13161. ;
  13162. _proto.handleMouseUp = function handleMouseUp(event) {
  13163. _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js
  13164. if (event) {
  13165. event.stopPropagation();
  13166. }
  13167. this.player_.scrubbing(false);
  13168. /**
  13169. * Trigger timeupdate because we're done seeking and the time has changed.
  13170. * This is particularly useful for if the player is paused to time the time displays.
  13171. *
  13172. * @event Tech#timeupdate
  13173. * @type {EventTarget~Event}
  13174. */
  13175. this.player_.trigger({
  13176. type: 'timeupdate',
  13177. target: this,
  13178. manuallyTriggered: true
  13179. });
  13180. if (this.videoWasPlaying) {
  13181. silencePromise(this.player_.play());
  13182. }
  13183. }
  13184. /**
  13185. * Move more quickly fast forward for keyboard-only users
  13186. */
  13187. ;
  13188. _proto.stepForward = function stepForward() {
  13189. this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
  13190. }
  13191. /**
  13192. * Move more quickly rewind for keyboard-only users
  13193. */
  13194. ;
  13195. _proto.stepBack = function stepBack() {
  13196. this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
  13197. }
  13198. /**
  13199. * Toggles the playback state of the player
  13200. * This gets called when enter or space is used on the seekbar
  13201. *
  13202. * @param {EventTarget~Event} event
  13203. * The `keydown` event that caused this function to be called
  13204. *
  13205. */
  13206. ;
  13207. _proto.handleAction = function handleAction(event) {
  13208. if (this.player_.paused()) {
  13209. this.player_.play();
  13210. } else {
  13211. this.player_.pause();
  13212. }
  13213. }
  13214. /**
  13215. * Called when this SeekBar has focus and a key gets pressed down.
  13216. * Supports the following keys:
  13217. *
  13218. * Space or Enter key fire a click event
  13219. * Home key moves to start of the timeline
  13220. * End key moves to end of the timeline
  13221. * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
  13222. * PageDown key moves back a larger step than ArrowDown
  13223. * PageUp key moves forward a large step
  13224. *
  13225. * @param {EventTarget~Event} event
  13226. * The `keydown` event that caused this function to be called.
  13227. *
  13228. * @listens keydown
  13229. */
  13230. ;
  13231. _proto.handleKeyPress = function handleKeyPress(event) {
  13232. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  13233. event.preventDefault();
  13234. this.handleAction(event);
  13235. } else if (keycode.isEventKey(event, 'Home')) {
  13236. event.preventDefault();
  13237. this.player_.currentTime(0);
  13238. } else if (keycode.isEventKey(event, 'End')) {
  13239. event.preventDefault();
  13240. this.player_.currentTime(this.player_.duration());
  13241. } else if (/^[0-9]$/.test(keycode(event))) {
  13242. event.preventDefault();
  13243. var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
  13244. this.player_.currentTime(this.player_.duration() * gotoFraction);
  13245. } else if (keycode.isEventKey(event, 'PgDn')) {
  13246. event.preventDefault();
  13247. this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  13248. } else if (keycode.isEventKey(event, 'PgUp')) {
  13249. event.preventDefault();
  13250. this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  13251. } else {
  13252. // Pass keypress handling up for unsupported keys
  13253. _Slider.prototype.handleKeyPress.call(this, event);
  13254. }
  13255. };
  13256. return SeekBar;
  13257. }(Slider);
  13258. /**
  13259. * Default options for the `SeekBar`
  13260. *
  13261. * @type {Object}
  13262. * @private
  13263. */
  13264. SeekBar.prototype.options_ = {
  13265. children: ['loadProgressBar', 'playProgressBar'],
  13266. barName: 'playProgressBar'
  13267. }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices
  13268. if (!IS_IOS && !IS_ANDROID) {
  13269. SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
  13270. }
  13271. Component.registerComponent('SeekBar', SeekBar);
  13272. /**
  13273. * The Progress Control component contains the seek bar, load progress,
  13274. * and play progress.
  13275. *
  13276. * @extends Component
  13277. */
  13278. var ProgressControl =
  13279. /*#__PURE__*/
  13280. function (_Component) {
  13281. _inheritsLoose(ProgressControl, _Component);
  13282. /**
  13283. * Creates an instance of this class.
  13284. *
  13285. * @param {Player} player
  13286. * The `Player` that this class should be attached to.
  13287. *
  13288. * @param {Object} [options]
  13289. * The key/value store of player options.
  13290. */
  13291. function ProgressControl(player, options) {
  13292. var _this;
  13293. _this = _Component.call(this, player, options) || this;
  13294. _this.handleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25);
  13295. _this.throttledHandleMouseSeek = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseSeek), 25);
  13296. _this.enable();
  13297. return _this;
  13298. }
  13299. /**
  13300. * Create the `Component`'s DOM element
  13301. *
  13302. * @return {Element}
  13303. * The element that was created.
  13304. */
  13305. var _proto = ProgressControl.prototype;
  13306. _proto.createEl = function createEl$$1() {
  13307. return _Component.prototype.createEl.call(this, 'div', {
  13308. className: 'vjs-progress-control vjs-control'
  13309. });
  13310. }
  13311. /**
  13312. * When the mouse moves over the `ProgressControl`, the pointer position
  13313. * gets passed down to the `MouseTimeDisplay` component.
  13314. *
  13315. * @param {EventTarget~Event} event
  13316. * The `mousemove` event that caused this function to run.
  13317. *
  13318. * @listen mousemove
  13319. */
  13320. ;
  13321. _proto.handleMouseMove = function handleMouseMove(event) {
  13322. var seekBar = this.getChild('seekBar');
  13323. if (seekBar) {
  13324. var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
  13325. var seekBarEl = seekBar.el();
  13326. var seekBarRect = getBoundingClientRect(seekBarEl);
  13327. var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
  13328. // that it's possible to trigger this behavior outside the boundaries of
  13329. // the `SeekBar`. This ensures we stay within it at all times.
  13330. if (seekBarPoint > 1) {
  13331. seekBarPoint = 1;
  13332. } else if (seekBarPoint < 0) {
  13333. seekBarPoint = 0;
  13334. }
  13335. if (mouseTimeDisplay) {
  13336. mouseTimeDisplay.update(seekBarRect, seekBarPoint);
  13337. }
  13338. }
  13339. }
  13340. /**
  13341. * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
  13342. *
  13343. * @method ProgressControl#throttledHandleMouseSeek
  13344. * @param {EventTarget~Event} event
  13345. * The `mousemove` event that caused this function to run.
  13346. *
  13347. * @listen mousemove
  13348. * @listen touchmove
  13349. */
  13350. /**
  13351. * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
  13352. *
  13353. * @param {EventTarget~Event} event
  13354. * `mousedown` or `touchstart` event that triggered this function
  13355. *
  13356. * @listens mousemove
  13357. * @listens touchmove
  13358. */
  13359. ;
  13360. _proto.handleMouseSeek = function handleMouseSeek(event) {
  13361. var seekBar = this.getChild('seekBar');
  13362. if (seekBar) {
  13363. seekBar.handleMouseMove(event);
  13364. }
  13365. }
  13366. /**
  13367. * Are controls are currently enabled for this progress control.
  13368. *
  13369. * @return {boolean}
  13370. * true if controls are enabled, false otherwise
  13371. */
  13372. ;
  13373. _proto.enabled = function enabled() {
  13374. return this.enabled_;
  13375. }
  13376. /**
  13377. * Disable all controls on the progress control and its children
  13378. */
  13379. ;
  13380. _proto.disable = function disable() {
  13381. this.children().forEach(function (child) {
  13382. return child.disable && child.disable();
  13383. });
  13384. if (!this.enabled()) {
  13385. return;
  13386. }
  13387. this.off(['mousedown', 'touchstart'], this.handleMouseDown);
  13388. this.off(this.el_, 'mousemove', this.handleMouseMove);
  13389. this.handleMouseUp();
  13390. this.addClass('disabled');
  13391. this.enabled_ = false;
  13392. }
  13393. /**
  13394. * Enable all controls on the progress control and its children
  13395. */
  13396. ;
  13397. _proto.enable = function enable() {
  13398. this.children().forEach(function (child) {
  13399. return child.enable && child.enable();
  13400. });
  13401. if (this.enabled()) {
  13402. return;
  13403. }
  13404. this.on(['mousedown', 'touchstart'], this.handleMouseDown);
  13405. this.on(this.el_, 'mousemove', this.handleMouseMove);
  13406. this.removeClass('disabled');
  13407. this.enabled_ = true;
  13408. }
  13409. /**
  13410. * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
  13411. *
  13412. * @param {EventTarget~Event} event
  13413. * `mousedown` or `touchstart` event that triggered this function
  13414. *
  13415. * @listens mousedown
  13416. * @listens touchstart
  13417. */
  13418. ;
  13419. _proto.handleMouseDown = function handleMouseDown(event) {
  13420. var doc = this.el_.ownerDocument;
  13421. var seekBar = this.getChild('seekBar');
  13422. if (seekBar) {
  13423. seekBar.handleMouseDown(event);
  13424. }
  13425. this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
  13426. this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
  13427. this.on(doc, 'mouseup', this.handleMouseUp);
  13428. this.on(doc, 'touchend', this.handleMouseUp);
  13429. }
  13430. /**
  13431. * Handle `mouseup` or `touchend` events on the `ProgressControl`.
  13432. *
  13433. * @param {EventTarget~Event} event
  13434. * `mouseup` or `touchend` event that triggered this function.
  13435. *
  13436. * @listens touchend
  13437. * @listens mouseup
  13438. */
  13439. ;
  13440. _proto.handleMouseUp = function handleMouseUp(event) {
  13441. var doc = this.el_.ownerDocument;
  13442. var seekBar = this.getChild('seekBar');
  13443. if (seekBar) {
  13444. seekBar.handleMouseUp(event);
  13445. }
  13446. this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
  13447. this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
  13448. this.off(doc, 'mouseup', this.handleMouseUp);
  13449. this.off(doc, 'touchend', this.handleMouseUp);
  13450. };
  13451. return ProgressControl;
  13452. }(Component);
  13453. /**
  13454. * Default options for `ProgressControl`
  13455. *
  13456. * @type {Object}
  13457. * @private
  13458. */
  13459. ProgressControl.prototype.options_ = {
  13460. children: ['seekBar']
  13461. };
  13462. Component.registerComponent('ProgressControl', ProgressControl);
  13463. /**
  13464. * Toggle fullscreen video
  13465. *
  13466. * @extends Button
  13467. */
  13468. var FullscreenToggle =
  13469. /*#__PURE__*/
  13470. function (_Button) {
  13471. _inheritsLoose(FullscreenToggle, _Button);
  13472. /**
  13473. * Creates an instance of this class.
  13474. *
  13475. * @param {Player} player
  13476. * The `Player` that this class should be attached to.
  13477. *
  13478. * @param {Object} [options]
  13479. * The key/value store of player options.
  13480. */
  13481. function FullscreenToggle(player, options) {
  13482. var _this;
  13483. _this = _Button.call(this, player, options) || this;
  13484. _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
  13485. if (document[FullscreenApi.fullscreenEnabled] === false) {
  13486. _this.disable();
  13487. }
  13488. return _this;
  13489. }
  13490. /**
  13491. * Builds the default DOM `className`.
  13492. *
  13493. * @return {string}
  13494. * The DOM `className` for this object.
  13495. */
  13496. var _proto = FullscreenToggle.prototype;
  13497. _proto.buildCSSClass = function buildCSSClass() {
  13498. return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this);
  13499. }
  13500. /**
  13501. * Handles fullscreenchange on the player and change control text accordingly.
  13502. *
  13503. * @param {EventTarget~Event} [event]
  13504. * The {@link Player#fullscreenchange} event that caused this function to be
  13505. * called.
  13506. *
  13507. * @listens Player#fullscreenchange
  13508. */
  13509. ;
  13510. _proto.handleFullscreenChange = function handleFullscreenChange(event) {
  13511. if (this.player_.isFullscreen()) {
  13512. this.controlText('Non-Fullscreen');
  13513. } else {
  13514. this.controlText('Fullscreen');
  13515. }
  13516. }
  13517. /**
  13518. * This gets called when an `FullscreenToggle` is "clicked". See
  13519. * {@link ClickableComponent} for more detailed information on what a click can be.
  13520. *
  13521. * @param {EventTarget~Event} [event]
  13522. * The `keydown`, `tap`, or `click` event that caused this function to be
  13523. * called.
  13524. *
  13525. * @listens tap
  13526. * @listens click
  13527. */
  13528. ;
  13529. _proto.handleClick = function handleClick(event) {
  13530. if (!this.player_.isFullscreen()) {
  13531. this.player_.requestFullscreen();
  13532. } else {
  13533. this.player_.exitFullscreen();
  13534. }
  13535. };
  13536. return FullscreenToggle;
  13537. }(Button);
  13538. /**
  13539. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  13540. *
  13541. * @type {string}
  13542. * @private
  13543. */
  13544. FullscreenToggle.prototype.controlText_ = 'Fullscreen';
  13545. Component.registerComponent('FullscreenToggle', FullscreenToggle);
  13546. /**
  13547. * Check if volume control is supported and if it isn't hide the
  13548. * `Component` that was passed using the `vjs-hidden` class.
  13549. *
  13550. * @param {Component} self
  13551. * The component that should be hidden if volume is unsupported
  13552. *
  13553. * @param {Player} player
  13554. * A reference to the player
  13555. *
  13556. * @private
  13557. */
  13558. var checkVolumeSupport = function checkVolumeSupport(self, player) {
  13559. // hide volume controls when they're not supported by the current tech
  13560. if (player.tech_ && !player.tech_.featuresVolumeControl) {
  13561. self.addClass('vjs-hidden');
  13562. }
  13563. self.on(player, 'loadstart', function () {
  13564. if (!player.tech_.featuresVolumeControl) {
  13565. self.addClass('vjs-hidden');
  13566. } else {
  13567. self.removeClass('vjs-hidden');
  13568. }
  13569. });
  13570. };
  13571. /**
  13572. * Shows volume level
  13573. *
  13574. * @extends Component
  13575. */
  13576. var VolumeLevel =
  13577. /*#__PURE__*/
  13578. function (_Component) {
  13579. _inheritsLoose(VolumeLevel, _Component);
  13580. function VolumeLevel() {
  13581. return _Component.apply(this, arguments) || this;
  13582. }
  13583. var _proto = VolumeLevel.prototype;
  13584. /**
  13585. * Create the `Component`'s DOM element
  13586. *
  13587. * @return {Element}
  13588. * The element that was created.
  13589. */
  13590. _proto.createEl = function createEl() {
  13591. return _Component.prototype.createEl.call(this, 'div', {
  13592. className: 'vjs-volume-level',
  13593. innerHTML: '<span class="vjs-control-text"></span>'
  13594. });
  13595. };
  13596. return VolumeLevel;
  13597. }(Component);
  13598. Component.registerComponent('VolumeLevel', VolumeLevel);
  13599. /**
  13600. * The bar that contains the volume level and can be clicked on to adjust the level
  13601. *
  13602. * @extends Slider
  13603. */
  13604. var VolumeBar =
  13605. /*#__PURE__*/
  13606. function (_Slider) {
  13607. _inheritsLoose(VolumeBar, _Slider);
  13608. /**
  13609. * Creates an instance of this class.
  13610. *
  13611. * @param {Player} player
  13612. * The `Player` that this class should be attached to.
  13613. *
  13614. * @param {Object} [options]
  13615. * The key/value store of player options.
  13616. */
  13617. function VolumeBar(player, options) {
  13618. var _this;
  13619. _this = _Slider.call(this, player, options) || this;
  13620. _this.on('slideractive', _this.updateLastVolume_);
  13621. _this.on(player, 'volumechange', _this.updateARIAAttributes);
  13622. player.ready(function () {
  13623. return _this.updateARIAAttributes();
  13624. });
  13625. return _this;
  13626. }
  13627. /**
  13628. * Create the `Component`'s DOM element
  13629. *
  13630. * @return {Element}
  13631. * The element that was created.
  13632. */
  13633. var _proto = VolumeBar.prototype;
  13634. _proto.createEl = function createEl$$1() {
  13635. return _Slider.prototype.createEl.call(this, 'div', {
  13636. className: 'vjs-volume-bar vjs-slider-bar'
  13637. }, {
  13638. 'aria-label': this.localize('Volume Level'),
  13639. 'aria-live': 'polite'
  13640. });
  13641. }
  13642. /**
  13643. * Handle mouse down on volume bar
  13644. *
  13645. * @param {EventTarget~Event} event
  13646. * The `mousedown` event that caused this to run.
  13647. *
  13648. * @listens mousedown
  13649. */
  13650. ;
  13651. _proto.handleMouseDown = function handleMouseDown(event) {
  13652. if (!isSingleLeftClick(event)) {
  13653. return;
  13654. }
  13655. _Slider.prototype.handleMouseDown.call(this, event);
  13656. }
  13657. /**
  13658. * Handle movement events on the {@link VolumeMenuButton}.
  13659. *
  13660. * @param {EventTarget~Event} event
  13661. * The event that caused this function to run.
  13662. *
  13663. * @listens mousemove
  13664. */
  13665. ;
  13666. _proto.handleMouseMove = function handleMouseMove(event) {
  13667. if (!isSingleLeftClick(event)) {
  13668. return;
  13669. }
  13670. this.checkMuted();
  13671. this.player_.volume(this.calculateDistance(event));
  13672. }
  13673. /**
  13674. * If the player is muted unmute it.
  13675. */
  13676. ;
  13677. _proto.checkMuted = function checkMuted() {
  13678. if (this.player_.muted()) {
  13679. this.player_.muted(false);
  13680. }
  13681. }
  13682. /**
  13683. * Get percent of volume level
  13684. *
  13685. * @return {number}
  13686. * Volume level percent as a decimal number.
  13687. */
  13688. ;
  13689. _proto.getPercent = function getPercent() {
  13690. if (this.player_.muted()) {
  13691. return 0;
  13692. }
  13693. return this.player_.volume();
  13694. }
  13695. /**
  13696. * Increase volume level for keyboard users
  13697. */
  13698. ;
  13699. _proto.stepForward = function stepForward() {
  13700. this.checkMuted();
  13701. this.player_.volume(this.player_.volume() + 0.1);
  13702. }
  13703. /**
  13704. * Decrease volume level for keyboard users
  13705. */
  13706. ;
  13707. _proto.stepBack = function stepBack() {
  13708. this.checkMuted();
  13709. this.player_.volume(this.player_.volume() - 0.1);
  13710. }
  13711. /**
  13712. * Update ARIA accessibility attributes
  13713. *
  13714. * @param {EventTarget~Event} [event]
  13715. * The `volumechange` event that caused this function to run.
  13716. *
  13717. * @listens Player#volumechange
  13718. */
  13719. ;
  13720. _proto.updateARIAAttributes = function updateARIAAttributes(event) {
  13721. var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
  13722. this.el_.setAttribute('aria-valuenow', ariaValue);
  13723. this.el_.setAttribute('aria-valuetext', ariaValue + '%');
  13724. }
  13725. /**
  13726. * Returns the current value of the player volume as a percentage
  13727. *
  13728. * @private
  13729. */
  13730. ;
  13731. _proto.volumeAsPercentage_ = function volumeAsPercentage_() {
  13732. return Math.round(this.player_.volume() * 100);
  13733. }
  13734. /**
  13735. * When user starts dragging the VolumeBar, store the volume and listen for
  13736. * the end of the drag. When the drag ends, if the volume was set to zero,
  13737. * set lastVolume to the stored volume.
  13738. *
  13739. * @listens slideractive
  13740. * @private
  13741. */
  13742. ;
  13743. _proto.updateLastVolume_ = function updateLastVolume_() {
  13744. var _this2 = this;
  13745. var volumeBeforeDrag = this.player_.volume();
  13746. this.one('sliderinactive', function () {
  13747. if (_this2.player_.volume() === 0) {
  13748. _this2.player_.lastVolume_(volumeBeforeDrag);
  13749. }
  13750. });
  13751. };
  13752. return VolumeBar;
  13753. }(Slider);
  13754. /**
  13755. * Default options for the `VolumeBar`
  13756. *
  13757. * @type {Object}
  13758. * @private
  13759. */
  13760. VolumeBar.prototype.options_ = {
  13761. children: ['volumeLevel'],
  13762. barName: 'volumeLevel'
  13763. };
  13764. /**
  13765. * Call the update event for this Slider when this event happens on the player.
  13766. *
  13767. * @type {string}
  13768. */
  13769. VolumeBar.prototype.playerEvent = 'volumechange';
  13770. Component.registerComponent('VolumeBar', VolumeBar);
  13771. /**
  13772. * The component for controlling the volume level
  13773. *
  13774. * @extends Component
  13775. */
  13776. var VolumeControl =
  13777. /*#__PURE__*/
  13778. function (_Component) {
  13779. _inheritsLoose(VolumeControl, _Component);
  13780. /**
  13781. * Creates an instance of this class.
  13782. *
  13783. * @param {Player} player
  13784. * The `Player` that this class should be attached to.
  13785. *
  13786. * @param {Object} [options={}]
  13787. * The key/value store of player options.
  13788. */
  13789. function VolumeControl(player, options) {
  13790. var _this;
  13791. if (options === void 0) {
  13792. options = {};
  13793. }
  13794. options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if
  13795. // the VolumeBar is turned on.
  13796. if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
  13797. options.volumeBar = options.volumeBar || {};
  13798. options.volumeBar.vertical = options.vertical;
  13799. }
  13800. _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing
  13801. checkVolumeSupport(_assertThisInitialized(_assertThisInitialized(_this)), player);
  13802. _this.throttledHandleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25);
  13803. _this.on('mousedown', _this.handleMouseDown);
  13804. _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and
  13805. // is dragging) or in focus we do not want to hide the VolumeBar
  13806. _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
  13807. _this.volumeBar.addClass('vjs-slider-active');
  13808. _this.addClass('vjs-slider-active');
  13809. _this.trigger('slideractive');
  13810. });
  13811. _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
  13812. _this.volumeBar.removeClass('vjs-slider-active');
  13813. _this.removeClass('vjs-slider-active');
  13814. _this.trigger('sliderinactive');
  13815. });
  13816. return _this;
  13817. }
  13818. /**
  13819. * Create the `Component`'s DOM element
  13820. *
  13821. * @return {Element}
  13822. * The element that was created.
  13823. */
  13824. var _proto = VolumeControl.prototype;
  13825. _proto.createEl = function createEl() {
  13826. var orientationClass = 'vjs-volume-horizontal';
  13827. if (this.options_.vertical) {
  13828. orientationClass = 'vjs-volume-vertical';
  13829. }
  13830. return _Component.prototype.createEl.call(this, 'div', {
  13831. className: "vjs-volume-control vjs-control " + orientationClass
  13832. });
  13833. }
  13834. /**
  13835. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  13836. *
  13837. * @param {EventTarget~Event} event
  13838. * `mousedown` or `touchstart` event that triggered this function
  13839. *
  13840. * @listens mousedown
  13841. * @listens touchstart
  13842. */
  13843. ;
  13844. _proto.handleMouseDown = function handleMouseDown(event) {
  13845. var doc = this.el_.ownerDocument;
  13846. this.on(doc, 'mousemove', this.throttledHandleMouseMove);
  13847. this.on(doc, 'touchmove', this.throttledHandleMouseMove);
  13848. this.on(doc, 'mouseup', this.handleMouseUp);
  13849. this.on(doc, 'touchend', this.handleMouseUp);
  13850. }
  13851. /**
  13852. * Handle `mouseup` or `touchend` events on the `VolumeControl`.
  13853. *
  13854. * @param {EventTarget~Event} event
  13855. * `mouseup` or `touchend` event that triggered this function.
  13856. *
  13857. * @listens touchend
  13858. * @listens mouseup
  13859. */
  13860. ;
  13861. _proto.handleMouseUp = function handleMouseUp(event) {
  13862. var doc = this.el_.ownerDocument;
  13863. this.off(doc, 'mousemove', this.throttledHandleMouseMove);
  13864. this.off(doc, 'touchmove', this.throttledHandleMouseMove);
  13865. this.off(doc, 'mouseup', this.handleMouseUp);
  13866. this.off(doc, 'touchend', this.handleMouseUp);
  13867. }
  13868. /**
  13869. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  13870. *
  13871. * @param {EventTarget~Event} event
  13872. * `mousedown` or `touchstart` event that triggered this function
  13873. *
  13874. * @listens mousedown
  13875. * @listens touchstart
  13876. */
  13877. ;
  13878. _proto.handleMouseMove = function handleMouseMove(event) {
  13879. this.volumeBar.handleMouseMove(event);
  13880. };
  13881. return VolumeControl;
  13882. }(Component);
  13883. /**
  13884. * Default options for the `VolumeControl`
  13885. *
  13886. * @type {Object}
  13887. * @private
  13888. */
  13889. VolumeControl.prototype.options_ = {
  13890. children: ['volumeBar']
  13891. };
  13892. Component.registerComponent('VolumeControl', VolumeControl);
  13893. /**
  13894. * Check if muting volume is supported and if it isn't hide the mute toggle
  13895. * button.
  13896. *
  13897. * @param {Component} self
  13898. * A reference to the mute toggle button
  13899. *
  13900. * @param {Player} player
  13901. * A reference to the player
  13902. *
  13903. * @private
  13904. */
  13905. var checkMuteSupport = function checkMuteSupport(self, player) {
  13906. // hide mute toggle button if it's not supported by the current tech
  13907. if (player.tech_ && !player.tech_.featuresMuteControl) {
  13908. self.addClass('vjs-hidden');
  13909. }
  13910. self.on(player, 'loadstart', function () {
  13911. if (!player.tech_.featuresMuteControl) {
  13912. self.addClass('vjs-hidden');
  13913. } else {
  13914. self.removeClass('vjs-hidden');
  13915. }
  13916. });
  13917. };
  13918. /**
  13919. * A button component for muting the audio.
  13920. *
  13921. * @extends Button
  13922. */
  13923. var MuteToggle =
  13924. /*#__PURE__*/
  13925. function (_Button) {
  13926. _inheritsLoose(MuteToggle, _Button);
  13927. /**
  13928. * Creates an instance of this class.
  13929. *
  13930. * @param {Player} player
  13931. * The `Player` that this class should be attached to.
  13932. *
  13933. * @param {Object} [options]
  13934. * The key/value store of player options.
  13935. */
  13936. function MuteToggle(player, options) {
  13937. var _this;
  13938. _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing
  13939. checkMuteSupport(_assertThisInitialized(_assertThisInitialized(_this)), player);
  13940. _this.on(player, ['loadstart', 'volumechange'], _this.update);
  13941. return _this;
  13942. }
  13943. /**
  13944. * Builds the default DOM `className`.
  13945. *
  13946. * @return {string}
  13947. * The DOM `className` for this object.
  13948. */
  13949. var _proto = MuteToggle.prototype;
  13950. _proto.buildCSSClass = function buildCSSClass() {
  13951. return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this);
  13952. }
  13953. /**
  13954. * This gets called when an `MuteToggle` is "clicked". See
  13955. * {@link ClickableComponent} for more detailed information on what a click can be.
  13956. *
  13957. * @param {EventTarget~Event} [event]
  13958. * The `keydown`, `tap`, or `click` event that caused this function to be
  13959. * called.
  13960. *
  13961. * @listens tap
  13962. * @listens click
  13963. */
  13964. ;
  13965. _proto.handleClick = function handleClick(event) {
  13966. var vol = this.player_.volume();
  13967. var lastVolume = this.player_.lastVolume_();
  13968. if (vol === 0) {
  13969. var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
  13970. this.player_.volume(volumeToSet);
  13971. this.player_.muted(false);
  13972. } else {
  13973. this.player_.muted(this.player_.muted() ? false : true);
  13974. }
  13975. }
  13976. /**
  13977. * Update the `MuteToggle` button based on the state of `volume` and `muted`
  13978. * on the player.
  13979. *
  13980. * @param {EventTarget~Event} [event]
  13981. * The {@link Player#loadstart} event if this function was called
  13982. * through an event.
  13983. *
  13984. * @listens Player#loadstart
  13985. * @listens Player#volumechange
  13986. */
  13987. ;
  13988. _proto.update = function update(event) {
  13989. this.updateIcon_();
  13990. this.updateControlText_();
  13991. }
  13992. /**
  13993. * Update the appearance of the `MuteToggle` icon.
  13994. *
  13995. * Possible states (given `level` variable below):
  13996. * - 0: crossed out
  13997. * - 1: zero bars of volume
  13998. * - 2: one bar of volume
  13999. * - 3: two bars of volume
  14000. *
  14001. * @private
  14002. */
  14003. ;
  14004. _proto.updateIcon_ = function updateIcon_() {
  14005. var vol = this.player_.volume();
  14006. var level = 3; // in iOS when a player is loaded with muted attribute
  14007. // and volume is changed with a native mute button
  14008. // we want to make sure muted state is updated
  14009. if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
  14010. this.player_.muted(this.player_.tech_.el_.muted);
  14011. }
  14012. if (vol === 0 || this.player_.muted()) {
  14013. level = 0;
  14014. } else if (vol < 0.33) {
  14015. level = 1;
  14016. } else if (vol < 0.67) {
  14017. level = 2;
  14018. } // TODO improve muted icon classes
  14019. for (var i = 0; i < 4; i++) {
  14020. removeClass(this.el_, "vjs-vol-" + i);
  14021. }
  14022. addClass(this.el_, "vjs-vol-" + level);
  14023. }
  14024. /**
  14025. * If `muted` has changed on the player, update the control text
  14026. * (`title` attribute on `vjs-mute-control` element and content of
  14027. * `vjs-control-text` element).
  14028. *
  14029. * @private
  14030. */
  14031. ;
  14032. _proto.updateControlText_ = function updateControlText_() {
  14033. var soundOff = this.player_.muted() || this.player_.volume() === 0;
  14034. var text = soundOff ? 'Unmute' : 'Mute';
  14035. if (this.controlText() !== text) {
  14036. this.controlText(text);
  14037. }
  14038. };
  14039. return MuteToggle;
  14040. }(Button);
  14041. /**
  14042. * The text that should display over the `MuteToggle`s controls. Added for localization.
  14043. *
  14044. * @type {string}
  14045. * @private
  14046. */
  14047. MuteToggle.prototype.controlText_ = 'Mute';
  14048. Component.registerComponent('MuteToggle', MuteToggle);
  14049. /**
  14050. * A Component to contain the MuteToggle and VolumeControl so that
  14051. * they can work together.
  14052. *
  14053. * @extends Component
  14054. */
  14055. var VolumePanel =
  14056. /*#__PURE__*/
  14057. function (_Component) {
  14058. _inheritsLoose(VolumePanel, _Component);
  14059. /**
  14060. * Creates an instance of this class.
  14061. *
  14062. * @param {Player} player
  14063. * The `Player` that this class should be attached to.
  14064. *
  14065. * @param {Object} [options={}]
  14066. * The key/value store of player options.
  14067. */
  14068. function VolumePanel(player, options) {
  14069. var _this;
  14070. if (options === void 0) {
  14071. options = {};
  14072. }
  14073. if (typeof options.inline !== 'undefined') {
  14074. options.inline = options.inline;
  14075. } else {
  14076. options.inline = true;
  14077. } // pass the inline option down to the VolumeControl as vertical if
  14078. // the VolumeControl is on.
  14079. if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
  14080. options.volumeControl = options.volumeControl || {};
  14081. options.volumeControl.vertical = !options.inline;
  14082. }
  14083. _this = _Component.call(this, player, options) || this;
  14084. _this.on(player, ['loadstart'], _this.volumePanelState_); // while the slider is active (the mouse has been pressed down and
  14085. // is dragging) we do not want to hide the VolumeBar
  14086. _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
  14087. _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
  14088. return _this;
  14089. }
  14090. /**
  14091. * Add vjs-slider-active class to the VolumePanel
  14092. *
  14093. * @listens VolumeControl#slideractive
  14094. * @private
  14095. */
  14096. var _proto = VolumePanel.prototype;
  14097. _proto.sliderActive_ = function sliderActive_() {
  14098. this.addClass('vjs-slider-active');
  14099. }
  14100. /**
  14101. * Removes vjs-slider-active class to the VolumePanel
  14102. *
  14103. * @listens VolumeControl#sliderinactive
  14104. * @private
  14105. */
  14106. ;
  14107. _proto.sliderInactive_ = function sliderInactive_() {
  14108. this.removeClass('vjs-slider-active');
  14109. }
  14110. /**
  14111. * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
  14112. * depending on MuteToggle and VolumeControl state
  14113. *
  14114. * @listens Player#loadstart
  14115. * @private
  14116. */
  14117. ;
  14118. _proto.volumePanelState_ = function volumePanelState_() {
  14119. // hide volume panel if neither volume control or mute toggle
  14120. // are displayed
  14121. if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
  14122. this.addClass('vjs-hidden');
  14123. } // if only mute toggle is visible we don't want
  14124. // volume panel expanding when hovered or active
  14125. if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
  14126. this.addClass('vjs-mute-toggle-only');
  14127. }
  14128. }
  14129. /**
  14130. * Create the `Component`'s DOM element
  14131. *
  14132. * @return {Element}
  14133. * The element that was created.
  14134. */
  14135. ;
  14136. _proto.createEl = function createEl() {
  14137. var orientationClass = 'vjs-volume-panel-horizontal';
  14138. if (!this.options_.inline) {
  14139. orientationClass = 'vjs-volume-panel-vertical';
  14140. }
  14141. return _Component.prototype.createEl.call(this, 'div', {
  14142. className: "vjs-volume-panel vjs-control " + orientationClass
  14143. });
  14144. };
  14145. return VolumePanel;
  14146. }(Component);
  14147. /**
  14148. * Default options for the `VolumeControl`
  14149. *
  14150. * @type {Object}
  14151. * @private
  14152. */
  14153. VolumePanel.prototype.options_ = {
  14154. children: ['muteToggle', 'volumeControl']
  14155. };
  14156. Component.registerComponent('VolumePanel', VolumePanel);
  14157. /**
  14158. * The Menu component is used to build popup menus, including subtitle and
  14159. * captions selection menus.
  14160. *
  14161. * @extends Component
  14162. */
  14163. var Menu =
  14164. /*#__PURE__*/
  14165. function (_Component) {
  14166. _inheritsLoose(Menu, _Component);
  14167. /**
  14168. * Create an instance of this class.
  14169. *
  14170. * @param {Player} player
  14171. * the player that this component should attach to
  14172. *
  14173. * @param {Object} [options]
  14174. * Object of option names and values
  14175. *
  14176. */
  14177. function Menu(player, options) {
  14178. var _this;
  14179. _this = _Component.call(this, player, options) || this;
  14180. if (options) {
  14181. _this.menuButton_ = options.menuButton;
  14182. }
  14183. _this.focusedChild_ = -1;
  14184. _this.on('keydown', _this.handleKeyPress); // All the menu item instances share the same blur handler provided by the menu container.
  14185. _this.boundHandleBlur_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleBlur);
  14186. _this.boundHandleTapClick_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTapClick);
  14187. return _this;
  14188. }
  14189. /**
  14190. * Add event listeners to the {@link MenuItem}.
  14191. *
  14192. * @param {Object} component
  14193. * The instance of the `MenuItem` to add listeners to.
  14194. *
  14195. */
  14196. var _proto = Menu.prototype;
  14197. _proto.addEventListenerForItem = function addEventListenerForItem(component) {
  14198. if (!(component instanceof Component)) {
  14199. return;
  14200. }
  14201. this.on(component, 'blur', this.boundHandleBlur_);
  14202. this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
  14203. }
  14204. /**
  14205. * Remove event listeners from the {@link MenuItem}.
  14206. *
  14207. * @param {Object} component
  14208. * The instance of the `MenuItem` to remove listeners.
  14209. *
  14210. */
  14211. ;
  14212. _proto.removeEventListenerForItem = function removeEventListenerForItem(component) {
  14213. if (!(component instanceof Component)) {
  14214. return;
  14215. }
  14216. this.off(component, 'blur', this.boundHandleBlur_);
  14217. this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
  14218. }
  14219. /**
  14220. * This method will be called indirectly when the component has been added
  14221. * before the component adds to the new menu instance by `addItem`.
  14222. * In this case, the original menu instance will remove the component
  14223. * by calling `removeChild`.
  14224. *
  14225. * @param {Object} component
  14226. * The instance of the `MenuItem`
  14227. */
  14228. ;
  14229. _proto.removeChild = function removeChild(component) {
  14230. if (typeof component === 'string') {
  14231. component = this.getChild(component);
  14232. }
  14233. this.removeEventListenerForItem(component);
  14234. _Component.prototype.removeChild.call(this, component);
  14235. }
  14236. /**
  14237. * Add a {@link MenuItem} to the menu.
  14238. *
  14239. * @param {Object|string} component
  14240. * The name or instance of the `MenuItem` to add.
  14241. *
  14242. */
  14243. ;
  14244. _proto.addItem = function addItem(component) {
  14245. var childComponent = this.addChild(component);
  14246. if (childComponent) {
  14247. this.addEventListenerForItem(childComponent);
  14248. }
  14249. }
  14250. /**
  14251. * Create the `Menu`s DOM element.
  14252. *
  14253. * @return {Element}
  14254. * the element that was created
  14255. */
  14256. ;
  14257. _proto.createEl = function createEl$$1() {
  14258. var contentElType = this.options_.contentElType || 'ul';
  14259. this.contentEl_ = createEl(contentElType, {
  14260. className: 'vjs-menu-content'
  14261. });
  14262. this.contentEl_.setAttribute('role', 'menu');
  14263. var el = _Component.prototype.createEl.call(this, 'div', {
  14264. append: this.contentEl_,
  14265. className: 'vjs-menu'
  14266. });
  14267. el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons,
  14268. // where a click on the parent is significant
  14269. on(el, 'click', function (event) {
  14270. event.preventDefault();
  14271. event.stopImmediatePropagation();
  14272. });
  14273. return el;
  14274. };
  14275. _proto.dispose = function dispose() {
  14276. this.contentEl_ = null;
  14277. this.boundHandleBlur_ = null;
  14278. this.boundHandleTapClick_ = null;
  14279. _Component.prototype.dispose.call(this);
  14280. }
  14281. /**
  14282. * Called when a `MenuItem` loses focus.
  14283. *
  14284. * @param {EventTarget~Event} event
  14285. * The `blur` event that caused this function to be called.
  14286. *
  14287. * @listens blur
  14288. */
  14289. ;
  14290. _proto.handleBlur = function handleBlur(event) {
  14291. var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu
  14292. if (!this.children().some(function (element) {
  14293. return element.el() === relatedTarget;
  14294. })) {
  14295. var btn = this.menuButton_;
  14296. if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
  14297. btn.unpressButton();
  14298. }
  14299. }
  14300. }
  14301. /**
  14302. * Called when a `MenuItem` gets clicked or tapped.
  14303. *
  14304. * @param {EventTarget~Event} event
  14305. * The `click` or `tap` event that caused this function to be called.
  14306. *
  14307. * @listens click,tap
  14308. */
  14309. ;
  14310. _proto.handleTapClick = function handleTapClick(event) {
  14311. // Unpress the associated MenuButton, and move focus back to it
  14312. if (this.menuButton_) {
  14313. this.menuButton_.unpressButton();
  14314. var childComponents = this.children();
  14315. if (!Array.isArray(childComponents)) {
  14316. return;
  14317. }
  14318. var foundComponent = childComponents.filter(function (component) {
  14319. return component.el() === event.target;
  14320. })[0];
  14321. if (!foundComponent) {
  14322. return;
  14323. } // don't focus menu button if item is a caption settings item
  14324. // because focus will move elsewhere
  14325. if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
  14326. this.menuButton_.focus();
  14327. }
  14328. }
  14329. }
  14330. /**
  14331. * Handle a `keydown` event on this menu. This listener is added in the constructor.
  14332. *
  14333. * @param {EventTarget~Event} event
  14334. * A `keydown` event that happened on the menu.
  14335. *
  14336. * @listens keydown
  14337. */
  14338. ;
  14339. _proto.handleKeyPress = function handleKeyPress(event) {
  14340. // Left and Down Arrows
  14341. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  14342. event.preventDefault();
  14343. this.stepForward(); // Up and Right Arrows
  14344. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  14345. event.preventDefault();
  14346. this.stepBack();
  14347. }
  14348. }
  14349. /**
  14350. * Move to next (lower) menu item for keyboard users.
  14351. */
  14352. ;
  14353. _proto.stepForward = function stepForward() {
  14354. var stepChild = 0;
  14355. if (this.focusedChild_ !== undefined) {
  14356. stepChild = this.focusedChild_ + 1;
  14357. }
  14358. this.focus(stepChild);
  14359. }
  14360. /**
  14361. * Move to previous (higher) menu item for keyboard users.
  14362. */
  14363. ;
  14364. _proto.stepBack = function stepBack() {
  14365. var stepChild = 0;
  14366. if (this.focusedChild_ !== undefined) {
  14367. stepChild = this.focusedChild_ - 1;
  14368. }
  14369. this.focus(stepChild);
  14370. }
  14371. /**
  14372. * Set focus on a {@link MenuItem} in the `Menu`.
  14373. *
  14374. * @param {Object|string} [item=0]
  14375. * Index of child item set focus on.
  14376. */
  14377. ;
  14378. _proto.focus = function focus(item) {
  14379. if (item === void 0) {
  14380. item = 0;
  14381. }
  14382. var children = this.children().slice();
  14383. var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
  14384. if (haveTitle) {
  14385. children.shift();
  14386. }
  14387. if (children.length > 0) {
  14388. if (item < 0) {
  14389. item = 0;
  14390. } else if (item >= children.length) {
  14391. item = children.length - 1;
  14392. }
  14393. this.focusedChild_ = item;
  14394. children[item].el_.focus();
  14395. }
  14396. };
  14397. return Menu;
  14398. }(Component);
  14399. Component.registerComponent('Menu', Menu);
  14400. /**
  14401. * A `MenuButton` class for any popup {@link Menu}.
  14402. *
  14403. * @extends Component
  14404. */
  14405. var MenuButton =
  14406. /*#__PURE__*/
  14407. function (_Component) {
  14408. _inheritsLoose(MenuButton, _Component);
  14409. /**
  14410. * Creates an instance of this class.
  14411. *
  14412. * @param {Player} player
  14413. * The `Player` that this class should be attached to.
  14414. *
  14415. * @param {Object} [options={}]
  14416. * The key/value store of player options.
  14417. */
  14418. function MenuButton(player, options) {
  14419. var _this;
  14420. if (options === void 0) {
  14421. options = {};
  14422. }
  14423. _this = _Component.call(this, player, options) || this;
  14424. _this.menuButton_ = new Button(player, options);
  14425. _this.menuButton_.controlText(_this.controlText_);
  14426. _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper
  14427. var buttonClass = Button.prototype.buildCSSClass();
  14428. _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
  14429. _this.menuButton_.removeClass('vjs-control');
  14430. _this.addChild(_this.menuButton_);
  14431. _this.update();
  14432. _this.enabled_ = true;
  14433. _this.on(_this.menuButton_, 'tap', _this.handleClick);
  14434. _this.on(_this.menuButton_, 'click', _this.handleClick);
  14435. _this.on(_this.menuButton_, 'focus', _this.handleFocus);
  14436. _this.on(_this.menuButton_, 'blur', _this.handleBlur);
  14437. _this.on(_this.menuButton_, 'mouseenter', function () {
  14438. _this.menu.show();
  14439. });
  14440. _this.on('keydown', _this.handleSubmenuKeyPress);
  14441. return _this;
  14442. }
  14443. /**
  14444. * Update the menu based on the current state of its items.
  14445. */
  14446. var _proto = MenuButton.prototype;
  14447. _proto.update = function update() {
  14448. var menu = this.createMenu();
  14449. if (this.menu) {
  14450. this.menu.dispose();
  14451. this.removeChild(this.menu);
  14452. }
  14453. this.menu = menu;
  14454. this.addChild(menu);
  14455. /**
  14456. * Track the state of the menu button
  14457. *
  14458. * @type {Boolean}
  14459. * @private
  14460. */
  14461. this.buttonPressed_ = false;
  14462. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  14463. if (this.items && this.items.length <= this.hideThreshold_) {
  14464. this.hide();
  14465. } else {
  14466. this.show();
  14467. }
  14468. }
  14469. /**
  14470. * Create the menu and add all items to it.
  14471. *
  14472. * @return {Menu}
  14473. * The constructed menu
  14474. */
  14475. ;
  14476. _proto.createMenu = function createMenu() {
  14477. var menu = new Menu(this.player_, {
  14478. menuButton: this
  14479. });
  14480. /**
  14481. * Hide the menu if the number of items is less than or equal to this threshold. This defaults
  14482. * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
  14483. * it here because every time we run `createMenu` we need to reset the value.
  14484. *
  14485. * @protected
  14486. * @type {Number}
  14487. */
  14488. this.hideThreshold_ = 0; // Add a title list item to the top
  14489. if (this.options_.title) {
  14490. var titleEl = createEl('li', {
  14491. className: 'vjs-menu-title',
  14492. innerHTML: toTitleCase(this.options_.title),
  14493. tabIndex: -1
  14494. });
  14495. this.hideThreshold_ += 1;
  14496. var titleComponent = new Component(this.player_, {
  14497. el: titleEl
  14498. });
  14499. menu.addItem(titleComponent);
  14500. }
  14501. this.items = this.createItems();
  14502. if (this.items) {
  14503. // Add menu items to the menu
  14504. for (var i = 0; i < this.items.length; i++) {
  14505. menu.addItem(this.items[i]);
  14506. }
  14507. }
  14508. return menu;
  14509. }
  14510. /**
  14511. * Create the list of menu items. Specific to each subclass.
  14512. *
  14513. * @abstract
  14514. */
  14515. ;
  14516. _proto.createItems = function createItems() {}
  14517. /**
  14518. * Create the `MenuButtons`s DOM element.
  14519. *
  14520. * @return {Element}
  14521. * The element that gets created.
  14522. */
  14523. ;
  14524. _proto.createEl = function createEl$$1() {
  14525. return _Component.prototype.createEl.call(this, 'div', {
  14526. className: this.buildWrapperCSSClass()
  14527. }, {});
  14528. }
  14529. /**
  14530. * Allow sub components to stack CSS class names for the wrapper element
  14531. *
  14532. * @return {string}
  14533. * The constructed wrapper DOM `className`
  14534. */
  14535. ;
  14536. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  14537. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  14538. if (this.options_.inline === true) {
  14539. menuButtonClass += '-inline';
  14540. } else {
  14541. menuButtonClass += '-popup';
  14542. } // TODO: Fix the CSS so that this isn't necessary
  14543. var buttonClass = Button.prototype.buildCSSClass();
  14544. return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this);
  14545. }
  14546. /**
  14547. * Builds the default DOM `className`.
  14548. *
  14549. * @return {string}
  14550. * The DOM `className` for this object.
  14551. */
  14552. ;
  14553. _proto.buildCSSClass = function buildCSSClass() {
  14554. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  14555. if (this.options_.inline === true) {
  14556. menuButtonClass += '-inline';
  14557. } else {
  14558. menuButtonClass += '-popup';
  14559. }
  14560. return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this);
  14561. }
  14562. /**
  14563. * Get or set the localized control text that will be used for accessibility.
  14564. *
  14565. * > NOTE: This will come from the internal `menuButton_` element.
  14566. *
  14567. * @param {string} [text]
  14568. * Control text for element.
  14569. *
  14570. * @param {Element} [el=this.menuButton_.el()]
  14571. * Element to set the title on.
  14572. *
  14573. * @return {string}
  14574. * - The control text when getting
  14575. */
  14576. ;
  14577. _proto.controlText = function controlText(text, el) {
  14578. if (el === void 0) {
  14579. el = this.menuButton_.el();
  14580. }
  14581. return this.menuButton_.controlText(text, el);
  14582. }
  14583. /**
  14584. * Handle a click on a `MenuButton`.
  14585. * See {@link ClickableComponent#handleClick} for instances where this is called.
  14586. *
  14587. * @param {EventTarget~Event} event
  14588. * The `keydown`, `tap`, or `click` event that caused this function to be
  14589. * called.
  14590. *
  14591. * @listens tap
  14592. * @listens click
  14593. */
  14594. ;
  14595. _proto.handleClick = function handleClick(event) {
  14596. if (this.buttonPressed_) {
  14597. this.unpressButton();
  14598. } else {
  14599. this.pressButton();
  14600. }
  14601. }
  14602. /**
  14603. * Set the focus to the actual button, not to this element
  14604. */
  14605. ;
  14606. _proto.focus = function focus() {
  14607. this.menuButton_.focus();
  14608. }
  14609. /**
  14610. * Remove the focus from the actual button, not this element
  14611. */
  14612. ;
  14613. _proto.blur = function blur() {
  14614. this.menuButton_.blur();
  14615. }
  14616. /**
  14617. * This gets called when a `MenuButton` gains focus via a `focus` event.
  14618. * Turns on listening for `keydown` events. When they happen it
  14619. * calls `this.handleKeyPress`.
  14620. *
  14621. * @param {EventTarget~Event} event
  14622. * The `focus` event that caused this function to be called.
  14623. *
  14624. * @listens focus
  14625. */
  14626. ;
  14627. _proto.handleFocus = function handleFocus() {
  14628. on(document, 'keydown', bind(this, this.handleKeyPress));
  14629. }
  14630. /**
  14631. * Called when a `MenuButton` loses focus. Turns off the listener for
  14632. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  14633. *
  14634. * @param {EventTarget~Event} event
  14635. * The `blur` event that caused this function to be called.
  14636. *
  14637. * @listens blur
  14638. */
  14639. ;
  14640. _proto.handleBlur = function handleBlur() {
  14641. off(document, 'keydown', bind(this, this.handleKeyPress));
  14642. }
  14643. /**
  14644. * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
  14645. * {@link ClickableComponent#handleKeyPress} for instances where this is called.
  14646. *
  14647. * @param {EventTarget~Event} event
  14648. * The `keydown` event that caused this function to be called.
  14649. *
  14650. * @listens keydown
  14651. */
  14652. ;
  14653. _proto.handleKeyPress = function handleKeyPress(event) {
  14654. // Escape or Tab unpress the 'button'
  14655. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  14656. if (this.buttonPressed_) {
  14657. this.unpressButton();
  14658. } // Don't preventDefault for Tab key - we still want to lose focus
  14659. if (!keycode.isEventKey(event, 'Tab')) {
  14660. event.preventDefault(); // Set focus back to the menu button's button
  14661. this.menuButton_.focus();
  14662. } // Up Arrow or Down Arrow also 'press' the button to open the menu
  14663. } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
  14664. if (!this.buttonPressed_) {
  14665. event.preventDefault();
  14666. this.pressButton();
  14667. }
  14668. }
  14669. }
  14670. /**
  14671. * Handle a `keydown` event on a sub-menu. The listener for this is added in
  14672. * the constructor.
  14673. *
  14674. * @param {EventTarget~Event} event
  14675. * Key press event
  14676. *
  14677. * @listens keydown
  14678. */
  14679. ;
  14680. _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
  14681. // Escape or Tab unpress the 'button'
  14682. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  14683. if (this.buttonPressed_) {
  14684. this.unpressButton();
  14685. } // Don't preventDefault for Tab key - we still want to lose focus
  14686. if (!keycode.isEventKey(event, 'Tab')) {
  14687. event.preventDefault(); // Set focus back to the menu button's button
  14688. this.menuButton_.focus();
  14689. }
  14690. }
  14691. }
  14692. /**
  14693. * Put the current `MenuButton` into a pressed state.
  14694. */
  14695. ;
  14696. _proto.pressButton = function pressButton() {
  14697. if (this.enabled_) {
  14698. this.buttonPressed_ = true;
  14699. this.menu.show();
  14700. this.menu.lockShowing();
  14701. this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in
  14702. // undesired scrolling behavior when the player is in an iframe
  14703. if (IS_IOS && isInFrame()) {
  14704. // Return early so that the menu isn't focused
  14705. return;
  14706. }
  14707. this.menu.focus();
  14708. }
  14709. }
  14710. /**
  14711. * Take the current `MenuButton` out of a pressed state.
  14712. */
  14713. ;
  14714. _proto.unpressButton = function unpressButton() {
  14715. if (this.enabled_) {
  14716. this.buttonPressed_ = false;
  14717. this.menu.unlockShowing();
  14718. this.menu.hide();
  14719. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  14720. }
  14721. }
  14722. /**
  14723. * Disable the `MenuButton`. Don't allow it to be clicked.
  14724. */
  14725. ;
  14726. _proto.disable = function disable() {
  14727. this.unpressButton();
  14728. this.enabled_ = false;
  14729. this.addClass('vjs-disabled');
  14730. this.menuButton_.disable();
  14731. }
  14732. /**
  14733. * Enable the `MenuButton`. Allow it to be clicked.
  14734. */
  14735. ;
  14736. _proto.enable = function enable() {
  14737. this.enabled_ = true;
  14738. this.removeClass('vjs-disabled');
  14739. this.menuButton_.enable();
  14740. };
  14741. return MenuButton;
  14742. }(Component);
  14743. Component.registerComponent('MenuButton', MenuButton);
  14744. /**
  14745. * The base class for buttons that toggle specific track types (e.g. subtitles).
  14746. *
  14747. * @extends MenuButton
  14748. */
  14749. var TrackButton =
  14750. /*#__PURE__*/
  14751. function (_MenuButton) {
  14752. _inheritsLoose(TrackButton, _MenuButton);
  14753. /**
  14754. * Creates an instance of this class.
  14755. *
  14756. * @param {Player} player
  14757. * The `Player` that this class should be attached to.
  14758. *
  14759. * @param {Object} [options]
  14760. * The key/value store of player options.
  14761. */
  14762. function TrackButton(player, options) {
  14763. var _this;
  14764. var tracks = options.tracks;
  14765. _this = _MenuButton.call(this, player, options) || this;
  14766. if (_this.items.length <= 1) {
  14767. _this.hide();
  14768. }
  14769. if (!tracks) {
  14770. return _assertThisInitialized(_this);
  14771. }
  14772. var updateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update);
  14773. tracks.addEventListener('removetrack', updateHandler);
  14774. tracks.addEventListener('addtrack', updateHandler);
  14775. _this.player_.on('ready', updateHandler);
  14776. _this.player_.on('dispose', function () {
  14777. tracks.removeEventListener('removetrack', updateHandler);
  14778. tracks.removeEventListener('addtrack', updateHandler);
  14779. });
  14780. return _this;
  14781. }
  14782. return TrackButton;
  14783. }(MenuButton);
  14784. Component.registerComponent('TrackButton', TrackButton);
  14785. /**
  14786. * @file menu-keys.js
  14787. */
  14788. /**
  14789. * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
  14790. * Note that 'Enter' and 'Space' are not included here (otherwise they would
  14791. * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
  14792. * @typedef MenuKeys
  14793. * @array
  14794. */
  14795. var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
  14796. /**
  14797. * The component for a menu item. `<li>`
  14798. *
  14799. * @extends ClickableComponent
  14800. */
  14801. var MenuItem =
  14802. /*#__PURE__*/
  14803. function (_ClickableComponent) {
  14804. _inheritsLoose(MenuItem, _ClickableComponent);
  14805. /**
  14806. * Creates an instance of the this class.
  14807. *
  14808. * @param {Player} player
  14809. * The `Player` that this class should be attached to.
  14810. *
  14811. * @param {Object} [options={}]
  14812. * The key/value store of player options.
  14813. *
  14814. */
  14815. function MenuItem(player, options) {
  14816. var _this;
  14817. _this = _ClickableComponent.call(this, player, options) || this;
  14818. _this.selectable = options.selectable;
  14819. _this.isSelected_ = options.selected || false;
  14820. _this.multiSelectable = options.multiSelectable;
  14821. _this.selected(_this.isSelected_);
  14822. if (_this.selectable) {
  14823. if (_this.multiSelectable) {
  14824. _this.el_.setAttribute('role', 'menuitemcheckbox');
  14825. } else {
  14826. _this.el_.setAttribute('role', 'menuitemradio');
  14827. }
  14828. } else {
  14829. _this.el_.setAttribute('role', 'menuitem');
  14830. }
  14831. return _this;
  14832. }
  14833. /**
  14834. * Create the `MenuItem's DOM element
  14835. *
  14836. * @param {string} [type=li]
  14837. * Element's node type, not actually used, always set to `li`.
  14838. *
  14839. * @param {Object} [props={}]
  14840. * An object of properties that should be set on the element
  14841. *
  14842. * @param {Object} [attrs={}]
  14843. * An object of attributes that should be set on the element
  14844. *
  14845. * @return {Element}
  14846. * The element that gets created.
  14847. */
  14848. var _proto = MenuItem.prototype;
  14849. _proto.createEl = function createEl(type, props, attrs) {
  14850. // The control is textual, not just an icon
  14851. this.nonIconControl = true;
  14852. return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
  14853. className: 'vjs-menu-item',
  14854. innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>",
  14855. tabIndex: -1
  14856. }, props), attrs);
  14857. }
  14858. /**
  14859. * Ignore keys which are used by the menu, but pass any other ones up. See
  14860. * {@link ClickableComponent#handleKeyPress} for instances where this is called.
  14861. *
  14862. * @param {EventTarget~Event} event
  14863. * The `keydown` event that caused this function to be called.
  14864. *
  14865. * @listens keydown
  14866. */
  14867. ;
  14868. _proto.handleKeyPress = function handleKeyPress(event) {
  14869. if (!MenuKeys.some(function (key) {
  14870. return keycode.isEventKey(event, key);
  14871. })) {
  14872. // Pass keypress handling up for unused keys
  14873. _ClickableComponent.prototype.handleKeyPress.call(this, event);
  14874. }
  14875. }
  14876. /**
  14877. * Any click on a `MenuItem` puts it into the selected state.
  14878. * See {@link ClickableComponent#handleClick} for instances where this is called.
  14879. *
  14880. * @param {EventTarget~Event} event
  14881. * The `keydown`, `tap`, or `click` event that caused this function to be
  14882. * called.
  14883. *
  14884. * @listens tap
  14885. * @listens click
  14886. */
  14887. ;
  14888. _proto.handleClick = function handleClick(event) {
  14889. this.selected(true);
  14890. }
  14891. /**
  14892. * Set the state for this menu item as selected or not.
  14893. *
  14894. * @param {boolean} selected
  14895. * if the menu item is selected or not
  14896. */
  14897. ;
  14898. _proto.selected = function selected(_selected) {
  14899. if (this.selectable) {
  14900. if (_selected) {
  14901. this.addClass('vjs-selected');
  14902. this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers,
  14903. // so indicate selected state to screen reader in the control text.
  14904. this.controlText(', selected');
  14905. this.isSelected_ = true;
  14906. } else {
  14907. this.removeClass('vjs-selected');
  14908. this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader
  14909. this.controlText('');
  14910. this.isSelected_ = false;
  14911. }
  14912. }
  14913. };
  14914. return MenuItem;
  14915. }(ClickableComponent);
  14916. Component.registerComponent('MenuItem', MenuItem);
  14917. /**
  14918. * The specific menu item type for selecting a language within a text track kind
  14919. *
  14920. * @extends MenuItem
  14921. */
  14922. var TextTrackMenuItem =
  14923. /*#__PURE__*/
  14924. function (_MenuItem) {
  14925. _inheritsLoose(TextTrackMenuItem, _MenuItem);
  14926. /**
  14927. * Creates an instance of this class.
  14928. *
  14929. * @param {Player} player
  14930. * The `Player` that this class should be attached to.
  14931. *
  14932. * @param {Object} [options]
  14933. * The key/value store of player options.
  14934. */
  14935. function TextTrackMenuItem(player, options) {
  14936. var _this;
  14937. var track = options.track;
  14938. var tracks = player.textTracks(); // Modify options for parent MenuItem class's init.
  14939. options.label = track.label || track.language || 'Unknown';
  14940. options.selected = track.mode === 'showing';
  14941. _this = _MenuItem.call(this, player, options) || this;
  14942. _this.track = track;
  14943. var changeHandler = function changeHandler() {
  14944. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  14945. args[_key] = arguments[_key];
  14946. }
  14947. _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  14948. };
  14949. var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
  14950. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  14951. args[_key2] = arguments[_key2];
  14952. }
  14953. _this.handleSelectedLanguageChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  14954. };
  14955. player.on(['loadstart', 'texttrackchange'], changeHandler);
  14956. tracks.addEventListener('change', changeHandler);
  14957. tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  14958. _this.on('dispose', function () {
  14959. player.off(['loadstart', 'texttrackchange'], changeHandler);
  14960. tracks.removeEventListener('change', changeHandler);
  14961. tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  14962. }); // iOS7 doesn't dispatch change events to TextTrackLists when an
  14963. // associated track's mode changes. Without something like
  14964. // Object.observe() (also not present on iOS7), it's not
  14965. // possible to detect changes to the mode attribute and polyfill
  14966. // the change event. As a poor substitute, we manually dispatch
  14967. // change events whenever the controls modify the mode.
  14968. if (tracks.onchange === undefined) {
  14969. var event;
  14970. _this.on(['tap', 'click'], function () {
  14971. if (typeof window$1.Event !== 'object') {
  14972. // Android 2.3 throws an Illegal Constructor error for window.Event
  14973. try {
  14974. event = new window$1.Event('change');
  14975. } catch (err) {// continue regardless of error
  14976. }
  14977. }
  14978. if (!event) {
  14979. event = document.createEvent('Event');
  14980. event.initEvent('change', true, true);
  14981. }
  14982. tracks.dispatchEvent(event);
  14983. });
  14984. } // set the default state based on current tracks
  14985. _this.handleTracksChange();
  14986. return _this;
  14987. }
  14988. /**
  14989. * This gets called when an `TextTrackMenuItem` is "clicked". See
  14990. * {@link ClickableComponent} for more detailed information on what a click can be.
  14991. *
  14992. * @param {EventTarget~Event} event
  14993. * The `keydown`, `tap`, or `click` event that caused this function to be
  14994. * called.
  14995. *
  14996. * @listens tap
  14997. * @listens click
  14998. */
  14999. var _proto = TextTrackMenuItem.prototype;
  15000. _proto.handleClick = function handleClick(event) {
  15001. var referenceTrack = this.track;
  15002. var tracks = this.player_.textTracks();
  15003. _MenuItem.prototype.handleClick.call(this, event);
  15004. if (!tracks) {
  15005. return;
  15006. } // Determine the relevant kind(s) of tracks for this component and filter
  15007. // out empty kinds.
  15008. var kinds = (referenceTrack.kinds || [referenceTrack.kind]).filter(Boolean);
  15009. for (var i = 0; i < tracks.length; i++) {
  15010. var track = tracks[i]; // If the track from the text tracks list is not of the right kind,
  15011. // skip it. We do not want to affect tracks of incompatible kind(s).
  15012. if (kinds.indexOf(track.kind) === -1) {
  15013. continue;
  15014. } // If this text track is the component's track and it is not showing,
  15015. // set it to showing.
  15016. if (track === referenceTrack) {
  15017. if (track.mode !== 'showing') {
  15018. track.mode = 'showing';
  15019. } // If this text track is not the component's track and it is not
  15020. // disabled, set it to disabled.
  15021. } else if (track.mode !== 'disabled') {
  15022. track.mode = 'disabled';
  15023. }
  15024. }
  15025. }
  15026. /**
  15027. * Handle text track list change
  15028. *
  15029. * @param {EventTarget~Event} event
  15030. * The `change` event that caused this function to be called.
  15031. *
  15032. * @listens TextTrackList#change
  15033. */
  15034. ;
  15035. _proto.handleTracksChange = function handleTracksChange(event) {
  15036. var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause
  15037. // screen readers to read the appended control text unnecessarily
  15038. if (shouldBeSelected !== this.isSelected_) {
  15039. this.selected(shouldBeSelected);
  15040. }
  15041. };
  15042. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  15043. if (this.track.mode === 'showing') {
  15044. var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language
  15045. if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
  15046. return;
  15047. }
  15048. this.player_.cache_.selectedLanguage = {
  15049. enabled: true,
  15050. language: this.track.language,
  15051. kind: this.track.kind
  15052. };
  15053. }
  15054. };
  15055. _proto.dispose = function dispose() {
  15056. // remove reference to track object on dispose
  15057. this.track = null;
  15058. _MenuItem.prototype.dispose.call(this);
  15059. };
  15060. return TextTrackMenuItem;
  15061. }(MenuItem);
  15062. Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
  15063. /**
  15064. * A special menu item for turning of a specific type of text track
  15065. *
  15066. * @extends TextTrackMenuItem
  15067. */
  15068. var OffTextTrackMenuItem =
  15069. /*#__PURE__*/
  15070. function (_TextTrackMenuItem) {
  15071. _inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
  15072. /**
  15073. * Creates an instance of this class.
  15074. *
  15075. * @param {Player} player
  15076. * The `Player` that this class should be attached to.
  15077. *
  15078. * @param {Object} [options]
  15079. * The key/value store of player options.
  15080. */
  15081. function OffTextTrackMenuItem(player, options) {
  15082. // Create pseudo track info
  15083. // Requires options['kind']
  15084. options.track = {
  15085. player: player,
  15086. kind: options.kind,
  15087. kinds: options.kinds,
  15088. default: false,
  15089. mode: 'disabled'
  15090. };
  15091. if (!options.kinds) {
  15092. options.kinds = [options.kind];
  15093. }
  15094. if (options.label) {
  15095. options.track.label = options.label;
  15096. } else {
  15097. options.track.label = options.kinds.join(' and ') + ' off';
  15098. } // MenuItem is selectable
  15099. options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  15100. options.multiSelectable = false;
  15101. return _TextTrackMenuItem.call(this, player, options) || this;
  15102. }
  15103. /**
  15104. * Handle text track change
  15105. *
  15106. * @param {EventTarget~Event} event
  15107. * The event that caused this function to run
  15108. */
  15109. var _proto = OffTextTrackMenuItem.prototype;
  15110. _proto.handleTracksChange = function handleTracksChange(event) {
  15111. var tracks = this.player().textTracks();
  15112. var shouldBeSelected = true;
  15113. for (var i = 0, l = tracks.length; i < l; i++) {
  15114. var track = tracks[i];
  15115. if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
  15116. shouldBeSelected = false;
  15117. break;
  15118. }
  15119. } // Prevent redundant selected() calls because they may cause
  15120. // screen readers to read the appended control text unnecessarily
  15121. if (shouldBeSelected !== this.isSelected_) {
  15122. this.selected(shouldBeSelected);
  15123. }
  15124. };
  15125. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  15126. var tracks = this.player().textTracks();
  15127. var allHidden = true;
  15128. for (var i = 0, l = tracks.length; i < l; i++) {
  15129. var track = tracks[i];
  15130. if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
  15131. allHidden = false;
  15132. break;
  15133. }
  15134. }
  15135. if (allHidden) {
  15136. this.player_.cache_.selectedLanguage = {
  15137. enabled: false
  15138. };
  15139. }
  15140. };
  15141. return OffTextTrackMenuItem;
  15142. }(TextTrackMenuItem);
  15143. Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
  15144. /**
  15145. * The base class for buttons that toggle specific text track types (e.g. subtitles)
  15146. *
  15147. * @extends MenuButton
  15148. */
  15149. var TextTrackButton =
  15150. /*#__PURE__*/
  15151. function (_TrackButton) {
  15152. _inheritsLoose(TextTrackButton, _TrackButton);
  15153. /**
  15154. * Creates an instance of this class.
  15155. *
  15156. * @param {Player} player
  15157. * The `Player` that this class should be attached to.
  15158. *
  15159. * @param {Object} [options={}]
  15160. * The key/value store of player options.
  15161. */
  15162. function TextTrackButton(player, options) {
  15163. if (options === void 0) {
  15164. options = {};
  15165. }
  15166. options.tracks = player.textTracks();
  15167. return _TrackButton.call(this, player, options) || this;
  15168. }
  15169. /**
  15170. * Create a menu item for each text track
  15171. *
  15172. * @param {TextTrackMenuItem[]} [items=[]]
  15173. * Existing array of items to use during creation
  15174. *
  15175. * @return {TextTrackMenuItem[]}
  15176. * Array of menu items that were created
  15177. */
  15178. var _proto = TextTrackButton.prototype;
  15179. _proto.createItems = function createItems(items, TrackMenuItem) {
  15180. if (items === void 0) {
  15181. items = [];
  15182. }
  15183. if (TrackMenuItem === void 0) {
  15184. TrackMenuItem = TextTrackMenuItem;
  15185. }
  15186. // Label is an override for the [track] off label
  15187. // USed to localise captions/subtitles
  15188. var label;
  15189. if (this.label_) {
  15190. label = this.label_ + " off";
  15191. } // Add an OFF menu item to turn all tracks off
  15192. items.push(new OffTextTrackMenuItem(this.player_, {
  15193. kinds: this.kinds_,
  15194. kind: this.kind_,
  15195. label: label
  15196. }));
  15197. this.hideThreshold_ += 1;
  15198. var tracks = this.player_.textTracks();
  15199. if (!Array.isArray(this.kinds_)) {
  15200. this.kinds_ = [this.kind_];
  15201. }
  15202. for (var i = 0; i < tracks.length; i++) {
  15203. var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label
  15204. if (this.kinds_.indexOf(track.kind) > -1) {
  15205. var item = new TrackMenuItem(this.player_, {
  15206. track: track,
  15207. // MenuItem is selectable
  15208. selectable: true,
  15209. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  15210. multiSelectable: false
  15211. });
  15212. item.addClass("vjs-" + track.kind + "-menu-item");
  15213. items.push(item);
  15214. }
  15215. }
  15216. return items;
  15217. };
  15218. return TextTrackButton;
  15219. }(TrackButton);
  15220. Component.registerComponent('TextTrackButton', TextTrackButton);
  15221. /**
  15222. * The chapter track menu item
  15223. *
  15224. * @extends MenuItem
  15225. */
  15226. var ChaptersTrackMenuItem =
  15227. /*#__PURE__*/
  15228. function (_MenuItem) {
  15229. _inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
  15230. /**
  15231. * Creates an instance of this class.
  15232. *
  15233. * @param {Player} player
  15234. * The `Player` that this class should be attached to.
  15235. *
  15236. * @param {Object} [options]
  15237. * The key/value store of player options.
  15238. */
  15239. function ChaptersTrackMenuItem(player, options) {
  15240. var _this;
  15241. var track = options.track;
  15242. var cue = options.cue;
  15243. var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init.
  15244. options.selectable = true;
  15245. options.multiSelectable = false;
  15246. options.label = cue.text;
  15247. options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
  15248. _this = _MenuItem.call(this, player, options) || this;
  15249. _this.track = track;
  15250. _this.cue = cue;
  15251. track.addEventListener('cuechange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update));
  15252. return _this;
  15253. }
  15254. /**
  15255. * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
  15256. * {@link ClickableComponent} for more detailed information on what a click can be.
  15257. *
  15258. * @param {EventTarget~Event} [event]
  15259. * The `keydown`, `tap`, or `click` event that caused this function to be
  15260. * called.
  15261. *
  15262. * @listens tap
  15263. * @listens click
  15264. */
  15265. var _proto = ChaptersTrackMenuItem.prototype;
  15266. _proto.handleClick = function handleClick(event) {
  15267. _MenuItem.prototype.handleClick.call(this);
  15268. this.player_.currentTime(this.cue.startTime);
  15269. this.update(this.cue.startTime);
  15270. }
  15271. /**
  15272. * Update chapter menu item
  15273. *
  15274. * @param {EventTarget~Event} [event]
  15275. * The `cuechange` event that caused this function to run.
  15276. *
  15277. * @listens TextTrack#cuechange
  15278. */
  15279. ;
  15280. _proto.update = function update(event) {
  15281. var cue = this.cue;
  15282. var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime);
  15283. this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
  15284. };
  15285. return ChaptersTrackMenuItem;
  15286. }(MenuItem);
  15287. Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
  15288. /**
  15289. * The button component for toggling and selecting chapters
  15290. * Chapters act much differently than other text tracks
  15291. * Cues are navigation vs. other tracks of alternative languages
  15292. *
  15293. * @extends TextTrackButton
  15294. */
  15295. var ChaptersButton =
  15296. /*#__PURE__*/
  15297. function (_TextTrackButton) {
  15298. _inheritsLoose(ChaptersButton, _TextTrackButton);
  15299. /**
  15300. * Creates an instance of this class.
  15301. *
  15302. * @param {Player} player
  15303. * The `Player` that this class should be attached to.
  15304. *
  15305. * @param {Object} [options]
  15306. * The key/value store of player options.
  15307. *
  15308. * @param {Component~ReadyCallback} [ready]
  15309. * The function to call when this function is ready.
  15310. */
  15311. function ChaptersButton(player, options, ready) {
  15312. return _TextTrackButton.call(this, player, options, ready) || this;
  15313. }
  15314. /**
  15315. * Builds the default DOM `className`.
  15316. *
  15317. * @return {string}
  15318. * The DOM `className` for this object.
  15319. */
  15320. var _proto = ChaptersButton.prototype;
  15321. _proto.buildCSSClass = function buildCSSClass() {
  15322. return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  15323. };
  15324. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15325. return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  15326. }
  15327. /**
  15328. * Update the menu based on the current state of its items.
  15329. *
  15330. * @param {EventTarget~Event} [event]
  15331. * An event that triggered this function to run.
  15332. *
  15333. * @listens TextTrackList#addtrack
  15334. * @listens TextTrackList#removetrack
  15335. * @listens TextTrackList#change
  15336. */
  15337. ;
  15338. _proto.update = function update(event) {
  15339. if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
  15340. this.setTrack(this.findChaptersTrack());
  15341. }
  15342. _TextTrackButton.prototype.update.call(this);
  15343. }
  15344. /**
  15345. * Set the currently selected track for the chapters button.
  15346. *
  15347. * @param {TextTrack} track
  15348. * The new track to select. Nothing will change if this is the currently selected
  15349. * track.
  15350. */
  15351. ;
  15352. _proto.setTrack = function setTrack(track) {
  15353. if (this.track_ === track) {
  15354. return;
  15355. }
  15356. if (!this.updateHandler_) {
  15357. this.updateHandler_ = this.update.bind(this);
  15358. } // here this.track_ refers to the old track instance
  15359. if (this.track_) {
  15360. var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  15361. if (remoteTextTrackEl) {
  15362. remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
  15363. }
  15364. this.track_ = null;
  15365. }
  15366. this.track_ = track; // here this.track_ refers to the new track instance
  15367. if (this.track_) {
  15368. this.track_.mode = 'hidden';
  15369. var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  15370. if (_remoteTextTrackEl) {
  15371. _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
  15372. }
  15373. }
  15374. }
  15375. /**
  15376. * Find the track object that is currently in use by this ChaptersButton
  15377. *
  15378. * @return {TextTrack|undefined}
  15379. * The current track or undefined if none was found.
  15380. */
  15381. ;
  15382. _proto.findChaptersTrack = function findChaptersTrack() {
  15383. var tracks = this.player_.textTracks() || [];
  15384. for (var i = tracks.length - 1; i >= 0; i--) {
  15385. // We will always choose the last track as our chaptersTrack
  15386. var track = tracks[i];
  15387. if (track.kind === this.kind_) {
  15388. return track;
  15389. }
  15390. }
  15391. }
  15392. /**
  15393. * Get the caption for the ChaptersButton based on the track label. This will also
  15394. * use the current tracks localized kind as a fallback if a label does not exist.
  15395. *
  15396. * @return {string}
  15397. * The tracks current label or the localized track kind.
  15398. */
  15399. ;
  15400. _proto.getMenuCaption = function getMenuCaption() {
  15401. if (this.track_ && this.track_.label) {
  15402. return this.track_.label;
  15403. }
  15404. return this.localize(toTitleCase(this.kind_));
  15405. }
  15406. /**
  15407. * Create menu from chapter track
  15408. *
  15409. * @return {Menu}
  15410. * New menu for the chapter buttons
  15411. */
  15412. ;
  15413. _proto.createMenu = function createMenu() {
  15414. this.options_.title = this.getMenuCaption();
  15415. return _TextTrackButton.prototype.createMenu.call(this);
  15416. }
  15417. /**
  15418. * Create a menu item for each text track
  15419. *
  15420. * @return {TextTrackMenuItem[]}
  15421. * Array of menu items
  15422. */
  15423. ;
  15424. _proto.createItems = function createItems() {
  15425. var items = [];
  15426. if (!this.track_) {
  15427. return items;
  15428. }
  15429. var cues = this.track_.cues;
  15430. if (!cues) {
  15431. return items;
  15432. }
  15433. for (var i = 0, l = cues.length; i < l; i++) {
  15434. var cue = cues[i];
  15435. var mi = new ChaptersTrackMenuItem(this.player_, {
  15436. track: this.track_,
  15437. cue: cue
  15438. });
  15439. items.push(mi);
  15440. }
  15441. return items;
  15442. };
  15443. return ChaptersButton;
  15444. }(TextTrackButton);
  15445. /**
  15446. * `kind` of TextTrack to look for to associate it with this menu.
  15447. *
  15448. * @type {string}
  15449. * @private
  15450. */
  15451. ChaptersButton.prototype.kind_ = 'chapters';
  15452. /**
  15453. * The text that should display over the `ChaptersButton`s controls. Added for localization.
  15454. *
  15455. * @type {string}
  15456. * @private
  15457. */
  15458. ChaptersButton.prototype.controlText_ = 'Chapters';
  15459. Component.registerComponent('ChaptersButton', ChaptersButton);
  15460. /**
  15461. * The button component for toggling and selecting descriptions
  15462. *
  15463. * @extends TextTrackButton
  15464. */
  15465. var DescriptionsButton =
  15466. /*#__PURE__*/
  15467. function (_TextTrackButton) {
  15468. _inheritsLoose(DescriptionsButton, _TextTrackButton);
  15469. /**
  15470. * Creates an instance of this class.
  15471. *
  15472. * @param {Player} player
  15473. * The `Player` that this class should be attached to.
  15474. *
  15475. * @param {Object} [options]
  15476. * The key/value store of player options.
  15477. *
  15478. * @param {Component~ReadyCallback} [ready]
  15479. * The function to call when this component is ready.
  15480. */
  15481. function DescriptionsButton(player, options, ready) {
  15482. var _this;
  15483. _this = _TextTrackButton.call(this, player, options, ready) || this;
  15484. var tracks = player.textTracks();
  15485. var changeHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTracksChange);
  15486. tracks.addEventListener('change', changeHandler);
  15487. _this.on('dispose', function () {
  15488. tracks.removeEventListener('change', changeHandler);
  15489. });
  15490. return _this;
  15491. }
  15492. /**
  15493. * Handle text track change
  15494. *
  15495. * @param {EventTarget~Event} event
  15496. * The event that caused this function to run
  15497. *
  15498. * @listens TextTrackList#change
  15499. */
  15500. var _proto = DescriptionsButton.prototype;
  15501. _proto.handleTracksChange = function handleTracksChange(event) {
  15502. var tracks = this.player().textTracks();
  15503. var disabled = false; // Check whether a track of a different kind is showing
  15504. for (var i = 0, l = tracks.length; i < l; i++) {
  15505. var track = tracks[i];
  15506. if (track.kind !== this.kind_ && track.mode === 'showing') {
  15507. disabled = true;
  15508. break;
  15509. }
  15510. } // If another track is showing, disable this menu button
  15511. if (disabled) {
  15512. this.disable();
  15513. } else {
  15514. this.enable();
  15515. }
  15516. }
  15517. /**
  15518. * Builds the default DOM `className`.
  15519. *
  15520. * @return {string}
  15521. * The DOM `className` for this object.
  15522. */
  15523. ;
  15524. _proto.buildCSSClass = function buildCSSClass() {
  15525. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  15526. };
  15527. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15528. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  15529. };
  15530. return DescriptionsButton;
  15531. }(TextTrackButton);
  15532. /**
  15533. * `kind` of TextTrack to look for to associate it with this menu.
  15534. *
  15535. * @type {string}
  15536. * @private
  15537. */
  15538. DescriptionsButton.prototype.kind_ = 'descriptions';
  15539. /**
  15540. * The text that should display over the `DescriptionsButton`s controls. Added for localization.
  15541. *
  15542. * @type {string}
  15543. * @private
  15544. */
  15545. DescriptionsButton.prototype.controlText_ = 'Descriptions';
  15546. Component.registerComponent('DescriptionsButton', DescriptionsButton);
  15547. /**
  15548. * The button component for toggling and selecting subtitles
  15549. *
  15550. * @extends TextTrackButton
  15551. */
  15552. var SubtitlesButton =
  15553. /*#__PURE__*/
  15554. function (_TextTrackButton) {
  15555. _inheritsLoose(SubtitlesButton, _TextTrackButton);
  15556. /**
  15557. * Creates an instance of this class.
  15558. *
  15559. * @param {Player} player
  15560. * The `Player` that this class should be attached to.
  15561. *
  15562. * @param {Object} [options]
  15563. * The key/value store of player options.
  15564. *
  15565. * @param {Component~ReadyCallback} [ready]
  15566. * The function to call when this component is ready.
  15567. */
  15568. function SubtitlesButton(player, options, ready) {
  15569. return _TextTrackButton.call(this, player, options, ready) || this;
  15570. }
  15571. /**
  15572. * Builds the default DOM `className`.
  15573. *
  15574. * @return {string}
  15575. * The DOM `className` for this object.
  15576. */
  15577. var _proto = SubtitlesButton.prototype;
  15578. _proto.buildCSSClass = function buildCSSClass() {
  15579. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  15580. };
  15581. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15582. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  15583. };
  15584. return SubtitlesButton;
  15585. }(TextTrackButton);
  15586. /**
  15587. * `kind` of TextTrack to look for to associate it with this menu.
  15588. *
  15589. * @type {string}
  15590. * @private
  15591. */
  15592. SubtitlesButton.prototype.kind_ = 'subtitles';
  15593. /**
  15594. * The text that should display over the `SubtitlesButton`s controls. Added for localization.
  15595. *
  15596. * @type {string}
  15597. * @private
  15598. */
  15599. SubtitlesButton.prototype.controlText_ = 'Subtitles';
  15600. Component.registerComponent('SubtitlesButton', SubtitlesButton);
  15601. /**
  15602. * The menu item for caption track settings menu
  15603. *
  15604. * @extends TextTrackMenuItem
  15605. */
  15606. var CaptionSettingsMenuItem =
  15607. /*#__PURE__*/
  15608. function (_TextTrackMenuItem) {
  15609. _inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
  15610. /**
  15611. * Creates an instance of this class.
  15612. *
  15613. * @param {Player} player
  15614. * The `Player` that this class should be attached to.
  15615. *
  15616. * @param {Object} [options]
  15617. * The key/value store of player options.
  15618. */
  15619. function CaptionSettingsMenuItem(player, options) {
  15620. var _this;
  15621. options.track = {
  15622. player: player,
  15623. kind: options.kind,
  15624. label: options.kind + ' settings',
  15625. selectable: false,
  15626. default: false,
  15627. mode: 'disabled'
  15628. }; // CaptionSettingsMenuItem has no concept of 'selected'
  15629. options.selectable = false;
  15630. options.name = 'CaptionSettingsMenuItem';
  15631. _this = _TextTrackMenuItem.call(this, player, options) || this;
  15632. _this.addClass('vjs-texttrack-settings');
  15633. _this.controlText(', opens ' + options.kind + ' settings dialog');
  15634. return _this;
  15635. }
  15636. /**
  15637. * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
  15638. * {@link ClickableComponent} for more detailed information on what a click can be.
  15639. *
  15640. * @param {EventTarget~Event} [event]
  15641. * The `keydown`, `tap`, or `click` event that caused this function to be
  15642. * called.
  15643. *
  15644. * @listens tap
  15645. * @listens click
  15646. */
  15647. var _proto = CaptionSettingsMenuItem.prototype;
  15648. _proto.handleClick = function handleClick(event) {
  15649. this.player().getChild('textTrackSettings').open();
  15650. };
  15651. return CaptionSettingsMenuItem;
  15652. }(TextTrackMenuItem);
  15653. Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
  15654. /**
  15655. * The button component for toggling and selecting captions
  15656. *
  15657. * @extends TextTrackButton
  15658. */
  15659. var CaptionsButton =
  15660. /*#__PURE__*/
  15661. function (_TextTrackButton) {
  15662. _inheritsLoose(CaptionsButton, _TextTrackButton);
  15663. /**
  15664. * Creates an instance of this class.
  15665. *
  15666. * @param {Player} player
  15667. * The `Player` that this class should be attached to.
  15668. *
  15669. * @param {Object} [options]
  15670. * The key/value store of player options.
  15671. *
  15672. * @param {Component~ReadyCallback} [ready]
  15673. * The function to call when this component is ready.
  15674. */
  15675. function CaptionsButton(player, options, ready) {
  15676. return _TextTrackButton.call(this, player, options, ready) || this;
  15677. }
  15678. /**
  15679. * Builds the default DOM `className`.
  15680. *
  15681. * @return {string}
  15682. * The DOM `className` for this object.
  15683. */
  15684. var _proto = CaptionsButton.prototype;
  15685. _proto.buildCSSClass = function buildCSSClass() {
  15686. return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  15687. };
  15688. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15689. return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  15690. }
  15691. /**
  15692. * Create caption menu items
  15693. *
  15694. * @return {CaptionSettingsMenuItem[]}
  15695. * The array of current menu items.
  15696. */
  15697. ;
  15698. _proto.createItems = function createItems() {
  15699. var items = [];
  15700. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  15701. items.push(new CaptionSettingsMenuItem(this.player_, {
  15702. kind: this.kind_
  15703. }));
  15704. this.hideThreshold_ += 1;
  15705. }
  15706. return _TextTrackButton.prototype.createItems.call(this, items);
  15707. };
  15708. return CaptionsButton;
  15709. }(TextTrackButton);
  15710. /**
  15711. * `kind` of TextTrack to look for to associate it with this menu.
  15712. *
  15713. * @type {string}
  15714. * @private
  15715. */
  15716. CaptionsButton.prototype.kind_ = 'captions';
  15717. /**
  15718. * The text that should display over the `CaptionsButton`s controls. Added for localization.
  15719. *
  15720. * @type {string}
  15721. * @private
  15722. */
  15723. CaptionsButton.prototype.controlText_ = 'Captions';
  15724. Component.registerComponent('CaptionsButton', CaptionsButton);
  15725. /**
  15726. * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
  15727. * in the SubsCapsMenu.
  15728. *
  15729. * @extends TextTrackMenuItem
  15730. */
  15731. var SubsCapsMenuItem =
  15732. /*#__PURE__*/
  15733. function (_TextTrackMenuItem) {
  15734. _inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
  15735. function SubsCapsMenuItem() {
  15736. return _TextTrackMenuItem.apply(this, arguments) || this;
  15737. }
  15738. var _proto = SubsCapsMenuItem.prototype;
  15739. _proto.createEl = function createEl(type, props, attrs) {
  15740. var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
  15741. if (this.options_.track.kind === 'captions') {
  15742. innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n ";
  15743. }
  15744. innerHTML += '</span>';
  15745. var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
  15746. innerHTML: innerHTML
  15747. }, props), attrs);
  15748. return el;
  15749. };
  15750. return SubsCapsMenuItem;
  15751. }(TextTrackMenuItem);
  15752. Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
  15753. /**
  15754. * The button component for toggling and selecting captions and/or subtitles
  15755. *
  15756. * @extends TextTrackButton
  15757. */
  15758. var SubsCapsButton =
  15759. /*#__PURE__*/
  15760. function (_TextTrackButton) {
  15761. _inheritsLoose(SubsCapsButton, _TextTrackButton);
  15762. function SubsCapsButton(player, options) {
  15763. var _this;
  15764. if (options === void 0) {
  15765. options = {};
  15766. }
  15767. _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for
  15768. // "captions and subtitles" other locales use "subtitles"
  15769. _this.label_ = 'subtitles';
  15770. if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
  15771. _this.label_ = 'captions';
  15772. }
  15773. _this.menuButton_.controlText(toTitleCase(_this.label_));
  15774. return _this;
  15775. }
  15776. /**
  15777. * Builds the default DOM `className`.
  15778. *
  15779. * @return {string}
  15780. * The DOM `className` for this object.
  15781. */
  15782. var _proto = SubsCapsButton.prototype;
  15783. _proto.buildCSSClass = function buildCSSClass() {
  15784. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  15785. };
  15786. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15787. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  15788. }
  15789. /**
  15790. * Create caption/subtitles menu items
  15791. *
  15792. * @return {CaptionSettingsMenuItem[]}
  15793. * The array of current menu items.
  15794. */
  15795. ;
  15796. _proto.createItems = function createItems() {
  15797. var items = [];
  15798. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  15799. items.push(new CaptionSettingsMenuItem(this.player_, {
  15800. kind: this.label_
  15801. }));
  15802. this.hideThreshold_ += 1;
  15803. }
  15804. items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
  15805. return items;
  15806. };
  15807. return SubsCapsButton;
  15808. }(TextTrackButton);
  15809. /**
  15810. * `kind`s of TextTrack to look for to associate it with this menu.
  15811. *
  15812. * @type {array}
  15813. * @private
  15814. */
  15815. SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
  15816. /**
  15817. * The text that should display over the `SubsCapsButton`s controls.
  15818. *
  15819. *
  15820. * @type {string}
  15821. * @private
  15822. */
  15823. SubsCapsButton.prototype.controlText_ = 'Subtitles';
  15824. Component.registerComponent('SubsCapsButton', SubsCapsButton);
  15825. /**
  15826. * An {@link AudioTrack} {@link MenuItem}
  15827. *
  15828. * @extends MenuItem
  15829. */
  15830. var AudioTrackMenuItem =
  15831. /*#__PURE__*/
  15832. function (_MenuItem) {
  15833. _inheritsLoose(AudioTrackMenuItem, _MenuItem);
  15834. /**
  15835. * Creates an instance of this class.
  15836. *
  15837. * @param {Player} player
  15838. * The `Player` that this class should be attached to.
  15839. *
  15840. * @param {Object} [options]
  15841. * The key/value store of player options.
  15842. */
  15843. function AudioTrackMenuItem(player, options) {
  15844. var _this;
  15845. var track = options.track;
  15846. var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init.
  15847. options.label = track.label || track.language || 'Unknown';
  15848. options.selected = track.enabled;
  15849. _this = _MenuItem.call(this, player, options) || this;
  15850. _this.track = track;
  15851. _this.addClass("vjs-" + track.kind + "-menu-item");
  15852. var changeHandler = function changeHandler() {
  15853. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  15854. args[_key] = arguments[_key];
  15855. }
  15856. _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  15857. };
  15858. tracks.addEventListener('change', changeHandler);
  15859. _this.on('dispose', function () {
  15860. tracks.removeEventListener('change', changeHandler);
  15861. });
  15862. return _this;
  15863. }
  15864. var _proto = AudioTrackMenuItem.prototype;
  15865. _proto.createEl = function createEl(type, props, attrs) {
  15866. var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
  15867. if (this.options_.track.kind === 'main-desc') {
  15868. innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n ";
  15869. }
  15870. innerHTML += '</span>';
  15871. var el = _MenuItem.prototype.createEl.call(this, type, assign({
  15872. innerHTML: innerHTML
  15873. }, props), attrs);
  15874. return el;
  15875. }
  15876. /**
  15877. * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
  15878. * for more detailed information on what a click can be.
  15879. *
  15880. * @param {EventTarget~Event} [event]
  15881. * The `keydown`, `tap`, or `click` event that caused this function to be
  15882. * called.
  15883. *
  15884. * @listens tap
  15885. * @listens click
  15886. */
  15887. ;
  15888. _proto.handleClick = function handleClick(event) {
  15889. var tracks = this.player_.audioTracks();
  15890. _MenuItem.prototype.handleClick.call(this, event);
  15891. for (var i = 0; i < tracks.length; i++) {
  15892. var track = tracks[i];
  15893. track.enabled = track === this.track;
  15894. }
  15895. }
  15896. /**
  15897. * Handle any {@link AudioTrack} change.
  15898. *
  15899. * @param {EventTarget~Event} [event]
  15900. * The {@link AudioTrackList#change} event that caused this to run.
  15901. *
  15902. * @listens AudioTrackList#change
  15903. */
  15904. ;
  15905. _proto.handleTracksChange = function handleTracksChange(event) {
  15906. this.selected(this.track.enabled);
  15907. };
  15908. return AudioTrackMenuItem;
  15909. }(MenuItem);
  15910. Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
  15911. /**
  15912. * The base class for buttons that toggle specific {@link AudioTrack} types.
  15913. *
  15914. * @extends TrackButton
  15915. */
  15916. var AudioTrackButton =
  15917. /*#__PURE__*/
  15918. function (_TrackButton) {
  15919. _inheritsLoose(AudioTrackButton, _TrackButton);
  15920. /**
  15921. * Creates an instance of this class.
  15922. *
  15923. * @param {Player} player
  15924. * The `Player` that this class should be attached to.
  15925. *
  15926. * @param {Object} [options={}]
  15927. * The key/value store of player options.
  15928. */
  15929. function AudioTrackButton(player, options) {
  15930. if (options === void 0) {
  15931. options = {};
  15932. }
  15933. options.tracks = player.audioTracks();
  15934. return _TrackButton.call(this, player, options) || this;
  15935. }
  15936. /**
  15937. * Builds the default DOM `className`.
  15938. *
  15939. * @return {string}
  15940. * The DOM `className` for this object.
  15941. */
  15942. var _proto = AudioTrackButton.prototype;
  15943. _proto.buildCSSClass = function buildCSSClass() {
  15944. return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this);
  15945. };
  15946. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15947. return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this);
  15948. }
  15949. /**
  15950. * Create a menu item for each audio track
  15951. *
  15952. * @param {AudioTrackMenuItem[]} [items=[]]
  15953. * An array of existing menu items to use.
  15954. *
  15955. * @return {AudioTrackMenuItem[]}
  15956. * An array of menu items
  15957. */
  15958. ;
  15959. _proto.createItems = function createItems(items) {
  15960. if (items === void 0) {
  15961. items = [];
  15962. }
  15963. // if there's only one audio track, there no point in showing it
  15964. this.hideThreshold_ = 1;
  15965. var tracks = this.player_.audioTracks();
  15966. for (var i = 0; i < tracks.length; i++) {
  15967. var track = tracks[i];
  15968. items.push(new AudioTrackMenuItem(this.player_, {
  15969. track: track,
  15970. // MenuItem is selectable
  15971. selectable: true,
  15972. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  15973. multiSelectable: false
  15974. }));
  15975. }
  15976. return items;
  15977. };
  15978. return AudioTrackButton;
  15979. }(TrackButton);
  15980. /**
  15981. * The text that should display over the `AudioTrackButton`s controls. Added for localization.
  15982. *
  15983. * @type {string}
  15984. * @private
  15985. */
  15986. AudioTrackButton.prototype.controlText_ = 'Audio Track';
  15987. Component.registerComponent('AudioTrackButton', AudioTrackButton);
  15988. /**
  15989. * The specific menu item type for selecting a playback rate.
  15990. *
  15991. * @extends MenuItem
  15992. */
  15993. var PlaybackRateMenuItem =
  15994. /*#__PURE__*/
  15995. function (_MenuItem) {
  15996. _inheritsLoose(PlaybackRateMenuItem, _MenuItem);
  15997. /**
  15998. * Creates an instance of this class.
  15999. *
  16000. * @param {Player} player
  16001. * The `Player` that this class should be attached to.
  16002. *
  16003. * @param {Object} [options]
  16004. * The key/value store of player options.
  16005. */
  16006. function PlaybackRateMenuItem(player, options) {
  16007. var _this;
  16008. var label = options.rate;
  16009. var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init.
  16010. options.label = label;
  16011. options.selected = rate === 1;
  16012. options.selectable = true;
  16013. options.multiSelectable = false;
  16014. _this = _MenuItem.call(this, player, options) || this;
  16015. _this.label = label;
  16016. _this.rate = rate;
  16017. _this.on(player, 'ratechange', _this.update);
  16018. return _this;
  16019. }
  16020. /**
  16021. * This gets called when an `PlaybackRateMenuItem` is "clicked". See
  16022. * {@link ClickableComponent} for more detailed information on what a click can be.
  16023. *
  16024. * @param {EventTarget~Event} [event]
  16025. * The `keydown`, `tap`, or `click` event that caused this function to be
  16026. * called.
  16027. *
  16028. * @listens tap
  16029. * @listens click
  16030. */
  16031. var _proto = PlaybackRateMenuItem.prototype;
  16032. _proto.handleClick = function handleClick(event) {
  16033. _MenuItem.prototype.handleClick.call(this);
  16034. this.player().playbackRate(this.rate);
  16035. }
  16036. /**
  16037. * Update the PlaybackRateMenuItem when the playbackrate changes.
  16038. *
  16039. * @param {EventTarget~Event} [event]
  16040. * The `ratechange` event that caused this function to run.
  16041. *
  16042. * @listens Player#ratechange
  16043. */
  16044. ;
  16045. _proto.update = function update(event) {
  16046. this.selected(this.player().playbackRate() === this.rate);
  16047. };
  16048. return PlaybackRateMenuItem;
  16049. }(MenuItem);
  16050. /**
  16051. * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
  16052. *
  16053. * @type {string}
  16054. * @private
  16055. */
  16056. PlaybackRateMenuItem.prototype.contentElType = 'button';
  16057. Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
  16058. /**
  16059. * The component for controlling the playback rate.
  16060. *
  16061. * @extends MenuButton
  16062. */
  16063. var PlaybackRateMenuButton =
  16064. /*#__PURE__*/
  16065. function (_MenuButton) {
  16066. _inheritsLoose(PlaybackRateMenuButton, _MenuButton);
  16067. /**
  16068. * Creates an instance of this class.
  16069. *
  16070. * @param {Player} player
  16071. * The `Player` that this class should be attached to.
  16072. *
  16073. * @param {Object} [options]
  16074. * The key/value store of player options.
  16075. */
  16076. function PlaybackRateMenuButton(player, options) {
  16077. var _this;
  16078. _this = _MenuButton.call(this, player, options) || this;
  16079. _this.updateVisibility();
  16080. _this.updateLabel();
  16081. _this.on(player, 'loadstart', _this.updateVisibility);
  16082. _this.on(player, 'ratechange', _this.updateLabel);
  16083. return _this;
  16084. }
  16085. /**
  16086. * Create the `Component`'s DOM element
  16087. *
  16088. * @return {Element}
  16089. * The element that was created.
  16090. */
  16091. var _proto = PlaybackRateMenuButton.prototype;
  16092. _proto.createEl = function createEl$$1() {
  16093. var el = _MenuButton.prototype.createEl.call(this);
  16094. this.labelEl_ = createEl('div', {
  16095. className: 'vjs-playback-rate-value',
  16096. innerHTML: '1x'
  16097. });
  16098. el.appendChild(this.labelEl_);
  16099. return el;
  16100. };
  16101. _proto.dispose = function dispose() {
  16102. this.labelEl_ = null;
  16103. _MenuButton.prototype.dispose.call(this);
  16104. }
  16105. /**
  16106. * Builds the default DOM `className`.
  16107. *
  16108. * @return {string}
  16109. * The DOM `className` for this object.
  16110. */
  16111. ;
  16112. _proto.buildCSSClass = function buildCSSClass() {
  16113. return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this);
  16114. };
  16115. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  16116. return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this);
  16117. }
  16118. /**
  16119. * Create the playback rate menu
  16120. *
  16121. * @return {Menu}
  16122. * Menu object populated with {@link PlaybackRateMenuItem}s
  16123. */
  16124. ;
  16125. _proto.createMenu = function createMenu() {
  16126. var menu = new Menu(this.player());
  16127. var rates = this.playbackRates();
  16128. if (rates) {
  16129. for (var i = rates.length - 1; i >= 0; i--) {
  16130. menu.addChild(new PlaybackRateMenuItem(this.player(), {
  16131. rate: rates[i] + 'x'
  16132. }));
  16133. }
  16134. }
  16135. return menu;
  16136. }
  16137. /**
  16138. * Updates ARIA accessibility attributes
  16139. */
  16140. ;
  16141. _proto.updateARIAAttributes = function updateARIAAttributes() {
  16142. // Current playback rate
  16143. this.el().setAttribute('aria-valuenow', this.player().playbackRate());
  16144. }
  16145. /**
  16146. * This gets called when an `PlaybackRateMenuButton` is "clicked". See
  16147. * {@link ClickableComponent} for more detailed information on what a click can be.
  16148. *
  16149. * @param {EventTarget~Event} [event]
  16150. * The `keydown`, `tap`, or `click` event that caused this function to be
  16151. * called.
  16152. *
  16153. * @listens tap
  16154. * @listens click
  16155. */
  16156. ;
  16157. _proto.handleClick = function handleClick(event) {
  16158. // select next rate option
  16159. var currentRate = this.player().playbackRate();
  16160. var rates = this.playbackRates(); // this will select first one if the last one currently selected
  16161. var newRate = rates[0];
  16162. for (var i = 0; i < rates.length; i++) {
  16163. if (rates[i] > currentRate) {
  16164. newRate = rates[i];
  16165. break;
  16166. }
  16167. }
  16168. this.player().playbackRate(newRate);
  16169. }
  16170. /**
  16171. * Get possible playback rates
  16172. *
  16173. * @return {Array}
  16174. * All possible playback rates
  16175. */
  16176. ;
  16177. _proto.playbackRates = function playbackRates() {
  16178. return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
  16179. }
  16180. /**
  16181. * Get whether playback rates is supported by the tech
  16182. * and an array of playback rates exists
  16183. *
  16184. * @return {boolean}
  16185. * Whether changing playback rate is supported
  16186. */
  16187. ;
  16188. _proto.playbackRateSupported = function playbackRateSupported() {
  16189. return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
  16190. }
  16191. /**
  16192. * Hide playback rate controls when they're no playback rate options to select
  16193. *
  16194. * @param {EventTarget~Event} [event]
  16195. * The event that caused this function to run.
  16196. *
  16197. * @listens Player#loadstart
  16198. */
  16199. ;
  16200. _proto.updateVisibility = function updateVisibility(event) {
  16201. if (this.playbackRateSupported()) {
  16202. this.removeClass('vjs-hidden');
  16203. } else {
  16204. this.addClass('vjs-hidden');
  16205. }
  16206. }
  16207. /**
  16208. * Update button label when rate changed
  16209. *
  16210. * @param {EventTarget~Event} [event]
  16211. * The event that caused this function to run.
  16212. *
  16213. * @listens Player#ratechange
  16214. */
  16215. ;
  16216. _proto.updateLabel = function updateLabel(event) {
  16217. if (this.playbackRateSupported()) {
  16218. this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
  16219. }
  16220. };
  16221. return PlaybackRateMenuButton;
  16222. }(MenuButton);
  16223. /**
  16224. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  16225. *
  16226. * @type {string}
  16227. * @private
  16228. */
  16229. PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
  16230. Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
  16231. /**
  16232. * Just an empty spacer element that can be used as an append point for plugins, etc.
  16233. * Also can be used to create space between elements when necessary.
  16234. *
  16235. * @extends Component
  16236. */
  16237. var Spacer =
  16238. /*#__PURE__*/
  16239. function (_Component) {
  16240. _inheritsLoose(Spacer, _Component);
  16241. function Spacer() {
  16242. return _Component.apply(this, arguments) || this;
  16243. }
  16244. var _proto = Spacer.prototype;
  16245. /**
  16246. * Builds the default DOM `className`.
  16247. *
  16248. * @return {string}
  16249. * The DOM `className` for this object.
  16250. */
  16251. _proto.buildCSSClass = function buildCSSClass() {
  16252. return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this);
  16253. }
  16254. /**
  16255. * Create the `Component`'s DOM element
  16256. *
  16257. * @return {Element}
  16258. * The element that was created.
  16259. */
  16260. ;
  16261. _proto.createEl = function createEl() {
  16262. return _Component.prototype.createEl.call(this, 'div', {
  16263. className: this.buildCSSClass()
  16264. });
  16265. };
  16266. return Spacer;
  16267. }(Component);
  16268. Component.registerComponent('Spacer', Spacer);
  16269. /**
  16270. * Spacer specifically meant to be used as an insertion point for new plugins, etc.
  16271. *
  16272. * @extends Spacer
  16273. */
  16274. var CustomControlSpacer =
  16275. /*#__PURE__*/
  16276. function (_Spacer) {
  16277. _inheritsLoose(CustomControlSpacer, _Spacer);
  16278. function CustomControlSpacer() {
  16279. return _Spacer.apply(this, arguments) || this;
  16280. }
  16281. var _proto = CustomControlSpacer.prototype;
  16282. /**
  16283. * Builds the default DOM `className`.
  16284. *
  16285. * @return {string}
  16286. * The DOM `className` for this object.
  16287. */
  16288. _proto.buildCSSClass = function buildCSSClass() {
  16289. return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this);
  16290. }
  16291. /**
  16292. * Create the `Component`'s DOM element
  16293. *
  16294. * @return {Element}
  16295. * The element that was created.
  16296. */
  16297. ;
  16298. _proto.createEl = function createEl() {
  16299. var el = _Spacer.prototype.createEl.call(this, {
  16300. className: this.buildCSSClass()
  16301. }); // No-flex/table-cell mode requires there be some content
  16302. // in the cell to fill the remaining space of the table.
  16303. el.innerHTML = "\xA0";
  16304. return el;
  16305. };
  16306. return CustomControlSpacer;
  16307. }(Spacer);
  16308. Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
  16309. /**
  16310. * Container of main controls.
  16311. *
  16312. * @extends Component
  16313. */
  16314. var ControlBar =
  16315. /*#__PURE__*/
  16316. function (_Component) {
  16317. _inheritsLoose(ControlBar, _Component);
  16318. function ControlBar() {
  16319. return _Component.apply(this, arguments) || this;
  16320. }
  16321. var _proto = ControlBar.prototype;
  16322. /**
  16323. * Create the `Component`'s DOM element
  16324. *
  16325. * @return {Element}
  16326. * The element that was created.
  16327. */
  16328. _proto.createEl = function createEl() {
  16329. return _Component.prototype.createEl.call(this, 'div', {
  16330. className: 'vjs-control-bar',
  16331. dir: 'ltr'
  16332. });
  16333. };
  16334. return ControlBar;
  16335. }(Component);
  16336. /**
  16337. * Default options for `ControlBar`
  16338. *
  16339. * @type {Object}
  16340. * @private
  16341. */
  16342. ControlBar.prototype.options_ = {
  16343. children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
  16344. };
  16345. Component.registerComponent('ControlBar', ControlBar);
  16346. /**
  16347. * A display that indicates an error has occurred. This means that the video
  16348. * is unplayable.
  16349. *
  16350. * @extends ModalDialog
  16351. */
  16352. var ErrorDisplay =
  16353. /*#__PURE__*/
  16354. function (_ModalDialog) {
  16355. _inheritsLoose(ErrorDisplay, _ModalDialog);
  16356. /**
  16357. * Creates an instance of this class.
  16358. *
  16359. * @param {Player} player
  16360. * The `Player` that this class should be attached to.
  16361. *
  16362. * @param {Object} [options]
  16363. * The key/value store of player options.
  16364. */
  16365. function ErrorDisplay(player, options) {
  16366. var _this;
  16367. _this = _ModalDialog.call(this, player, options) || this;
  16368. _this.on(player, 'error', _this.open);
  16369. return _this;
  16370. }
  16371. /**
  16372. * Builds the default DOM `className`.
  16373. *
  16374. * @return {string}
  16375. * The DOM `className` for this object.
  16376. *
  16377. * @deprecated Since version 5.
  16378. */
  16379. var _proto = ErrorDisplay.prototype;
  16380. _proto.buildCSSClass = function buildCSSClass() {
  16381. return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this);
  16382. }
  16383. /**
  16384. * Gets the localized error message based on the `Player`s error.
  16385. *
  16386. * @return {string}
  16387. * The `Player`s error message localized or an empty string.
  16388. */
  16389. ;
  16390. _proto.content = function content() {
  16391. var error = this.player().error();
  16392. return error ? this.localize(error.message) : '';
  16393. };
  16394. return ErrorDisplay;
  16395. }(ModalDialog);
  16396. /**
  16397. * The default options for an `ErrorDisplay`.
  16398. *
  16399. * @private
  16400. */
  16401. ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
  16402. pauseOnOpen: false,
  16403. fillAlways: true,
  16404. temporary: false,
  16405. uncloseable: true
  16406. });
  16407. Component.registerComponent('ErrorDisplay', ErrorDisplay);
  16408. var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
  16409. var COLOR_BLACK = ['#000', 'Black'];
  16410. var COLOR_BLUE = ['#00F', 'Blue'];
  16411. var COLOR_CYAN = ['#0FF', 'Cyan'];
  16412. var COLOR_GREEN = ['#0F0', 'Green'];
  16413. var COLOR_MAGENTA = ['#F0F', 'Magenta'];
  16414. var COLOR_RED = ['#F00', 'Red'];
  16415. var COLOR_WHITE = ['#FFF', 'White'];
  16416. var COLOR_YELLOW = ['#FF0', 'Yellow'];
  16417. var OPACITY_OPAQUE = ['1', 'Opaque'];
  16418. var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
  16419. var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component.
  16420. //
  16421. // Possible keys include:
  16422. //
  16423. // `default`:
  16424. // The default option index. Only needs to be provided if not zero.
  16425. // `parser`:
  16426. // A function which is used to parse the value from the selected option in
  16427. // a customized way.
  16428. // `selector`:
  16429. // The selector used to find the associated <select> element.
  16430. var selectConfigs = {
  16431. backgroundColor: {
  16432. selector: '.vjs-bg-color > select',
  16433. id: 'captions-background-color-%s',
  16434. label: 'Color',
  16435. options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  16436. },
  16437. backgroundOpacity: {
  16438. selector: '.vjs-bg-opacity > select',
  16439. id: 'captions-background-opacity-%s',
  16440. label: 'Transparency',
  16441. options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
  16442. },
  16443. color: {
  16444. selector: '.vjs-fg-color > select',
  16445. id: 'captions-foreground-color-%s',
  16446. label: 'Color',
  16447. options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  16448. },
  16449. edgeStyle: {
  16450. selector: '.vjs-edge-style > select',
  16451. id: '%s',
  16452. label: 'Text Edge Style',
  16453. options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
  16454. },
  16455. fontFamily: {
  16456. selector: '.vjs-font-family > select',
  16457. id: 'captions-font-family-%s',
  16458. label: 'Font Family',
  16459. options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
  16460. },
  16461. fontPercent: {
  16462. selector: '.vjs-font-percent > select',
  16463. id: 'captions-font-size-%s',
  16464. label: 'Font Size',
  16465. 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%']],
  16466. default: 2,
  16467. parser: function parser(v) {
  16468. return v === '1.00' ? null : Number(v);
  16469. }
  16470. },
  16471. textOpacity: {
  16472. selector: '.vjs-text-opacity > select',
  16473. id: 'captions-foreground-opacity-%s',
  16474. label: 'Transparency',
  16475. options: [OPACITY_OPAQUE, OPACITY_SEMI]
  16476. },
  16477. // Options for this object are defined below.
  16478. windowColor: {
  16479. selector: '.vjs-window-color > select',
  16480. id: 'captions-window-color-%s',
  16481. label: 'Color'
  16482. },
  16483. // Options for this object are defined below.
  16484. windowOpacity: {
  16485. selector: '.vjs-window-opacity > select',
  16486. id: 'captions-window-opacity-%s',
  16487. label: 'Transparency',
  16488. options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
  16489. }
  16490. };
  16491. selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
  16492. /**
  16493. * Get the actual value of an option.
  16494. *
  16495. * @param {string} value
  16496. * The value to get
  16497. *
  16498. * @param {Function} [parser]
  16499. * Optional function to adjust the value.
  16500. *
  16501. * @return {Mixed}
  16502. * - Will be `undefined` if no value exists
  16503. * - Will be `undefined` if the given value is "none".
  16504. * - Will be the actual value otherwise.
  16505. *
  16506. * @private
  16507. */
  16508. function parseOptionValue(value, parser) {
  16509. if (parser) {
  16510. value = parser(value);
  16511. }
  16512. if (value && value !== 'none') {
  16513. return value;
  16514. }
  16515. }
  16516. /**
  16517. * Gets the value of the selected <option> element within a <select> element.
  16518. *
  16519. * @param {Element} el
  16520. * the element to look in
  16521. *
  16522. * @param {Function} [parser]
  16523. * Optional function to adjust the value.
  16524. *
  16525. * @return {Mixed}
  16526. * - Will be `undefined` if no value exists
  16527. * - Will be `undefined` if the given value is "none".
  16528. * - Will be the actual value otherwise.
  16529. *
  16530. * @private
  16531. */
  16532. function getSelectedOptionValue(el, parser) {
  16533. var value = el.options[el.options.selectedIndex].value;
  16534. return parseOptionValue(value, parser);
  16535. }
  16536. /**
  16537. * Sets the selected <option> element within a <select> element based on a
  16538. * given value.
  16539. *
  16540. * @param {Element} el
  16541. * The element to look in.
  16542. *
  16543. * @param {string} value
  16544. * the property to look on.
  16545. *
  16546. * @param {Function} [parser]
  16547. * Optional function to adjust the value before comparing.
  16548. *
  16549. * @private
  16550. */
  16551. function setSelectedOption(el, value, parser) {
  16552. if (!value) {
  16553. return;
  16554. }
  16555. for (var i = 0; i < el.options.length; i++) {
  16556. if (parseOptionValue(el.options[i].value, parser) === value) {
  16557. el.selectedIndex = i;
  16558. break;
  16559. }
  16560. }
  16561. }
  16562. /**
  16563. * Manipulate Text Tracks settings.
  16564. *
  16565. * @extends ModalDialog
  16566. */
  16567. var TextTrackSettings =
  16568. /*#__PURE__*/
  16569. function (_ModalDialog) {
  16570. _inheritsLoose(TextTrackSettings, _ModalDialog);
  16571. /**
  16572. * Creates an instance of this class.
  16573. *
  16574. * @param {Player} player
  16575. * The `Player` that this class should be attached to.
  16576. *
  16577. * @param {Object} [options]
  16578. * The key/value store of player options.
  16579. */
  16580. function TextTrackSettings(player, options) {
  16581. var _this;
  16582. options.temporary = false;
  16583. _this = _ModalDialog.call(this, player, options) || this;
  16584. _this.updateDisplay = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay); // fill the modal and pretend we have opened it
  16585. _this.fill();
  16586. _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
  16587. _this.endDialog = createEl('p', {
  16588. className: 'vjs-control-text',
  16589. textContent: _this.localize('End of dialog window.')
  16590. });
  16591. _this.el().appendChild(_this.endDialog);
  16592. _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options
  16593. if (options.persistTextTrackSettings === undefined) {
  16594. _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
  16595. }
  16596. _this.on(_this.$('.vjs-done-button'), 'click', function () {
  16597. _this.saveSettings();
  16598. _this.close();
  16599. });
  16600. _this.on(_this.$('.vjs-default-button'), 'click', function () {
  16601. _this.setDefaults();
  16602. _this.updateDisplay();
  16603. });
  16604. each(selectConfigs, function (config) {
  16605. _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
  16606. });
  16607. if (_this.options_.persistTextTrackSettings) {
  16608. _this.restoreSettings();
  16609. }
  16610. return _this;
  16611. }
  16612. var _proto = TextTrackSettings.prototype;
  16613. _proto.dispose = function dispose() {
  16614. this.endDialog = null;
  16615. _ModalDialog.prototype.dispose.call(this);
  16616. }
  16617. /**
  16618. * Create a <select> element with configured options.
  16619. *
  16620. * @param {string} key
  16621. * Configuration key to use during creation.
  16622. *
  16623. * @return {string}
  16624. * An HTML string.
  16625. *
  16626. * @private
  16627. */
  16628. ;
  16629. _proto.createElSelect_ = function createElSelect_(key, legendId, type) {
  16630. var _this2 = this;
  16631. if (legendId === void 0) {
  16632. legendId = '';
  16633. }
  16634. if (type === void 0) {
  16635. type = 'label';
  16636. }
  16637. var config = selectConfigs[key];
  16638. var id = config.id.replace('%s', this.id_);
  16639. var selectLabelledbyIds = [legendId, id].join(' ').trim();
  16640. return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) {
  16641. var optionId = id + '-' + o[1].replace(/\W+/g, '');
  16642. return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join('');
  16643. })).concat('</select>').join('');
  16644. }
  16645. /**
  16646. * Create foreground color element for the component
  16647. *
  16648. * @return {string}
  16649. * An HTML string.
  16650. *
  16651. * @private
  16652. */
  16653. ;
  16654. _proto.createElFgColor_ = function createElFgColor_() {
  16655. var legendId = "captions-text-legend-" + this.id_;
  16656. 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('');
  16657. }
  16658. /**
  16659. * Create background color element for the component
  16660. *
  16661. * @return {string}
  16662. * An HTML string.
  16663. *
  16664. * @private
  16665. */
  16666. ;
  16667. _proto.createElBgColor_ = function createElBgColor_() {
  16668. var legendId = "captions-background-" + this.id_;
  16669. 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('');
  16670. }
  16671. /**
  16672. * Create window color element for the component
  16673. *
  16674. * @return {string}
  16675. * An HTML string.
  16676. *
  16677. * @private
  16678. */
  16679. ;
  16680. _proto.createElWinColor_ = function createElWinColor_() {
  16681. var legendId = "captions-window-" + this.id_;
  16682. 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('');
  16683. }
  16684. /**
  16685. * Create color elements for the component
  16686. *
  16687. * @return {Element}
  16688. * The element that was created
  16689. *
  16690. * @private
  16691. */
  16692. ;
  16693. _proto.createElColors_ = function createElColors_() {
  16694. return createEl('div', {
  16695. className: 'vjs-track-settings-colors',
  16696. innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
  16697. });
  16698. }
  16699. /**
  16700. * Create font elements for the component
  16701. *
  16702. * @return {Element}
  16703. * The element that was created.
  16704. *
  16705. * @private
  16706. */
  16707. ;
  16708. _proto.createElFont_ = function createElFont_() {
  16709. return createEl('div', {
  16710. className: 'vjs-track-settings-font',
  16711. 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('')
  16712. });
  16713. }
  16714. /**
  16715. * Create controls for the component
  16716. *
  16717. * @return {Element}
  16718. * The element that was created.
  16719. *
  16720. * @private
  16721. */
  16722. ;
  16723. _proto.createElControls_ = function createElControls_() {
  16724. var defaultsDescription = this.localize('restore all settings to the default values');
  16725. return createEl('div', {
  16726. className: 'vjs-track-settings-controls',
  16727. 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('')
  16728. });
  16729. };
  16730. _proto.content = function content() {
  16731. return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
  16732. };
  16733. _proto.label = function label() {
  16734. return this.localize('Caption Settings Dialog');
  16735. };
  16736. _proto.description = function description() {
  16737. return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
  16738. };
  16739. _proto.buildCSSClass = function buildCSSClass() {
  16740. return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
  16741. }
  16742. /**
  16743. * Gets an object of text track settings (or null).
  16744. *
  16745. * @return {Object}
  16746. * An object with config values parsed from the DOM or localStorage.
  16747. */
  16748. ;
  16749. _proto.getValues = function getValues() {
  16750. var _this3 = this;
  16751. return reduce(selectConfigs, function (accum, config, key) {
  16752. var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
  16753. if (value !== undefined) {
  16754. accum[key] = value;
  16755. }
  16756. return accum;
  16757. }, {});
  16758. }
  16759. /**
  16760. * Sets text track settings from an object of values.
  16761. *
  16762. * @param {Object} values
  16763. * An object with config values parsed from the DOM or localStorage.
  16764. */
  16765. ;
  16766. _proto.setValues = function setValues(values) {
  16767. var _this4 = this;
  16768. each(selectConfigs, function (config, key) {
  16769. setSelectedOption(_this4.$(config.selector), values[key], config.parser);
  16770. });
  16771. }
  16772. /**
  16773. * Sets all `<select>` elements to their default values.
  16774. */
  16775. ;
  16776. _proto.setDefaults = function setDefaults() {
  16777. var _this5 = this;
  16778. each(selectConfigs, function (config) {
  16779. var index = config.hasOwnProperty('default') ? config.default : 0;
  16780. _this5.$(config.selector).selectedIndex = index;
  16781. });
  16782. }
  16783. /**
  16784. * Restore texttrack settings from localStorage
  16785. */
  16786. ;
  16787. _proto.restoreSettings = function restoreSettings() {
  16788. var values;
  16789. try {
  16790. values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY));
  16791. } catch (err) {
  16792. log.warn(err);
  16793. }
  16794. if (values) {
  16795. this.setValues(values);
  16796. }
  16797. }
  16798. /**
  16799. * Save text track settings to localStorage
  16800. */
  16801. ;
  16802. _proto.saveSettings = function saveSettings() {
  16803. if (!this.options_.persistTextTrackSettings) {
  16804. return;
  16805. }
  16806. var values = this.getValues();
  16807. try {
  16808. if (Object.keys(values).length) {
  16809. window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
  16810. } else {
  16811. window$1.localStorage.removeItem(LOCAL_STORAGE_KEY);
  16812. }
  16813. } catch (err) {
  16814. log.warn(err);
  16815. }
  16816. }
  16817. /**
  16818. * Update display of text track settings
  16819. */
  16820. ;
  16821. _proto.updateDisplay = function updateDisplay() {
  16822. var ttDisplay = this.player_.getChild('textTrackDisplay');
  16823. if (ttDisplay) {
  16824. ttDisplay.updateDisplay();
  16825. }
  16826. }
  16827. /**
  16828. * conditionally blur the element and refocus the captions button
  16829. *
  16830. * @private
  16831. */
  16832. ;
  16833. _proto.conditionalBlur_ = function conditionalBlur_() {
  16834. this.previouslyActiveEl_ = null;
  16835. this.off(document, 'keydown', this.handleKeyDown);
  16836. var cb = this.player_.controlBar;
  16837. var subsCapsBtn = cb && cb.subsCapsButton;
  16838. var ccBtn = cb && cb.captionsButton;
  16839. if (subsCapsBtn) {
  16840. subsCapsBtn.focus();
  16841. } else if (ccBtn) {
  16842. ccBtn.focus();
  16843. }
  16844. };
  16845. return TextTrackSettings;
  16846. }(ModalDialog);
  16847. Component.registerComponent('TextTrackSettings', TextTrackSettings);
  16848. /**
  16849. * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
  16850. *
  16851. * 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}.
  16852. *
  16853. * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
  16854. * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
  16855. * @example <caption>How to disable the resize manager</caption>
  16856. * const player = videojs('#vid', {
  16857. * resizeManager: false
  16858. * });
  16859. *
  16860. * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
  16861. *
  16862. * @extends Component
  16863. */
  16864. var ResizeManager =
  16865. /*#__PURE__*/
  16866. function (_Component) {
  16867. _inheritsLoose(ResizeManager, _Component);
  16868. /**
  16869. * Create the ResizeManager.
  16870. *
  16871. * @param {Object} player
  16872. * The `Player` that this class should be attached to.
  16873. *
  16874. * @param {Object} [options]
  16875. * The key/value store of ResizeManager options.
  16876. *
  16877. * @param {Object} [options.ResizeObserver]
  16878. * A polyfill for ResizeObserver can be passed in here.
  16879. * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
  16880. */
  16881. function ResizeManager(player, options) {
  16882. var _this;
  16883. var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
  16884. if (options.ResizeObserver === null) {
  16885. RESIZE_OBSERVER_AVAILABLE = false;
  16886. } // Only create an element when ResizeObserver isn't available
  16887. var options_ = mergeOptions({
  16888. createEl: !RESIZE_OBSERVER_AVAILABLE,
  16889. reportTouchActivity: false
  16890. }, options);
  16891. _this = _Component.call(this, player, options_) || this;
  16892. _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver;
  16893. _this.loadListener_ = null;
  16894. _this.resizeObserver_ = null;
  16895. _this.debouncedHandler_ = debounce(function () {
  16896. _this.resizeHandler();
  16897. }, 100, false, _assertThisInitialized(_assertThisInitialized(_this)));
  16898. if (RESIZE_OBSERVER_AVAILABLE) {
  16899. _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
  16900. _this.resizeObserver_.observe(player.el());
  16901. } else {
  16902. _this.loadListener_ = function () {
  16903. if (!_this.el_ || !_this.el_.contentWindow) {
  16904. return;
  16905. }
  16906. var debouncedHandler_ = _this.debouncedHandler_;
  16907. var unloadListener_ = _this.unloadListener_ = function () {
  16908. off(this, 'resize', debouncedHandler_);
  16909. off(this, 'unload', unloadListener_);
  16910. unloadListener_ = null;
  16911. }; // safari and edge can unload the iframe before resizemanager dispose
  16912. // we have to dispose of event handlers correctly before that happens
  16913. on(_this.el_.contentWindow, 'unload', unloadListener_);
  16914. on(_this.el_.contentWindow, 'resize', debouncedHandler_);
  16915. };
  16916. _this.one('load', _this.loadListener_);
  16917. }
  16918. return _this;
  16919. }
  16920. var _proto = ResizeManager.prototype;
  16921. _proto.createEl = function createEl() {
  16922. return _Component.prototype.createEl.call(this, 'iframe', {
  16923. className: 'vjs-resize-manager',
  16924. tabIndex: -1
  16925. }, {
  16926. 'aria-hidden': 'true'
  16927. });
  16928. }
  16929. /**
  16930. * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
  16931. *
  16932. * @fires Player#playerresize
  16933. */
  16934. ;
  16935. _proto.resizeHandler = function resizeHandler() {
  16936. /**
  16937. * Called when the player size has changed
  16938. *
  16939. * @event Player#playerresize
  16940. * @type {EventTarget~Event}
  16941. */
  16942. // make sure player is still around to trigger
  16943. // prevents this from causing an error after dispose
  16944. if (!this.player_ || !this.player_.trigger) {
  16945. return;
  16946. }
  16947. this.player_.trigger('playerresize');
  16948. };
  16949. _proto.dispose = function dispose() {
  16950. if (this.debouncedHandler_) {
  16951. this.debouncedHandler_.cancel();
  16952. }
  16953. if (this.resizeObserver_) {
  16954. if (this.player_.el()) {
  16955. this.resizeObserver_.unobserve(this.player_.el());
  16956. }
  16957. this.resizeObserver_.disconnect();
  16958. }
  16959. if (this.loadListener_) {
  16960. this.off('load', this.loadListener_);
  16961. }
  16962. if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
  16963. this.unloadListener_.call(this.el_.contentWindow);
  16964. }
  16965. this.ResizeObserver = null;
  16966. this.resizeObserver = null;
  16967. this.debouncedHandler_ = null;
  16968. this.loadListener_ = null;
  16969. _Component.prototype.dispose.call(this);
  16970. };
  16971. return ResizeManager;
  16972. }(Component);
  16973. Component.registerComponent('ResizeManager', ResizeManager);
  16974. /* track when we are at the live edge, and other helpers for live playback */
  16975. var LiveTracker =
  16976. /*#__PURE__*/
  16977. function (_Component) {
  16978. _inheritsLoose(LiveTracker, _Component);
  16979. function LiveTracker(player, options) {
  16980. var _this;
  16981. // LiveTracker does not need an element
  16982. var options_ = mergeOptions({
  16983. createEl: false
  16984. }, options);
  16985. _this = _Component.call(this, player, options_) || this;
  16986. _this.reset_();
  16987. _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden,
  16988. // also, tracking when the document is hidden can
  16989. // cause the CPU to spike and eventually crash the page on IE11.
  16990. if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
  16991. _this.on(document, 'visibilitychange', _this.handleVisibilityChange);
  16992. }
  16993. return _this;
  16994. }
  16995. var _proto = LiveTracker.prototype;
  16996. _proto.handleVisibilityChange = function handleVisibilityChange() {
  16997. if (this.player_.duration() !== Infinity) {
  16998. return;
  16999. }
  17000. if (document.hidden) {
  17001. this.stopTracking();
  17002. } else {
  17003. this.startTracking();
  17004. }
  17005. };
  17006. _proto.isBehind_ = function isBehind_() {
  17007. // don't report that we are behind until a timeupdate has been seen
  17008. if (!this.timeupdateSeen_) {
  17009. return false;
  17010. }
  17011. var liveCurrentTime = this.liveCurrentTime();
  17012. var currentTime = this.player_.currentTime();
  17013. var seekableIncrement = this.seekableIncrement_; // the live edge window is the amount of seconds away from live
  17014. // that a player can be, but still be considered live.
  17015. // we add 0.07 because the live tracking happens every 30ms
  17016. // and we want some wiggle room for short segment live playback
  17017. var liveEdgeWindow = seekableIncrement * 2 + 0.07; // on Android liveCurrentTime can bee Infinity, because seekableEnd
  17018. // can be Infinity, so we handle that case.
  17019. return liveCurrentTime !== Infinity && liveCurrentTime - liveEdgeWindow >= currentTime;
  17020. } // all the functionality for tracking when seek end changes
  17021. // and for tracking how far past seek end we should be
  17022. ;
  17023. _proto.trackLive_ = function trackLive_() {
  17024. this.pastSeekEnd_ = this.pastSeekEnd_;
  17025. var seekable = this.player_.seekable(); // skip undefined seekable
  17026. if (!seekable || !seekable.length) {
  17027. return;
  17028. }
  17029. var newSeekEnd = this.seekableEnd(); // we can only tell if we are behind live, when seekable changes
  17030. // once we detect that seekable has changed we check the new seek
  17031. // end against current time, with a fudge value of half a second.
  17032. if (newSeekEnd !== this.lastSeekEnd_) {
  17033. if (this.lastSeekEnd_) {
  17034. this.seekableIncrement_ = Math.abs(newSeekEnd - this.lastSeekEnd_);
  17035. }
  17036. this.pastSeekEnd_ = 0;
  17037. this.lastSeekEnd_ = newSeekEnd;
  17038. this.trigger('seekableendchange');
  17039. }
  17040. this.pastSeekEnd_ = this.pastSeekEnd() + 0.03;
  17041. if (this.isBehind_() !== this.behindLiveEdge()) {
  17042. this.behindLiveEdge_ = this.isBehind_();
  17043. this.trigger('liveedgechange');
  17044. }
  17045. }
  17046. /**
  17047. * handle a durationchange event on the player
  17048. * and start/stop tracking accordingly.
  17049. */
  17050. ;
  17051. _proto.handleDurationchange = function handleDurationchange() {
  17052. if (this.player_.duration() === Infinity) {
  17053. this.startTracking();
  17054. } else {
  17055. this.stopTracking();
  17056. }
  17057. }
  17058. /**
  17059. * start tracking live playback
  17060. */
  17061. ;
  17062. _proto.startTracking = function startTracking() {
  17063. var _this2 = this;
  17064. if (this.isTracking()) {
  17065. return;
  17066. }
  17067. this.trackingInterval_ = this.setInterval(this.trackLive_, 30);
  17068. this.trackLive_();
  17069. this.on(this.player_, 'play', this.trackLive_);
  17070. this.on(this.player_, 'pause', this.trackLive_);
  17071. this.one(this.player_, 'play', this.handlePlay); // this is to prevent showing that we are not live
  17072. // before a video starts to play
  17073. if (!this.timeupdateSeen_) {
  17074. this.handleTimeupdate = function () {
  17075. _this2.timeupdateSeen_ = true;
  17076. _this2.handleTimeupdate = null;
  17077. };
  17078. this.one(this.player_, 'timeupdate', this.handleTimeupdate);
  17079. }
  17080. };
  17081. _proto.handlePlay = function handlePlay() {
  17082. this.one(this.player_, 'timeupdate', this.seekToLiveEdge);
  17083. }
  17084. /**
  17085. * Stop tracking, and set all internal variables to
  17086. * their initial value.
  17087. */
  17088. ;
  17089. _proto.reset_ = function reset_() {
  17090. this.pastSeekEnd_ = 0;
  17091. this.lastSeekEnd_ = null;
  17092. this.behindLiveEdge_ = null;
  17093. this.timeupdateSeen_ = false;
  17094. this.clearInterval(this.trackingInterval_);
  17095. this.trackingInterval_ = null;
  17096. this.seekableIncrement_ = 12;
  17097. this.off(this.player_, 'play', this.trackLive_);
  17098. this.off(this.player_, 'pause', this.trackLive_);
  17099. this.off(this.player_, 'play', this.handlePlay);
  17100. this.off(this.player_, 'timeupdate', this.seekToLiveEdge);
  17101. if (this.handleTimeupdate) {
  17102. this.off(this.player_, 'timeupdate', this.handleTimeupdate);
  17103. this.handleTimeupdate = null;
  17104. }
  17105. }
  17106. /**
  17107. * stop tracking live playback
  17108. */
  17109. ;
  17110. _proto.stopTracking = function stopTracking() {
  17111. if (!this.isTracking()) {
  17112. return;
  17113. }
  17114. this.reset_();
  17115. }
  17116. /**
  17117. * A helper to get the player seekable end
  17118. * so that we don't have to null check everywhere
  17119. */
  17120. ;
  17121. _proto.seekableEnd = function seekableEnd() {
  17122. var seekable = this.player_.seekable();
  17123. var seekableEnds = [];
  17124. var i = seekable ? seekable.length : 0;
  17125. while (i--) {
  17126. seekableEnds.push(seekable.end(i));
  17127. } // grab the furthest seekable end after sorting, or if there are none
  17128. // default to Infinity
  17129. return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
  17130. }
  17131. /**
  17132. * A helper to get the player seekable start
  17133. * so that we don't have to null check everywhere
  17134. */
  17135. ;
  17136. _proto.seekableStart = function seekableStart() {
  17137. var seekable = this.player_.seekable();
  17138. var seekableStarts = [];
  17139. var i = seekable ? seekable.length : 0;
  17140. while (i--) {
  17141. seekableStarts.push(seekable.start(i));
  17142. } // grab the first seekable start after sorting, or if there are none
  17143. // default to 0
  17144. return seekableStarts.length ? seekableStarts.sort()[0] : 0;
  17145. }
  17146. /**
  17147. * Get the live time window
  17148. */
  17149. ;
  17150. _proto.liveWindow = function liveWindow() {
  17151. var liveCurrentTime = this.liveCurrentTime();
  17152. if (liveCurrentTime === Infinity) {
  17153. return Infinity;
  17154. }
  17155. return liveCurrentTime - this.seekableStart();
  17156. }
  17157. /**
  17158. * Determines if the player is live, only checks if this component
  17159. * is tracking live playback or not
  17160. */
  17161. ;
  17162. _proto.isLive = function isLive() {
  17163. return this.isTracking();
  17164. }
  17165. /**
  17166. * Determines if currentTime is at the live edge and won't fall behind
  17167. * on each seekableendchange
  17168. */
  17169. ;
  17170. _proto.atLiveEdge = function atLiveEdge() {
  17171. return !this.behindLiveEdge();
  17172. }
  17173. /**
  17174. * get what we expect the live current time to be
  17175. */
  17176. ;
  17177. _proto.liveCurrentTime = function liveCurrentTime() {
  17178. return this.pastSeekEnd() + this.seekableEnd();
  17179. }
  17180. /**
  17181. * Returns how far past seek end we expect current time to be
  17182. */
  17183. ;
  17184. _proto.pastSeekEnd = function pastSeekEnd() {
  17185. return this.pastSeekEnd_;
  17186. }
  17187. /**
  17188. * If we are currently behind the live edge, aka currentTime will be
  17189. * behind on a seekableendchange
  17190. */
  17191. ;
  17192. _proto.behindLiveEdge = function behindLiveEdge() {
  17193. return this.behindLiveEdge_;
  17194. };
  17195. _proto.isTracking = function isTracking() {
  17196. return typeof this.trackingInterval_ === 'number';
  17197. }
  17198. /**
  17199. * Seek to the live edge if we are behind the live edge
  17200. */
  17201. ;
  17202. _proto.seekToLiveEdge = function seekToLiveEdge() {
  17203. if (this.atLiveEdge()) {
  17204. return;
  17205. }
  17206. this.player_.currentTime(this.liveCurrentTime());
  17207. if (this.player_.paused()) {
  17208. this.player_.play();
  17209. }
  17210. };
  17211. _proto.dispose = function dispose() {
  17212. this.stopTracking();
  17213. _Component.prototype.dispose.call(this);
  17214. };
  17215. return LiveTracker;
  17216. }(Component);
  17217. Component.registerComponent('LiveTracker', LiveTracker);
  17218. /**
  17219. * This function is used to fire a sourceset when there is something
  17220. * similar to `mediaEl.load()` being called. It will try to find the source via
  17221. * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
  17222. * with the source that was found or empty string if we cannot know. If it cannot
  17223. * find a source then `sourceset` will not be fired.
  17224. *
  17225. * @param {Html5} tech
  17226. * The tech object that sourceset was setup on
  17227. *
  17228. * @return {boolean}
  17229. * returns false if the sourceset was not fired and true otherwise.
  17230. */
  17231. var sourcesetLoad = function sourcesetLoad(tech) {
  17232. var el = tech.el(); // if `el.src` is set, that source will be loaded.
  17233. if (el.hasAttribute('src')) {
  17234. tech.triggerSourceset(el.src);
  17235. return true;
  17236. }
  17237. /**
  17238. * Since there isn't a src property on the media element, source elements will be used for
  17239. * implementing the source selection algorithm. This happens asynchronously and
  17240. * for most cases were there is more than one source we cannot tell what source will
  17241. * be loaded, without re-implementing the source selection algorithm. At this time we are not
  17242. * going to do that. There are three special cases that we do handle here though:
  17243. *
  17244. * 1. If there are no sources, do not fire `sourceset`.
  17245. * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
  17246. * 3. If there is more than one `<source>` but all of them have the same `src` url.
  17247. * That will be our src.
  17248. */
  17249. var sources = tech.$$('source');
  17250. var srcUrls = [];
  17251. var src = ''; // if there are no sources, do not fire sourceset
  17252. if (!sources.length) {
  17253. return false;
  17254. } // only count valid/non-duplicate source elements
  17255. for (var i = 0; i < sources.length; i++) {
  17256. var url = sources[i].src;
  17257. if (url && srcUrls.indexOf(url) === -1) {
  17258. srcUrls.push(url);
  17259. }
  17260. } // there were no valid sources
  17261. if (!srcUrls.length) {
  17262. return false;
  17263. } // there is only one valid source element url
  17264. // use that
  17265. if (srcUrls.length === 1) {
  17266. src = srcUrls[0];
  17267. }
  17268. tech.triggerSourceset(src);
  17269. return true;
  17270. };
  17271. /**
  17272. * our implementation of an `innerHTML` descriptor for browsers
  17273. * that do not have one.
  17274. */
  17275. var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
  17276. get: function get() {
  17277. return this.cloneNode(true).innerHTML;
  17278. },
  17279. set: function set(v) {
  17280. // make a dummy node to use innerHTML on
  17281. var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided
  17282. dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy
  17283. var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy
  17284. // to the document fragment
  17285. while (dummy.childNodes.length) {
  17286. docFrag.appendChild(dummy.childNodes[0]);
  17287. } // remove content
  17288. this.innerText = ''; // now we add all of that html in one by appending the
  17289. // document fragment. This is how innerHTML does it.
  17290. window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would
  17291. return this.innerHTML;
  17292. }
  17293. });
  17294. /**
  17295. * Get a property descriptor given a list of priorities and the
  17296. * property to get.
  17297. */
  17298. var getDescriptor = function getDescriptor(priority, prop) {
  17299. var descriptor = {};
  17300. for (var i = 0; i < priority.length; i++) {
  17301. descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
  17302. if (descriptor && descriptor.set && descriptor.get) {
  17303. break;
  17304. }
  17305. }
  17306. descriptor.enumerable = true;
  17307. descriptor.configurable = true;
  17308. return descriptor;
  17309. };
  17310. var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
  17311. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
  17312. };
  17313. /**
  17314. * Patches browser internal functions so that we can tell synchronously
  17315. * if a `<source>` was appended to the media element. For some reason this
  17316. * causes a `sourceset` if the the media element is ready and has no source.
  17317. * This happens when:
  17318. * - The page has just loaded and the media element does not have a source.
  17319. * - The media element was emptied of all sources, then `load()` was called.
  17320. *
  17321. * It does this by patching the following functions/properties when they are supported:
  17322. *
  17323. * - `append()` - can be used to add a `<source>` element to the media element
  17324. * - `appendChild()` - can be used to add a `<source>` element to the media element
  17325. * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element
  17326. * - `innerHTML` - can be used to add a `<source>` element to the media element
  17327. *
  17328. * @param {Html5} tech
  17329. * The tech object that sourceset is being setup on.
  17330. */
  17331. var firstSourceWatch = function firstSourceWatch(tech) {
  17332. var el = tech.el(); // make sure firstSourceWatch isn't setup twice.
  17333. if (el.resetSourceWatch_) {
  17334. return;
  17335. }
  17336. var old = {};
  17337. var innerDescriptor = getInnerHTMLDescriptor(tech);
  17338. var appendWrapper = function appendWrapper(appendFn) {
  17339. return function () {
  17340. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  17341. args[_key] = arguments[_key];
  17342. }
  17343. var retval = appendFn.apply(el, args);
  17344. sourcesetLoad(tech);
  17345. return retval;
  17346. };
  17347. };
  17348. ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
  17349. if (!el[k]) {
  17350. return;
  17351. } // store the old function
  17352. old[k] = el[k]; // call the old function with a sourceset if a source
  17353. // was loaded
  17354. el[k] = appendWrapper(old[k]);
  17355. });
  17356. Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
  17357. set: appendWrapper(innerDescriptor.set)
  17358. }));
  17359. el.resetSourceWatch_ = function () {
  17360. el.resetSourceWatch_ = null;
  17361. Object.keys(old).forEach(function (k) {
  17362. el[k] = old[k];
  17363. });
  17364. Object.defineProperty(el, 'innerHTML', innerDescriptor);
  17365. }; // on the first sourceset, we need to revert our changes
  17366. tech.one('sourceset', el.resetSourceWatch_);
  17367. };
  17368. /**
  17369. * our implementation of a `src` descriptor for browsers
  17370. * that do not have one.
  17371. */
  17372. var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
  17373. get: function get() {
  17374. if (this.hasAttribute('src')) {
  17375. return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src'));
  17376. }
  17377. return '';
  17378. },
  17379. set: function set(v) {
  17380. window$1.Element.prototype.setAttribute.call(this, 'src', v);
  17381. return v;
  17382. }
  17383. });
  17384. var getSrcDescriptor = function getSrcDescriptor(tech) {
  17385. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
  17386. };
  17387. /**
  17388. * setup `sourceset` handling on the `Html5` tech. This function
  17389. * patches the following element properties/functions:
  17390. *
  17391. * - `src` - to determine when `src` is set
  17392. * - `setAttribute()` - to determine when `src` is set
  17393. * - `load()` - this re-triggers the source selection algorithm, and can
  17394. * cause a sourceset.
  17395. *
  17396. * If there is no source when we are adding `sourceset` support or during a `load()`
  17397. * we also patch the functions listed in `firstSourceWatch`.
  17398. *
  17399. * @param {Html5} tech
  17400. * The tech to patch
  17401. */
  17402. var setupSourceset = function setupSourceset(tech) {
  17403. if (!tech.featuresSourceset) {
  17404. return;
  17405. }
  17406. var el = tech.el(); // make sure sourceset isn't setup twice.
  17407. if (el.resetSourceset_) {
  17408. return;
  17409. }
  17410. var srcDescriptor = getSrcDescriptor(tech);
  17411. var oldSetAttribute = el.setAttribute;
  17412. var oldLoad = el.load;
  17413. Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
  17414. set: function set(v) {
  17415. var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src
  17416. tech.triggerSourceset(el.src);
  17417. return retval;
  17418. }
  17419. }));
  17420. el.setAttribute = function (n, v) {
  17421. var retval = oldSetAttribute.call(el, n, v);
  17422. if (/src/i.test(n)) {
  17423. tech.triggerSourceset(el.src);
  17424. }
  17425. return retval;
  17426. };
  17427. el.load = function () {
  17428. var retval = oldLoad.call(el); // if load was called, but there was no source to fire
  17429. // sourceset on. We have to watch for a source append
  17430. // as that can trigger a `sourceset` when the media element
  17431. // has no source
  17432. if (!sourcesetLoad(tech)) {
  17433. tech.triggerSourceset('');
  17434. firstSourceWatch(tech);
  17435. }
  17436. return retval;
  17437. };
  17438. if (el.currentSrc) {
  17439. tech.triggerSourceset(el.currentSrc);
  17440. } else if (!sourcesetLoad(tech)) {
  17441. firstSourceWatch(tech);
  17442. }
  17443. el.resetSourceset_ = function () {
  17444. el.resetSourceset_ = null;
  17445. el.load = oldLoad;
  17446. el.setAttribute = oldSetAttribute;
  17447. Object.defineProperty(el, 'src', srcDescriptor);
  17448. if (el.resetSourceWatch_) {
  17449. el.resetSourceWatch_();
  17450. }
  17451. };
  17452. };
  17453. function _templateObject$1() {
  17454. var data = _taggedTemplateLiteralLoose(["Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.\n This may prevent text tracks from loading."]);
  17455. _templateObject$1 = function _templateObject() {
  17456. return data;
  17457. };
  17458. return data;
  17459. }
  17460. /**
  17461. * HTML5 Media Controller - Wrapper for HTML5 Media API
  17462. *
  17463. * @mixes Tech~SourceHandlerAdditions
  17464. * @extends Tech
  17465. */
  17466. var Html5 =
  17467. /*#__PURE__*/
  17468. function (_Tech) {
  17469. _inheritsLoose(Html5, _Tech);
  17470. /**
  17471. * Create an instance of this Tech.
  17472. *
  17473. * @param {Object} [options]
  17474. * The key/value store of player options.
  17475. *
  17476. * @param {Component~ReadyCallback} ready
  17477. * Callback function to call when the `HTML5` Tech is ready.
  17478. */
  17479. function Html5(options, ready) {
  17480. var _this;
  17481. _this = _Tech.call(this, options, ready) || this;
  17482. var source = options.source;
  17483. var crossoriginTracks = false; // Set the source if one is provided
  17484. // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
  17485. // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
  17486. // anyway so the error gets fired.
  17487. if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
  17488. _this.setSource(source);
  17489. } else {
  17490. _this.handleLateInit_(_this.el_);
  17491. } // setup sourceset after late sourceset/init
  17492. if (options.enableSourceset) {
  17493. _this.setupSourcesetHandling_();
  17494. }
  17495. if (_this.el_.hasChildNodes()) {
  17496. var nodes = _this.el_.childNodes;
  17497. var nodesLength = nodes.length;
  17498. var removeNodes = [];
  17499. while (nodesLength--) {
  17500. var node = nodes[nodesLength];
  17501. var nodeName = node.nodeName.toLowerCase();
  17502. if (nodeName === 'track') {
  17503. if (!_this.featuresNativeTextTracks) {
  17504. // Empty video tag tracks so the built-in player doesn't use them also.
  17505. // This may not be fast enough to stop HTML5 browsers from reading the tags
  17506. // so we'll need to turn off any default tracks if we're manually doing
  17507. // captions and subtitles. videoElement.textTracks
  17508. removeNodes.push(node);
  17509. } else {
  17510. // store HTMLTrackElement and TextTrack to remote list
  17511. _this.remoteTextTrackEls().addTrackElement_(node);
  17512. _this.remoteTextTracks().addTrack(node.track);
  17513. _this.textTracks().addTrack(node.track);
  17514. if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
  17515. crossoriginTracks = true;
  17516. }
  17517. }
  17518. }
  17519. }
  17520. for (var i = 0; i < removeNodes.length; i++) {
  17521. _this.el_.removeChild(removeNodes[i]);
  17522. }
  17523. }
  17524. _this.proxyNativeTracks_();
  17525. if (_this.featuresNativeTextTracks && crossoriginTracks) {
  17526. log.warn(tsml(_templateObject$1()));
  17527. } // prevent iOS Safari from disabling metadata text tracks during native playback
  17528. _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used
  17529. // Our goal should be to get the custom controls on mobile solid everywhere
  17530. // so we can remove this all together. Right now this will block custom
  17531. // controls on touch enabled laptops like the Chrome Pixel
  17532. if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
  17533. _this.setControls(true);
  17534. } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
  17535. // into a `fullscreenchange` event
  17536. _this.proxyWebkitFullscreen_();
  17537. _this.triggerReady();
  17538. return _this;
  17539. }
  17540. /**
  17541. * Dispose of `HTML5` media element and remove all tracks.
  17542. */
  17543. var _proto = Html5.prototype;
  17544. _proto.dispose = function dispose() {
  17545. if (this.el_ && this.el_.resetSourceset_) {
  17546. this.el_.resetSourceset_();
  17547. }
  17548. Html5.disposeMediaElement(this.el_);
  17549. this.options_ = null; // tech will handle clearing of the emulated track list
  17550. _Tech.prototype.dispose.call(this);
  17551. }
  17552. /**
  17553. * Modify the media element so that we can detect when
  17554. * the source is changed. Fires `sourceset` just after the source has changed
  17555. */
  17556. ;
  17557. _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() {
  17558. setupSourceset(this);
  17559. }
  17560. /**
  17561. * When a captions track is enabled in the iOS Safari native player, all other
  17562. * tracks are disabled (including metadata tracks), which nulls all of their
  17563. * associated cue points. This will restore metadata tracks to their pre-fullscreen
  17564. * state in those cases so that cue points are not needlessly lost.
  17565. *
  17566. * @private
  17567. */
  17568. ;
  17569. _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
  17570. var textTracks = this.textTracks();
  17571. var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state
  17572. var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
  17573. metadataTracksPreFullscreenState = [];
  17574. for (var i = 0; i < textTracks.length; i++) {
  17575. var track = textTracks[i];
  17576. if (track.kind === 'metadata') {
  17577. metadataTracksPreFullscreenState.push({
  17578. track: track,
  17579. storedMode: track.mode
  17580. });
  17581. }
  17582. }
  17583. }; // snapshot each metadata track's initial state, and update the snapshot
  17584. // each time there is a track 'change' event
  17585. takeMetadataTrackSnapshot();
  17586. textTracks.addEventListener('change', takeMetadataTrackSnapshot);
  17587. this.on('dispose', function () {
  17588. return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  17589. });
  17590. var restoreTrackMode = function restoreTrackMode() {
  17591. for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
  17592. var storedTrack = metadataTracksPreFullscreenState[i];
  17593. if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
  17594. storedTrack.track.mode = storedTrack.storedMode;
  17595. }
  17596. } // we only want this handler to be executed on the first 'change' event
  17597. textTracks.removeEventListener('change', restoreTrackMode);
  17598. }; // when we enter fullscreen playback, stop updating the snapshot and
  17599. // restore all track modes to their pre-fullscreen state
  17600. this.on('webkitbeginfullscreen', function () {
  17601. textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed
  17602. textTracks.removeEventListener('change', restoreTrackMode);
  17603. textTracks.addEventListener('change', restoreTrackMode);
  17604. }); // start updating the snapshot again after leaving fullscreen
  17605. this.on('webkitendfullscreen', function () {
  17606. // remove the listener before adding it just in case it wasn't previously removed
  17607. textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  17608. textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
  17609. textTracks.removeEventListener('change', restoreTrackMode);
  17610. });
  17611. }
  17612. /**
  17613. * Attempt to force override of tracks for the given type
  17614. *
  17615. * @param {string} type - Track type to override, possible values include 'Audio',
  17616. * 'Video', and 'Text'.
  17617. * @param {boolean} override - If set to true native audio/video will be overridden,
  17618. * otherwise native audio/video will potentially be used.
  17619. * @private
  17620. */
  17621. ;
  17622. _proto.overrideNative_ = function overrideNative_(type, override) {
  17623. var _this2 = this;
  17624. // If there is no behavioral change don't add/remove listeners
  17625. if (override !== this["featuresNative" + type + "Tracks"]) {
  17626. return;
  17627. }
  17628. var lowerCaseType = type.toLowerCase();
  17629. if (this[lowerCaseType + "TracksListeners_"]) {
  17630. Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) {
  17631. var elTracks = _this2.el()[lowerCaseType + "Tracks"];
  17632. elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]);
  17633. });
  17634. }
  17635. this["featuresNative" + type + "Tracks"] = !override;
  17636. this[lowerCaseType + "TracksListeners_"] = null;
  17637. this.proxyNativeTracksForType_(lowerCaseType);
  17638. }
  17639. /**
  17640. * Attempt to force override of native audio tracks.
  17641. *
  17642. * @param {boolean} override - If set to true native audio will be overridden,
  17643. * otherwise native audio will potentially be used.
  17644. */
  17645. ;
  17646. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
  17647. this.overrideNative_('Audio', override);
  17648. }
  17649. /**
  17650. * Attempt to force override of native video tracks.
  17651. *
  17652. * @param {boolean} override - If set to true native video will be overridden,
  17653. * otherwise native video will potentially be used.
  17654. */
  17655. ;
  17656. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
  17657. this.overrideNative_('Video', override);
  17658. }
  17659. /**
  17660. * Proxy native track list events for the given type to our track
  17661. * lists if the browser we are playing in supports that type of track list.
  17662. *
  17663. * @param {string} name - Track type; values include 'audio', 'video', and 'text'
  17664. * @private
  17665. */
  17666. ;
  17667. _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
  17668. var _this3 = this;
  17669. var props = NORMAL[name];
  17670. var elTracks = this.el()[props.getterName];
  17671. var techTracks = this[props.getterName]();
  17672. if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) {
  17673. return;
  17674. }
  17675. var listeners = {
  17676. change: function change(e) {
  17677. techTracks.trigger({
  17678. type: 'change',
  17679. target: techTracks,
  17680. currentTarget: techTracks,
  17681. srcElement: techTracks
  17682. });
  17683. },
  17684. addtrack: function addtrack(e) {
  17685. techTracks.addTrack(e.track);
  17686. },
  17687. removetrack: function removetrack(e) {
  17688. techTracks.removeTrack(e.track);
  17689. }
  17690. };
  17691. var removeOldTracks = function removeOldTracks() {
  17692. var removeTracks = [];
  17693. for (var i = 0; i < techTracks.length; i++) {
  17694. var found = false;
  17695. for (var j = 0; j < elTracks.length; j++) {
  17696. if (elTracks[j] === techTracks[i]) {
  17697. found = true;
  17698. break;
  17699. }
  17700. }
  17701. if (!found) {
  17702. removeTracks.push(techTracks[i]);
  17703. }
  17704. }
  17705. while (removeTracks.length) {
  17706. techTracks.removeTrack(removeTracks.shift());
  17707. }
  17708. };
  17709. this[props.getterName + 'Listeners_'] = listeners;
  17710. Object.keys(listeners).forEach(function (eventName) {
  17711. var listener = listeners[eventName];
  17712. elTracks.addEventListener(eventName, listener);
  17713. _this3.on('dispose', function (e) {
  17714. return elTracks.removeEventListener(eventName, listener);
  17715. });
  17716. }); // Remove (native) tracks that are not used anymore
  17717. this.on('loadstart', removeOldTracks);
  17718. this.on('dispose', function (e) {
  17719. return _this3.off('loadstart', removeOldTracks);
  17720. });
  17721. }
  17722. /**
  17723. * Proxy all native track list events to our track lists if the browser we are playing
  17724. * in supports that type of track list.
  17725. *
  17726. * @private
  17727. */
  17728. ;
  17729. _proto.proxyNativeTracks_ = function proxyNativeTracks_() {
  17730. var _this4 = this;
  17731. NORMAL.names.forEach(function (name) {
  17732. _this4.proxyNativeTracksForType_(name);
  17733. });
  17734. }
  17735. /**
  17736. * Create the `Html5` Tech's DOM element.
  17737. *
  17738. * @return {Element}
  17739. * The element that gets created.
  17740. */
  17741. ;
  17742. _proto.createEl = function createEl$$1() {
  17743. var el = this.options_.tag; // Check if this browser supports moving the element into the box.
  17744. // On the iPhone video will break if you move the element,
  17745. // So we have to create a brand new element.
  17746. // If we ingested the player div, we do not need to move the media element.
  17747. if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
  17748. // If the original tag is still there, clone and remove it.
  17749. if (el) {
  17750. var clone = el.cloneNode(true);
  17751. if (el.parentNode) {
  17752. el.parentNode.insertBefore(clone, el);
  17753. }
  17754. Html5.disposeMediaElement(el);
  17755. el = clone;
  17756. } else {
  17757. el = document.createElement('video'); // determine if native controls should be used
  17758. var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
  17759. var attributes = mergeOptions({}, tagAttributes);
  17760. if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
  17761. delete attributes.controls;
  17762. }
  17763. setAttributes(el, assign(attributes, {
  17764. id: this.options_.techId,
  17765. class: 'vjs-tech'
  17766. }));
  17767. }
  17768. el.playerId = this.options_.playerId;
  17769. }
  17770. if (typeof this.options_.preload !== 'undefined') {
  17771. setAttribute(el, 'preload', this.options_.preload);
  17772. } // Update specific tag settings, in case they were overridden
  17773. // `autoplay` has to be *last* so that `muted` and `playsinline` are present
  17774. // when iOS/Safari or other browsers attempt to autoplay.
  17775. var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
  17776. for (var i = 0; i < settingsAttrs.length; i++) {
  17777. var attr = settingsAttrs[i];
  17778. var value = this.options_[attr];
  17779. if (typeof value !== 'undefined') {
  17780. if (value) {
  17781. setAttribute(el, attr, attr);
  17782. } else {
  17783. removeAttribute(el, attr);
  17784. }
  17785. el[attr] = value;
  17786. }
  17787. }
  17788. return el;
  17789. }
  17790. /**
  17791. * This will be triggered if the loadstart event has already fired, before videojs was
  17792. * ready. Two known examples of when this can happen are:
  17793. * 1. If we're loading the playback object after it has started loading
  17794. * 2. The media is already playing the (often with autoplay on) then
  17795. *
  17796. * This function will fire another loadstart so that videojs can catchup.
  17797. *
  17798. * @fires Tech#loadstart
  17799. *
  17800. * @return {undefined}
  17801. * returns nothing.
  17802. */
  17803. ;
  17804. _proto.handleLateInit_ = function handleLateInit_(el) {
  17805. if (el.networkState === 0 || el.networkState === 3) {
  17806. // The video element hasn't started loading the source yet
  17807. // or didn't find a source
  17808. return;
  17809. }
  17810. if (el.readyState === 0) {
  17811. // NetworkState is set synchronously BUT loadstart is fired at the
  17812. // end of the current stack, usually before setInterval(fn, 0).
  17813. // So at this point we know loadstart may have already fired or is
  17814. // about to fire, and either way the player hasn't seen it yet.
  17815. // We don't want to fire loadstart prematurely here and cause a
  17816. // double loadstart so we'll wait and see if it happens between now
  17817. // and the next loop, and fire it if not.
  17818. // HOWEVER, we also want to make sure it fires before loadedmetadata
  17819. // which could also happen between now and the next loop, so we'll
  17820. // watch for that also.
  17821. var loadstartFired = false;
  17822. var setLoadstartFired = function setLoadstartFired() {
  17823. loadstartFired = true;
  17824. };
  17825. this.on('loadstart', setLoadstartFired);
  17826. var triggerLoadstart = function triggerLoadstart() {
  17827. // We did miss the original loadstart. Make sure the player
  17828. // sees loadstart before loadedmetadata
  17829. if (!loadstartFired) {
  17830. this.trigger('loadstart');
  17831. }
  17832. };
  17833. this.on('loadedmetadata', triggerLoadstart);
  17834. this.ready(function () {
  17835. this.off('loadstart', setLoadstartFired);
  17836. this.off('loadedmetadata', triggerLoadstart);
  17837. if (!loadstartFired) {
  17838. // We did miss the original native loadstart. Fire it now.
  17839. this.trigger('loadstart');
  17840. }
  17841. });
  17842. return;
  17843. } // From here on we know that loadstart already fired and we missed it.
  17844. // The other readyState events aren't as much of a problem if we double
  17845. // them, so not going to go to as much trouble as loadstart to prevent
  17846. // that unless we find reason to.
  17847. var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
  17848. eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
  17849. if (el.readyState >= 2) {
  17850. eventsToTrigger.push('loadeddata');
  17851. } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
  17852. if (el.readyState >= 3) {
  17853. eventsToTrigger.push('canplay');
  17854. } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
  17855. if (el.readyState >= 4) {
  17856. eventsToTrigger.push('canplaythrough');
  17857. } // We still need to give the player time to add event listeners
  17858. this.ready(function () {
  17859. eventsToTrigger.forEach(function (type) {
  17860. this.trigger(type);
  17861. }, this);
  17862. });
  17863. }
  17864. /**
  17865. * Set current time for the `HTML5` tech.
  17866. *
  17867. * @param {number} seconds
  17868. * Set the current time of the media to this.
  17869. */
  17870. ;
  17871. _proto.setCurrentTime = function setCurrentTime(seconds) {
  17872. try {
  17873. this.el_.currentTime = seconds;
  17874. } catch (e) {
  17875. log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady);
  17876. }
  17877. }
  17878. /**
  17879. * Get the current duration of the HTML5 media element.
  17880. *
  17881. * @return {number}
  17882. * The duration of the media or 0 if there is no duration.
  17883. */
  17884. ;
  17885. _proto.duration = function duration() {
  17886. var _this5 = this;
  17887. // Android Chrome will report duration as Infinity for VOD HLS until after
  17888. // playback has started, which triggers the live display erroneously.
  17889. // Return NaN if playback has not started and trigger a durationupdate once
  17890. // the duration can be reliably known.
  17891. if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
  17892. // Wait for the first `timeupdate` with currentTime > 0 - there may be
  17893. // several with 0
  17894. var checkProgress = function checkProgress() {
  17895. if (_this5.el_.currentTime > 0) {
  17896. // Trigger durationchange for genuinely live video
  17897. if (_this5.el_.duration === Infinity) {
  17898. _this5.trigger('durationchange');
  17899. }
  17900. _this5.off('timeupdate', checkProgress);
  17901. }
  17902. };
  17903. this.on('timeupdate', checkProgress);
  17904. return NaN;
  17905. }
  17906. return this.el_.duration || NaN;
  17907. }
  17908. /**
  17909. * Get the current width of the HTML5 media element.
  17910. *
  17911. * @return {number}
  17912. * The width of the HTML5 media element.
  17913. */
  17914. ;
  17915. _proto.width = function width() {
  17916. return this.el_.offsetWidth;
  17917. }
  17918. /**
  17919. * Get the current height of the HTML5 media element.
  17920. *
  17921. * @return {number}
  17922. * The height of the HTML5 media element.
  17923. */
  17924. ;
  17925. _proto.height = function height() {
  17926. return this.el_.offsetHeight;
  17927. }
  17928. /**
  17929. * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
  17930. * `fullscreenchange` event.
  17931. *
  17932. * @private
  17933. * @fires fullscreenchange
  17934. * @listens webkitendfullscreen
  17935. * @listens webkitbeginfullscreen
  17936. * @listens webkitbeginfullscreen
  17937. */
  17938. ;
  17939. _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
  17940. var _this6 = this;
  17941. if (!('webkitDisplayingFullscreen' in this.el_)) {
  17942. return;
  17943. }
  17944. var endFn = function endFn() {
  17945. this.trigger('fullscreenchange', {
  17946. isFullscreen: false
  17947. });
  17948. };
  17949. var beginFn = function beginFn() {
  17950. if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
  17951. this.one('webkitendfullscreen', endFn);
  17952. this.trigger('fullscreenchange', {
  17953. isFullscreen: true
  17954. });
  17955. }
  17956. };
  17957. this.on('webkitbeginfullscreen', beginFn);
  17958. this.on('dispose', function () {
  17959. _this6.off('webkitbeginfullscreen', beginFn);
  17960. _this6.off('webkitendfullscreen', endFn);
  17961. });
  17962. }
  17963. /**
  17964. * Check if fullscreen is supported on the current playback device.
  17965. *
  17966. * @return {boolean}
  17967. * - True if fullscreen is supported.
  17968. * - False if fullscreen is not supported.
  17969. */
  17970. ;
  17971. _proto.supportsFullScreen = function supportsFullScreen() {
  17972. if (typeof this.el_.webkitEnterFullScreen === 'function') {
  17973. var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard
  17974. if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
  17975. return true;
  17976. }
  17977. }
  17978. return false;
  17979. }
  17980. /**
  17981. * Request that the `HTML5` Tech enter fullscreen.
  17982. */
  17983. ;
  17984. _proto.enterFullScreen = function enterFullScreen() {
  17985. var video = this.el_;
  17986. if (video.paused && video.networkState <= video.HAVE_METADATA) {
  17987. // attempt to prime the video element for programmatic access
  17988. // this isn't necessary on the desktop but shouldn't hurt
  17989. this.el_.play(); // playing and pausing synchronously during the transition to fullscreen
  17990. // can get iOS ~6.1 devices into a play/pause loop
  17991. this.setTimeout(function () {
  17992. video.pause();
  17993. video.webkitEnterFullScreen();
  17994. }, 0);
  17995. } else {
  17996. video.webkitEnterFullScreen();
  17997. }
  17998. }
  17999. /**
  18000. * Request that the `HTML5` Tech exit fullscreen.
  18001. */
  18002. ;
  18003. _proto.exitFullScreen = function exitFullScreen() {
  18004. this.el_.webkitExitFullScreen();
  18005. }
  18006. /**
  18007. * A getter/setter for the `Html5` Tech's source object.
  18008. * > Note: Please use {@link Html5#setSource}
  18009. *
  18010. * @param {Tech~SourceObject} [src]
  18011. * The source object you want to set on the `HTML5` techs element.
  18012. *
  18013. * @return {Tech~SourceObject|undefined}
  18014. * - The current source object when a source is not passed in.
  18015. * - undefined when setting
  18016. *
  18017. * @deprecated Since version 5.
  18018. */
  18019. ;
  18020. _proto.src = function src(_src) {
  18021. if (_src === undefined) {
  18022. return this.el_.src;
  18023. } // Setting src through `src` instead of `setSrc` will be deprecated
  18024. this.setSrc(_src);
  18025. }
  18026. /**
  18027. * Reset the tech by removing all sources and then calling
  18028. * {@link Html5.resetMediaElement}.
  18029. */
  18030. ;
  18031. _proto.reset = function reset() {
  18032. Html5.resetMediaElement(this.el_);
  18033. }
  18034. /**
  18035. * Get the current source on the `HTML5` Tech. Falls back to returning the source from
  18036. * the HTML5 media element.
  18037. *
  18038. * @return {Tech~SourceObject}
  18039. * The current source object from the HTML5 tech. With a fallback to the
  18040. * elements source.
  18041. */
  18042. ;
  18043. _proto.currentSrc = function currentSrc() {
  18044. if (this.currentSource_) {
  18045. return this.currentSource_.src;
  18046. }
  18047. return this.el_.currentSrc;
  18048. }
  18049. /**
  18050. * Set controls attribute for the HTML5 media Element.
  18051. *
  18052. * @param {string} val
  18053. * Value to set the controls attribute to
  18054. */
  18055. ;
  18056. _proto.setControls = function setControls(val) {
  18057. this.el_.controls = !!val;
  18058. }
  18059. /**
  18060. * Create and returns a remote {@link TextTrack} object.
  18061. *
  18062. * @param {string} kind
  18063. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  18064. *
  18065. * @param {string} [label]
  18066. * Label to identify the text track
  18067. *
  18068. * @param {string} [language]
  18069. * Two letter language abbreviation
  18070. *
  18071. * @return {TextTrack}
  18072. * The TextTrack that gets created.
  18073. */
  18074. ;
  18075. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  18076. if (!this.featuresNativeTextTracks) {
  18077. return _Tech.prototype.addTextTrack.call(this, kind, label, language);
  18078. }
  18079. return this.el_.addTextTrack(kind, label, language);
  18080. }
  18081. /**
  18082. * Creates either native TextTrack or an emulated TextTrack depending
  18083. * on the value of `featuresNativeTextTracks`
  18084. *
  18085. * @param {Object} options
  18086. * The object should contain the options to initialize the TextTrack with.
  18087. *
  18088. * @param {string} [options.kind]
  18089. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  18090. *
  18091. * @param {string} [options.label]
  18092. * Label to identify the text track
  18093. *
  18094. * @param {string} [options.language]
  18095. * Two letter language abbreviation.
  18096. *
  18097. * @param {boolean} [options.default]
  18098. * Default this track to on.
  18099. *
  18100. * @param {string} [options.id]
  18101. * The internal id to assign this track.
  18102. *
  18103. * @param {string} [options.src]
  18104. * A source url for the track.
  18105. *
  18106. * @return {HTMLTrackElement}
  18107. * The track element that gets created.
  18108. */
  18109. ;
  18110. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  18111. if (!this.featuresNativeTextTracks) {
  18112. return _Tech.prototype.createRemoteTextTrack.call(this, options);
  18113. }
  18114. var htmlTrackElement = document.createElement('track');
  18115. if (options.kind) {
  18116. htmlTrackElement.kind = options.kind;
  18117. }
  18118. if (options.label) {
  18119. htmlTrackElement.label = options.label;
  18120. }
  18121. if (options.language || options.srclang) {
  18122. htmlTrackElement.srclang = options.language || options.srclang;
  18123. }
  18124. if (options.default) {
  18125. htmlTrackElement.default = options.default;
  18126. }
  18127. if (options.id) {
  18128. htmlTrackElement.id = options.id;
  18129. }
  18130. if (options.src) {
  18131. htmlTrackElement.src = options.src;
  18132. }
  18133. return htmlTrackElement;
  18134. }
  18135. /**
  18136. * Creates a remote text track object and returns an html track element.
  18137. *
  18138. * @param {Object} options The object should contain values for
  18139. * kind, language, label, and src (location of the WebVTT file)
  18140. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  18141. * automatically removed from the video element whenever the source changes
  18142. * @return {HTMLTrackElement} An Html Track Element.
  18143. * This can be an emulated {@link HTMLTrackElement} or a native one.
  18144. * @deprecated The default value of the "manualCleanup" parameter will default
  18145. * to "false" in upcoming versions of Video.js
  18146. */
  18147. ;
  18148. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  18149. var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
  18150. if (this.featuresNativeTextTracks) {
  18151. this.el().appendChild(htmlTrackElement);
  18152. }
  18153. return htmlTrackElement;
  18154. }
  18155. /**
  18156. * Remove remote `TextTrack` from `TextTrackList` object
  18157. *
  18158. * @param {TextTrack} track
  18159. * `TextTrack` object to remove
  18160. */
  18161. ;
  18162. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  18163. _Tech.prototype.removeRemoteTextTrack.call(this, track);
  18164. if (this.featuresNativeTextTracks) {
  18165. var tracks = this.$$('track');
  18166. var i = tracks.length;
  18167. while (i--) {
  18168. if (track === tracks[i] || track === tracks[i].track) {
  18169. this.el().removeChild(tracks[i]);
  18170. }
  18171. }
  18172. }
  18173. }
  18174. /**
  18175. * Gets available media playback quality metrics as specified by the W3C's Media
  18176. * Playback Quality API.
  18177. *
  18178. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  18179. *
  18180. * @return {Object}
  18181. * An object with supported media playback quality metrics
  18182. */
  18183. ;
  18184. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  18185. if (typeof this.el().getVideoPlaybackQuality === 'function') {
  18186. return this.el().getVideoPlaybackQuality();
  18187. }
  18188. var videoPlaybackQuality = {};
  18189. if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
  18190. videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
  18191. videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
  18192. }
  18193. if (window$1.performance && typeof window$1.performance.now === 'function') {
  18194. videoPlaybackQuality.creationTime = window$1.performance.now();
  18195. } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') {
  18196. videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart;
  18197. }
  18198. return videoPlaybackQuality;
  18199. };
  18200. return Html5;
  18201. }(Tech);
  18202. /* HTML5 Support Testing ---------------------------------------------------- */
  18203. if (isReal()) {
  18204. /**
  18205. * Element for testing browser HTML5 media capabilities
  18206. *
  18207. * @type {Element}
  18208. * @constant
  18209. * @private
  18210. */
  18211. Html5.TEST_VID = document.createElement('video');
  18212. var track = document.createElement('track');
  18213. track.kind = 'captions';
  18214. track.srclang = 'en';
  18215. track.label = 'English';
  18216. Html5.TEST_VID.appendChild(track);
  18217. }
  18218. /**
  18219. * Check if HTML5 media is supported by this browser/device.
  18220. *
  18221. * @return {boolean}
  18222. * - True if HTML5 media is supported.
  18223. * - False if HTML5 media is not supported.
  18224. */
  18225. Html5.isSupported = function () {
  18226. // IE with no Media Player is a LIAR! (#984)
  18227. try {
  18228. Html5.TEST_VID.volume = 0.5;
  18229. } catch (e) {
  18230. return false;
  18231. }
  18232. return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
  18233. };
  18234. /**
  18235. * Check if the tech can support the given type
  18236. *
  18237. * @param {string} type
  18238. * The mimetype to check
  18239. * @return {string} 'probably', 'maybe', or '' (empty string)
  18240. */
  18241. Html5.canPlayType = function (type) {
  18242. return Html5.TEST_VID.canPlayType(type);
  18243. };
  18244. /**
  18245. * Check if the tech can support the given source
  18246. *
  18247. * @param {Object} srcObj
  18248. * The source object
  18249. * @param {Object} options
  18250. * The options passed to the tech
  18251. * @return {string} 'probably', 'maybe', or '' (empty string)
  18252. */
  18253. Html5.canPlaySource = function (srcObj, options) {
  18254. return Html5.canPlayType(srcObj.type);
  18255. };
  18256. /**
  18257. * Check if the volume can be changed in this browser/device.
  18258. * Volume cannot be changed in a lot of mobile devices.
  18259. * Specifically, it can't be changed from 1 on iOS.
  18260. *
  18261. * @return {boolean}
  18262. * - True if volume can be controlled
  18263. * - False otherwise
  18264. */
  18265. Html5.canControlVolume = function () {
  18266. // IE will error if Windows Media Player not installed #3315
  18267. try {
  18268. var volume = Html5.TEST_VID.volume;
  18269. Html5.TEST_VID.volume = volume / 2 + 0.1;
  18270. return volume !== Html5.TEST_VID.volume;
  18271. } catch (e) {
  18272. return false;
  18273. }
  18274. };
  18275. /**
  18276. * Check if the volume can be muted in this browser/device.
  18277. * Some devices, e.g. iOS, don't allow changing volume
  18278. * but permits muting/unmuting.
  18279. *
  18280. * @return {bolean}
  18281. * - True if volume can be muted
  18282. * - False otherwise
  18283. */
  18284. Html5.canMuteVolume = function () {
  18285. try {
  18286. var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always
  18287. // work, so we want to set both property and attribute
  18288. Html5.TEST_VID.muted = !muted;
  18289. if (Html5.TEST_VID.muted) {
  18290. setAttribute(Html5.TEST_VID, 'muted', 'muted');
  18291. } else {
  18292. removeAttribute(Html5.TEST_VID, 'muted', 'muted');
  18293. }
  18294. return muted !== Html5.TEST_VID.muted;
  18295. } catch (e) {
  18296. return false;
  18297. }
  18298. };
  18299. /**
  18300. * Check if the playback rate can be changed in this browser/device.
  18301. *
  18302. * @return {boolean}
  18303. * - True if playback rate can be controlled
  18304. * - False otherwise
  18305. */
  18306. Html5.canControlPlaybackRate = function () {
  18307. // Playback rate API is implemented in Android Chrome, but doesn't do anything
  18308. // https://github.com/videojs/video.js/issues/3180
  18309. if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
  18310. return false;
  18311. } // IE will error if Windows Media Player not installed #3315
  18312. try {
  18313. var playbackRate = Html5.TEST_VID.playbackRate;
  18314. Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
  18315. return playbackRate !== Html5.TEST_VID.playbackRate;
  18316. } catch (e) {
  18317. return false;
  18318. }
  18319. };
  18320. /**
  18321. * Check if we can override a video/audio elements attributes, with
  18322. * Object.defineProperty.
  18323. *
  18324. * @return {boolean}
  18325. * - True if builtin attributes can be overridden
  18326. * - False otherwise
  18327. */
  18328. Html5.canOverrideAttributes = function () {
  18329. // if we cannot overwrite the src/innerHTML property, there is no support
  18330. // iOS 7 safari for instance cannot do this.
  18331. try {
  18332. var noop = function noop() {};
  18333. Object.defineProperty(document.createElement('video'), 'src', {
  18334. get: noop,
  18335. set: noop
  18336. });
  18337. Object.defineProperty(document.createElement('audio'), 'src', {
  18338. get: noop,
  18339. set: noop
  18340. });
  18341. Object.defineProperty(document.createElement('video'), 'innerHTML', {
  18342. get: noop,
  18343. set: noop
  18344. });
  18345. Object.defineProperty(document.createElement('audio'), 'innerHTML', {
  18346. get: noop,
  18347. set: noop
  18348. });
  18349. } catch (e) {
  18350. return false;
  18351. }
  18352. return true;
  18353. };
  18354. /**
  18355. * Check to see if native `TextTrack`s are supported by this browser/device.
  18356. *
  18357. * @return {boolean}
  18358. * - True if native `TextTrack`s are supported.
  18359. * - False otherwise
  18360. */
  18361. Html5.supportsNativeTextTracks = function () {
  18362. return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
  18363. };
  18364. /**
  18365. * Check to see if native `VideoTrack`s are supported by this browser/device
  18366. *
  18367. * @return {boolean}
  18368. * - True if native `VideoTrack`s are supported.
  18369. * - False otherwise
  18370. */
  18371. Html5.supportsNativeVideoTracks = function () {
  18372. return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
  18373. };
  18374. /**
  18375. * Check to see if native `AudioTrack`s are supported by this browser/device
  18376. *
  18377. * @return {boolean}
  18378. * - True if native `AudioTrack`s are supported.
  18379. * - False otherwise
  18380. */
  18381. Html5.supportsNativeAudioTracks = function () {
  18382. return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
  18383. };
  18384. /**
  18385. * An array of events available on the Html5 tech.
  18386. *
  18387. * @private
  18388. * @type {Array}
  18389. */
  18390. 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'];
  18391. /**
  18392. * Boolean indicating whether the `Tech` supports volume control.
  18393. *
  18394. * @type {boolean}
  18395. * @default {@link Html5.canControlVolume}
  18396. */
  18397. Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
  18398. /**
  18399. * Boolean indicating whether the `Tech` supports muting volume.
  18400. *
  18401. * @type {bolean}
  18402. * @default {@link Html5.canMuteVolume}
  18403. */
  18404. Html5.prototype.featuresMuteControl = Html5.canMuteVolume();
  18405. /**
  18406. * Boolean indicating whether the `Tech` supports changing the speed at which the media
  18407. * plays. Examples:
  18408. * - Set player to play 2x (twice) as fast
  18409. * - Set player to play 0.5x (half) as fast
  18410. *
  18411. * @type {boolean}
  18412. * @default {@link Html5.canControlPlaybackRate}
  18413. */
  18414. Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
  18415. /**
  18416. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  18417. *
  18418. * @type {boolean}
  18419. * @default
  18420. */
  18421. Html5.prototype.featuresSourceset = Html5.canOverrideAttributes();
  18422. /**
  18423. * Boolean indicating whether the `HTML5` tech currently supports the media element
  18424. * moving in the DOM. iOS breaks if you move the media element, so this is set this to
  18425. * false there. Everywhere else this should be true.
  18426. *
  18427. * @type {boolean}
  18428. * @default
  18429. */
  18430. Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed.
  18431. // Is this true?
  18432. /**
  18433. * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
  18434. * when going into fullscreen.
  18435. *
  18436. * @type {boolean}
  18437. * @default
  18438. */
  18439. Html5.prototype.featuresFullscreenResize = true;
  18440. /**
  18441. * Boolean indicating whether the `HTML5` tech currently supports the progress event.
  18442. * If this is false, manual `progress` events will be triggered instead.
  18443. *
  18444. * @type {boolean}
  18445. * @default
  18446. */
  18447. Html5.prototype.featuresProgressEvents = true;
  18448. /**
  18449. * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
  18450. * If this is false, manual `timeupdate` events will be triggered instead.
  18451. *
  18452. * @default
  18453. */
  18454. Html5.prototype.featuresTimeupdateEvents = true;
  18455. /**
  18456. * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
  18457. *
  18458. * @type {boolean}
  18459. * @default {@link Html5.supportsNativeTextTracks}
  18460. */
  18461. Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks();
  18462. /**
  18463. * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
  18464. *
  18465. * @type {boolean}
  18466. * @default {@link Html5.supportsNativeVideoTracks}
  18467. */
  18468. Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
  18469. /**
  18470. * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
  18471. *
  18472. * @type {boolean}
  18473. * @default {@link Html5.supportsNativeAudioTracks}
  18474. */
  18475. Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); // HTML5 Feature detection and Device Fixes --------------------------------- //
  18476. var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
  18477. var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
  18478. Html5.patchCanPlayType = function () {
  18479. // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
  18480. // Firefox and Chrome report correctly
  18481. if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
  18482. Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
  18483. if (type && mpegurlRE.test(type)) {
  18484. return 'maybe';
  18485. }
  18486. return canPlayType.call(this, type);
  18487. };
  18488. }
  18489. };
  18490. Html5.unpatchCanPlayType = function () {
  18491. var r = Html5.TEST_VID.constructor.prototype.canPlayType;
  18492. Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
  18493. return r;
  18494. }; // by default, patch the media element
  18495. Html5.patchCanPlayType();
  18496. Html5.disposeMediaElement = function (el) {
  18497. if (!el) {
  18498. return;
  18499. }
  18500. if (el.parentNode) {
  18501. el.parentNode.removeChild(el);
  18502. } // remove any child track or source nodes to prevent their loading
  18503. while (el.hasChildNodes()) {
  18504. el.removeChild(el.firstChild);
  18505. } // remove any src reference. not setting `src=''` because that causes a warning
  18506. // in firefox
  18507. el.removeAttribute('src'); // force the media element to update its loading state by calling load()
  18508. // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
  18509. if (typeof el.load === 'function') {
  18510. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  18511. (function () {
  18512. try {
  18513. el.load();
  18514. } catch (e) {// not supported
  18515. }
  18516. })();
  18517. }
  18518. };
  18519. Html5.resetMediaElement = function (el) {
  18520. if (!el) {
  18521. return;
  18522. }
  18523. var sources = el.querySelectorAll('source');
  18524. var i = sources.length;
  18525. while (i--) {
  18526. el.removeChild(sources[i]);
  18527. } // remove any src reference.
  18528. // not setting `src=''` because that throws an error
  18529. el.removeAttribute('src');
  18530. if (typeof el.load === 'function') {
  18531. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  18532. (function () {
  18533. try {
  18534. el.load();
  18535. } catch (e) {// satisfy linter
  18536. }
  18537. })();
  18538. }
  18539. };
  18540. /* Native HTML5 element property wrapping ----------------------------------- */
  18541. // Wrap native boolean attributes with getters that check both property and attribute
  18542. // The list is as followed:
  18543. // muted, defaultMuted, autoplay, controls, loop, playsinline
  18544. [
  18545. /**
  18546. * Get the value of `muted` from the media element. `muted` indicates
  18547. * that the volume for the media should be set to silent. This does not actually change
  18548. * the `volume` attribute.
  18549. *
  18550. * @method Html5#muted
  18551. * @return {boolean}
  18552. * - True if the value of `volume` should be ignored and the audio set to silent.
  18553. * - False if the value of `volume` should be used.
  18554. *
  18555. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  18556. */
  18557. 'muted',
  18558. /**
  18559. * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
  18560. * whether the media should start muted or not. Only changes the default state of the
  18561. * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
  18562. * current state.
  18563. *
  18564. * @method Html5#defaultMuted
  18565. * @return {boolean}
  18566. * - The value of `defaultMuted` from the media element.
  18567. * - True indicates that the media should start muted.
  18568. * - False indicates that the media should not start muted
  18569. *
  18570. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  18571. */
  18572. 'defaultMuted',
  18573. /**
  18574. * Get the value of `autoplay` from the media element. `autoplay` indicates
  18575. * that the media should start to play as soon as the page is ready.
  18576. *
  18577. * @method Html5#autoplay
  18578. * @return {boolean}
  18579. * - The value of `autoplay` from the media element.
  18580. * - True indicates that the media should start as soon as the page loads.
  18581. * - False indicates that the media should not start as soon as the page loads.
  18582. *
  18583. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  18584. */
  18585. 'autoplay',
  18586. /**
  18587. * Get the value of `controls` from the media element. `controls` indicates
  18588. * whether the native media controls should be shown or hidden.
  18589. *
  18590. * @method Html5#controls
  18591. * @return {boolean}
  18592. * - The value of `controls` from the media element.
  18593. * - True indicates that native controls should be showing.
  18594. * - False indicates that native controls should be hidden.
  18595. *
  18596. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
  18597. */
  18598. 'controls',
  18599. /**
  18600. * Get the value of `loop` from the media element. `loop` indicates
  18601. * that the media should return to the start of the media and continue playing once
  18602. * it reaches the end.
  18603. *
  18604. * @method Html5#loop
  18605. * @return {boolean}
  18606. * - The value of `loop` from the media element.
  18607. * - True indicates that playback should seek back to start once
  18608. * the end of a media is reached.
  18609. * - False indicates that playback should not loop back to the start when the
  18610. * end of the media is reached.
  18611. *
  18612. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  18613. */
  18614. 'loop',
  18615. /**
  18616. * Get the value of `playsinline` from the media element. `playsinline` indicates
  18617. * to the browser that non-fullscreen playback is preferred when fullscreen
  18618. * playback is the native default, such as in iOS Safari.
  18619. *
  18620. * @method Html5#playsinline
  18621. * @return {boolean}
  18622. * - The value of `playsinline` from the media element.
  18623. * - True indicates that the media should play inline.
  18624. * - False indicates that the media should not play inline.
  18625. *
  18626. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  18627. */
  18628. 'playsinline'].forEach(function (prop) {
  18629. Html5.prototype[prop] = function () {
  18630. return this.el_[prop] || this.el_.hasAttribute(prop);
  18631. };
  18632. }); // Wrap native boolean attributes with setters that set both property and attribute
  18633. // The list is as followed:
  18634. // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
  18635. // setControls is special-cased above
  18636. [
  18637. /**
  18638. * Set the value of `muted` on the media element. `muted` indicates that the current
  18639. * audio level should be silent.
  18640. *
  18641. * @method Html5#setMuted
  18642. * @param {boolean} muted
  18643. * - True if the audio should be set to silent
  18644. * - False otherwise
  18645. *
  18646. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  18647. */
  18648. 'muted',
  18649. /**
  18650. * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
  18651. * audio level should be silent, but will only effect the muted level on intial playback..
  18652. *
  18653. * @method Html5.prototype.setDefaultMuted
  18654. * @param {boolean} defaultMuted
  18655. * - True if the audio should be set to silent
  18656. * - False otherwise
  18657. *
  18658. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  18659. */
  18660. 'defaultMuted',
  18661. /**
  18662. * Set the value of `autoplay` on the media element. `autoplay` indicates
  18663. * that the media should start to play as soon as the page is ready.
  18664. *
  18665. * @method Html5#setAutoplay
  18666. * @param {boolean} autoplay
  18667. * - True indicates that the media should start as soon as the page loads.
  18668. * - False indicates that the media should not start as soon as the page loads.
  18669. *
  18670. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  18671. */
  18672. 'autoplay',
  18673. /**
  18674. * Set the value of `loop` on the media element. `loop` indicates
  18675. * that the media should return to the start of the media and continue playing once
  18676. * it reaches the end.
  18677. *
  18678. * @method Html5#setLoop
  18679. * @param {boolean} loop
  18680. * - True indicates that playback should seek back to start once
  18681. * the end of a media is reached.
  18682. * - False indicates that playback should not loop back to the start when the
  18683. * end of the media is reached.
  18684. *
  18685. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  18686. */
  18687. 'loop',
  18688. /**
  18689. * Set the value of `playsinline` from the media element. `playsinline` indicates
  18690. * to the browser that non-fullscreen playback is preferred when fullscreen
  18691. * playback is the native default, such as in iOS Safari.
  18692. *
  18693. * @method Html5#setPlaysinline
  18694. * @param {boolean} playsinline
  18695. * - True indicates that the media should play inline.
  18696. * - False indicates that the media should not play inline.
  18697. *
  18698. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  18699. */
  18700. 'playsinline'].forEach(function (prop) {
  18701. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  18702. this.el_[prop] = v;
  18703. if (v) {
  18704. this.el_.setAttribute(prop, prop);
  18705. } else {
  18706. this.el_.removeAttribute(prop);
  18707. }
  18708. };
  18709. }); // Wrap native properties with a getter
  18710. // The list is as followed
  18711. // paused, currentTime, buffered, volume, poster, preload, error, seeking
  18712. // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
  18713. // readyState, videoWidth, videoHeight
  18714. [
  18715. /**
  18716. * Get the value of `paused` from the media element. `paused` indicates whether the media element
  18717. * is currently paused or not.
  18718. *
  18719. * @method Html5#paused
  18720. * @return {boolean}
  18721. * The value of `paused` from the media element.
  18722. *
  18723. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
  18724. */
  18725. 'paused',
  18726. /**
  18727. * Get the value of `currentTime` from the media element. `currentTime` indicates
  18728. * the current second that the media is at in playback.
  18729. *
  18730. * @method Html5#currentTime
  18731. * @return {number}
  18732. * The value of `currentTime` from the media element.
  18733. *
  18734. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
  18735. */
  18736. 'currentTime',
  18737. /**
  18738. * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
  18739. * object that represents the parts of the media that are already downloaded and
  18740. * available for playback.
  18741. *
  18742. * @method Html5#buffered
  18743. * @return {TimeRange}
  18744. * The value of `buffered` from the media element.
  18745. *
  18746. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
  18747. */
  18748. 'buffered',
  18749. /**
  18750. * Get the value of `volume` from the media element. `volume` indicates
  18751. * the current playback volume of audio for a media. `volume` will be a value from 0
  18752. * (silent) to 1 (loudest and default).
  18753. *
  18754. * @method Html5#volume
  18755. * @return {number}
  18756. * The value of `volume` from the media element. Value will be between 0-1.
  18757. *
  18758. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  18759. */
  18760. 'volume',
  18761. /**
  18762. * Get the value of `poster` from the media element. `poster` indicates
  18763. * that the url of an image file that can/will be shown when no media data is available.
  18764. *
  18765. * @method Html5#poster
  18766. * @return {string}
  18767. * The value of `poster` from the media element. Value will be a url to an
  18768. * image.
  18769. *
  18770. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
  18771. */
  18772. 'poster',
  18773. /**
  18774. * Get the value of `preload` from the media element. `preload` indicates
  18775. * what should download before the media is interacted with. It can have the following
  18776. * values:
  18777. * - none: nothing should be downloaded
  18778. * - metadata: poster and the first few frames of the media may be downloaded to get
  18779. * media dimensions and other metadata
  18780. * - auto: allow the media and metadata for the media to be downloaded before
  18781. * interaction
  18782. *
  18783. * @method Html5#preload
  18784. * @return {string}
  18785. * The value of `preload` from the media element. Will be 'none', 'metadata',
  18786. * or 'auto'.
  18787. *
  18788. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  18789. */
  18790. 'preload',
  18791. /**
  18792. * Get the value of the `error` from the media element. `error` indicates any
  18793. * MediaError that may have occurred during playback. If error returns null there is no
  18794. * current error.
  18795. *
  18796. * @method Html5#error
  18797. * @return {MediaError|null}
  18798. * The value of `error` from the media element. Will be `MediaError` if there
  18799. * is a current error and null otherwise.
  18800. *
  18801. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
  18802. */
  18803. 'error',
  18804. /**
  18805. * Get the value of `seeking` from the media element. `seeking` indicates whether the
  18806. * media is currently seeking to a new position or not.
  18807. *
  18808. * @method Html5#seeking
  18809. * @return {boolean}
  18810. * - The value of `seeking` from the media element.
  18811. * - True indicates that the media is currently seeking to a new position.
  18812. * - False indicates that the media is not seeking to a new position at this time.
  18813. *
  18814. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
  18815. */
  18816. 'seeking',
  18817. /**
  18818. * Get the value of `seekable` from the media element. `seekable` returns a
  18819. * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
  18820. *
  18821. * @method Html5#seekable
  18822. * @return {TimeRange}
  18823. * The value of `seekable` from the media element. A `TimeRange` object
  18824. * indicating the current ranges of time that can be seeked to.
  18825. *
  18826. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
  18827. */
  18828. 'seekable',
  18829. /**
  18830. * Get the value of `ended` from the media element. `ended` indicates whether
  18831. * the media has reached the end or not.
  18832. *
  18833. * @method Html5#ended
  18834. * @return {boolean}
  18835. * - The value of `ended` from the media element.
  18836. * - True indicates that the media has ended.
  18837. * - False indicates that the media has not ended.
  18838. *
  18839. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
  18840. */
  18841. 'ended',
  18842. /**
  18843. * Get the value of `playbackRate` from the media element. `playbackRate` indicates
  18844. * the rate at which the media is currently playing back. Examples:
  18845. * - if playbackRate is set to 2, media will play twice as fast.
  18846. * - if playbackRate is set to 0.5, media will play half as fast.
  18847. *
  18848. * @method Html5#playbackRate
  18849. * @return {number}
  18850. * The value of `playbackRate` from the media element. A number indicating
  18851. * the current playback speed of the media, where 1 is normal speed.
  18852. *
  18853. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  18854. */
  18855. 'playbackRate',
  18856. /**
  18857. * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
  18858. * the rate at which the media is currently playing back. This value will not indicate the current
  18859. * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
  18860. *
  18861. * Examples:
  18862. * - if defaultPlaybackRate is set to 2, media will play twice as fast.
  18863. * - if defaultPlaybackRate is set to 0.5, media will play half as fast.
  18864. *
  18865. * @method Html5.prototype.defaultPlaybackRate
  18866. * @return {number}
  18867. * The value of `defaultPlaybackRate` from the media element. A number indicating
  18868. * the current playback speed of the media, where 1 is normal speed.
  18869. *
  18870. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  18871. */
  18872. 'defaultPlaybackRate',
  18873. /**
  18874. * Get the value of `played` from the media element. `played` returns a `TimeRange`
  18875. * object representing points in the media timeline that have been played.
  18876. *
  18877. * @method Html5#played
  18878. * @return {TimeRange}
  18879. * The value of `played` from the media element. A `TimeRange` object indicating
  18880. * the ranges of time that have been played.
  18881. *
  18882. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
  18883. */
  18884. 'played',
  18885. /**
  18886. * Get the value of `networkState` from the media element. `networkState` indicates
  18887. * the current network state. It returns an enumeration from the following list:
  18888. * - 0: NETWORK_EMPTY
  18889. * - 1: NETWORK_IDLE
  18890. * - 2: NETWORK_LOADING
  18891. * - 3: NETWORK_NO_SOURCE
  18892. *
  18893. * @method Html5#networkState
  18894. * @return {number}
  18895. * The value of `networkState` from the media element. This will be a number
  18896. * from the list in the description.
  18897. *
  18898. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
  18899. */
  18900. 'networkState',
  18901. /**
  18902. * Get the value of `readyState` from the media element. `readyState` indicates
  18903. * the current state of the media element. It returns an enumeration from the
  18904. * following list:
  18905. * - 0: HAVE_NOTHING
  18906. * - 1: HAVE_METADATA
  18907. * - 2: HAVE_CURRENT_DATA
  18908. * - 3: HAVE_FUTURE_DATA
  18909. * - 4: HAVE_ENOUGH_DATA
  18910. *
  18911. * @method Html5#readyState
  18912. * @return {number}
  18913. * The value of `readyState` from the media element. This will be a number
  18914. * from the list in the description.
  18915. *
  18916. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
  18917. */
  18918. 'readyState',
  18919. /**
  18920. * Get the value of `videoWidth` from the video element. `videoWidth` indicates
  18921. * the current width of the video in css pixels.
  18922. *
  18923. * @method Html5#videoWidth
  18924. * @return {number}
  18925. * The value of `videoWidth` from the video element. This will be a number
  18926. * in css pixels.
  18927. *
  18928. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  18929. */
  18930. 'videoWidth',
  18931. /**
  18932. * Get the value of `videoHeight` from the video element. `videoHeight` indicates
  18933. * the current height of the video in css pixels.
  18934. *
  18935. * @method Html5#videoHeight
  18936. * @return {number}
  18937. * The value of `videoHeight` from the video element. This will be a number
  18938. * in css pixels.
  18939. *
  18940. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  18941. */
  18942. 'videoHeight'].forEach(function (prop) {
  18943. Html5.prototype[prop] = function () {
  18944. return this.el_[prop];
  18945. };
  18946. }); // Wrap native properties with a setter in this format:
  18947. // set + toTitleCase(name)
  18948. // The list is as follows:
  18949. // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
  18950. [
  18951. /**
  18952. * Set the value of `volume` on the media element. `volume` indicates the current
  18953. * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
  18954. * so on.
  18955. *
  18956. * @method Html5#setVolume
  18957. * @param {number} percentAsDecimal
  18958. * The volume percent as a decimal. Valid range is from 0-1.
  18959. *
  18960. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  18961. */
  18962. 'volume',
  18963. /**
  18964. * Set the value of `src` on the media element. `src` indicates the current
  18965. * {@link Tech~SourceObject} for the media.
  18966. *
  18967. * @method Html5#setSrc
  18968. * @param {Tech~SourceObject} src
  18969. * The source object to set as the current source.
  18970. *
  18971. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
  18972. */
  18973. 'src',
  18974. /**
  18975. * Set the value of `poster` on the media element. `poster` is the url to
  18976. * an image file that can/will be shown when no media data is available.
  18977. *
  18978. * @method Html5#setPoster
  18979. * @param {string} poster
  18980. * The url to an image that should be used as the `poster` for the media
  18981. * element.
  18982. *
  18983. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
  18984. */
  18985. 'poster',
  18986. /**
  18987. * Set the value of `preload` on the media element. `preload` indicates
  18988. * what should download before the media is interacted with. It can have the following
  18989. * values:
  18990. * - none: nothing should be downloaded
  18991. * - metadata: poster and the first few frames of the media may be downloaded to get
  18992. * media dimensions and other metadata
  18993. * - auto: allow the media and metadata for the media to be downloaded before
  18994. * interaction
  18995. *
  18996. * @method Html5#setPreload
  18997. * @param {string} preload
  18998. * The value of `preload` to set on the media element. Must be 'none', 'metadata',
  18999. * or 'auto'.
  19000. *
  19001. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  19002. */
  19003. 'preload',
  19004. /**
  19005. * Set the value of `playbackRate` on the media element. `playbackRate` indicates
  19006. * the rate at which the media should play back. Examples:
  19007. * - if playbackRate is set to 2, media will play twice as fast.
  19008. * - if playbackRate is set to 0.5, media will play half as fast.
  19009. *
  19010. * @method Html5#setPlaybackRate
  19011. * @return {number}
  19012. * The value of `playbackRate` from the media element. A number indicating
  19013. * the current playback speed of the media, where 1 is normal speed.
  19014. *
  19015. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  19016. */
  19017. 'playbackRate',
  19018. /**
  19019. * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
  19020. * the rate at which the media should play back upon initial startup. Changing this value
  19021. * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
  19022. *
  19023. * Example Values:
  19024. * - if playbackRate is set to 2, media will play twice as fast.
  19025. * - if playbackRate is set to 0.5, media will play half as fast.
  19026. *
  19027. * @method Html5.prototype.setDefaultPlaybackRate
  19028. * @return {number}
  19029. * The value of `defaultPlaybackRate` from the media element. A number indicating
  19030. * the current playback speed of the media, where 1 is normal speed.
  19031. *
  19032. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
  19033. */
  19034. 'defaultPlaybackRate'].forEach(function (prop) {
  19035. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  19036. this.el_[prop] = v;
  19037. };
  19038. }); // wrap native functions with a function
  19039. // The list is as follows:
  19040. // pause, load, play
  19041. [
  19042. /**
  19043. * A wrapper around the media elements `pause` function. This will call the `HTML5`
  19044. * media elements `pause` function.
  19045. *
  19046. * @method Html5#pause
  19047. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
  19048. */
  19049. 'pause',
  19050. /**
  19051. * A wrapper around the media elements `load` function. This will call the `HTML5`s
  19052. * media element `load` function.
  19053. *
  19054. * @method Html5#load
  19055. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
  19056. */
  19057. 'load',
  19058. /**
  19059. * A wrapper around the media elements `play` function. This will call the `HTML5`s
  19060. * media element `play` function.
  19061. *
  19062. * @method Html5#play
  19063. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
  19064. */
  19065. 'play'].forEach(function (prop) {
  19066. Html5.prototype[prop] = function () {
  19067. return this.el_[prop]();
  19068. };
  19069. });
  19070. Tech.withSourceHandlers(Html5);
  19071. /**
  19072. * Native source handler for Html5, simply passes the source to the media element.
  19073. *
  19074. * @property {Tech~SourceObject} source
  19075. * The source object
  19076. *
  19077. * @property {Html5} tech
  19078. * The instance of the HTML5 tech.
  19079. */
  19080. Html5.nativeSourceHandler = {};
  19081. /**
  19082. * Check if the media element can play the given mime type.
  19083. *
  19084. * @param {string} type
  19085. * The mimetype to check
  19086. *
  19087. * @return {string}
  19088. * 'probably', 'maybe', or '' (empty string)
  19089. */
  19090. Html5.nativeSourceHandler.canPlayType = function (type) {
  19091. // IE without MediaPlayer throws an error (#519)
  19092. try {
  19093. return Html5.TEST_VID.canPlayType(type);
  19094. } catch (e) {
  19095. return '';
  19096. }
  19097. };
  19098. /**
  19099. * Check if the media element can handle a source natively.
  19100. *
  19101. * @param {Tech~SourceObject} source
  19102. * The source object
  19103. *
  19104. * @param {Object} [options]
  19105. * Options to be passed to the tech.
  19106. *
  19107. * @return {string}
  19108. * 'probably', 'maybe', or '' (empty string).
  19109. */
  19110. Html5.nativeSourceHandler.canHandleSource = function (source, options) {
  19111. // If a type was provided we should rely on that
  19112. if (source.type) {
  19113. return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]'
  19114. } else if (source.src) {
  19115. var ext = getFileExtension(source.src);
  19116. return Html5.nativeSourceHandler.canPlayType("video/" + ext);
  19117. }
  19118. return '';
  19119. };
  19120. /**
  19121. * Pass the source to the native media element.
  19122. *
  19123. * @param {Tech~SourceObject} source
  19124. * The source object
  19125. *
  19126. * @param {Html5} tech
  19127. * The instance of the Html5 tech
  19128. *
  19129. * @param {Object} [options]
  19130. * The options to pass to the source
  19131. */
  19132. Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
  19133. tech.setSrc(source.src);
  19134. };
  19135. /**
  19136. * A noop for the native dispose function, as cleanup is not needed.
  19137. */
  19138. Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler
  19139. Html5.registerSourceHandler(Html5.nativeSourceHandler);
  19140. Tech.registerTech('Html5', Html5);
  19141. function _templateObject$2() {
  19142. var data = _taggedTemplateLiteralLoose(["\n Using the tech directly can be dangerous. I hope you know what you're doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n "]);
  19143. _templateObject$2 = function _templateObject() {
  19144. return data;
  19145. };
  19146. return data;
  19147. }
  19148. // on the player when they happen
  19149. var TECH_EVENTS_RETRIGGER = [
  19150. /**
  19151. * Fired while the user agent is downloading media data.
  19152. *
  19153. * @event Player#progress
  19154. * @type {EventTarget~Event}
  19155. */
  19156. /**
  19157. * Retrigger the `progress` event that was triggered by the {@link Tech}.
  19158. *
  19159. * @private
  19160. * @method Player#handleTechProgress_
  19161. * @fires Player#progress
  19162. * @listens Tech#progress
  19163. */
  19164. 'progress',
  19165. /**
  19166. * Fires when the loading of an audio/video is aborted.
  19167. *
  19168. * @event Player#abort
  19169. * @type {EventTarget~Event}
  19170. */
  19171. /**
  19172. * Retrigger the `abort` event that was triggered by the {@link Tech}.
  19173. *
  19174. * @private
  19175. * @method Player#handleTechAbort_
  19176. * @fires Player#abort
  19177. * @listens Tech#abort
  19178. */
  19179. 'abort',
  19180. /**
  19181. * Fires when the browser is intentionally not getting media data.
  19182. *
  19183. * @event Player#suspend
  19184. * @type {EventTarget~Event}
  19185. */
  19186. /**
  19187. * Retrigger the `suspend` event that was triggered by the {@link Tech}.
  19188. *
  19189. * @private
  19190. * @method Player#handleTechSuspend_
  19191. * @fires Player#suspend
  19192. * @listens Tech#suspend
  19193. */
  19194. 'suspend',
  19195. /**
  19196. * Fires when the current playlist is empty.
  19197. *
  19198. * @event Player#emptied
  19199. * @type {EventTarget~Event}
  19200. */
  19201. /**
  19202. * Retrigger the `emptied` event that was triggered by the {@link Tech}.
  19203. *
  19204. * @private
  19205. * @method Player#handleTechEmptied_
  19206. * @fires Player#emptied
  19207. * @listens Tech#emptied
  19208. */
  19209. 'emptied',
  19210. /**
  19211. * Fires when the browser is trying to get media data, but data is not available.
  19212. *
  19213. * @event Player#stalled
  19214. * @type {EventTarget~Event}
  19215. */
  19216. /**
  19217. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  19218. *
  19219. * @private
  19220. * @method Player#handleTechStalled_
  19221. * @fires Player#stalled
  19222. * @listens Tech#stalled
  19223. */
  19224. 'stalled',
  19225. /**
  19226. * Fires when the browser has loaded meta data for the audio/video.
  19227. *
  19228. * @event Player#loadedmetadata
  19229. * @type {EventTarget~Event}
  19230. */
  19231. /**
  19232. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  19233. *
  19234. * @private
  19235. * @method Player#handleTechLoadedmetadata_
  19236. * @fires Player#loadedmetadata
  19237. * @listens Tech#loadedmetadata
  19238. */
  19239. 'loadedmetadata',
  19240. /**
  19241. * Fires when the browser has loaded the current frame of the audio/video.
  19242. *
  19243. * @event Player#loadeddata
  19244. * @type {event}
  19245. */
  19246. /**
  19247. * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
  19248. *
  19249. * @private
  19250. * @method Player#handleTechLoaddeddata_
  19251. * @fires Player#loadeddata
  19252. * @listens Tech#loadeddata
  19253. */
  19254. 'loadeddata',
  19255. /**
  19256. * Fires when the current playback position has changed.
  19257. *
  19258. * @event Player#timeupdate
  19259. * @type {event}
  19260. */
  19261. /**
  19262. * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
  19263. *
  19264. * @private
  19265. * @method Player#handleTechTimeUpdate_
  19266. * @fires Player#timeupdate
  19267. * @listens Tech#timeupdate
  19268. */
  19269. 'timeupdate',
  19270. /**
  19271. * Fires when the video's intrinsic dimensions change
  19272. *
  19273. * @event Player#resize
  19274. * @type {event}
  19275. */
  19276. /**
  19277. * Retrigger the `resize` event that was triggered by the {@link Tech}.
  19278. *
  19279. * @private
  19280. * @method Player#handleTechResize_
  19281. * @fires Player#resize
  19282. * @listens Tech#resize
  19283. */
  19284. 'resize',
  19285. /**
  19286. * Fires when the volume has been changed
  19287. *
  19288. * @event Player#volumechange
  19289. * @type {event}
  19290. */
  19291. /**
  19292. * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
  19293. *
  19294. * @private
  19295. * @method Player#handleTechVolumechange_
  19296. * @fires Player#volumechange
  19297. * @listens Tech#volumechange
  19298. */
  19299. 'volumechange',
  19300. /**
  19301. * Fires when the text track has been changed
  19302. *
  19303. * @event Player#texttrackchange
  19304. * @type {event}
  19305. */
  19306. /**
  19307. * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
  19308. *
  19309. * @private
  19310. * @method Player#handleTechTexttrackchange_
  19311. * @fires Player#texttrackchange
  19312. * @listens Tech#texttrackchange
  19313. */
  19314. 'texttrackchange']; // events to queue when playback rate is zero
  19315. // this is a hash for the sole purpose of mapping non-camel-cased event names
  19316. // to camel-cased function names
  19317. var TECH_EVENTS_QUEUE = {
  19318. canplay: 'CanPlay',
  19319. canplaythrough: 'CanPlayThrough',
  19320. playing: 'Playing',
  19321. seeked: 'Seeked'
  19322. };
  19323. var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
  19324. var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny
  19325. // grep: vjs-layout-x-small
  19326. // grep: vjs-layout-small
  19327. // grep: vjs-layout-medium
  19328. // grep: vjs-layout-large
  19329. // grep: vjs-layout-x-large
  19330. // grep: vjs-layout-huge
  19331. BREAKPOINT_ORDER.forEach(function (k) {
  19332. var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k;
  19333. BREAKPOINT_CLASSES[k] = "vjs-layout-" + v;
  19334. });
  19335. var DEFAULT_BREAKPOINTS = {
  19336. tiny: 210,
  19337. xsmall: 320,
  19338. small: 425,
  19339. medium: 768,
  19340. large: 1440,
  19341. xlarge: 2560,
  19342. huge: Infinity
  19343. };
  19344. /**
  19345. * An instance of the `Player` class is created when any of the Video.js setup methods
  19346. * are used to initialize a video.
  19347. *
  19348. * After an instance has been created it can be accessed globally in two ways:
  19349. * 1. By calling `videojs('example_video_1');`
  19350. * 2. By using it directly via `videojs.players.example_video_1;`
  19351. *
  19352. * @extends Component
  19353. */
  19354. var Player =
  19355. /*#__PURE__*/
  19356. function (_Component) {
  19357. _inheritsLoose(Player, _Component);
  19358. /**
  19359. * Create an instance of this class.
  19360. *
  19361. * @param {Element} tag
  19362. * The original video DOM element used for configuring options.
  19363. *
  19364. * @param {Object} [options]
  19365. * Object of option names and values.
  19366. *
  19367. * @param {Component~ReadyCallback} [ready]
  19368. * Ready callback function.
  19369. */
  19370. function Player(tag, options, ready) {
  19371. var _this;
  19372. // Make sure tag ID exists
  19373. tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options
  19374. // The options argument overrides options set in the video tag
  19375. // which overrides globally set options.
  19376. // This latter part coincides with the load order
  19377. // (tag must exist before Player)
  19378. options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up
  19379. // player properties first, and can't use `this` before `super()`
  19380. options.initChildren = false; // Same with creating the element
  19381. options.createEl = false; // don't auto mixin the evented mixin
  19382. options.evented = false; // we don't want the player to report touch activity on itself
  19383. // see enableTouchActivity in Component
  19384. options.reportTouchActivity = false; // If language is not set, get the closest lang attribute
  19385. if (!options.language) {
  19386. if (typeof tag.closest === 'function') {
  19387. var closest = tag.closest('[lang]');
  19388. if (closest && closest.getAttribute) {
  19389. options.language = closest.getAttribute('lang');
  19390. }
  19391. } else {
  19392. var element = tag;
  19393. while (element && element.nodeType === 1) {
  19394. if (getAttributes(element).hasOwnProperty('lang')) {
  19395. options.language = element.getAttribute('lang');
  19396. break;
  19397. }
  19398. element = element.parentNode;
  19399. }
  19400. }
  19401. } // Run base component initializing with new options
  19402. _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners.
  19403. _this.boundDocumentFullscreenChange_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.documentFullscreenChange_);
  19404. _this.boundFullWindowOnEscKey_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.fullWindowOnEscKey);
  19405. _this.boundHandleKeyPress_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleKeyPress); // create logger
  19406. _this.log = createLogger$1(_this.id_); // Tracks when a tech changes the poster
  19407. _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero
  19408. // and a seek is happening
  19409. _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously
  19410. _this.isReady_ = false; // Init state hasStarted_
  19411. _this.hasStarted_ = false; // Init state userActive_
  19412. _this.userActive_ = false; // if the global option object was accidentally blown away by
  19413. // someone, bail early with an informative error
  19414. if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
  19415. throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
  19416. } // Store the original tag used to set options
  19417. _this.tag = tag; // Store the tag attributes used to restore html5 element
  19418. _this.tagAttributes = tag && getAttributes(tag); // Update current language
  19419. _this.language(_this.options_.language); // Update Supported Languages
  19420. if (options.languages) {
  19421. // Normalise player option languages to lowercase
  19422. var languagesToLower = {};
  19423. Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) {
  19424. languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1];
  19425. });
  19426. _this.languages_ = languagesToLower;
  19427. } else {
  19428. _this.languages_ = Player.prototype.options_.languages;
  19429. }
  19430. _this.resetCache_(); // Set poster
  19431. _this.poster_ = options.poster || ''; // Set controls
  19432. _this.controls_ = !!options.controls; // Original tag settings stored in options
  19433. // now remove immediately so native controls don't flash.
  19434. // May be turned back on by HTML5 tech if nativeControlsForTouch is true
  19435. tag.controls = false;
  19436. tag.removeAttribute('controls');
  19437. _this.changingSrc_ = false;
  19438. _this.playCallbacks_ = [];
  19439. _this.playTerminatedQueue_ = []; // the attribute overrides the option
  19440. if (tag.hasAttribute('autoplay')) {
  19441. _this.autoplay(true);
  19442. } else {
  19443. // otherwise use the setter to validate and
  19444. // set the correct value.
  19445. _this.autoplay(_this.options_.autoplay);
  19446. } // check plugins
  19447. if (options.plugins) {
  19448. Object.keys(options.plugins).forEach(function (name$$1) {
  19449. if (typeof _this[name$$1] !== 'function') {
  19450. throw new Error("plugin \"" + name$$1 + "\" does not exist");
  19451. }
  19452. });
  19453. }
  19454. /*
  19455. * Store the internal state of scrubbing
  19456. *
  19457. * @private
  19458. * @return {Boolean} True if the user is scrubbing
  19459. */
  19460. _this.scrubbing_ = false;
  19461. _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus.
  19462. evented(_assertThisInitialized(_assertThisInitialized(_this)), {
  19463. eventBusKey: 'el_'
  19464. });
  19465. if (_this.fluid_) {
  19466. _this.on('playerreset', _this.updateStyleEl_);
  19467. } // We also want to pass the original player options to each component and plugin
  19468. // as well so they don't need to reach back into the player for options later.
  19469. // We also need to do another copy of this.options_ so we don't end up with
  19470. // an infinite loop.
  19471. var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins
  19472. if (options.plugins) {
  19473. Object.keys(options.plugins).forEach(function (name$$1) {
  19474. _this[name$$1](options.plugins[name$$1]);
  19475. });
  19476. }
  19477. _this.options_.playerOptions = playerOptionsCopy;
  19478. _this.middleware_ = [];
  19479. _this.initChildren(); // Set isAudio based on whether or not an audio tag was used
  19480. _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially
  19481. // set because the element doesn't exist yet.
  19482. if (_this.controls()) {
  19483. _this.addClass('vjs-controls-enabled');
  19484. } else {
  19485. _this.addClass('vjs-controls-disabled');
  19486. } // Set ARIA label and region role depending on player type
  19487. _this.el_.setAttribute('role', 'region');
  19488. if (_this.isAudio()) {
  19489. _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
  19490. } else {
  19491. _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
  19492. }
  19493. if (_this.isAudio()) {
  19494. _this.addClass('vjs-audio');
  19495. }
  19496. if (_this.flexNotSupported_()) {
  19497. _this.addClass('vjs-no-flex');
  19498. } // TODO: Make this smarter. Toggle user state between touching/mousing
  19499. // using events, since devices can have both touch and mouse events.
  19500. // TODO: Make this check be performed again when the window switches between monitors
  19501. // (See https://github.com/videojs/video.js/issues/5683)
  19502. if (TOUCH_ENABLED) {
  19503. _this.addClass('vjs-touch-enabled');
  19504. } // iOS Safari has broken hover handling
  19505. if (!IS_IOS) {
  19506. _this.addClass('vjs-workinghover');
  19507. } // Make player easily findable by ID
  19508. Player.players[_this.id_] = _assertThisInitialized(_assertThisInitialized(_this)); // Add a major version class to aid css in plugins
  19509. var majorVersion = version.split('.')[0];
  19510. _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components
  19511. // like the control bar show themselves if needed
  19512. _this.userActive(true);
  19513. _this.reportUserActivity();
  19514. _this.one('play', _this.listenForUserActivity_);
  19515. _this.on('focus', _this.handleFocus);
  19516. _this.on('blur', _this.handleBlur);
  19517. _this.on('stageclick', _this.handleStageClick_);
  19518. _this.breakpoints(_this.options_.breakpoints);
  19519. _this.responsive(_this.options_.responsive);
  19520. return _this;
  19521. }
  19522. /**
  19523. * Destroys the video player and does any necessary cleanup.
  19524. *
  19525. * This is especially helpful if you are dynamically adding and removing videos
  19526. * to/from the DOM.
  19527. *
  19528. * @fires Player#dispose
  19529. */
  19530. var _proto = Player.prototype;
  19531. _proto.dispose = function dispose() {
  19532. var _this2 = this;
  19533. /**
  19534. * Called when the player is being disposed of.
  19535. *
  19536. * @event Player#dispose
  19537. * @type {EventTarget~Event}
  19538. */
  19539. this.trigger('dispose'); // prevent dispose from being called twice
  19540. this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is
  19541. off(document, FullscreenApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  19542. off(document, 'keydown', this.boundFullWindowOnEscKey_);
  19543. off(document, 'keydown', this.boundHandleKeyPress_);
  19544. if (this.styleEl_ && this.styleEl_.parentNode) {
  19545. this.styleEl_.parentNode.removeChild(this.styleEl_);
  19546. this.styleEl_ = null;
  19547. } // Kill reference to this player
  19548. Player.players[this.id_] = null;
  19549. if (this.tag && this.tag.player) {
  19550. this.tag.player = null;
  19551. }
  19552. if (this.el_ && this.el_.player) {
  19553. this.el_.player = null;
  19554. }
  19555. if (this.tech_) {
  19556. this.tech_.dispose();
  19557. this.isPosterFromTech_ = false;
  19558. this.poster_ = '';
  19559. }
  19560. if (this.playerElIngest_) {
  19561. this.playerElIngest_ = null;
  19562. }
  19563. if (this.tag) {
  19564. this.tag = null;
  19565. }
  19566. clearCacheForPlayer(this); // remove all event handlers for track lists
  19567. // all tracks and track listeners are removed on
  19568. // tech dispose
  19569. ALL.names.forEach(function (name$$1) {
  19570. var props = ALL[name$$1];
  19571. var list = _this2[props.getterName](); // if it is not a native list
  19572. // we have to manually remove event listeners
  19573. if (list && list.off) {
  19574. list.off();
  19575. }
  19576. }); // the actual .el_ is removed here
  19577. _Component.prototype.dispose.call(this);
  19578. }
  19579. /**
  19580. * Create the `Player`'s DOM element.
  19581. *
  19582. * @return {Element}
  19583. * The DOM element that gets created.
  19584. */
  19585. ;
  19586. _proto.createEl = function createEl$$1() {
  19587. var tag = this.tag;
  19588. var el;
  19589. var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
  19590. var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
  19591. if (playerElIngest) {
  19592. el = this.el_ = tag.parentNode;
  19593. } else if (!divEmbed) {
  19594. el = this.el_ = _Component.prototype.createEl.call(this, 'div');
  19595. } // Copy over all the attributes from the tag, including ID and class
  19596. // ID will now reference player box, not the video tag
  19597. var attrs = getAttributes(tag);
  19598. if (divEmbed) {
  19599. el = this.el_ = tag;
  19600. tag = this.tag = document.createElement('video');
  19601. while (el.children.length) {
  19602. tag.appendChild(el.firstChild);
  19603. }
  19604. if (!hasClass(el, 'video-js')) {
  19605. addClass(el, 'video-js');
  19606. }
  19607. el.appendChild(tag);
  19608. playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element
  19609. // to our new `video` element. This will move things like
  19610. // `src` or `controls` that were set via js before the player
  19611. // was initialized.
  19612. Object.keys(el).forEach(function (k) {
  19613. tag[k] = el[k];
  19614. });
  19615. } // set tabindex to -1 to remove the video element from the focus order
  19616. tag.setAttribute('tabindex', '-1');
  19617. attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and
  19618. // for the same issue with Chrome (on Windows) with JAWS.
  19619. // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
  19620. // Note that we can't detect if JAWS is being used, but this ARIA attribute
  19621. // doesn't change behavior of IE11 or Chrome if JAWS is not being used
  19622. if (IE_VERSION || IS_CHROME && IS_WINDOWS) {
  19623. tag.setAttribute('role', 'application');
  19624. attrs.role = 'application';
  19625. } // Remove width/height attrs from tag so CSS can make it 100% width/height
  19626. tag.removeAttribute('width');
  19627. tag.removeAttribute('height');
  19628. if ('width' in attrs) {
  19629. delete attrs.width;
  19630. }
  19631. if ('height' in attrs) {
  19632. delete attrs.height;
  19633. }
  19634. Object.getOwnPropertyNames(attrs).forEach(function (attr) {
  19635. // don't copy over the class attribute to the player element when we're in a div embed
  19636. // the class is already set up properly in the divEmbed case
  19637. // and we want to make sure that the `video-js` class doesn't get lost
  19638. if (!(divEmbed && attr === 'class')) {
  19639. el.setAttribute(attr, attrs[attr]);
  19640. }
  19641. if (divEmbed) {
  19642. tag.setAttribute(attr, attrs[attr]);
  19643. }
  19644. }); // Update tag id/class for use as HTML5 playback tech
  19645. // Might think we should do this after embedding in container so .vjs-tech class
  19646. // doesn't flash 100% width/height, but class only applies with .video-js parent
  19647. tag.playerId = tag.id;
  19648. tag.id += '_html5_api';
  19649. tag.className = 'vjs-tech'; // Make player findable on elements
  19650. tag.player = el.player = this; // Default state of video is paused
  19651. this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height
  19652. // of the player in a way that's still overrideable by CSS, just like the
  19653. // video element
  19654. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
  19655. this.styleEl_ = createStyleElement('vjs-styles-dimensions');
  19656. var defaultsStyleEl = $('.vjs-styles-defaults');
  19657. var head = $('head');
  19658. head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
  19659. }
  19660. this.fill_ = false;
  19661. this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el
  19662. this.width(this.options_.width);
  19663. this.height(this.options_.height);
  19664. this.fill(this.options_.fill);
  19665. this.fluid(this.options_.fluid);
  19666. this.aspectRatio(this.options_.aspectRatio); // Hide any links within the video/audio tag,
  19667. // because IE doesn't hide them completely from screen readers.
  19668. var links = tag.getElementsByTagName('a');
  19669. for (var i = 0; i < links.length; i++) {
  19670. var linkEl = links.item(i);
  19671. addClass(linkEl, 'vjs-hidden');
  19672. linkEl.setAttribute('hidden', 'hidden');
  19673. } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
  19674. // keep track of the original for later so we can know if the source originally failed
  19675. tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container
  19676. if (tag.parentNode && !playerElIngest) {
  19677. tag.parentNode.insertBefore(el, tag);
  19678. } // insert the tag as the first child of the player element
  19679. // then manually add it to the children array so that this.addChild
  19680. // will work properly for other components
  19681. //
  19682. // Breaks iPhone, fixed in HTML5 setup.
  19683. prependTo(tag, el);
  19684. this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player
  19685. // if it's been set to something different to the doc
  19686. this.el_.setAttribute('lang', this.language_);
  19687. this.el_ = el;
  19688. return el;
  19689. }
  19690. /**
  19691. * A getter/setter for the `Player`'s width. Returns the player's configured value.
  19692. * To get the current width use `currentWidth()`.
  19693. *
  19694. * @param {number} [value]
  19695. * The value to set the `Player`'s width to.
  19696. *
  19697. * @return {number}
  19698. * The current width of the `Player` when getting.
  19699. */
  19700. ;
  19701. _proto.width = function width(value) {
  19702. return this.dimension('width', value);
  19703. }
  19704. /**
  19705. * A getter/setter for the `Player`'s height. Returns the player's configured value.
  19706. * To get the current height use `currentheight()`.
  19707. *
  19708. * @param {number} [value]
  19709. * The value to set the `Player`'s heigth to.
  19710. *
  19711. * @return {number}
  19712. * The current height of the `Player` when getting.
  19713. */
  19714. ;
  19715. _proto.height = function height(value) {
  19716. return this.dimension('height', value);
  19717. }
  19718. /**
  19719. * A getter/setter for the `Player`'s width & height.
  19720. *
  19721. * @param {string} dimension
  19722. * This string can be:
  19723. * - 'width'
  19724. * - 'height'
  19725. *
  19726. * @param {number} [value]
  19727. * Value for dimension specified in the first argument.
  19728. *
  19729. * @return {number}
  19730. * The dimension arguments value when getting (width/height).
  19731. */
  19732. ;
  19733. _proto.dimension = function dimension(_dimension, value) {
  19734. var privDimension = _dimension + '_';
  19735. if (value === undefined) {
  19736. return this[privDimension] || 0;
  19737. }
  19738. if (value === '') {
  19739. // If an empty string is given, reset the dimension to be automatic
  19740. this[privDimension] = undefined;
  19741. this.updateStyleEl_();
  19742. return;
  19743. }
  19744. var parsedVal = parseFloat(value);
  19745. if (isNaN(parsedVal)) {
  19746. log.error("Improper value \"" + value + "\" supplied for for " + _dimension);
  19747. return;
  19748. }
  19749. this[privDimension] = parsedVal;
  19750. this.updateStyleEl_();
  19751. }
  19752. /**
  19753. * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
  19754. *
  19755. * Turning this on will turn off fill mode.
  19756. *
  19757. * @param {boolean} [bool]
  19758. * - A value of true adds the class.
  19759. * - A value of false removes the class.
  19760. * - No value will be a getter.
  19761. *
  19762. * @return {boolean|undefined}
  19763. * - The value of fluid when getting.
  19764. * - `undefined` when setting.
  19765. */
  19766. ;
  19767. _proto.fluid = function fluid(bool) {
  19768. if (bool === undefined) {
  19769. return !!this.fluid_;
  19770. }
  19771. this.fluid_ = !!bool;
  19772. if (isEvented(this)) {
  19773. this.off('playerreset', this.updateStyleEl_);
  19774. }
  19775. if (bool) {
  19776. this.addClass('vjs-fluid');
  19777. this.fill(false);
  19778. addEventedCallback(function () {
  19779. this.on('playerreset', this.updateStyleEl_);
  19780. });
  19781. } else {
  19782. this.removeClass('vjs-fluid');
  19783. }
  19784. this.updateStyleEl_();
  19785. }
  19786. /**
  19787. * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
  19788. *
  19789. * Turning this on will turn off fluid mode.
  19790. *
  19791. * @param {boolean} [bool]
  19792. * - A value of true adds the class.
  19793. * - A value of false removes the class.
  19794. * - No value will be a getter.
  19795. *
  19796. * @return {boolean|undefined}
  19797. * - The value of fluid when getting.
  19798. * - `undefined` when setting.
  19799. */
  19800. ;
  19801. _proto.fill = function fill(bool) {
  19802. if (bool === undefined) {
  19803. return !!this.fill_;
  19804. }
  19805. this.fill_ = !!bool;
  19806. if (bool) {
  19807. this.addClass('vjs-fill');
  19808. this.fluid(false);
  19809. } else {
  19810. this.removeClass('vjs-fill');
  19811. }
  19812. }
  19813. /**
  19814. * Get/Set the aspect ratio
  19815. *
  19816. * @param {string} [ratio]
  19817. * Aspect ratio for player
  19818. *
  19819. * @return {string|undefined}
  19820. * returns the current aspect ratio when getting
  19821. */
  19822. /**
  19823. * A getter/setter for the `Player`'s aspect ratio.
  19824. *
  19825. * @param {string} [ratio]
  19826. * The value to set the `Player's aspect ratio to.
  19827. *
  19828. * @return {string|undefined}
  19829. * - The current aspect ratio of the `Player` when getting.
  19830. * - undefined when setting
  19831. */
  19832. ;
  19833. _proto.aspectRatio = function aspectRatio(ratio) {
  19834. if (ratio === undefined) {
  19835. return this.aspectRatio_;
  19836. } // Check for width:height format
  19837. if (!/^\d+\:\d+$/.test(ratio)) {
  19838. throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
  19839. }
  19840. this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode,
  19841. // because in fixed mode you could calculate width and height yourself.
  19842. this.fluid(true);
  19843. this.updateStyleEl_();
  19844. }
  19845. /**
  19846. * Update styles of the `Player` element (height, width and aspect ratio).
  19847. *
  19848. * @private
  19849. * @listens Tech#loadedmetadata
  19850. */
  19851. ;
  19852. _proto.updateStyleEl_ = function updateStyleEl_() {
  19853. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
  19854. var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
  19855. var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
  19856. var techEl = this.tech_ && this.tech_.el();
  19857. if (techEl) {
  19858. if (_width >= 0) {
  19859. techEl.width = _width;
  19860. }
  19861. if (_height >= 0) {
  19862. techEl.height = _height;
  19863. }
  19864. }
  19865. return;
  19866. }
  19867. var width;
  19868. var height;
  19869. var aspectRatio;
  19870. var idClass; // The aspect ratio is either used directly or to calculate width and height.
  19871. if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
  19872. // Use any aspectRatio that's been specifically set
  19873. aspectRatio = this.aspectRatio_;
  19874. } else if (this.videoWidth() > 0) {
  19875. // Otherwise try to get the aspect ratio from the video metadata
  19876. aspectRatio = this.videoWidth() + ':' + this.videoHeight();
  19877. } else {
  19878. // Or use a default. The video element's is 2:1, but 16:9 is more common.
  19879. aspectRatio = '16:9';
  19880. } // Get the ratio as a decimal we can use to calculate dimensions
  19881. var ratioParts = aspectRatio.split(':');
  19882. var ratioMultiplier = ratioParts[1] / ratioParts[0];
  19883. if (this.width_ !== undefined) {
  19884. // Use any width that's been specifically set
  19885. width = this.width_;
  19886. } else if (this.height_ !== undefined) {
  19887. // Or calulate the width from the aspect ratio if a height has been set
  19888. width = this.height_ / ratioMultiplier;
  19889. } else {
  19890. // Or use the video's metadata, or use the video el's default of 300
  19891. width = this.videoWidth() || 300;
  19892. }
  19893. if (this.height_ !== undefined) {
  19894. // Use any height that's been specifically set
  19895. height = this.height_;
  19896. } else {
  19897. // Otherwise calculate the height from the ratio and the width
  19898. height = width * ratioMultiplier;
  19899. } // Ensure the CSS class is valid by starting with an alpha character
  19900. if (/^[^a-zA-Z]/.test(this.id())) {
  19901. idClass = 'dimensions-' + this.id();
  19902. } else {
  19903. idClass = this.id() + '-dimensions';
  19904. } // Ensure the right class is still on the player for the style element
  19905. this.addClass(idClass);
  19906. setTextContent(this.styleEl_, "\n ." + idClass + " {\n width: " + width + "px;\n height: " + height + "px;\n }\n\n ." + idClass + ".vjs-fluid {\n padding-top: " + ratioMultiplier * 100 + "%;\n }\n ");
  19907. }
  19908. /**
  19909. * Load/Create an instance of playback {@link Tech} including element
  19910. * and API methods. Then append the `Tech` element in `Player` as a child.
  19911. *
  19912. * @param {string} techName
  19913. * name of the playback technology
  19914. *
  19915. * @param {string} source
  19916. * video source
  19917. *
  19918. * @private
  19919. */
  19920. ;
  19921. _proto.loadTech_ = function loadTech_(techName, source) {
  19922. var _this3 = this;
  19923. // Pause and remove current playback technology
  19924. if (this.tech_) {
  19925. this.unloadTech_();
  19926. }
  19927. var titleTechName = toTitleCase(techName);
  19928. var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech
  19929. if (titleTechName !== 'Html5' && this.tag) {
  19930. Tech.getTech('Html5').disposeMediaElement(this.tag);
  19931. this.tag.player = null;
  19932. this.tag = null;
  19933. }
  19934. this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously
  19935. this.isReady_ = false; // if autoplay is a string we pass false to the tech
  19936. // because the player is going to handle autoplay on `loadstart`
  19937. var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use.
  19938. var techOptions = {
  19939. source: source,
  19940. autoplay: autoplay,
  19941. 'nativeControlsForTouch': this.options_.nativeControlsForTouch,
  19942. 'playerId': this.id(),
  19943. 'techId': this.id() + "_" + camelTechName + "_api",
  19944. 'playsinline': this.options_.playsinline,
  19945. 'preload': this.options_.preload,
  19946. 'loop': this.options_.loop,
  19947. 'muted': this.options_.muted,
  19948. 'poster': this.poster(),
  19949. 'language': this.language(),
  19950. 'playerElIngest': this.playerElIngest_ || false,
  19951. 'vtt.js': this.options_['vtt.js'],
  19952. 'canOverridePoster': !!this.options_.techCanOverridePoster,
  19953. 'enableSourceset': this.options_.enableSourceset
  19954. };
  19955. ALL.names.forEach(function (name$$1) {
  19956. var props = ALL[name$$1];
  19957. techOptions[props.getterName] = _this3[props.privateName];
  19958. });
  19959. assign(techOptions, this.options_[titleTechName]);
  19960. assign(techOptions, this.options_[camelTechName]);
  19961. assign(techOptions, this.options_[techName.toLowerCase()]);
  19962. if (this.tag) {
  19963. techOptions.tag = this.tag;
  19964. }
  19965. if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
  19966. techOptions.startTime = this.cache_.currentTime;
  19967. } // Initialize tech instance
  19968. var TechClass = Tech.getTech(techName);
  19969. if (!TechClass) {
  19970. throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'");
  19971. }
  19972. this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async
  19973. this.tech_.ready(bind(this, this.handleTechReady_), true);
  19974. textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player
  19975. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  19976. _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]);
  19977. });
  19978. Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
  19979. _this3.on(_this3.tech_, event, function (eventObj) {
  19980. if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) {
  19981. _this3.queuedCallbacks_.push({
  19982. callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3),
  19983. event: eventObj
  19984. });
  19985. return;
  19986. }
  19987. _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj);
  19988. });
  19989. });
  19990. this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
  19991. this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
  19992. this.on(this.tech_, 'waiting', this.handleTechWaiting_);
  19993. this.on(this.tech_, 'ended', this.handleTechEnded_);
  19994. this.on(this.tech_, 'seeking', this.handleTechSeeking_);
  19995. this.on(this.tech_, 'play', this.handleTechPlay_);
  19996. this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
  19997. this.on(this.tech_, 'pause', this.handleTechPause_);
  19998. this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
  19999. this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
  20000. this.on(this.tech_, 'error', this.handleTechError_);
  20001. this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
  20002. this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
  20003. this.on(this.tech_, 'textdata', this.handleTechTextData_);
  20004. this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
  20005. this.usingNativeControls(this.techGet_('controls'));
  20006. if (this.controls() && !this.usingNativeControls()) {
  20007. this.addTechControlsListeners_();
  20008. } // Add the tech element in the DOM if it was not already there
  20009. // Make sure to not insert the original video element if using Html5
  20010. if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
  20011. prependTo(this.tech_.el(), this.el());
  20012. } // Get rid of the original video tag reference after the first tech is loaded
  20013. if (this.tag) {
  20014. this.tag.player = null;
  20015. this.tag = null;
  20016. }
  20017. }
  20018. /**
  20019. * Unload and dispose of the current playback {@link Tech}.
  20020. *
  20021. * @private
  20022. */
  20023. ;
  20024. _proto.unloadTech_ = function unloadTech_() {
  20025. var _this4 = this;
  20026. // Save the current text tracks so that we can reuse the same text tracks with the next tech
  20027. ALL.names.forEach(function (name$$1) {
  20028. var props = ALL[name$$1];
  20029. _this4[props.privateName] = _this4[props.getterName]();
  20030. });
  20031. this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
  20032. this.isReady_ = false;
  20033. this.tech_.dispose();
  20034. this.tech_ = false;
  20035. if (this.isPosterFromTech_) {
  20036. this.poster_ = '';
  20037. this.trigger('posterchange');
  20038. }
  20039. this.isPosterFromTech_ = false;
  20040. }
  20041. /**
  20042. * Return a reference to the current {@link Tech}.
  20043. * It will print a warning by default about the danger of using the tech directly
  20044. * but any argument that is passed in will silence the warning.
  20045. *
  20046. * @param {*} [safety]
  20047. * Anything passed in to silence the warning
  20048. *
  20049. * @return {Tech}
  20050. * The Tech
  20051. */
  20052. ;
  20053. _proto.tech = function tech(safety) {
  20054. if (safety === undefined) {
  20055. log.warn(tsml(_templateObject$2()));
  20056. }
  20057. return this.tech_;
  20058. }
  20059. /**
  20060. * Set up click and touch listeners for the playback element
  20061. *
  20062. * - On desktops: a click on the video itself will toggle playback
  20063. * - On mobile devices: a click on the video toggles controls
  20064. * which is done by toggling the user state between active and
  20065. * inactive
  20066. * - A tap can signal that a user has become active or has become inactive
  20067. * e.g. a quick tap on an iPhone movie should reveal the controls. Another
  20068. * quick tap should hide them again (signaling the user is in an inactive
  20069. * viewing state)
  20070. * - In addition to this, we still want the user to be considered inactive after
  20071. * a few seconds of inactivity.
  20072. *
  20073. * > Note: the only part of iOS interaction we can't mimic with this setup
  20074. * is a touch and hold on the video element counting as activity in order to
  20075. * keep the controls showing, but that shouldn't be an issue. A touch and hold
  20076. * on any controls will still keep the user active
  20077. *
  20078. * @private
  20079. */
  20080. ;
  20081. _proto.addTechControlsListeners_ = function addTechControlsListeners_() {
  20082. // Make sure to remove all the previous listeners in case we are called multiple times.
  20083. this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
  20084. // trigger mousedown/up.
  20085. // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
  20086. // Any touch events are set to block the mousedown event from happening
  20087. this.on(this.tech_, 'mousedown', this.handleTechClick_);
  20088. this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event
  20089. // so we'll check if the controls were already showing before reporting user
  20090. // activity
  20091. this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
  20092. this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
  20093. this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap
  20094. // listener cancels out any reportedUserActivity when setting userActive(false)
  20095. this.on(this.tech_, 'tap', this.handleTechTap_);
  20096. }
  20097. /**
  20098. * Remove the listeners used for click and tap controls. This is needed for
  20099. * toggling to controls disabled, where a tap/touch should do nothing.
  20100. *
  20101. * @private
  20102. */
  20103. ;
  20104. _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() {
  20105. // We don't want to just use `this.off()` because there might be other needed
  20106. // listeners added by techs that extend this.
  20107. this.off(this.tech_, 'tap', this.handleTechTap_);
  20108. this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
  20109. this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
  20110. this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
  20111. this.off(this.tech_, 'mousedown', this.handleTechClick_);
  20112. this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_);
  20113. }
  20114. /**
  20115. * Player waits for the tech to be ready
  20116. *
  20117. * @private
  20118. */
  20119. ;
  20120. _proto.handleTechReady_ = function handleTechReady_() {
  20121. this.triggerReady(); // Keep the same volume as before
  20122. if (this.cache_.volume) {
  20123. this.techCall_('setVolume', this.cache_.volume);
  20124. } // Look if the tech found a higher resolution poster while loading
  20125. this.handleTechPosterChange_(); // Update the duration if available
  20126. this.handleTechDurationChange_();
  20127. }
  20128. /**
  20129. * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
  20130. * function will also trigger {@link Player#firstplay} if it is the first loadstart
  20131. * for a video.
  20132. *
  20133. * @fires Player#loadstart
  20134. * @fires Player#firstplay
  20135. * @listens Tech#loadstart
  20136. * @private
  20137. */
  20138. ;
  20139. _proto.handleTechLoadStart_ = function handleTechLoadStart_() {
  20140. // TODO: Update to use `emptied` event instead. See #1277.
  20141. this.removeClass('vjs-ended');
  20142. this.removeClass('vjs-seeking'); // reset the error state
  20143. this.error(null); // Update the duration
  20144. this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now.
  20145. // The firstplay event relies on both the play and loadstart events
  20146. // which can happen in any order for a new source
  20147. if (!this.paused()) {
  20148. /**
  20149. * Fired when the user agent begins looking for media data
  20150. *
  20151. * @event Player#loadstart
  20152. * @type {EventTarget~Event}
  20153. */
  20154. this.trigger('loadstart');
  20155. this.trigger('firstplay');
  20156. } else {
  20157. // reset the hasStarted state
  20158. this.hasStarted(false);
  20159. this.trigger('loadstart');
  20160. } // autoplay happens after loadstart for the browser,
  20161. // so we mimic that behavior
  20162. this.manualAutoplay_(this.autoplay());
  20163. }
  20164. /**
  20165. * Handle autoplay string values, rather than the typical boolean
  20166. * values that should be handled by the tech. Note that this is not
  20167. * part of any specification. Valid values and what they do can be
  20168. * found on the autoplay getter at Player#autoplay()
  20169. */
  20170. ;
  20171. _proto.manualAutoplay_ = function manualAutoplay_(type) {
  20172. var _this5 = this;
  20173. if (!this.tech_ || typeof type !== 'string') {
  20174. return;
  20175. }
  20176. var muted = function muted() {
  20177. var previouslyMuted = _this5.muted();
  20178. _this5.muted(true);
  20179. var restoreMuted = function restoreMuted() {
  20180. _this5.muted(previouslyMuted);
  20181. }; // restore muted on play terminatation
  20182. _this5.playTerminatedQueue_.push(restoreMuted);
  20183. var mutedPromise = _this5.play();
  20184. if (!isPromise(mutedPromise)) {
  20185. return;
  20186. }
  20187. return mutedPromise.catch(restoreMuted);
  20188. };
  20189. var promise; // if muted defaults to true
  20190. // the only thing we can do is call play
  20191. if (type === 'any' && this.muted() !== true) {
  20192. promise = this.play();
  20193. if (isPromise(promise)) {
  20194. promise = promise.catch(muted);
  20195. }
  20196. } else if (type === 'muted' && this.muted() !== true) {
  20197. promise = muted();
  20198. } else {
  20199. promise = this.play();
  20200. }
  20201. if (!isPromise(promise)) {
  20202. return;
  20203. }
  20204. return promise.then(function () {
  20205. _this5.trigger({
  20206. type: 'autoplay-success',
  20207. autoplay: type
  20208. });
  20209. }).catch(function (e) {
  20210. _this5.trigger({
  20211. type: 'autoplay-failure',
  20212. autoplay: type
  20213. });
  20214. });
  20215. }
  20216. /**
  20217. * Update the internal source caches so that we return the correct source from
  20218. * `src()`, `currentSource()`, and `currentSources()`.
  20219. *
  20220. * > Note: `currentSources` will not be updated if the source that is passed in exists
  20221. * in the current `currentSources` cache.
  20222. *
  20223. *
  20224. * @param {Tech~SourceObject} srcObj
  20225. * A string or object source to update our caches to.
  20226. */
  20227. ;
  20228. _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) {
  20229. if (srcObj === void 0) {
  20230. srcObj = '';
  20231. }
  20232. var src = srcObj;
  20233. var type = '';
  20234. if (typeof src !== 'string') {
  20235. src = srcObj.src;
  20236. type = srcObj.type;
  20237. } // make sure all the caches are set to default values
  20238. // to prevent null checking
  20239. this.cache_.source = this.cache_.source || {};
  20240. this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in
  20241. if (src && !type) {
  20242. type = findMimetype(this, src);
  20243. } // update `currentSource` cache always
  20244. this.cache_.source = mergeOptions({}, srcObj, {
  20245. src: src,
  20246. type: type
  20247. });
  20248. var matchingSources = this.cache_.sources.filter(function (s) {
  20249. return s.src && s.src === src;
  20250. });
  20251. var sourceElSources = [];
  20252. var sourceEls = this.$$('source');
  20253. var matchingSourceEls = [];
  20254. for (var i = 0; i < sourceEls.length; i++) {
  20255. var sourceObj = getAttributes(sourceEls[i]);
  20256. sourceElSources.push(sourceObj);
  20257. if (sourceObj.src && sourceObj.src === src) {
  20258. matchingSourceEls.push(sourceObj.src);
  20259. }
  20260. } // if we have matching source els but not matching sources
  20261. // the current source cache is not up to date
  20262. if (matchingSourceEls.length && !matchingSources.length) {
  20263. this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the
  20264. // sources cache to the `currentSource` cache
  20265. } else if (!matchingSources.length) {
  20266. this.cache_.sources = [this.cache_.source];
  20267. } // update the tech `src` cache
  20268. this.cache_.src = src;
  20269. }
  20270. /**
  20271. * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
  20272. * causing the media element to reload.
  20273. *
  20274. * It will fire for the initial source and each subsequent source.
  20275. * This event is a custom event from Video.js and is triggered by the {@link Tech}.
  20276. *
  20277. * The event object for this event contains a `src` property that will contain the source
  20278. * that was available when the event was triggered. This is generally only necessary if Video.js
  20279. * is switching techs while the source was being changed.
  20280. *
  20281. * It is also fired when `load` is called on the player (or media element)
  20282. * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
  20283. * says that the resource selection algorithm needs to be aborted and restarted.
  20284. * In this case, it is very likely that the `src` property will be set to the
  20285. * empty string `""` to indicate we do not know what the source will be but
  20286. * that it is changing.
  20287. *
  20288. * *This event is currently still experimental and may change in minor releases.*
  20289. * __To use this, pass `enableSourceset` option to the player.__
  20290. *
  20291. * @event Player#sourceset
  20292. * @type {EventTarget~Event}
  20293. * @prop {string} src
  20294. * The source url available when the `sourceset` was triggered.
  20295. * It will be an empty string if we cannot know what the source is
  20296. * but know that the source will change.
  20297. */
  20298. /**
  20299. * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
  20300. *
  20301. * @fires Player#sourceset
  20302. * @listens Tech#sourceset
  20303. * @private
  20304. */
  20305. ;
  20306. _proto.handleTechSourceset_ = function handleTechSourceset_(event) {
  20307. var _this6 = this;
  20308. // only update the source cache when the source
  20309. // was not updated using the player api
  20310. if (!this.changingSrc_) {
  20311. var updateSourceCaches = function updateSourceCaches(src) {
  20312. return _this6.updateSourceCaches_(src);
  20313. };
  20314. var playerSrc = this.currentSource().src;
  20315. var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob
  20316. if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
  20317. // if both the tech source and the player source were updated we assume
  20318. // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
  20319. if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
  20320. updateSourceCaches = function updateSourceCaches() {};
  20321. }
  20322. } // update the source to the intial source right away
  20323. // in some cases this will be empty string
  20324. updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string
  20325. // wait for a `loadstart` to update the cache to `currentSrc`.
  20326. // If a sourceset happens before a `loadstart`, we reset the state
  20327. // as this function will be called again.
  20328. if (!event.src) {
  20329. var updateCache = function updateCache(e) {
  20330. if (e.type !== 'sourceset') {
  20331. var techSrc = _this6.techGet('currentSrc');
  20332. _this6.lastSource_.tech = techSrc;
  20333. _this6.updateSourceCaches_(techSrc);
  20334. }
  20335. _this6.tech_.off(['sourceset', 'loadstart'], updateCache);
  20336. };
  20337. this.tech_.one(['sourceset', 'loadstart'], updateCache);
  20338. }
  20339. }
  20340. this.lastSource_ = {
  20341. player: this.currentSource().src,
  20342. tech: event.src
  20343. };
  20344. this.trigger({
  20345. src: event.src,
  20346. type: 'sourceset'
  20347. });
  20348. }
  20349. /**
  20350. * Add/remove the vjs-has-started class
  20351. *
  20352. * @fires Player#firstplay
  20353. *
  20354. * @param {boolean} request
  20355. * - true: adds the class
  20356. * - false: remove the class
  20357. *
  20358. * @return {boolean}
  20359. * the boolean value of hasStarted_
  20360. */
  20361. ;
  20362. _proto.hasStarted = function hasStarted(request) {
  20363. if (request === undefined) {
  20364. // act as getter, if we have no request to change
  20365. return this.hasStarted_;
  20366. }
  20367. if (request === this.hasStarted_) {
  20368. return;
  20369. }
  20370. this.hasStarted_ = request;
  20371. if (this.hasStarted_) {
  20372. this.addClass('vjs-has-started');
  20373. this.trigger('firstplay');
  20374. } else {
  20375. this.removeClass('vjs-has-started');
  20376. }
  20377. }
  20378. /**
  20379. * Fired whenever the media begins or resumes playback
  20380. *
  20381. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
  20382. * @fires Player#play
  20383. * @listens Tech#play
  20384. * @private
  20385. */
  20386. ;
  20387. _proto.handleTechPlay_ = function handleTechPlay_() {
  20388. this.removeClass('vjs-ended');
  20389. this.removeClass('vjs-paused');
  20390. this.addClass('vjs-playing'); // hide the poster when the user hits play
  20391. this.hasStarted(true);
  20392. /**
  20393. * Triggered whenever an {@link Tech#play} event happens. Indicates that
  20394. * playback has started or resumed.
  20395. *
  20396. * @event Player#play
  20397. * @type {EventTarget~Event}
  20398. */
  20399. this.trigger('play');
  20400. }
  20401. /**
  20402. * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
  20403. *
  20404. * If there were any events queued while the playback rate was zero, fire
  20405. * those events now.
  20406. *
  20407. * @private
  20408. * @method Player#handleTechRateChange_
  20409. * @fires Player#ratechange
  20410. * @listens Tech#ratechange
  20411. */
  20412. ;
  20413. _proto.handleTechRateChange_ = function handleTechRateChange_() {
  20414. if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
  20415. this.queuedCallbacks_.forEach(function (queued) {
  20416. return queued.callback(queued.event);
  20417. });
  20418. this.queuedCallbacks_ = [];
  20419. }
  20420. this.cache_.lastPlaybackRate = this.tech_.playbackRate();
  20421. /**
  20422. * Fires when the playing speed of the audio/video is changed
  20423. *
  20424. * @event Player#ratechange
  20425. * @type {event}
  20426. */
  20427. this.trigger('ratechange');
  20428. }
  20429. /**
  20430. * Retrigger the `waiting` event that was triggered by the {@link Tech}.
  20431. *
  20432. * @fires Player#waiting
  20433. * @listens Tech#waiting
  20434. * @private
  20435. */
  20436. ;
  20437. _proto.handleTechWaiting_ = function handleTechWaiting_() {
  20438. var _this7 = this;
  20439. this.addClass('vjs-waiting');
  20440. /**
  20441. * A readyState change on the DOM element has caused playback to stop.
  20442. *
  20443. * @event Player#waiting
  20444. * @type {EventTarget~Event}
  20445. */
  20446. this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent
  20447. // premature removal of the waiting class, wait for the time to change.
  20448. var timeWhenWaiting = this.currentTime();
  20449. var timeUpdateListener = function timeUpdateListener() {
  20450. if (timeWhenWaiting !== _this7.currentTime()) {
  20451. _this7.removeClass('vjs-waiting');
  20452. _this7.off('timeupdate', timeUpdateListener);
  20453. }
  20454. };
  20455. this.on('timeupdate', timeUpdateListener);
  20456. }
  20457. /**
  20458. * Retrigger the `canplay` event that was triggered by the {@link Tech}.
  20459. * > Note: This is not consistent between browsers. See #1351
  20460. *
  20461. * @fires Player#canplay
  20462. * @listens Tech#canplay
  20463. * @private
  20464. */
  20465. ;
  20466. _proto.handleTechCanPlay_ = function handleTechCanPlay_() {
  20467. this.removeClass('vjs-waiting');
  20468. /**
  20469. * The media has a readyState of HAVE_FUTURE_DATA or greater.
  20470. *
  20471. * @event Player#canplay
  20472. * @type {EventTarget~Event}
  20473. */
  20474. this.trigger('canplay');
  20475. }
  20476. /**
  20477. * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
  20478. *
  20479. * @fires Player#canplaythrough
  20480. * @listens Tech#canplaythrough
  20481. * @private
  20482. */
  20483. ;
  20484. _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
  20485. this.removeClass('vjs-waiting');
  20486. /**
  20487. * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
  20488. * entire media file can be played without buffering.
  20489. *
  20490. * @event Player#canplaythrough
  20491. * @type {EventTarget~Event}
  20492. */
  20493. this.trigger('canplaythrough');
  20494. }
  20495. /**
  20496. * Retrigger the `playing` event that was triggered by the {@link Tech}.
  20497. *
  20498. * @fires Player#playing
  20499. * @listens Tech#playing
  20500. * @private
  20501. */
  20502. ;
  20503. _proto.handleTechPlaying_ = function handleTechPlaying_() {
  20504. this.removeClass('vjs-waiting');
  20505. /**
  20506. * The media is no longer blocked from playback, and has started playing.
  20507. *
  20508. * @event Player#playing
  20509. * @type {EventTarget~Event}
  20510. */
  20511. this.trigger('playing');
  20512. }
  20513. /**
  20514. * Retrigger the `seeking` event that was triggered by the {@link Tech}.
  20515. *
  20516. * @fires Player#seeking
  20517. * @listens Tech#seeking
  20518. * @private
  20519. */
  20520. ;
  20521. _proto.handleTechSeeking_ = function handleTechSeeking_() {
  20522. this.addClass('vjs-seeking');
  20523. /**
  20524. * Fired whenever the player is jumping to a new time
  20525. *
  20526. * @event Player#seeking
  20527. * @type {EventTarget~Event}
  20528. */
  20529. this.trigger('seeking');
  20530. }
  20531. /**
  20532. * Retrigger the `seeked` event that was triggered by the {@link Tech}.
  20533. *
  20534. * @fires Player#seeked
  20535. * @listens Tech#seeked
  20536. * @private
  20537. */
  20538. ;
  20539. _proto.handleTechSeeked_ = function handleTechSeeked_() {
  20540. this.removeClass('vjs-seeking');
  20541. this.removeClass('vjs-ended');
  20542. /**
  20543. * Fired when the player has finished jumping to a new time
  20544. *
  20545. * @event Player#seeked
  20546. * @type {EventTarget~Event}
  20547. */
  20548. this.trigger('seeked');
  20549. }
  20550. /**
  20551. * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
  20552. *
  20553. * @fires Player#firstplay
  20554. * @listens Tech#firstplay
  20555. * @deprecated As of 6.0 firstplay event is deprecated.
  20556. * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
  20557. * @private
  20558. */
  20559. ;
  20560. _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() {
  20561. // If the first starttime attribute is specified
  20562. // then we will start at the given offset in seconds
  20563. if (this.options_.starttime) {
  20564. log.warn('Passing the `starttime` option to the player will be deprecated in 6.0');
  20565. this.currentTime(this.options_.starttime);
  20566. }
  20567. this.addClass('vjs-has-started');
  20568. /**
  20569. * Fired the first time a video is played. Not part of the HLS spec, and this is
  20570. * probably not the best implementation yet, so use sparingly. If you don't have a
  20571. * reason to prevent playback, use `myPlayer.one('play');` instead.
  20572. *
  20573. * @event Player#firstplay
  20574. * @deprecated As of 6.0 firstplay event is deprecated.
  20575. * @type {EventTarget~Event}
  20576. */
  20577. this.trigger('firstplay');
  20578. }
  20579. /**
  20580. * Retrigger the `pause` event that was triggered by the {@link Tech}.
  20581. *
  20582. * @fires Player#pause
  20583. * @listens Tech#pause
  20584. * @private
  20585. */
  20586. ;
  20587. _proto.handleTechPause_ = function handleTechPause_() {
  20588. this.removeClass('vjs-playing');
  20589. this.addClass('vjs-paused');
  20590. /**
  20591. * Fired whenever the media has been paused
  20592. *
  20593. * @event Player#pause
  20594. * @type {EventTarget~Event}
  20595. */
  20596. this.trigger('pause');
  20597. }
  20598. /**
  20599. * Retrigger the `ended` event that was triggered by the {@link Tech}.
  20600. *
  20601. * @fires Player#ended
  20602. * @listens Tech#ended
  20603. * @private
  20604. */
  20605. ;
  20606. _proto.handleTechEnded_ = function handleTechEnded_() {
  20607. this.addClass('vjs-ended');
  20608. if (this.options_.loop) {
  20609. this.currentTime(0);
  20610. this.play();
  20611. } else if (!this.paused()) {
  20612. this.pause();
  20613. }
  20614. /**
  20615. * Fired when the end of the media resource is reached (currentTime == duration)
  20616. *
  20617. * @event Player#ended
  20618. * @type {EventTarget~Event}
  20619. */
  20620. this.trigger('ended');
  20621. }
  20622. /**
  20623. * Fired when the duration of the media resource is first known or changed
  20624. *
  20625. * @listens Tech#durationchange
  20626. * @private
  20627. */
  20628. ;
  20629. _proto.handleTechDurationChange_ = function handleTechDurationChange_() {
  20630. this.duration(this.techGet_('duration'));
  20631. }
  20632. /**
  20633. * Handle a click on the media element to play/pause
  20634. *
  20635. * @param {EventTarget~Event} event
  20636. * the event that caused this function to trigger
  20637. *
  20638. * @listens Tech#mousedown
  20639. * @private
  20640. */
  20641. ;
  20642. _proto.handleTechClick_ = function handleTechClick_(event) {
  20643. if (!isSingleLeftClick(event)) {
  20644. return;
  20645. } // When controls are disabled a click should not toggle playback because
  20646. // the click is considered a control
  20647. if (!this.controls_) {
  20648. return;
  20649. }
  20650. if (this.paused()) {
  20651. silencePromise(this.play());
  20652. } else {
  20653. this.pause();
  20654. }
  20655. }
  20656. /**
  20657. * Handle a double-click on the media element to enter/exit fullscreen
  20658. *
  20659. * @param {EventTarget~Event} event
  20660. * the event that caused this function to trigger
  20661. *
  20662. * @listens Tech#dblclick
  20663. * @private
  20664. */
  20665. ;
  20666. _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
  20667. if (!this.controls_) {
  20668. return;
  20669. } // we do not want to toggle fullscreen state
  20670. // when double-clicking inside a control bar or a modal
  20671. var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
  20672. return el.contains(event.target);
  20673. });
  20674. if (!inAllowedEls) {
  20675. /*
  20676. * options.userActions.doubleClick
  20677. *
  20678. * If `undefined` or `true`, double-click toggles fullscreen if controls are present
  20679. * Set to `false` to disable double-click handling
  20680. * Set to a function to substitute an external double-click handler
  20681. */
  20682. if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
  20683. if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
  20684. this.options_.userActions.doubleClick.call(this, event);
  20685. } else if (this.isFullscreen()) {
  20686. this.exitFullscreen();
  20687. } else {
  20688. this.requestFullscreen();
  20689. }
  20690. }
  20691. }
  20692. }
  20693. /**
  20694. * Handle a tap on the media element. It will toggle the user
  20695. * activity state, which hides and shows the controls.
  20696. *
  20697. * @listens Tech#tap
  20698. * @private
  20699. */
  20700. ;
  20701. _proto.handleTechTap_ = function handleTechTap_() {
  20702. this.userActive(!this.userActive());
  20703. }
  20704. /**
  20705. * Handle touch to start
  20706. *
  20707. * @listens Tech#touchstart
  20708. * @private
  20709. */
  20710. ;
  20711. _proto.handleTechTouchStart_ = function handleTechTouchStart_() {
  20712. this.userWasActive = this.userActive();
  20713. }
  20714. /**
  20715. * Handle touch to move
  20716. *
  20717. * @listens Tech#touchmove
  20718. * @private
  20719. */
  20720. ;
  20721. _proto.handleTechTouchMove_ = function handleTechTouchMove_() {
  20722. if (this.userWasActive) {
  20723. this.reportUserActivity();
  20724. }
  20725. }
  20726. /**
  20727. * Handle touch to end
  20728. *
  20729. * @param {EventTarget~Event} event
  20730. * the touchend event that triggered
  20731. * this function
  20732. *
  20733. * @listens Tech#touchend
  20734. * @private
  20735. */
  20736. ;
  20737. _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
  20738. // Stop the mouse events from also happening
  20739. event.preventDefault();
  20740. }
  20741. /**
  20742. * native click events on the SWF aren't triggered on IE11, Win8.1RT
  20743. * use stageclick events triggered from inside the SWF instead
  20744. *
  20745. * @private
  20746. * @listens stageclick
  20747. */
  20748. ;
  20749. _proto.handleStageClick_ = function handleStageClick_() {
  20750. this.reportUserActivity();
  20751. }
  20752. /**
  20753. * @private
  20754. */
  20755. ;
  20756. _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() {
  20757. if (this.isFullscreen()) {
  20758. this.addClass('vjs-fullscreen');
  20759. } else {
  20760. this.removeClass('vjs-fullscreen');
  20761. }
  20762. }
  20763. /**
  20764. * when the document fschange event triggers it calls this
  20765. */
  20766. ;
  20767. _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) {
  20768. var fsApi = FullscreenApi;
  20769. this.isFullscreen(document[fsApi.fullscreenElement] === this.el() || this.el().matches(':' + fsApi.fullscreen)); // If cancelling fullscreen, remove event listener.
  20770. if (this.isFullscreen() === false) {
  20771. off(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  20772. }
  20773. if (!prefixedAPI) {
  20774. /**
  20775. * @event Player#fullscreenchange
  20776. * @type {EventTarget~Event}
  20777. */
  20778. this.trigger('fullscreenchange');
  20779. }
  20780. }
  20781. /**
  20782. * Handle Tech Fullscreen Change
  20783. *
  20784. * @param {EventTarget~Event} event
  20785. * the fullscreenchange event that triggered this function
  20786. *
  20787. * @param {Object} data
  20788. * the data that was sent with the event
  20789. *
  20790. * @private
  20791. * @listens Tech#fullscreenchange
  20792. * @fires Player#fullscreenchange
  20793. */
  20794. ;
  20795. _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
  20796. if (data) {
  20797. this.isFullscreen(data.isFullscreen);
  20798. }
  20799. /**
  20800. * Fired when going in and out of fullscreen.
  20801. *
  20802. * @event Player#fullscreenchange
  20803. * @type {EventTarget~Event}
  20804. */
  20805. this.trigger('fullscreenchange');
  20806. }
  20807. /**
  20808. * Fires when an error occurred during the loading of an audio/video.
  20809. *
  20810. * @private
  20811. * @listens Tech#error
  20812. */
  20813. ;
  20814. _proto.handleTechError_ = function handleTechError_() {
  20815. var error = this.tech_.error();
  20816. this.error(error);
  20817. }
  20818. /**
  20819. * Retrigger the `textdata` event that was triggered by the {@link Tech}.
  20820. *
  20821. * @fires Player#textdata
  20822. * @listens Tech#textdata
  20823. * @private
  20824. */
  20825. ;
  20826. _proto.handleTechTextData_ = function handleTechTextData_() {
  20827. var data = null;
  20828. if (arguments.length > 1) {
  20829. data = arguments[1];
  20830. }
  20831. /**
  20832. * Fires when we get a textdata event from tech
  20833. *
  20834. * @event Player#textdata
  20835. * @type {EventTarget~Event}
  20836. */
  20837. this.trigger('textdata', data);
  20838. }
  20839. /**
  20840. * Get object for cached values.
  20841. *
  20842. * @return {Object}
  20843. * get the current object cache
  20844. */
  20845. ;
  20846. _proto.getCache = function getCache() {
  20847. return this.cache_;
  20848. }
  20849. /**
  20850. * Resets the internal cache object.
  20851. *
  20852. * Using this function outside the player constructor or reset method may
  20853. * have unintended side-effects.
  20854. *
  20855. * @private
  20856. */
  20857. ;
  20858. _proto.resetCache_ = function resetCache_() {
  20859. this.cache_ = {
  20860. // Right now, the currentTime is not _really_ cached because it is always
  20861. // retrieved from the tech (see: currentTime). However, for completeness,
  20862. // we set it to zero here to ensure that if we do start actually caching
  20863. // it, we reset it along with everything else.
  20864. currentTime: 0,
  20865. inactivityTimeout: this.options_.inactivityTimeout,
  20866. duration: NaN,
  20867. lastVolume: 1,
  20868. lastPlaybackRate: this.defaultPlaybackRate(),
  20869. media: null,
  20870. src: '',
  20871. source: {},
  20872. sources: [],
  20873. volume: 1
  20874. };
  20875. }
  20876. /**
  20877. * Pass values to the playback tech
  20878. *
  20879. * @param {string} [method]
  20880. * the method to call
  20881. *
  20882. * @param {Object} arg
  20883. * the argument to pass
  20884. *
  20885. * @private
  20886. */
  20887. ;
  20888. _proto.techCall_ = function techCall_(method, arg) {
  20889. // If it's not ready yet, call method when it is
  20890. this.ready(function () {
  20891. if (method in allowedSetters) {
  20892. return set$1(this.middleware_, this.tech_, method, arg);
  20893. } else if (method in allowedMediators) {
  20894. return mediate(this.middleware_, this.tech_, method, arg);
  20895. }
  20896. try {
  20897. if (this.tech_) {
  20898. this.tech_[method](arg);
  20899. }
  20900. } catch (e) {
  20901. log(e);
  20902. throw e;
  20903. }
  20904. }, true);
  20905. }
  20906. /**
  20907. * Get calls can't wait for the tech, and sometimes don't need to.
  20908. *
  20909. * @param {string} method
  20910. * Tech method
  20911. *
  20912. * @return {Function|undefined}
  20913. * the method or undefined
  20914. *
  20915. * @private
  20916. */
  20917. ;
  20918. _proto.techGet_ = function techGet_(method) {
  20919. if (!this.tech_ || !this.tech_.isReady_) {
  20920. return;
  20921. }
  20922. if (method in allowedGetters) {
  20923. return get(this.middleware_, this.tech_, method);
  20924. } else if (method in allowedMediators) {
  20925. return mediate(this.middleware_, this.tech_, method);
  20926. } // Flash likes to die and reload when you hide or reposition it.
  20927. // In these cases the object methods go away and we get errors.
  20928. // When that happens we'll catch the errors and inform tech that it's not ready any more.
  20929. try {
  20930. return this.tech_[method]();
  20931. } catch (e) {
  20932. // When building additional tech libs, an expected method may not be defined yet
  20933. if (this.tech_[method] === undefined) {
  20934. log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e);
  20935. throw e;
  20936. } // When a method isn't available on the object it throws a TypeError
  20937. if (e.name === 'TypeError') {
  20938. log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e);
  20939. this.tech_.isReady_ = false;
  20940. throw e;
  20941. } // If error unknown, just log and throw
  20942. log(e);
  20943. throw e;
  20944. }
  20945. }
  20946. /**
  20947. * Attempt to begin playback at the first opportunity.
  20948. *
  20949. * @return {Promise|undefined}
  20950. * Returns a promise if the browser supports Promises (or one
  20951. * was passed in as an option). This promise will be resolved on
  20952. * the return value of play. If this is undefined it will fulfill the
  20953. * promise chain otherwise the promise chain will be fulfilled when
  20954. * the promise from play is fulfilled.
  20955. */
  20956. ;
  20957. _proto.play = function play() {
  20958. var _this8 = this;
  20959. var PromiseClass = this.options_.Promise || window$1.Promise;
  20960. if (PromiseClass) {
  20961. return new PromiseClass(function (resolve) {
  20962. _this8.play_(resolve);
  20963. });
  20964. }
  20965. return this.play_();
  20966. }
  20967. /**
  20968. * The actual logic for play, takes a callback that will be resolved on the
  20969. * return value of play. This allows us to resolve to the play promise if there
  20970. * is one on modern browsers.
  20971. *
  20972. * @private
  20973. * @param {Function} [callback]
  20974. * The callback that should be called when the techs play is actually called
  20975. */
  20976. ;
  20977. _proto.play_ = function play_(callback) {
  20978. var _this9 = this;
  20979. if (callback === void 0) {
  20980. callback = silencePromise;
  20981. }
  20982. this.playCallbacks_.push(callback);
  20983. var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function
  20984. if (this.waitToPlay_) {
  20985. this.off(['ready', 'loadstart'], this.waitToPlay_);
  20986. this.waitToPlay_ = null;
  20987. } // if the player/tech is not ready or the src itself is not ready
  20988. // queue up a call to play on `ready` or `loadstart`
  20989. if (!this.isReady_ || !isSrcReady) {
  20990. this.waitToPlay_ = function (e) {
  20991. _this9.play_();
  20992. };
  20993. this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
  20994. // in that case, we need to prime the video element by calling load so it'll be ready in time
  20995. if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) {
  20996. this.load();
  20997. }
  20998. return;
  20999. } // If the player/tech is ready and we have a source, we can attempt playback.
  21000. var val = this.techGet_('play'); // play was terminated if the returned value is null
  21001. if (val === null) {
  21002. this.runPlayTerminatedQueue_();
  21003. } else {
  21004. this.runPlayCallbacks_(val);
  21005. }
  21006. }
  21007. /**
  21008. * These functions will be run when if play is terminated. If play
  21009. * runPlayCallbacks_ is run these function will not be run. This allows us
  21010. * to differenciate between a terminated play and an actual call to play.
  21011. */
  21012. ;
  21013. _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() {
  21014. var queue = this.playTerminatedQueue_.slice(0);
  21015. this.playTerminatedQueue_ = [];
  21016. queue.forEach(function (q) {
  21017. q();
  21018. });
  21019. }
  21020. /**
  21021. * When a callback to play is delayed we have to run these
  21022. * callbacks when play is actually called on the tech. This function
  21023. * runs the callbacks that were delayed and accepts the return value
  21024. * from the tech.
  21025. *
  21026. * @param {undefined|Promise} val
  21027. * The return value from the tech.
  21028. */
  21029. ;
  21030. _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) {
  21031. var callbacks = this.playCallbacks_.slice(0);
  21032. this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play
  21033. this.playTerminatedQueue_ = [];
  21034. callbacks.forEach(function (cb) {
  21035. cb(val);
  21036. });
  21037. }
  21038. /**
  21039. * Pause the video playback
  21040. *
  21041. * @return {Player}
  21042. * A reference to the player object this function was called on
  21043. */
  21044. ;
  21045. _proto.pause = function pause() {
  21046. this.techCall_('pause');
  21047. }
  21048. /**
  21049. * Check if the player is paused or has yet to play
  21050. *
  21051. * @return {boolean}
  21052. * - false: if the media is currently playing
  21053. * - true: if media is not currently playing
  21054. */
  21055. ;
  21056. _proto.paused = function paused() {
  21057. // The initial state of paused should be true (in Safari it's actually false)
  21058. return this.techGet_('paused') === false ? false : true;
  21059. }
  21060. /**
  21061. * Get a TimeRange object representing the current ranges of time that the user
  21062. * has played.
  21063. *
  21064. * @return {TimeRange}
  21065. * A time range object that represents all the increments of time that have
  21066. * been played.
  21067. */
  21068. ;
  21069. _proto.played = function played() {
  21070. return this.techGet_('played') || createTimeRanges(0, 0);
  21071. }
  21072. /**
  21073. * Returns whether or not the user is "scrubbing". Scrubbing is
  21074. * when the user has clicked the progress bar handle and is
  21075. * dragging it along the progress bar.
  21076. *
  21077. * @param {boolean} [isScrubbing]
  21078. * whether the user is or is not scrubbing
  21079. *
  21080. * @return {boolean}
  21081. * The value of scrubbing when getting
  21082. */
  21083. ;
  21084. _proto.scrubbing = function scrubbing(isScrubbing) {
  21085. if (typeof isScrubbing === 'undefined') {
  21086. return this.scrubbing_;
  21087. }
  21088. this.scrubbing_ = !!isScrubbing;
  21089. if (isScrubbing) {
  21090. this.addClass('vjs-scrubbing');
  21091. } else {
  21092. this.removeClass('vjs-scrubbing');
  21093. }
  21094. }
  21095. /**
  21096. * Get or set the current time (in seconds)
  21097. *
  21098. * @param {number|string} [seconds]
  21099. * The time to seek to in seconds
  21100. *
  21101. * @return {number}
  21102. * - the current time in seconds when getting
  21103. */
  21104. ;
  21105. _proto.currentTime = function currentTime(seconds) {
  21106. if (typeof seconds !== 'undefined') {
  21107. if (seconds < 0) {
  21108. seconds = 0;
  21109. }
  21110. this.techCall_('setCurrentTime', seconds);
  21111. return;
  21112. } // cache last currentTime and return. default to 0 seconds
  21113. //
  21114. // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
  21115. // currentTime when scrubbing, but may not provide much performance benefit afterall.
  21116. // Should be tested. Also something has to read the actual current time or the cache will
  21117. // never get updated.
  21118. this.cache_.currentTime = this.techGet_('currentTime') || 0;
  21119. return this.cache_.currentTime;
  21120. }
  21121. /**
  21122. * Normally gets the length in time of the video in seconds;
  21123. * in all but the rarest use cases an argument will NOT be passed to the method
  21124. *
  21125. * > **NOTE**: The video must have started loading before the duration can be
  21126. * known, and in the case of Flash, may not be known until the video starts
  21127. * playing.
  21128. *
  21129. * @fires Player#durationchange
  21130. *
  21131. * @param {number} [seconds]
  21132. * The duration of the video to set in seconds
  21133. *
  21134. * @return {number}
  21135. * - The duration of the video in seconds when getting
  21136. */
  21137. ;
  21138. _proto.duration = function duration(seconds) {
  21139. if (seconds === undefined) {
  21140. // return NaN if the duration is not known
  21141. return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
  21142. }
  21143. seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live
  21144. if (seconds < 0) {
  21145. seconds = Infinity;
  21146. }
  21147. if (seconds !== this.cache_.duration) {
  21148. // Cache the last set value for optimized scrubbing (esp. Flash)
  21149. this.cache_.duration = seconds;
  21150. if (seconds === Infinity) {
  21151. this.addClass('vjs-live');
  21152. if (this.options_.liveui && this.player_.liveTracker) {
  21153. this.addClass('vjs-liveui');
  21154. }
  21155. } else {
  21156. this.removeClass('vjs-live');
  21157. this.removeClass('vjs-liveui');
  21158. }
  21159. if (!isNaN(seconds)) {
  21160. // Do not fire durationchange unless the duration value is known.
  21161. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  21162. /**
  21163. * @event Player#durationchange
  21164. * @type {EventTarget~Event}
  21165. */
  21166. this.trigger('durationchange');
  21167. }
  21168. }
  21169. }
  21170. /**
  21171. * Calculates how much time is left in the video. Not part
  21172. * of the native video API.
  21173. *
  21174. * @return {number}
  21175. * The time remaining in seconds
  21176. */
  21177. ;
  21178. _proto.remainingTime = function remainingTime() {
  21179. return this.duration() - this.currentTime();
  21180. }
  21181. /**
  21182. * A remaining time function that is intented to be used when
  21183. * the time is to be displayed directly to the user.
  21184. *
  21185. * @return {number}
  21186. * The rounded time remaining in seconds
  21187. */
  21188. ;
  21189. _proto.remainingTimeDisplay = function remainingTimeDisplay() {
  21190. return Math.floor(this.duration()) - Math.floor(this.currentTime());
  21191. } //
  21192. // Kind of like an array of portions of the video that have been downloaded.
  21193. /**
  21194. * Get a TimeRange object with an array of the times of the video
  21195. * that have been downloaded. If you just want the percent of the
  21196. * video that's been downloaded, use bufferedPercent.
  21197. *
  21198. * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
  21199. *
  21200. * @return {TimeRange}
  21201. * A mock TimeRange object (following HTML spec)
  21202. */
  21203. ;
  21204. _proto.buffered = function buffered() {
  21205. var buffered = this.techGet_('buffered');
  21206. if (!buffered || !buffered.length) {
  21207. buffered = createTimeRanges(0, 0);
  21208. }
  21209. return buffered;
  21210. }
  21211. /**
  21212. * Get the percent (as a decimal) of the video that's been downloaded.
  21213. * This method is not a part of the native HTML video API.
  21214. *
  21215. * @return {number}
  21216. * A decimal between 0 and 1 representing the percent
  21217. * that is buffered 0 being 0% and 1 being 100%
  21218. */
  21219. ;
  21220. _proto.bufferedPercent = function bufferedPercent$$1() {
  21221. return bufferedPercent(this.buffered(), this.duration());
  21222. }
  21223. /**
  21224. * Get the ending time of the last buffered time range
  21225. * This is used in the progress bar to encapsulate all time ranges.
  21226. *
  21227. * @return {number}
  21228. * The end of the last buffered time range
  21229. */
  21230. ;
  21231. _proto.bufferedEnd = function bufferedEnd() {
  21232. var buffered = this.buffered();
  21233. var duration = this.duration();
  21234. var end = buffered.end(buffered.length - 1);
  21235. if (end > duration) {
  21236. end = duration;
  21237. }
  21238. return end;
  21239. }
  21240. /**
  21241. * Get or set the current volume of the media
  21242. *
  21243. * @param {number} [percentAsDecimal]
  21244. * The new volume as a decimal percent:
  21245. * - 0 is muted/0%/off
  21246. * - 1.0 is 100%/full
  21247. * - 0.5 is half volume or 50%
  21248. *
  21249. * @return {number}
  21250. * The current volume as a percent when getting
  21251. */
  21252. ;
  21253. _proto.volume = function volume(percentAsDecimal) {
  21254. var vol;
  21255. if (percentAsDecimal !== undefined) {
  21256. // Force value to between 0 and 1
  21257. vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
  21258. this.cache_.volume = vol;
  21259. this.techCall_('setVolume', vol);
  21260. if (vol > 0) {
  21261. this.lastVolume_(vol);
  21262. }
  21263. return;
  21264. } // Default to 1 when returning current volume.
  21265. vol = parseFloat(this.techGet_('volume'));
  21266. return isNaN(vol) ? 1 : vol;
  21267. }
  21268. /**
  21269. * Get the current muted state, or turn mute on or off
  21270. *
  21271. * @param {boolean} [muted]
  21272. * - true to mute
  21273. * - false to unmute
  21274. *
  21275. * @return {boolean}
  21276. * - true if mute is on and getting
  21277. * - false if mute is off and getting
  21278. */
  21279. ;
  21280. _proto.muted = function muted(_muted) {
  21281. if (_muted !== undefined) {
  21282. this.techCall_('setMuted', _muted);
  21283. return;
  21284. }
  21285. return this.techGet_('muted') || false;
  21286. }
  21287. /**
  21288. * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
  21289. * indicates the state of muted on initial playback.
  21290. *
  21291. * ```js
  21292. * var myPlayer = videojs('some-player-id');
  21293. *
  21294. * myPlayer.src("http://www.example.com/path/to/video.mp4");
  21295. *
  21296. * // get, should be false
  21297. * console.log(myPlayer.defaultMuted());
  21298. * // set to true
  21299. * myPlayer.defaultMuted(true);
  21300. * // get should be true
  21301. * console.log(myPlayer.defaultMuted());
  21302. * ```
  21303. *
  21304. * @param {boolean} [defaultMuted]
  21305. * - true to mute
  21306. * - false to unmute
  21307. *
  21308. * @return {boolean|Player}
  21309. * - true if defaultMuted is on and getting
  21310. * - false if defaultMuted is off and getting
  21311. * - A reference to the current player when setting
  21312. */
  21313. ;
  21314. _proto.defaultMuted = function defaultMuted(_defaultMuted) {
  21315. if (_defaultMuted !== undefined) {
  21316. return this.techCall_('setDefaultMuted', _defaultMuted);
  21317. }
  21318. return this.techGet_('defaultMuted') || false;
  21319. }
  21320. /**
  21321. * Get the last volume, or set it
  21322. *
  21323. * @param {number} [percentAsDecimal]
  21324. * The new last volume as a decimal percent:
  21325. * - 0 is muted/0%/off
  21326. * - 1.0 is 100%/full
  21327. * - 0.5 is half volume or 50%
  21328. *
  21329. * @return {number}
  21330. * the current value of lastVolume as a percent when getting
  21331. *
  21332. * @private
  21333. */
  21334. ;
  21335. _proto.lastVolume_ = function lastVolume_(percentAsDecimal) {
  21336. if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
  21337. this.cache_.lastVolume = percentAsDecimal;
  21338. return;
  21339. }
  21340. return this.cache_.lastVolume;
  21341. }
  21342. /**
  21343. * Check if current tech can support native fullscreen
  21344. * (e.g. with built in controls like iOS, so not our flash swf)
  21345. *
  21346. * @return {boolean}
  21347. * if native fullscreen is supported
  21348. */
  21349. ;
  21350. _proto.supportsFullScreen = function supportsFullScreen() {
  21351. return this.techGet_('supportsFullScreen') || false;
  21352. }
  21353. /**
  21354. * Check if the player is in fullscreen mode or tell the player that it
  21355. * is or is not in fullscreen mode.
  21356. *
  21357. * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
  21358. * property and instead document.fullscreenElement is used. But isFullscreen is
  21359. * still a valuable property for internal player workings.
  21360. *
  21361. * @param {boolean} [isFS]
  21362. * Set the players current fullscreen state
  21363. *
  21364. * @return {boolean}
  21365. * - true if fullscreen is on and getting
  21366. * - false if fullscreen is off and getting
  21367. */
  21368. ;
  21369. _proto.isFullscreen = function isFullscreen(isFS) {
  21370. if (isFS !== undefined) {
  21371. this.isFullscreen_ = !!isFS;
  21372. this.toggleFullscreenClass_();
  21373. return;
  21374. }
  21375. return !!this.isFullscreen_;
  21376. }
  21377. /**
  21378. * Increase the size of the video to full screen
  21379. * In some browsers, full screen is not supported natively, so it enters
  21380. * "full window mode", where the video fills the browser window.
  21381. * In browsers and devices that support native full screen, sometimes the
  21382. * browser's default controls will be shown, and not the Video.js custom skin.
  21383. * This includes most mobile devices (iOS, Android) and older versions of
  21384. * Safari.
  21385. *
  21386. * @fires Player#fullscreenchange
  21387. */
  21388. ;
  21389. _proto.requestFullscreen = function requestFullscreen() {
  21390. var fsApi = FullscreenApi;
  21391. this.isFullscreen(true);
  21392. if (fsApi.requestFullscreen) {
  21393. // the browser supports going fullscreen at the element level so we can
  21394. // take the controls fullscreen as well as the video
  21395. // Trigger fullscreenchange event after change
  21396. // We have to specifically add this each time, and remove
  21397. // when canceling fullscreen. Otherwise if there's multiple
  21398. // players on a page, they would all be reacting to the same fullscreen
  21399. // events
  21400. on(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  21401. silencePromise(this.el_[fsApi.requestFullscreen]());
  21402. } else if (this.tech_.supportsFullScreen()) {
  21403. // we can't take the video.js controls fullscreen but we can go fullscreen
  21404. // with native controls
  21405. this.techCall_('enterFullScreen');
  21406. } else {
  21407. // fullscreen isn't supported so we'll just stretch the video element to
  21408. // fill the viewport
  21409. this.enterFullWindow();
  21410. /**
  21411. * @event Player#fullscreenchange
  21412. * @type {EventTarget~Event}
  21413. */
  21414. this.trigger('fullscreenchange');
  21415. }
  21416. }
  21417. /**
  21418. * Return the video to its normal size after having been in full screen mode
  21419. *
  21420. * @fires Player#fullscreenchange
  21421. */
  21422. ;
  21423. _proto.exitFullscreen = function exitFullscreen() {
  21424. var fsApi = FullscreenApi;
  21425. this.isFullscreen(false); // Check for browser element fullscreen support
  21426. if (fsApi.requestFullscreen) {
  21427. silencePromise(document[fsApi.exitFullscreen]());
  21428. } else if (this.tech_.supportsFullScreen()) {
  21429. this.techCall_('exitFullScreen');
  21430. } else {
  21431. this.exitFullWindow();
  21432. /**
  21433. * @event Player#fullscreenchange
  21434. * @type {EventTarget~Event}
  21435. */
  21436. this.trigger('fullscreenchange');
  21437. }
  21438. }
  21439. /**
  21440. * When fullscreen isn't supported we can stretch the
  21441. * video container to as wide as the browser will let us.
  21442. *
  21443. * @fires Player#enterFullWindow
  21444. */
  21445. ;
  21446. _proto.enterFullWindow = function enterFullWindow() {
  21447. this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off
  21448. this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen
  21449. on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars
  21450. document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles
  21451. addClass(document.body, 'vjs-full-window');
  21452. /**
  21453. * @event Player#enterFullWindow
  21454. * @type {EventTarget~Event}
  21455. */
  21456. this.trigger('enterFullWindow');
  21457. }
  21458. /**
  21459. * Check for call to either exit full window or
  21460. * full screen on ESC key
  21461. *
  21462. * @param {string} event
  21463. * Event to check for key press
  21464. */
  21465. ;
  21466. _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
  21467. if (keycode.isEventKey(event, 'Esc')) {
  21468. if (this.isFullscreen() === true) {
  21469. this.exitFullscreen();
  21470. } else {
  21471. this.exitFullWindow();
  21472. }
  21473. }
  21474. }
  21475. /**
  21476. * Exit full window
  21477. *
  21478. * @fires Player#exitFullWindow
  21479. */
  21480. ;
  21481. _proto.exitFullWindow = function exitFullWindow() {
  21482. this.isFullWindow = false;
  21483. off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars.
  21484. document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles
  21485. removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes
  21486. // this.positionAll();
  21487. /**
  21488. * @event Player#exitFullWindow
  21489. * @type {EventTarget~Event}
  21490. */
  21491. this.trigger('exitFullWindow');
  21492. }
  21493. /**
  21494. * This gets called when a `Player` gains focus via a `focus` event.
  21495. * Turns on listening for `keydown` events. When they happen it
  21496. * calls `this.handleKeyPress`.
  21497. *
  21498. * @param {EventTarget~Event} event
  21499. * The `focus` event that caused this function to be called.
  21500. *
  21501. * @listens focus
  21502. */
  21503. ;
  21504. _proto.handleFocus = function handleFocus(event) {
  21505. // call off first to make sure we don't keep adding keydown handlers
  21506. off(document, 'keydown', this.boundHandleKeyPress_);
  21507. on(document, 'keydown', this.boundHandleKeyPress_);
  21508. }
  21509. /**
  21510. * Called when a `Player` loses focus. Turns off the listener for
  21511. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  21512. *
  21513. * @param {EventTarget~Event} event
  21514. * The `blur` event that caused this function to be called.
  21515. *
  21516. * @listens blur
  21517. */
  21518. ;
  21519. _proto.handleBlur = function handleBlur(event) {
  21520. off(document, 'keydown', this.boundHandleKeyPress_);
  21521. }
  21522. /**
  21523. * Called when this Player has focus and a key gets pressed down, or when
  21524. * any Component of this player receives a key press that it doesn't handle.
  21525. * This allows player-wide hotkeys (either as defined below, or optionally
  21526. * by an external function).
  21527. *
  21528. * @param {EventTarget~Event} event
  21529. * The `keydown` event that caused this function to be called.
  21530. *
  21531. * @listens keydown
  21532. */
  21533. ;
  21534. _proto.handleKeyPress = function handleKeyPress(event) {
  21535. if (this.options_.userActions && this.options_.userActions.hotkeys && this.options_.userActions.hotkeys !== false) {
  21536. if (typeof this.options_.userActions.hotkeys === 'function') {
  21537. this.options_.userActions.hotkeys.call(this, event);
  21538. } else {
  21539. this.handleHotkeys(event);
  21540. }
  21541. }
  21542. }
  21543. /**
  21544. * Called when this Player receives a hotkey keydown event.
  21545. * Supported player-wide hotkeys are:
  21546. *
  21547. * f - toggle fullscreen
  21548. * m - toggle mute
  21549. * k or Space - toggle play/pause
  21550. *
  21551. * @param {EventTarget~Event} event
  21552. * The `keydown` event that caused this function to be called.
  21553. */
  21554. ;
  21555. _proto.handleHotkeys = function handleHotkeys(event) {
  21556. var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
  21557. var _hotkeys$fullscreenKe = hotkeys.fullscreenKey,
  21558. fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) {
  21559. return keycode.isEventKey(keydownEvent, 'f');
  21560. } : _hotkeys$fullscreenKe,
  21561. _hotkeys$muteKey = hotkeys.muteKey,
  21562. muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) {
  21563. return keycode.isEventKey(keydownEvent, 'm');
  21564. } : _hotkeys$muteKey,
  21565. _hotkeys$playPauseKey = hotkeys.playPauseKey,
  21566. playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) {
  21567. return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space');
  21568. } : _hotkeys$playPauseKey;
  21569. if (fullscreenKey.call(this, event)) {
  21570. event.preventDefault();
  21571. var FSToggle = Component.getComponent('FullscreenToggle');
  21572. if (document[FullscreenApi.fullscreenEnabled] !== false) {
  21573. FSToggle.prototype.handleClick.call(this);
  21574. }
  21575. } else if (muteKey.call(this, event)) {
  21576. event.preventDefault();
  21577. var MuteToggle = Component.getComponent('MuteToggle');
  21578. MuteToggle.prototype.handleClick.call(this);
  21579. } else if (playPauseKey.call(this, event)) {
  21580. event.preventDefault();
  21581. var PlayToggle = Component.getComponent('PlayToggle');
  21582. PlayToggle.prototype.handleClick.call(this);
  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.getComponent(techName);
  21605. } // Check if the current tech is defined before continuing
  21606. if (!tech) {
  21607. log.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 _this10 = 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.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, _this10.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. * Get or set the video source.
  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. *
  21699. * @return {string|undefined}
  21700. * If the `source` argument is missing, returns the current source
  21701. * URL. Otherwise, returns nothing/undefined.
  21702. */
  21703. ;
  21704. _proto.src = function src(source) {
  21705. var _this11 = this;
  21706. // getter usage
  21707. if (typeof source === 'undefined') {
  21708. return this.cache_.src || '';
  21709. } // filter out invalid sources and turn our source into
  21710. // an array of source objects
  21711. var sources = filterSource(source); // if a source was passed in then it is invalid because
  21712. // it was filtered to a zero length Array. So we have to
  21713. // show an error
  21714. if (!sources.length) {
  21715. this.setTimeout(function () {
  21716. this.error({
  21717. code: 4,
  21718. message: this.localize(this.options_.notSupportedMessage)
  21719. });
  21720. }, 0);
  21721. return;
  21722. } // intial sources
  21723. this.changingSrc_ = true;
  21724. this.cache_.sources = sources;
  21725. this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware
  21726. setSource(this, sources[0], function (middlewareSource, mws) {
  21727. _this11.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
  21728. // the source that is selected could be out of order from the cache update above this callback.
  21729. _this11.cache_.sources = sources;
  21730. _this11.updateSourceCaches_(middlewareSource);
  21731. var err = _this11.src_(middlewareSource);
  21732. if (err) {
  21733. if (sources.length > 1) {
  21734. return _this11.src(sources.slice(1));
  21735. }
  21736. _this11.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
  21737. _this11.setTimeout(function () {
  21738. this.error({
  21739. code: 4,
  21740. message: this.localize(this.options_.notSupportedMessage)
  21741. });
  21742. }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it
  21743. // this needs a better comment about why this is needed
  21744. _this11.triggerReady();
  21745. return;
  21746. }
  21747. setTech(mws, _this11.tech_);
  21748. });
  21749. }
  21750. /**
  21751. * Set the source object on the tech, returns a boolean that indicates whether
  21752. * there is a tech that can play the source or not
  21753. *
  21754. * @param {Tech~SourceObject} source
  21755. * The source object to set on the Tech
  21756. *
  21757. * @return {boolean}
  21758. * - True if there is no Tech to playback this source
  21759. * - False otherwise
  21760. *
  21761. * @private
  21762. */
  21763. ;
  21764. _proto.src_ = function src_(source) {
  21765. var _this12 = this;
  21766. var sourceTech = this.selectSource([source]);
  21767. if (!sourceTech) {
  21768. return true;
  21769. }
  21770. if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
  21771. this.changingSrc_ = true; // load this technology with the chosen source
  21772. this.loadTech_(sourceTech.tech, sourceTech.source);
  21773. this.tech_.ready(function () {
  21774. _this12.changingSrc_ = false;
  21775. });
  21776. return false;
  21777. } // wait until the tech is ready to set the source
  21778. // and set it synchronously if possible (#2326)
  21779. this.ready(function () {
  21780. // The setSource tech method was added with source handlers
  21781. // so older techs won't support it
  21782. // We need to check the direct prototype for the case where subclasses
  21783. // of the tech do not support source handlers
  21784. if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
  21785. this.techCall_('setSource', source);
  21786. } else {
  21787. this.techCall_('src', source.src);
  21788. }
  21789. this.changingSrc_ = false;
  21790. }, true);
  21791. return false;
  21792. }
  21793. /**
  21794. * Begin loading the src data.
  21795. */
  21796. ;
  21797. _proto.load = function load() {
  21798. this.techCall_('load');
  21799. }
  21800. /**
  21801. * Reset the player. Loads the first tech in the techOrder,
  21802. * removes all the text tracks in the existing `tech`,
  21803. * and calls `reset` on the `tech`.
  21804. */
  21805. ;
  21806. _proto.reset = function reset() {
  21807. var _this13 = this;
  21808. var PromiseClass = this.options_.Promise || window$1.Promise;
  21809. if (this.paused() || !PromiseClass) {
  21810. this.doReset_();
  21811. } else {
  21812. var playPromise = this.play();
  21813. silencePromise(playPromise.then(function () {
  21814. return _this13.doReset_();
  21815. }));
  21816. }
  21817. };
  21818. _proto.doReset_ = function doReset_() {
  21819. if (this.tech_) {
  21820. this.tech_.clearTracks('text');
  21821. }
  21822. this.resetCache_();
  21823. this.poster('');
  21824. this.loadTech_(this.options_.techOrder[0], null);
  21825. this.techCall_('reset');
  21826. this.resetControlBarUI_();
  21827. if (isEvented(this)) {
  21828. this.trigger('playerreset');
  21829. }
  21830. }
  21831. /**
  21832. * Reset Control Bar's UI by calling sub-methods that reset
  21833. * all of Control Bar's components
  21834. */
  21835. ;
  21836. _proto.resetControlBarUI_ = function resetControlBarUI_() {
  21837. this.resetProgressBar_();
  21838. this.resetPlaybackRate_();
  21839. this.resetVolumeBar_();
  21840. }
  21841. /**
  21842. * Reset tech's progress so progress bar is reset in the UI
  21843. */
  21844. ;
  21845. _proto.resetProgressBar_ = function resetProgressBar_() {
  21846. this.currentTime(0);
  21847. var _this$controlBar = this.controlBar,
  21848. durationDisplay = _this$controlBar.durationDisplay,
  21849. remainingTimeDisplay = _this$controlBar.remainingTimeDisplay;
  21850. if (durationDisplay) {
  21851. durationDisplay.updateContent();
  21852. }
  21853. if (remainingTimeDisplay) {
  21854. remainingTimeDisplay.updateContent();
  21855. }
  21856. }
  21857. /**
  21858. * Reset Playback ratio
  21859. */
  21860. ;
  21861. _proto.resetPlaybackRate_ = function resetPlaybackRate_() {
  21862. this.playbackRate(this.defaultPlaybackRate());
  21863. this.handleTechRateChange_();
  21864. }
  21865. /**
  21866. * Reset Volume bar
  21867. */
  21868. ;
  21869. _proto.resetVolumeBar_ = function resetVolumeBar_() {
  21870. this.volume(1.0);
  21871. this.trigger('volumechange');
  21872. }
  21873. /**
  21874. * Returns all of the current source objects.
  21875. *
  21876. * @return {Tech~SourceObject[]}
  21877. * The current source objects
  21878. */
  21879. ;
  21880. _proto.currentSources = function currentSources() {
  21881. var source = this.currentSource();
  21882. var sources = []; // assume `{}` or `{ src }`
  21883. if (Object.keys(source).length !== 0) {
  21884. sources.push(source);
  21885. }
  21886. return this.cache_.sources || sources;
  21887. }
  21888. /**
  21889. * Returns the current source object.
  21890. *
  21891. * @return {Tech~SourceObject}
  21892. * The current source object
  21893. */
  21894. ;
  21895. _proto.currentSource = function currentSource() {
  21896. return this.cache_.source || {};
  21897. }
  21898. /**
  21899. * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
  21900. * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
  21901. *
  21902. * @return {string}
  21903. * The current source
  21904. */
  21905. ;
  21906. _proto.currentSrc = function currentSrc() {
  21907. return this.currentSource() && this.currentSource().src || '';
  21908. }
  21909. /**
  21910. * Get the current source type e.g. video/mp4
  21911. * This can allow you rebuild the current source object so that you could load the same
  21912. * source and tech later
  21913. *
  21914. * @return {string}
  21915. * The source MIME type
  21916. */
  21917. ;
  21918. _proto.currentType = function currentType() {
  21919. return this.currentSource() && this.currentSource().type || '';
  21920. }
  21921. /**
  21922. * Get or set the preload attribute
  21923. *
  21924. * @param {boolean} [value]
  21925. * - true means that we should preload
  21926. * - false means that we should not preload
  21927. *
  21928. * @return {string}
  21929. * The preload attribute value when getting
  21930. */
  21931. ;
  21932. _proto.preload = function preload(value) {
  21933. if (value !== undefined) {
  21934. this.techCall_('setPreload', value);
  21935. this.options_.preload = value;
  21936. return;
  21937. }
  21938. return this.techGet_('preload');
  21939. }
  21940. /**
  21941. * Get or set the autoplay option. When this is a boolean it will
  21942. * modify the attribute on the tech. When this is a string the attribute on
  21943. * the tech will be removed and `Player` will handle autoplay on loadstarts.
  21944. *
  21945. * @param {boolean|string} [value]
  21946. * - true: autoplay using the browser behavior
  21947. * - false: do not autoplay
  21948. * - 'play': call play() on every loadstart
  21949. * - 'muted': call muted() then play() on every loadstart
  21950. * - 'any': call play() on every loadstart. if that fails call muted() then play().
  21951. * - *: values other than those listed here will be set `autoplay` to true
  21952. *
  21953. * @return {boolean|string}
  21954. * The current value of autoplay when getting
  21955. */
  21956. ;
  21957. _proto.autoplay = function autoplay(value) {
  21958. // getter usage
  21959. if (value === undefined) {
  21960. return this.options_.autoplay || false;
  21961. }
  21962. var techAutoplay; // if the value is a valid string set it to that
  21963. if (typeof value === 'string' && /(any|play|muted)/.test(value)) {
  21964. this.options_.autoplay = value;
  21965. this.manualAutoplay_(value);
  21966. techAutoplay = false; // any falsy value sets autoplay to false in the browser,
  21967. // lets do the same
  21968. } else if (!value) {
  21969. this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true
  21970. } else {
  21971. this.options_.autoplay = true;
  21972. }
  21973. techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up
  21974. // a setAutoplay call on tech ready. We do this because the
  21975. // autoplay option will be passed in the constructor and we
  21976. // do not need to set it twice
  21977. if (this.tech_) {
  21978. this.techCall_('setAutoplay', techAutoplay);
  21979. }
  21980. }
  21981. /**
  21982. * Set or unset the playsinline attribute.
  21983. * Playsinline tells the browser that non-fullscreen playback is preferred.
  21984. *
  21985. * @param {boolean} [value]
  21986. * - true means that we should try to play inline by default
  21987. * - false means that we should use the browser's default playback mode,
  21988. * which in most cases is inline. iOS Safari is a notable exception
  21989. * and plays fullscreen by default.
  21990. *
  21991. * @return {string|Player}
  21992. * - the current value of playsinline
  21993. * - the player when setting
  21994. *
  21995. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  21996. */
  21997. ;
  21998. _proto.playsinline = function playsinline(value) {
  21999. if (value !== undefined) {
  22000. this.techCall_('setPlaysinline', value);
  22001. this.options_.playsinline = value;
  22002. return this;
  22003. }
  22004. return this.techGet_('playsinline');
  22005. }
  22006. /**
  22007. * Get or set the loop attribute on the video element.
  22008. *
  22009. * @param {boolean} [value]
  22010. * - true means that we should loop the video
  22011. * - false means that we should not loop the video
  22012. *
  22013. * @return {boolean}
  22014. * The current value of loop when getting
  22015. */
  22016. ;
  22017. _proto.loop = function loop(value) {
  22018. if (value !== undefined) {
  22019. this.techCall_('setLoop', value);
  22020. this.options_.loop = value;
  22021. return;
  22022. }
  22023. return this.techGet_('loop');
  22024. }
  22025. /**
  22026. * Get or set the poster image source url
  22027. *
  22028. * @fires Player#posterchange
  22029. *
  22030. * @param {string} [src]
  22031. * Poster image source URL
  22032. *
  22033. * @return {string}
  22034. * The current value of poster when getting
  22035. */
  22036. ;
  22037. _proto.poster = function poster(src) {
  22038. if (src === undefined) {
  22039. return this.poster_;
  22040. } // The correct way to remove a poster is to set as an empty string
  22041. // other falsey values will throw errors
  22042. if (!src) {
  22043. src = '';
  22044. }
  22045. if (src === this.poster_) {
  22046. return;
  22047. } // update the internal poster variable
  22048. this.poster_ = src; // update the tech's poster
  22049. this.techCall_('setPoster', src);
  22050. this.isPosterFromTech_ = false; // alert components that the poster has been set
  22051. /**
  22052. * This event fires when the poster image is changed on the player.
  22053. *
  22054. * @event Player#posterchange
  22055. * @type {EventTarget~Event}
  22056. */
  22057. this.trigger('posterchange');
  22058. }
  22059. /**
  22060. * Some techs (e.g. YouTube) can provide a poster source in an
  22061. * asynchronous way. We want the poster component to use this
  22062. * poster source so that it covers up the tech's controls.
  22063. * (YouTube's play button). However we only want to use this
  22064. * source if the player user hasn't set a poster through
  22065. * the normal APIs.
  22066. *
  22067. * @fires Player#posterchange
  22068. * @listens Tech#posterchange
  22069. * @private
  22070. */
  22071. ;
  22072. _proto.handleTechPosterChange_ = function handleTechPosterChange_() {
  22073. if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
  22074. var newPoster = this.tech_.poster() || '';
  22075. if (newPoster !== this.poster_) {
  22076. this.poster_ = newPoster;
  22077. this.isPosterFromTech_ = true; // Let components know the poster has changed
  22078. this.trigger('posterchange');
  22079. }
  22080. }
  22081. }
  22082. /**
  22083. * Get or set whether or not the controls are showing.
  22084. *
  22085. * @fires Player#controlsenabled
  22086. *
  22087. * @param {boolean} [bool]
  22088. * - true to turn controls on
  22089. * - false to turn controls off
  22090. *
  22091. * @return {boolean}
  22092. * The current value of controls when getting
  22093. */
  22094. ;
  22095. _proto.controls = function controls(bool) {
  22096. if (bool === undefined) {
  22097. return !!this.controls_;
  22098. }
  22099. bool = !!bool; // Don't trigger a change event unless it actually changed
  22100. if (this.controls_ === bool) {
  22101. return;
  22102. }
  22103. this.controls_ = bool;
  22104. if (this.usingNativeControls()) {
  22105. this.techCall_('setControls', bool);
  22106. }
  22107. if (this.controls_) {
  22108. this.removeClass('vjs-controls-disabled');
  22109. this.addClass('vjs-controls-enabled');
  22110. /**
  22111. * @event Player#controlsenabled
  22112. * @type {EventTarget~Event}
  22113. */
  22114. this.trigger('controlsenabled');
  22115. if (!this.usingNativeControls()) {
  22116. this.addTechControlsListeners_();
  22117. }
  22118. } else {
  22119. this.removeClass('vjs-controls-enabled');
  22120. this.addClass('vjs-controls-disabled');
  22121. /**
  22122. * @event Player#controlsdisabled
  22123. * @type {EventTarget~Event}
  22124. */
  22125. this.trigger('controlsdisabled');
  22126. if (!this.usingNativeControls()) {
  22127. this.removeTechControlsListeners_();
  22128. }
  22129. }
  22130. }
  22131. /**
  22132. * Toggle native controls on/off. Native controls are the controls built into
  22133. * devices (e.g. default iPhone controls), Flash, or other techs
  22134. * (e.g. Vimeo Controls)
  22135. * **This should only be set by the current tech, because only the tech knows
  22136. * if it can support native controls**
  22137. *
  22138. * @fires Player#usingnativecontrols
  22139. * @fires Player#usingcustomcontrols
  22140. *
  22141. * @param {boolean} [bool]
  22142. * - true to turn native controls on
  22143. * - false to turn native controls off
  22144. *
  22145. * @return {boolean}
  22146. * The current value of native controls when getting
  22147. */
  22148. ;
  22149. _proto.usingNativeControls = function usingNativeControls(bool) {
  22150. if (bool === undefined) {
  22151. return !!this.usingNativeControls_;
  22152. }
  22153. bool = !!bool; // Don't trigger a change event unless it actually changed
  22154. if (this.usingNativeControls_ === bool) {
  22155. return;
  22156. }
  22157. this.usingNativeControls_ = bool;
  22158. if (this.usingNativeControls_) {
  22159. this.addClass('vjs-using-native-controls');
  22160. /**
  22161. * player is using the native device controls
  22162. *
  22163. * @event Player#usingnativecontrols
  22164. * @type {EventTarget~Event}
  22165. */
  22166. this.trigger('usingnativecontrols');
  22167. } else {
  22168. this.removeClass('vjs-using-native-controls');
  22169. /**
  22170. * player is using the custom HTML controls
  22171. *
  22172. * @event Player#usingcustomcontrols
  22173. * @type {EventTarget~Event}
  22174. */
  22175. this.trigger('usingcustomcontrols');
  22176. }
  22177. }
  22178. /**
  22179. * Set or get the current MediaError
  22180. *
  22181. * @fires Player#error
  22182. *
  22183. * @param {MediaError|string|number} [err]
  22184. * A MediaError or a string/number to be turned
  22185. * into a MediaError
  22186. *
  22187. * @return {MediaError|null}
  22188. * The current MediaError when getting (or null)
  22189. */
  22190. ;
  22191. _proto.error = function error(err) {
  22192. if (err === undefined) {
  22193. return this.error_ || null;
  22194. } // restoring to default
  22195. if (err === null) {
  22196. this.error_ = err;
  22197. this.removeClass('vjs-error');
  22198. if (this.errorDisplay) {
  22199. this.errorDisplay.close();
  22200. }
  22201. return;
  22202. }
  22203. this.error_ = new MediaError(err); // add the vjs-error classname to the player
  22204. this.addClass('vjs-error'); // log the name of the error type and any message
  22205. // IE11 logs "[object object]" and required you to expand message to see error object
  22206. log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_);
  22207. /**
  22208. * @event Player#error
  22209. * @type {EventTarget~Event}
  22210. */
  22211. this.trigger('error');
  22212. return;
  22213. }
  22214. /**
  22215. * Report user activity
  22216. *
  22217. * @param {Object} event
  22218. * Event object
  22219. */
  22220. ;
  22221. _proto.reportUserActivity = function reportUserActivity(event) {
  22222. this.userActivity_ = true;
  22223. }
  22224. /**
  22225. * Get/set if user is active
  22226. *
  22227. * @fires Player#useractive
  22228. * @fires Player#userinactive
  22229. *
  22230. * @param {boolean} [bool]
  22231. * - true if the user is active
  22232. * - false if the user is inactive
  22233. *
  22234. * @return {boolean}
  22235. * The current value of userActive when getting
  22236. */
  22237. ;
  22238. _proto.userActive = function userActive(bool) {
  22239. if (bool === undefined) {
  22240. return this.userActive_;
  22241. }
  22242. bool = !!bool;
  22243. if (bool === this.userActive_) {
  22244. return;
  22245. }
  22246. this.userActive_ = bool;
  22247. if (this.userActive_) {
  22248. this.userActivity_ = true;
  22249. this.removeClass('vjs-user-inactive');
  22250. this.addClass('vjs-user-active');
  22251. /**
  22252. * @event Player#useractive
  22253. * @type {EventTarget~Event}
  22254. */
  22255. this.trigger('useractive');
  22256. return;
  22257. } // Chrome/Safari/IE have bugs where when you change the cursor it can
  22258. // trigger a mousemove event. This causes an issue when you're hiding
  22259. // the cursor when the user is inactive, and a mousemove signals user
  22260. // activity. Making it impossible to go into inactive mode. Specifically
  22261. // this happens in fullscreen when we really need to hide the cursor.
  22262. //
  22263. // When this gets resolved in ALL browsers it can be removed
  22264. // https://code.google.com/p/chromium/issues/detail?id=103041
  22265. if (this.tech_) {
  22266. this.tech_.one('mousemove', function (e) {
  22267. e.stopPropagation();
  22268. e.preventDefault();
  22269. });
  22270. }
  22271. this.userActivity_ = false;
  22272. this.removeClass('vjs-user-active');
  22273. this.addClass('vjs-user-inactive');
  22274. /**
  22275. * @event Player#userinactive
  22276. * @type {EventTarget~Event}
  22277. */
  22278. this.trigger('userinactive');
  22279. }
  22280. /**
  22281. * Listen for user activity based on timeout value
  22282. *
  22283. * @private
  22284. */
  22285. ;
  22286. _proto.listenForUserActivity_ = function listenForUserActivity_() {
  22287. var mouseInProgress;
  22288. var lastMoveX;
  22289. var lastMoveY;
  22290. var handleActivity = bind(this, this.reportUserActivity);
  22291. var handleMouseMove = function handleMouseMove(e) {
  22292. // #1068 - Prevent mousemove spamming
  22293. // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
  22294. if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
  22295. lastMoveX = e.screenX;
  22296. lastMoveY = e.screenY;
  22297. handleActivity();
  22298. }
  22299. };
  22300. var handleMouseDown = function handleMouseDown() {
  22301. handleActivity(); // For as long as the they are touching the device or have their mouse down,
  22302. // we consider them active even if they're not moving their finger or mouse.
  22303. // So we want to continue to update that they are active
  22304. this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time
  22305. // as the activityCheck interval (250) should ensure we never miss the
  22306. // next activityCheck
  22307. mouseInProgress = this.setInterval(handleActivity, 250);
  22308. };
  22309. var handleMouseUp = function handleMouseUp(event) {
  22310. handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down
  22311. this.clearInterval(mouseInProgress);
  22312. }; // Any mouse movement will be considered user activity
  22313. this.on('mousedown', handleMouseDown);
  22314. this.on('mousemove', handleMouseMove);
  22315. this.on('mouseup', handleMouseUp);
  22316. var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
  22317. // controlBar would no longer be hidden by default timeout.
  22318. if (controlBar && !IS_IOS && !IS_ANDROID) {
  22319. controlBar.on('mouseenter', function (event) {
  22320. this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
  22321. this.player().options_.inactivityTimeout = 0;
  22322. });
  22323. controlBar.on('mouseleave', function (event) {
  22324. this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
  22325. });
  22326. } // Listen for keyboard navigation
  22327. // Shouldn't need to use inProgress interval because of key repeat
  22328. this.on('keydown', handleActivity);
  22329. this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into
  22330. // the mousemove/touchmove function itself, to prevent performance degradation.
  22331. // `this.reportUserActivity` simply sets this.userActivity_ to true, which
  22332. // then gets picked up by this loop
  22333. // http://ejohn.org/blog/learning-from-twitter/
  22334. var inactivityTimeout;
  22335. this.setInterval(function () {
  22336. // Check to see if mouse/touch activity has happened
  22337. if (!this.userActivity_) {
  22338. return;
  22339. } // Reset the activity tracker
  22340. this.userActivity_ = false; // If the user state was inactive, set the state to active
  22341. this.userActive(true); // Clear any existing inactivity timeout to start the timer over
  22342. this.clearTimeout(inactivityTimeout);
  22343. var timeout = this.options_.inactivityTimeout;
  22344. if (timeout <= 0) {
  22345. return;
  22346. } // In <timeout> milliseconds, if no more activity has occurred the
  22347. // user will be considered inactive
  22348. inactivityTimeout = this.setTimeout(function () {
  22349. // Protect against the case where the inactivityTimeout can trigger just
  22350. // before the next user activity is picked up by the activity check loop
  22351. // causing a flicker
  22352. if (!this.userActivity_) {
  22353. this.userActive(false);
  22354. }
  22355. }, timeout);
  22356. }, 250);
  22357. }
  22358. /**
  22359. * Gets or sets the current playback rate. A playback rate of
  22360. * 1.0 represents normal speed and 0.5 would indicate half-speed
  22361. * playback, for instance.
  22362. *
  22363. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
  22364. *
  22365. * @param {number} [rate]
  22366. * New playback rate to set.
  22367. *
  22368. * @return {number}
  22369. * The current playback rate when getting or 1.0
  22370. */
  22371. ;
  22372. _proto.playbackRate = function playbackRate(rate) {
  22373. if (rate !== undefined) {
  22374. // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
  22375. // that is registered above
  22376. this.techCall_('setPlaybackRate', rate);
  22377. return;
  22378. }
  22379. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  22380. return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
  22381. }
  22382. return 1.0;
  22383. }
  22384. /**
  22385. * Gets or sets the current default playback rate. A default playback rate of
  22386. * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
  22387. * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
  22388. * not the current playbackRate.
  22389. *
  22390. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
  22391. *
  22392. * @param {number} [rate]
  22393. * New default playback rate to set.
  22394. *
  22395. * @return {number|Player}
  22396. * - The default playback rate when getting or 1.0
  22397. * - the player when setting
  22398. */
  22399. ;
  22400. _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) {
  22401. if (rate !== undefined) {
  22402. return this.techCall_('setDefaultPlaybackRate', rate);
  22403. }
  22404. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  22405. return this.techGet_('defaultPlaybackRate');
  22406. }
  22407. return 1.0;
  22408. }
  22409. /**
  22410. * Gets or sets the audio flag
  22411. *
  22412. * @param {boolean} bool
  22413. * - true signals that this is an audio player
  22414. * - false signals that this is not an audio player
  22415. *
  22416. * @return {boolean}
  22417. * The current value of isAudio when getting
  22418. */
  22419. ;
  22420. _proto.isAudio = function isAudio(bool) {
  22421. if (bool !== undefined) {
  22422. this.isAudio_ = !!bool;
  22423. return;
  22424. }
  22425. return !!this.isAudio_;
  22426. }
  22427. /**
  22428. * A helper method for adding a {@link TextTrack} to our
  22429. * {@link TextTrackList}.
  22430. *
  22431. * In addition to the W3C settings we allow adding additional info through options.
  22432. *
  22433. * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
  22434. *
  22435. * @param {string} [kind]
  22436. * the kind of TextTrack you are adding
  22437. *
  22438. * @param {string} [label]
  22439. * the label to give the TextTrack label
  22440. *
  22441. * @param {string} [language]
  22442. * the language to set on the TextTrack
  22443. *
  22444. * @return {TextTrack|undefined}
  22445. * the TextTrack that was added or undefined
  22446. * if there is no tech
  22447. */
  22448. ;
  22449. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  22450. if (this.tech_) {
  22451. return this.tech_.addTextTrack(kind, label, language);
  22452. }
  22453. }
  22454. /**
  22455. * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will
  22456. * automatically removed from the video element whenever the source changes, unless
  22457. * manualCleanup is set to false.
  22458. *
  22459. * @param {Object} options
  22460. * Options to pass to {@link HTMLTrackElement} during creation. See
  22461. * {@link HTMLTrackElement} for object properties that you should use.
  22462. *
  22463. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  22464. *
  22465. * @return {HtmlTrackElement}
  22466. * the HTMLTrackElement that was created and added
  22467. * to the HtmlTrackElementList and the remote
  22468. * TextTrackList
  22469. *
  22470. * @deprecated The default value of the "manualCleanup" parameter will default
  22471. * to "false" in upcoming versions of Video.js
  22472. */
  22473. ;
  22474. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  22475. if (this.tech_) {
  22476. return this.tech_.addRemoteTextTrack(options, manualCleanup);
  22477. }
  22478. }
  22479. /**
  22480. * Remove a remote {@link TextTrack} from the respective
  22481. * {@link TextTrackList} and {@link HtmlTrackElementList}.
  22482. *
  22483. * @param {Object} track
  22484. * Remote {@link TextTrack} to remove
  22485. *
  22486. * @return {undefined}
  22487. * does not return anything
  22488. */
  22489. ;
  22490. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) {
  22491. if (obj === void 0) {
  22492. obj = {};
  22493. }
  22494. var _obj = obj,
  22495. track = _obj.track;
  22496. if (!track) {
  22497. track = obj;
  22498. } // destructure the input into an object with a track argument, defaulting to arguments[0]
  22499. // default the whole argument to an empty object if nothing was passed in
  22500. if (this.tech_) {
  22501. return this.tech_.removeRemoteTextTrack(track);
  22502. }
  22503. }
  22504. /**
  22505. * Gets available media playback quality metrics as specified by the W3C's Media
  22506. * Playback Quality API.
  22507. *
  22508. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  22509. *
  22510. * @return {Object|undefined}
  22511. * An object with supported media playback quality metrics or undefined if there
  22512. * is no tech or the tech does not support it.
  22513. */
  22514. ;
  22515. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  22516. return this.techGet_('getVideoPlaybackQuality');
  22517. }
  22518. /**
  22519. * Get video width
  22520. *
  22521. * @return {number}
  22522. * current video width
  22523. */
  22524. ;
  22525. _proto.videoWidth = function videoWidth() {
  22526. return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
  22527. }
  22528. /**
  22529. * Get video height
  22530. *
  22531. * @return {number}
  22532. * current video height
  22533. */
  22534. ;
  22535. _proto.videoHeight = function videoHeight() {
  22536. return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
  22537. }
  22538. /**
  22539. * The player's language code
  22540. * NOTE: The language should be set in the player options if you want the
  22541. * the controls to be built with a specific language. Changing the language
  22542. * later will not update controls text.
  22543. *
  22544. * @param {string} [code]
  22545. * the language code to set the player to
  22546. *
  22547. * @return {string}
  22548. * The current language code when getting
  22549. */
  22550. ;
  22551. _proto.language = function language(code) {
  22552. if (code === undefined) {
  22553. return this.language_;
  22554. }
  22555. this.language_ = String(code).toLowerCase();
  22556. }
  22557. /**
  22558. * Get the player's language dictionary
  22559. * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
  22560. * Languages specified directly in the player options have precedence
  22561. *
  22562. * @return {Array}
  22563. * An array of of supported languages
  22564. */
  22565. ;
  22566. _proto.languages = function languages() {
  22567. return mergeOptions(Player.prototype.options_.languages, this.languages_);
  22568. }
  22569. /**
  22570. * returns a JavaScript object reperesenting the current track
  22571. * information. **DOES not return it as JSON**
  22572. *
  22573. * @return {Object}
  22574. * Object representing the current of track info
  22575. */
  22576. ;
  22577. _proto.toJSON = function toJSON() {
  22578. var options = mergeOptions(this.options_);
  22579. var tracks = options.tracks;
  22580. options.tracks = [];
  22581. for (var i = 0; i < tracks.length; i++) {
  22582. var track = tracks[i]; // deep merge tracks and null out player so no circular references
  22583. track = mergeOptions(track);
  22584. track.player = undefined;
  22585. options.tracks[i] = track;
  22586. }
  22587. return options;
  22588. }
  22589. /**
  22590. * Creates a simple modal dialog (an instance of the {@link ModalDialog}
  22591. * component) that immediately overlays the player with arbitrary
  22592. * content and removes itself when closed.
  22593. *
  22594. * @param {string|Function|Element|Array|null} content
  22595. * Same as {@link ModalDialog#content}'s param of the same name.
  22596. * The most straight-forward usage is to provide a string or DOM
  22597. * element.
  22598. *
  22599. * @param {Object} [options]
  22600. * Extra options which will be passed on to the {@link ModalDialog}.
  22601. *
  22602. * @return {ModalDialog}
  22603. * the {@link ModalDialog} that was created
  22604. */
  22605. ;
  22606. _proto.createModal = function createModal(content, options) {
  22607. var _this14 = this;
  22608. options = options || {};
  22609. options.content = content || '';
  22610. var modal = new ModalDialog(this, options);
  22611. this.addChild(modal);
  22612. modal.on('dispose', function () {
  22613. _this14.removeChild(modal);
  22614. });
  22615. modal.open();
  22616. return modal;
  22617. }
  22618. /**
  22619. * Change breakpoint classes when the player resizes.
  22620. *
  22621. * @private
  22622. */
  22623. ;
  22624. _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() {
  22625. if (!this.responsive()) {
  22626. return;
  22627. }
  22628. var currentBreakpoint = this.currentBreakpoint();
  22629. var currentWidth = this.currentWidth();
  22630. for (var i = 0; i < BREAKPOINT_ORDER.length; i++) {
  22631. var candidateBreakpoint = BREAKPOINT_ORDER[i];
  22632. var maxWidth = this.breakpoints_[candidateBreakpoint];
  22633. if (currentWidth <= maxWidth) {
  22634. // The current breakpoint did not change, nothing to do.
  22635. if (currentBreakpoint === candidateBreakpoint) {
  22636. return;
  22637. } // Only remove a class if there is a current breakpoint.
  22638. if (currentBreakpoint) {
  22639. this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
  22640. }
  22641. this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
  22642. this.breakpoint_ = candidateBreakpoint;
  22643. break;
  22644. }
  22645. }
  22646. }
  22647. /**
  22648. * Removes the current breakpoint.
  22649. *
  22650. * @private
  22651. */
  22652. ;
  22653. _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() {
  22654. var className = this.currentBreakpointClass();
  22655. this.breakpoint_ = '';
  22656. if (className) {
  22657. this.removeClass(className);
  22658. }
  22659. }
  22660. /**
  22661. * Get or set breakpoints on the player.
  22662. *
  22663. * Calling this method with an object or `true` will remove any previous
  22664. * custom breakpoints and start from the defaults again.
  22665. *
  22666. * @param {Object|boolean} [breakpoints]
  22667. * If an object is given, it can be used to provide custom
  22668. * breakpoints. If `true` is given, will set default breakpoints.
  22669. * If this argument is not given, will simply return the current
  22670. * breakpoints.
  22671. *
  22672. * @param {number} [breakpoints.tiny]
  22673. * The maximum width for the "vjs-layout-tiny" class.
  22674. *
  22675. * @param {number} [breakpoints.xsmall]
  22676. * The maximum width for the "vjs-layout-x-small" class.
  22677. *
  22678. * @param {number} [breakpoints.small]
  22679. * The maximum width for the "vjs-layout-small" class.
  22680. *
  22681. * @param {number} [breakpoints.medium]
  22682. * The maximum width for the "vjs-layout-medium" class.
  22683. *
  22684. * @param {number} [breakpoints.large]
  22685. * The maximum width for the "vjs-layout-large" class.
  22686. *
  22687. * @param {number} [breakpoints.xlarge]
  22688. * The maximum width for the "vjs-layout-x-large" class.
  22689. *
  22690. * @param {number} [breakpoints.huge]
  22691. * The maximum width for the "vjs-layout-huge" class.
  22692. *
  22693. * @return {Object}
  22694. * An object mapping breakpoint names to maximum width values.
  22695. */
  22696. ;
  22697. _proto.breakpoints = function breakpoints(_breakpoints) {
  22698. // Used as a getter.
  22699. if (_breakpoints === undefined) {
  22700. return assign(this.breakpoints_);
  22701. }
  22702. this.breakpoint_ = '';
  22703. this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently
  22704. // selected breakpoint.
  22705. this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning.
  22706. return assign(this.breakpoints_);
  22707. }
  22708. /**
  22709. * Get or set a flag indicating whether or not this player should adjust
  22710. * its UI based on its dimensions.
  22711. *
  22712. * @param {boolean} value
  22713. * Should be `true` if the player should adjust its UI based on its
  22714. * dimensions; otherwise, should be `false`.
  22715. *
  22716. * @return {boolean}
  22717. * Will be `true` if this player should adjust its UI based on its
  22718. * dimensions; otherwise, will be `false`.
  22719. */
  22720. ;
  22721. _proto.responsive = function responsive(value) {
  22722. // Used as a getter.
  22723. if (value === undefined) {
  22724. return this.responsive_;
  22725. }
  22726. value = Boolean(value);
  22727. var current = this.responsive_; // Nothing changed.
  22728. if (value === current) {
  22729. return;
  22730. } // The value actually changed, set it.
  22731. this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the
  22732. // player is now responsive.
  22733. if (value) {
  22734. this.on('playerresize', this.updateCurrentBreakpoint_);
  22735. this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive.
  22736. } else {
  22737. this.off('playerresize', this.updateCurrentBreakpoint_);
  22738. this.removeCurrentBreakpoint_();
  22739. }
  22740. return value;
  22741. }
  22742. /**
  22743. * Get current breakpoint name, if any.
  22744. *
  22745. * @return {string}
  22746. * If there is currently a breakpoint set, returns a the key from the
  22747. * breakpoints object matching it. Otherwise, returns an empty string.
  22748. */
  22749. ;
  22750. _proto.currentBreakpoint = function currentBreakpoint() {
  22751. return this.breakpoint_;
  22752. }
  22753. /**
  22754. * Get the current breakpoint class name.
  22755. *
  22756. * @return {string}
  22757. * The matching class name (e.g. `"vjs-layout-tiny"` or
  22758. * `"vjs-layout-large"`) for the current breakpoint. Empty string if
  22759. * there is no current breakpoint.
  22760. */
  22761. ;
  22762. _proto.currentBreakpointClass = function currentBreakpointClass() {
  22763. return BREAKPOINT_CLASSES[this.breakpoint_] || '';
  22764. }
  22765. /**
  22766. * An object that describes a single piece of media.
  22767. *
  22768. * Properties that are not part of this type description will be retained; so,
  22769. * this can be viewed as a generic metadata storage mechanism as well.
  22770. *
  22771. * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
  22772. * @typedef {Object} Player~MediaObject
  22773. *
  22774. * @property {string} [album]
  22775. * Unused, except if this object is passed to the `MediaSession`
  22776. * API.
  22777. *
  22778. * @property {string} [artist]
  22779. * Unused, except if this object is passed to the `MediaSession`
  22780. * API.
  22781. *
  22782. * @property {Object[]} [artwork]
  22783. * Unused, except if this object is passed to the `MediaSession`
  22784. * API. If not specified, will be populated via the `poster`, if
  22785. * available.
  22786. *
  22787. * @property {string} [poster]
  22788. * URL to an image that will display before playback.
  22789. *
  22790. * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
  22791. * A single source object, an array of source objects, or a string
  22792. * referencing a URL to a media source. It is _highly recommended_
  22793. * that an object or array of objects is used here, so that source
  22794. * selection algorithms can take the `type` into account.
  22795. *
  22796. * @property {string} [title]
  22797. * Unused, except if this object is passed to the `MediaSession`
  22798. * API.
  22799. *
  22800. * @property {Object[]} [textTracks]
  22801. * An array of objects to be used to create text tracks, following
  22802. * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
  22803. * For ease of removal, these will be created as "remote" text
  22804. * tracks and set to automatically clean up on source changes.
  22805. *
  22806. * These objects may have properties like `src`, `kind`, `label`,
  22807. * and `language`, see {@link Tech#createRemoteTextTrack}.
  22808. */
  22809. /**
  22810. * Populate the player using a {@link Player~MediaObject|MediaObject}.
  22811. *
  22812. * @param {Player~MediaObject} media
  22813. * A media object.
  22814. *
  22815. * @param {Function} ready
  22816. * A callback to be called when the player is ready.
  22817. */
  22818. ;
  22819. _proto.loadMedia = function loadMedia(media, ready) {
  22820. var _this15 = this;
  22821. if (!media || typeof media !== 'object') {
  22822. return;
  22823. }
  22824. this.reset(); // Clone the media object so it cannot be mutated from outside.
  22825. this.cache_.media = mergeOptions(media);
  22826. var _this$cache_$media = this.cache_.media,
  22827. artwork = _this$cache_$media.artwork,
  22828. poster = _this$cache_$media.poster,
  22829. src = _this$cache_$media.src,
  22830. textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`.
  22831. if (!artwork && poster) {
  22832. this.cache_.media.artwork = [{
  22833. src: poster,
  22834. type: getMimetype(poster)
  22835. }];
  22836. }
  22837. if (src) {
  22838. this.src(src);
  22839. }
  22840. if (poster) {
  22841. this.poster(poster);
  22842. }
  22843. if (Array.isArray(textTracks)) {
  22844. textTracks.forEach(function (tt) {
  22845. return _this15.addRemoteTextTrack(tt, false);
  22846. });
  22847. }
  22848. this.ready(ready);
  22849. }
  22850. /**
  22851. * Get a clone of the current {@link Player~MediaObject} for this player.
  22852. *
  22853. * If the `loadMedia` method has not been used, will attempt to return a
  22854. * {@link Player~MediaObject} based on the current state of the player.
  22855. *
  22856. * @return {Player~MediaObject}
  22857. */
  22858. ;
  22859. _proto.getMedia = function getMedia() {
  22860. if (!this.cache_.media) {
  22861. var poster = this.poster();
  22862. var src = this.currentSources();
  22863. var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) {
  22864. return {
  22865. kind: tt.kind,
  22866. label: tt.label,
  22867. language: tt.language,
  22868. src: tt.src
  22869. };
  22870. });
  22871. var media = {
  22872. src: src,
  22873. textTracks: textTracks
  22874. };
  22875. if (poster) {
  22876. media.poster = poster;
  22877. media.artwork = [{
  22878. src: media.poster,
  22879. type: getMimetype(media.poster)
  22880. }];
  22881. }
  22882. return media;
  22883. }
  22884. return mergeOptions(this.cache_.media);
  22885. }
  22886. /**
  22887. * Gets tag settings
  22888. *
  22889. * @param {Element} tag
  22890. * The player tag
  22891. *
  22892. * @return {Object}
  22893. * An object containing all of the settings
  22894. * for a player tag
  22895. */
  22896. ;
  22897. Player.getTagSettings = function getTagSettings(tag) {
  22898. var baseOptions = {
  22899. sources: [],
  22900. tracks: []
  22901. };
  22902. var tagOptions = getAttributes(tag);
  22903. var dataSetup = tagOptions['data-setup'];
  22904. if (hasClass(tag, 'vjs-fill')) {
  22905. tagOptions.fill = true;
  22906. }
  22907. if (hasClass(tag, 'vjs-fluid')) {
  22908. tagOptions.fluid = true;
  22909. } // Check if data-setup attr exists.
  22910. if (dataSetup !== null) {
  22911. // Parse options JSON
  22912. // If empty string, make it a parsable json object.
  22913. var _safeParseTuple = tuple(dataSetup || '{}'),
  22914. err = _safeParseTuple[0],
  22915. data = _safeParseTuple[1];
  22916. if (err) {
  22917. log.error(err);
  22918. }
  22919. assign(tagOptions, data);
  22920. }
  22921. assign(baseOptions, tagOptions); // Get tag children settings
  22922. if (tag.hasChildNodes()) {
  22923. var children = tag.childNodes;
  22924. for (var i = 0, j = children.length; i < j; i++) {
  22925. var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
  22926. var childName = child.nodeName.toLowerCase();
  22927. if (childName === 'source') {
  22928. baseOptions.sources.push(getAttributes(child));
  22929. } else if (childName === 'track') {
  22930. baseOptions.tracks.push(getAttributes(child));
  22931. }
  22932. }
  22933. }
  22934. return baseOptions;
  22935. }
  22936. /**
  22937. * Determine whether or not flexbox is supported
  22938. *
  22939. * @return {boolean}
  22940. * - true if flexbox is supported
  22941. * - false if flexbox is not supported
  22942. */
  22943. ;
  22944. _proto.flexNotSupported_ = function flexNotSupported_() {
  22945. var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
  22946. // common flex features that we can rely on when checking for flex support.
  22947. 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
  22948. 'msFlexOrder' in elem.style);
  22949. };
  22950. return Player;
  22951. }(Component);
  22952. /**
  22953. * Get the {@link VideoTrackList}
  22954. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
  22955. *
  22956. * @return {VideoTrackList}
  22957. * the current video track list
  22958. *
  22959. * @method Player.prototype.videoTracks
  22960. */
  22961. /**
  22962. * Get the {@link AudioTrackList}
  22963. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
  22964. *
  22965. * @return {AudioTrackList}
  22966. * the current audio track list
  22967. *
  22968. * @method Player.prototype.audioTracks
  22969. */
  22970. /**
  22971. * Get the {@link TextTrackList}
  22972. *
  22973. * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
  22974. *
  22975. * @return {TextTrackList}
  22976. * the current text track list
  22977. *
  22978. * @method Player.prototype.textTracks
  22979. */
  22980. /**
  22981. * Get the remote {@link TextTrackList}
  22982. *
  22983. * @return {TextTrackList}
  22984. * The current remote text track list
  22985. *
  22986. * @method Player.prototype.remoteTextTracks
  22987. */
  22988. /**
  22989. * Get the remote {@link HtmlTrackElementList} tracks.
  22990. *
  22991. * @return {HtmlTrackElementList}
  22992. * The current remote text track element list
  22993. *
  22994. * @method Player.prototype.remoteTextTrackEls
  22995. */
  22996. ALL.names.forEach(function (name$$1) {
  22997. var props = ALL[name$$1];
  22998. Player.prototype[props.getterName] = function () {
  22999. if (this.tech_) {
  23000. return this.tech_[props.getterName]();
  23001. } // if we have not yet loadTech_, we create {video,audio,text}Tracks_
  23002. // these will be passed to the tech during loading
  23003. this[props.privateName] = this[props.privateName] || new props.ListClass();
  23004. return this[props.privateName];
  23005. };
  23006. });
  23007. /**
  23008. * Global enumeration of players.
  23009. *
  23010. * The keys are the player IDs and the values are either the {@link Player}
  23011. * instance or `null` for disposed players.
  23012. *
  23013. * @type {Object}
  23014. */
  23015. Player.players = {};
  23016. var navigator = window$1.navigator;
  23017. /*
  23018. * Player instance options, surfaced using options
  23019. * options = Player.prototype.options_
  23020. * Make changes in options, not here.
  23021. *
  23022. * @type {Object}
  23023. * @private
  23024. */
  23025. Player.prototype.options_ = {
  23026. // Default order of fallback technology
  23027. techOrder: Tech.defaultTechOrder_,
  23028. html5: {},
  23029. flash: {},
  23030. // default inactivity timeout
  23031. inactivityTimeout: 2000,
  23032. // default playback rates
  23033. playbackRates: [],
  23034. // Add playback rate selection by adding rates
  23035. // 'playbackRates': [0.5, 1, 1.5, 2],
  23036. liveui: false,
  23037. // Included control sets
  23038. children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
  23039. language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
  23040. // locales and their language translations
  23041. languages: {},
  23042. // Default message to show when a video cannot be played.
  23043. notSupportedMessage: 'No compatible source was found for this media.',
  23044. breakpoints: {},
  23045. responsive: false
  23046. };
  23047. [
  23048. /**
  23049. * Returns whether or not the player is in the "ended" state.
  23050. *
  23051. * @return {Boolean} True if the player is in the ended state, false if not.
  23052. * @method Player#ended
  23053. */
  23054. 'ended',
  23055. /**
  23056. * Returns whether or not the player is in the "seeking" state.
  23057. *
  23058. * @return {Boolean} True if the player is in the seeking state, false if not.
  23059. * @method Player#seeking
  23060. */
  23061. 'seeking',
  23062. /**
  23063. * Returns the TimeRanges of the media that are currently available
  23064. * for seeking to.
  23065. *
  23066. * @return {TimeRanges} the seekable intervals of the media timeline
  23067. * @method Player#seekable
  23068. */
  23069. 'seekable',
  23070. /**
  23071. * Returns the current state of network activity for the element, from
  23072. * the codes in the list below.
  23073. * - NETWORK_EMPTY (numeric value 0)
  23074. * The element has not yet been initialised. All attributes are in
  23075. * their initial states.
  23076. * - NETWORK_IDLE (numeric value 1)
  23077. * The element's resource selection algorithm is active and has
  23078. * selected a resource, but it is not actually using the network at
  23079. * this time.
  23080. * - NETWORK_LOADING (numeric value 2)
  23081. * The user agent is actively trying to download data.
  23082. * - NETWORK_NO_SOURCE (numeric value 3)
  23083. * The element's resource selection algorithm is active, but it has
  23084. * not yet found a resource to use.
  23085. *
  23086. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
  23087. * @return {number} the current network activity state
  23088. * @method Player#networkState
  23089. */
  23090. 'networkState',
  23091. /**
  23092. * Returns a value that expresses the current state of the element
  23093. * with respect to rendering the current playback position, from the
  23094. * codes in the list below.
  23095. * - HAVE_NOTHING (numeric value 0)
  23096. * No information regarding the media resource is available.
  23097. * - HAVE_METADATA (numeric value 1)
  23098. * Enough of the resource has been obtained that the duration of the
  23099. * resource is available.
  23100. * - HAVE_CURRENT_DATA (numeric value 2)
  23101. * Data for the immediate current playback position is available.
  23102. * - HAVE_FUTURE_DATA (numeric value 3)
  23103. * Data for the immediate current playback position is available, as
  23104. * well as enough data for the user agent to advance the current
  23105. * playback position in the direction of playback.
  23106. * - HAVE_ENOUGH_DATA (numeric value 4)
  23107. * The user agent estimates that enough data is available for
  23108. * playback to proceed uninterrupted.
  23109. *
  23110. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
  23111. * @return {number} the current playback rendering state
  23112. * @method Player#readyState
  23113. */
  23114. 'readyState'].forEach(function (fn) {
  23115. Player.prototype[fn] = function () {
  23116. return this.techGet_(fn);
  23117. };
  23118. });
  23119. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  23120. Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () {
  23121. return this.trigger(event);
  23122. };
  23123. });
  23124. /**
  23125. * Fired when the player has initial duration and dimension information
  23126. *
  23127. * @event Player#loadedmetadata
  23128. * @type {EventTarget~Event}
  23129. */
  23130. /**
  23131. * Fired when the player has downloaded data at the current playback position
  23132. *
  23133. * @event Player#loadeddata
  23134. * @type {EventTarget~Event}
  23135. */
  23136. /**
  23137. * Fired when the current playback position has changed *
  23138. * During playback this is fired every 15-250 milliseconds, depending on the
  23139. * playback technology in use.
  23140. *
  23141. * @event Player#timeupdate
  23142. * @type {EventTarget~Event}
  23143. */
  23144. /**
  23145. * Fired when the volume changes
  23146. *
  23147. * @event Player#volumechange
  23148. * @type {EventTarget~Event}
  23149. */
  23150. /**
  23151. * Reports whether or not a player has a plugin available.
  23152. *
  23153. * This does not report whether or not the plugin has ever been initialized
  23154. * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
  23155. *
  23156. * @method Player#hasPlugin
  23157. * @param {string} name
  23158. * The name of a plugin.
  23159. *
  23160. * @return {boolean}
  23161. * Whether or not this player has the requested plugin available.
  23162. */
  23163. /**
  23164. * Reports whether or not a player is using a plugin by name.
  23165. *
  23166. * For basic plugins, this only reports whether the plugin has _ever_ been
  23167. * initialized on this player.
  23168. *
  23169. * @method Player#usingPlugin
  23170. * @param {string} name
  23171. * The name of a plugin.
  23172. *
  23173. * @return {boolean}
  23174. * Whether or not this player is using the requested plugin.
  23175. */
  23176. Component.registerComponent('Player', Player);
  23177. /**
  23178. * The base plugin name.
  23179. *
  23180. * @private
  23181. * @constant
  23182. * @type {string}
  23183. */
  23184. var BASE_PLUGIN_NAME = 'plugin';
  23185. /**
  23186. * The key on which a player's active plugins cache is stored.
  23187. *
  23188. * @private
  23189. * @constant
  23190. * @type {string}
  23191. */
  23192. var PLUGIN_CACHE_KEY = 'activePlugins_';
  23193. /**
  23194. * Stores registered plugins in a private space.
  23195. *
  23196. * @private
  23197. * @type {Object}
  23198. */
  23199. var pluginStorage = {};
  23200. /**
  23201. * Reports whether or not a plugin has been registered.
  23202. *
  23203. * @private
  23204. * @param {string} name
  23205. * The name of a plugin.
  23206. *
  23207. * @return {boolean}
  23208. * Whether or not the plugin has been registered.
  23209. */
  23210. var pluginExists = function pluginExists(name) {
  23211. return pluginStorage.hasOwnProperty(name);
  23212. };
  23213. /**
  23214. * Get a single registered plugin by name.
  23215. *
  23216. * @private
  23217. * @param {string} name
  23218. * The name of a plugin.
  23219. *
  23220. * @return {Function|undefined}
  23221. * The plugin (or undefined).
  23222. */
  23223. var getPlugin = function getPlugin(name) {
  23224. return pluginExists(name) ? pluginStorage[name] : undefined;
  23225. };
  23226. /**
  23227. * Marks a plugin as "active" on a player.
  23228. *
  23229. * Also, ensures that the player has an object for tracking active plugins.
  23230. *
  23231. * @private
  23232. * @param {Player} player
  23233. * A Video.js player instance.
  23234. *
  23235. * @param {string} name
  23236. * The name of a plugin.
  23237. */
  23238. var markPluginAsActive = function markPluginAsActive(player, name) {
  23239. player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
  23240. player[PLUGIN_CACHE_KEY][name] = true;
  23241. };
  23242. /**
  23243. * Triggers a pair of plugin setup events.
  23244. *
  23245. * @private
  23246. * @param {Player} player
  23247. * A Video.js player instance.
  23248. *
  23249. * @param {Plugin~PluginEventHash} hash
  23250. * A plugin event hash.
  23251. *
  23252. * @param {boolean} [before]
  23253. * If true, prefixes the event name with "before". In other words,
  23254. * use this to trigger "beforepluginsetup" instead of "pluginsetup".
  23255. */
  23256. var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
  23257. var eventName = (before ? 'before' : '') + 'pluginsetup';
  23258. player.trigger(eventName, hash);
  23259. player.trigger(eventName + ':' + hash.name, hash);
  23260. };
  23261. /**
  23262. * Takes a basic plugin function and returns a wrapper function which marks
  23263. * on the player that the plugin has been activated.
  23264. *
  23265. * @private
  23266. * @param {string} name
  23267. * The name of the plugin.
  23268. *
  23269. * @param {Function} plugin
  23270. * The basic plugin.
  23271. *
  23272. * @return {Function}
  23273. * A wrapper function for the given plugin.
  23274. */
  23275. var createBasicPlugin = function createBasicPlugin(name, plugin) {
  23276. var basicPluginWrapper = function basicPluginWrapper() {
  23277. // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
  23278. // regardless, but we want the hash to be consistent with the hash provided
  23279. // for advanced plugins.
  23280. //
  23281. // The only potentially counter-intuitive thing here is the `instance` in
  23282. // the "pluginsetup" event is the value returned by the `plugin` function.
  23283. triggerSetupEvent(this, {
  23284. name: name,
  23285. plugin: plugin,
  23286. instance: null
  23287. }, true);
  23288. var instance = plugin.apply(this, arguments);
  23289. markPluginAsActive(this, name);
  23290. triggerSetupEvent(this, {
  23291. name: name,
  23292. plugin: plugin,
  23293. instance: instance
  23294. });
  23295. return instance;
  23296. };
  23297. Object.keys(plugin).forEach(function (prop) {
  23298. basicPluginWrapper[prop] = plugin[prop];
  23299. });
  23300. return basicPluginWrapper;
  23301. };
  23302. /**
  23303. * Takes a plugin sub-class and returns a factory function for generating
  23304. * instances of it.
  23305. *
  23306. * This factory function will replace itself with an instance of the requested
  23307. * sub-class of Plugin.
  23308. *
  23309. * @private
  23310. * @param {string} name
  23311. * The name of the plugin.
  23312. *
  23313. * @param {Plugin} PluginSubClass
  23314. * The advanced plugin.
  23315. *
  23316. * @return {Function}
  23317. */
  23318. var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
  23319. // Add a `name` property to the plugin prototype so that each plugin can
  23320. // refer to itself by name.
  23321. PluginSubClass.prototype.name = name;
  23322. return function () {
  23323. triggerSetupEvent(this, {
  23324. name: name,
  23325. plugin: PluginSubClass,
  23326. instance: null
  23327. }, true);
  23328. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  23329. args[_key] = arguments[_key];
  23330. }
  23331. var instance = _construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance.
  23332. this[name] = function () {
  23333. return instance;
  23334. };
  23335. triggerSetupEvent(this, instance.getEventHash());
  23336. return instance;
  23337. };
  23338. };
  23339. /**
  23340. * Parent class for all advanced plugins.
  23341. *
  23342. * @mixes module:evented~EventedMixin
  23343. * @mixes module:stateful~StatefulMixin
  23344. * @fires Player#beforepluginsetup
  23345. * @fires Player#beforepluginsetup:$name
  23346. * @fires Player#pluginsetup
  23347. * @fires Player#pluginsetup:$name
  23348. * @listens Player#dispose
  23349. * @throws {Error}
  23350. * If attempting to instantiate the base {@link Plugin} class
  23351. * directly instead of via a sub-class.
  23352. */
  23353. var Plugin =
  23354. /*#__PURE__*/
  23355. function () {
  23356. /**
  23357. * Creates an instance of this class.
  23358. *
  23359. * Sub-classes should call `super` to ensure plugins are properly initialized.
  23360. *
  23361. * @param {Player} player
  23362. * A Video.js player instance.
  23363. */
  23364. function Plugin(player) {
  23365. if (this.constructor === Plugin) {
  23366. throw new Error('Plugin must be sub-classed; not directly instantiated.');
  23367. }
  23368. this.player = player; // Make this object evented, but remove the added `trigger` method so we
  23369. // use the prototype version instead.
  23370. evented(this);
  23371. delete this.trigger;
  23372. stateful(this, this.constructor.defaultState);
  23373. markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind
  23374. // it later easily.
  23375. this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin.
  23376. player.on('dispose', this.dispose);
  23377. }
  23378. /**
  23379. * Get the version of the plugin that was set on <pluginName>.VERSION
  23380. */
  23381. var _proto = Plugin.prototype;
  23382. _proto.version = function version() {
  23383. return this.constructor.VERSION;
  23384. }
  23385. /**
  23386. * Each event triggered by plugins includes a hash of additional data with
  23387. * conventional properties.
  23388. *
  23389. * This returns that object or mutates an existing hash.
  23390. *
  23391. * @param {Object} [hash={}]
  23392. * An object to be used as event an event hash.
  23393. *
  23394. * @return {Plugin~PluginEventHash}
  23395. * An event hash object with provided properties mixed-in.
  23396. */
  23397. ;
  23398. _proto.getEventHash = function getEventHash(hash) {
  23399. if (hash === void 0) {
  23400. hash = {};
  23401. }
  23402. hash.name = this.name;
  23403. hash.plugin = this.constructor;
  23404. hash.instance = this;
  23405. return hash;
  23406. }
  23407. /**
  23408. * Triggers an event on the plugin object and overrides
  23409. * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
  23410. *
  23411. * @param {string|Object} event
  23412. * An event type or an object with a type property.
  23413. *
  23414. * @param {Object} [hash={}]
  23415. * Additional data hash to merge with a
  23416. * {@link Plugin~PluginEventHash|PluginEventHash}.
  23417. *
  23418. * @return {boolean}
  23419. * Whether or not default was prevented.
  23420. */
  23421. ;
  23422. _proto.trigger = function trigger$$1(event, hash) {
  23423. if (hash === void 0) {
  23424. hash = {};
  23425. }
  23426. return trigger(this.eventBusEl_, event, this.getEventHash(hash));
  23427. }
  23428. /**
  23429. * Handles "statechanged" events on the plugin. No-op by default, override by
  23430. * subclassing.
  23431. *
  23432. * @abstract
  23433. * @param {Event} e
  23434. * An event object provided by a "statechanged" event.
  23435. *
  23436. * @param {Object} e.changes
  23437. * An object describing changes that occurred with the "statechanged"
  23438. * event.
  23439. */
  23440. ;
  23441. _proto.handleStateChanged = function handleStateChanged(e) {}
  23442. /**
  23443. * Disposes a plugin.
  23444. *
  23445. * Subclasses can override this if they want, but for the sake of safety,
  23446. * it's probably best to subscribe the "dispose" event.
  23447. *
  23448. * @fires Plugin#dispose
  23449. */
  23450. ;
  23451. _proto.dispose = function dispose() {
  23452. var name = this.name,
  23453. player = this.player;
  23454. /**
  23455. * Signals that a advanced plugin is about to be disposed.
  23456. *
  23457. * @event Plugin#dispose
  23458. * @type {EventTarget~Event}
  23459. */
  23460. this.trigger('dispose');
  23461. this.off();
  23462. player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up
  23463. // references between the player and the plugin instance and nulling out
  23464. // the plugin's state and replacing methods with a function that throws.
  23465. player[PLUGIN_CACHE_KEY][name] = false;
  23466. this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory
  23467. // function, so that the plugin is ready to be set up again.
  23468. player[name] = createPluginFactory(name, pluginStorage[name]);
  23469. }
  23470. /**
  23471. * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
  23472. *
  23473. * @param {string|Function} plugin
  23474. * If a string, matches the name of a plugin. If a function, will be
  23475. * tested directly.
  23476. *
  23477. * @return {boolean}
  23478. * Whether or not a plugin is a basic plugin.
  23479. */
  23480. ;
  23481. Plugin.isBasic = function isBasic(plugin) {
  23482. var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
  23483. return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
  23484. }
  23485. /**
  23486. * Register a Video.js plugin.
  23487. *
  23488. * @param {string} name
  23489. * The name of the plugin to be registered. Must be a string and
  23490. * must not match an existing plugin or a method on the `Player`
  23491. * prototype.
  23492. *
  23493. * @param {Function} plugin
  23494. * A sub-class of `Plugin` or a function for basic plugins.
  23495. *
  23496. * @return {Function}
  23497. * For advanced plugins, a factory function for that plugin. For
  23498. * basic plugins, a wrapper function that initializes the plugin.
  23499. */
  23500. ;
  23501. Plugin.registerPlugin = function registerPlugin(name, plugin) {
  23502. if (typeof name !== 'string') {
  23503. throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + ".");
  23504. }
  23505. if (pluginExists(name)) {
  23506. log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!");
  23507. } else if (Player.prototype.hasOwnProperty(name)) {
  23508. throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!");
  23509. }
  23510. if (typeof plugin !== 'function') {
  23511. throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + ".");
  23512. }
  23513. pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for
  23514. // the base Plugin class).
  23515. if (name !== BASE_PLUGIN_NAME) {
  23516. if (Plugin.isBasic(plugin)) {
  23517. Player.prototype[name] = createBasicPlugin(name, plugin);
  23518. } else {
  23519. Player.prototype[name] = createPluginFactory(name, plugin);
  23520. }
  23521. }
  23522. return plugin;
  23523. }
  23524. /**
  23525. * De-register a Video.js plugin.
  23526. *
  23527. * @param {string} name
  23528. * The name of the plugin to be de-registered. Must be a string that
  23529. * matches an existing plugin.
  23530. *
  23531. * @throws {Error}
  23532. * If an attempt is made to de-register the base plugin.
  23533. */
  23534. ;
  23535. Plugin.deregisterPlugin = function deregisterPlugin(name) {
  23536. if (name === BASE_PLUGIN_NAME) {
  23537. throw new Error('Cannot de-register base plugin.');
  23538. }
  23539. if (pluginExists(name)) {
  23540. delete pluginStorage[name];
  23541. delete Player.prototype[name];
  23542. }
  23543. }
  23544. /**
  23545. * Gets an object containing multiple Video.js plugins.
  23546. *
  23547. * @param {Array} [names]
  23548. * If provided, should be an array of plugin names. Defaults to _all_
  23549. * plugin names.
  23550. *
  23551. * @return {Object|undefined}
  23552. * An object containing plugin(s) associated with their name(s) or
  23553. * `undefined` if no matching plugins exist).
  23554. */
  23555. ;
  23556. Plugin.getPlugins = function getPlugins(names) {
  23557. if (names === void 0) {
  23558. names = Object.keys(pluginStorage);
  23559. }
  23560. var result;
  23561. names.forEach(function (name) {
  23562. var plugin = getPlugin(name);
  23563. if (plugin) {
  23564. result = result || {};
  23565. result[name] = plugin;
  23566. }
  23567. });
  23568. return result;
  23569. }
  23570. /**
  23571. * Gets a plugin's version, if available
  23572. *
  23573. * @param {string} name
  23574. * The name of a plugin.
  23575. *
  23576. * @return {string}
  23577. * The plugin's version or an empty string.
  23578. */
  23579. ;
  23580. Plugin.getPluginVersion = function getPluginVersion(name) {
  23581. var plugin = getPlugin(name);
  23582. return plugin && plugin.VERSION || '';
  23583. };
  23584. return Plugin;
  23585. }();
  23586. /**
  23587. * Gets a plugin by name if it exists.
  23588. *
  23589. * @static
  23590. * @method getPlugin
  23591. * @memberOf Plugin
  23592. * @param {string} name
  23593. * The name of a plugin.
  23594. *
  23595. * @returns {Function|undefined}
  23596. * The plugin (or `undefined`).
  23597. */
  23598. Plugin.getPlugin = getPlugin;
  23599. /**
  23600. * The name of the base plugin class as it is registered.
  23601. *
  23602. * @type {string}
  23603. */
  23604. Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
  23605. Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
  23606. /**
  23607. * Documented in player.js
  23608. *
  23609. * @ignore
  23610. */
  23611. Player.prototype.usingPlugin = function (name) {
  23612. return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
  23613. };
  23614. /**
  23615. * Documented in player.js
  23616. *
  23617. * @ignore
  23618. */
  23619. Player.prototype.hasPlugin = function (name) {
  23620. return !!pluginExists(name);
  23621. };
  23622. /**
  23623. * Signals that a plugin is about to be set up on a player.
  23624. *
  23625. * @event Player#beforepluginsetup
  23626. * @type {Plugin~PluginEventHash}
  23627. */
  23628. /**
  23629. * Signals that a plugin is about to be set up on a player - by name. The name
  23630. * is the name of the plugin.
  23631. *
  23632. * @event Player#beforepluginsetup:$name
  23633. * @type {Plugin~PluginEventHash}
  23634. */
  23635. /**
  23636. * Signals that a plugin has just been set up on a player.
  23637. *
  23638. * @event Player#pluginsetup
  23639. * @type {Plugin~PluginEventHash}
  23640. */
  23641. /**
  23642. * Signals that a plugin has just been set up on a player - by name. The name
  23643. * is the name of the plugin.
  23644. *
  23645. * @event Player#pluginsetup:$name
  23646. * @type {Plugin~PluginEventHash}
  23647. */
  23648. /**
  23649. * @typedef {Object} Plugin~PluginEventHash
  23650. *
  23651. * @property {string} instance
  23652. * For basic plugins, the return value of the plugin function. For
  23653. * advanced plugins, the plugin instance on which the event is fired.
  23654. *
  23655. * @property {string} name
  23656. * The name of the plugin.
  23657. *
  23658. * @property {string} plugin
  23659. * For basic plugins, the plugin function. For advanced plugins, the
  23660. * plugin class/constructor.
  23661. */
  23662. /**
  23663. * @file extend.js
  23664. * @module extend
  23665. */
  23666. /**
  23667. * A combination of node inherits and babel's inherits (after transpile).
  23668. * Both work the same but node adds `super_` to the subClass
  23669. * and Bable adds the superClass as __proto__. Both seem useful.
  23670. *
  23671. * @param {Object} subClass
  23672. * The class to inherit to
  23673. *
  23674. * @param {Object} superClass
  23675. * The class to inherit from
  23676. *
  23677. * @private
  23678. */
  23679. var _inherits$1 = function _inherits(subClass, superClass) {
  23680. if (typeof superClass !== 'function' && superClass !== null) {
  23681. throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
  23682. }
  23683. subClass.prototype = Object.create(superClass && superClass.prototype, {
  23684. constructor: {
  23685. value: subClass,
  23686. enumerable: false,
  23687. writable: true,
  23688. configurable: true
  23689. }
  23690. });
  23691. if (superClass) {
  23692. // node
  23693. subClass.super_ = superClass;
  23694. }
  23695. };
  23696. /**
  23697. * Used to subclass an existing class by emulating ES subclassing using the
  23698. * `extends` keyword.
  23699. *
  23700. * @function
  23701. * @example
  23702. * var MyComponent = videojs.extend(videojs.getComponent('Component'), {
  23703. * myCustomMethod: function() {
  23704. * // Do things in my method.
  23705. * }
  23706. * });
  23707. *
  23708. * @param {Function} superClass
  23709. * The class to inherit from
  23710. *
  23711. * @param {Object} [subClassMethods={}]
  23712. * Methods of the new class
  23713. *
  23714. * @return {Function}
  23715. * The new class with subClassMethods that inherited superClass.
  23716. */
  23717. var extend$1 = function extend(superClass, subClassMethods) {
  23718. if (subClassMethods === void 0) {
  23719. subClassMethods = {};
  23720. }
  23721. var subClass = function subClass() {
  23722. superClass.apply(this, arguments);
  23723. };
  23724. var methods = {};
  23725. if (typeof subClassMethods === 'object') {
  23726. if (subClassMethods.constructor !== Object.prototype.constructor) {
  23727. subClass = subClassMethods.constructor;
  23728. }
  23729. methods = subClassMethods;
  23730. } else if (typeof subClassMethods === 'function') {
  23731. subClass = subClassMethods;
  23732. }
  23733. _inherits$1(subClass, superClass); // Extend subObj's prototype with functions and other properties from props
  23734. for (var name in methods) {
  23735. if (methods.hasOwnProperty(name)) {
  23736. subClass.prototype[name] = methods[name];
  23737. }
  23738. }
  23739. return subClass;
  23740. };
  23741. /**
  23742. * @file video.js
  23743. * @module videojs
  23744. */
  23745. /**
  23746. * Normalize an `id` value by trimming off a leading `#`
  23747. *
  23748. * @private
  23749. * @param {string} id
  23750. * A string, maybe with a leading `#`.
  23751. *
  23752. * @return {string}
  23753. * The string, without any leading `#`.
  23754. */
  23755. var normalizeId = function normalizeId(id) {
  23756. return id.indexOf('#') === 0 ? id.slice(1) : id;
  23757. };
  23758. /**
  23759. * The `videojs()` function doubles as the main function for users to create a
  23760. * {@link Player} instance as well as the main library namespace.
  23761. *
  23762. * It can also be used as a getter for a pre-existing {@link Player} instance.
  23763. * However, we _strongly_ recommend using `videojs.getPlayer()` for this
  23764. * purpose because it avoids any potential for unintended initialization.
  23765. *
  23766. * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
  23767. * of our JSDoc template, we cannot properly document this as both a function
  23768. * and a namespace, so its function signature is documented here.
  23769. *
  23770. * #### Arguments
  23771. * ##### id
  23772. * string|Element, **required**
  23773. *
  23774. * Video element or video element ID.
  23775. *
  23776. * ##### options
  23777. * Object, optional
  23778. *
  23779. * Options object for providing settings.
  23780. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  23781. *
  23782. * ##### ready
  23783. * {@link Component~ReadyCallback}, optional
  23784. *
  23785. * A function to be called when the {@link Player} and {@link Tech} are ready.
  23786. *
  23787. * #### Return Value
  23788. *
  23789. * The `videojs()` function returns a {@link Player} instance.
  23790. *
  23791. * @namespace
  23792. *
  23793. * @borrows AudioTrack as AudioTrack
  23794. * @borrows Component.getComponent as getComponent
  23795. * @borrows module:computed-style~computedStyle as computedStyle
  23796. * @borrows module:events.on as on
  23797. * @borrows module:events.one as one
  23798. * @borrows module:events.off as off
  23799. * @borrows module:events.trigger as trigger
  23800. * @borrows EventTarget as EventTarget
  23801. * @borrows module:extend~extend as extend
  23802. * @borrows module:fn.bind as bind
  23803. * @borrows module:format-time.formatTime as formatTime
  23804. * @borrows module:format-time.resetFormatTime as resetFormatTime
  23805. * @borrows module:format-time.setFormatTime as setFormatTime
  23806. * @borrows module:merge-options.mergeOptions as mergeOptions
  23807. * @borrows module:middleware.use as use
  23808. * @borrows Player.players as players
  23809. * @borrows Plugin.registerPlugin as registerPlugin
  23810. * @borrows Plugin.deregisterPlugin as deregisterPlugin
  23811. * @borrows Plugin.getPlugins as getPlugins
  23812. * @borrows Plugin.getPlugin as getPlugin
  23813. * @borrows Plugin.getPluginVersion as getPluginVersion
  23814. * @borrows Tech.getTech as getTech
  23815. * @borrows Tech.registerTech as registerTech
  23816. * @borrows TextTrack as TextTrack
  23817. * @borrows module:time-ranges.createTimeRanges as createTimeRange
  23818. * @borrows module:time-ranges.createTimeRanges as createTimeRanges
  23819. * @borrows module:url.isCrossOrigin as isCrossOrigin
  23820. * @borrows module:url.parseUrl as parseUrl
  23821. * @borrows VideoTrack as VideoTrack
  23822. *
  23823. * @param {string|Element} id
  23824. * Video element or video element ID.
  23825. *
  23826. * @param {Object} [options]
  23827. * Options object for providing settings.
  23828. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  23829. *
  23830. * @param {Component~ReadyCallback} [ready]
  23831. * A function to be called when the {@link Player} and {@link Tech} are
  23832. * ready.
  23833. *
  23834. * @return {Player}
  23835. * The `videojs()` function returns a {@link Player|Player} instance.
  23836. */
  23837. function videojs$1(id, options, ready) {
  23838. var player = videojs$1.getPlayer(id);
  23839. if (player) {
  23840. if (options) {
  23841. log.warn("Player \"" + id + "\" is already initialised. Options will not be applied.");
  23842. }
  23843. if (ready) {
  23844. player.ready(ready);
  23845. }
  23846. return player;
  23847. }
  23848. var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
  23849. if (!isEl(el)) {
  23850. throw new TypeError('The element or ID supplied is not valid. (videojs)');
  23851. } // document.body.contains(el) will only check if el is contained within that one document.
  23852. // This causes problems for elements in iframes.
  23853. // Instead, use the element's ownerDocument instead of the global document.
  23854. // This will make sure that the element is indeed in the dom of that document.
  23855. // Additionally, check that the document in question has a default view.
  23856. // If the document is no longer attached to the dom, the defaultView of the document will be null.
  23857. if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) {
  23858. log.warn('The element supplied is not included in the DOM');
  23859. }
  23860. options = options || {};
  23861. videojs$1.hooks('beforesetup').forEach(function (hookFunction) {
  23862. var opts = hookFunction(el, mergeOptions(options));
  23863. if (!isObject(opts) || Array.isArray(opts)) {
  23864. log.error('please return an object in beforesetup hooks');
  23865. return;
  23866. }
  23867. options = mergeOptions(options, opts);
  23868. }); // We get the current "Player" component here in case an integration has
  23869. // replaced it with a custom player.
  23870. var PlayerComponent = Component.getComponent('Player');
  23871. player = new PlayerComponent(el, options, ready);
  23872. videojs$1.hooks('setup').forEach(function (hookFunction) {
  23873. return hookFunction(player);
  23874. });
  23875. return player;
  23876. }
  23877. /**
  23878. * An Object that contains lifecycle hooks as keys which point to an array
  23879. * of functions that are run when a lifecycle is triggered
  23880. *
  23881. * @private
  23882. */
  23883. videojs$1.hooks_ = {};
  23884. /**
  23885. * Get a list of hooks for a specific lifecycle
  23886. *
  23887. * @param {string} type
  23888. * the lifecyle to get hooks from
  23889. *
  23890. * @param {Function|Function[]} [fn]
  23891. * Optionally add a hook (or hooks) to the lifecycle that your are getting.
  23892. *
  23893. * @return {Array}
  23894. * an array of hooks, or an empty array if there are none.
  23895. */
  23896. videojs$1.hooks = function (type, fn) {
  23897. videojs$1.hooks_[type] = videojs$1.hooks_[type] || [];
  23898. if (fn) {
  23899. videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn);
  23900. }
  23901. return videojs$1.hooks_[type];
  23902. };
  23903. /**
  23904. * Add a function hook to a specific videojs lifecycle.
  23905. *
  23906. * @param {string} type
  23907. * the lifecycle to hook the function to.
  23908. *
  23909. * @param {Function|Function[]}
  23910. * The function or array of functions to attach.
  23911. */
  23912. videojs$1.hook = function (type, fn) {
  23913. videojs$1.hooks(type, fn);
  23914. };
  23915. /**
  23916. * Add a function hook that will only run once to a specific videojs lifecycle.
  23917. *
  23918. * @param {string} type
  23919. * the lifecycle to hook the function to.
  23920. *
  23921. * @param {Function|Function[]}
  23922. * The function or array of functions to attach.
  23923. */
  23924. videojs$1.hookOnce = function (type, fn) {
  23925. videojs$1.hooks(type, [].concat(fn).map(function (original) {
  23926. var wrapper = function wrapper() {
  23927. videojs$1.removeHook(type, wrapper);
  23928. return original.apply(void 0, arguments);
  23929. };
  23930. return wrapper;
  23931. }));
  23932. };
  23933. /**
  23934. * Remove a hook from a specific videojs lifecycle.
  23935. *
  23936. * @param {string} type
  23937. * the lifecycle that the function hooked to
  23938. *
  23939. * @param {Function} fn
  23940. * The hooked function to remove
  23941. *
  23942. * @return {boolean}
  23943. * The function that was removed or undef
  23944. */
  23945. videojs$1.removeHook = function (type, fn) {
  23946. var index = videojs$1.hooks(type).indexOf(fn);
  23947. if (index <= -1) {
  23948. return false;
  23949. }
  23950. videojs$1.hooks_[type] = videojs$1.hooks_[type].slice();
  23951. videojs$1.hooks_[type].splice(index, 1);
  23952. return true;
  23953. }; // Add default styles
  23954. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
  23955. var style$1 = $('.vjs-styles-defaults');
  23956. if (!style$1) {
  23957. style$1 = createStyleElement('vjs-styles-defaults');
  23958. var head = $('head');
  23959. if (head) {
  23960. head.insertBefore(style$1, head.firstChild);
  23961. }
  23962. setTextContent(style$1, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n ");
  23963. }
  23964. } // Run Auto-load players
  23965. // You have to wait at least once in case this script is loaded after your
  23966. // video in the DOM (weird behavior only with minified version)
  23967. autoSetupTimeout(1, videojs$1);
  23968. /**
  23969. * Current Video.js version. Follows [semantic versioning](https://semver.org/).
  23970. *
  23971. * @type {string}
  23972. */
  23973. videojs$1.VERSION = version;
  23974. /**
  23975. * The global options object. These are the settings that take effect
  23976. * if no overrides are specified when the player is created.
  23977. *
  23978. * @type {Object}
  23979. */
  23980. videojs$1.options = Player.prototype.options_;
  23981. /**
  23982. * Get an object with the currently created players, keyed by player ID
  23983. *
  23984. * @return {Object}
  23985. * The created players
  23986. */
  23987. videojs$1.getPlayers = function () {
  23988. return Player.players;
  23989. };
  23990. /**
  23991. * Get a single player based on an ID or DOM element.
  23992. *
  23993. * This is useful if you want to check if an element or ID has an associated
  23994. * Video.js player, but not create one if it doesn't.
  23995. *
  23996. * @param {string|Element} id
  23997. * An HTML element - `<video>`, `<audio>`, or `<video-js>` -
  23998. * or a string matching the `id` of such an element.
  23999. *
  24000. * @return {Player|undefined}
  24001. * A player instance or `undefined` if there is no player instance
  24002. * matching the argument.
  24003. */
  24004. videojs$1.getPlayer = function (id) {
  24005. var players = Player.players;
  24006. var tag;
  24007. if (typeof id === 'string') {
  24008. var nId = normalizeId(id);
  24009. var player = players[nId];
  24010. if (player) {
  24011. return player;
  24012. }
  24013. tag = $('#' + nId);
  24014. } else {
  24015. tag = id;
  24016. }
  24017. if (isEl(tag)) {
  24018. var _tag = tag,
  24019. _player = _tag.player,
  24020. playerId = _tag.playerId; // Element may have a `player` property referring to an already created
  24021. // player instance. If so, return that.
  24022. if (_player || players[playerId]) {
  24023. return _player || players[playerId];
  24024. }
  24025. }
  24026. };
  24027. /**
  24028. * Returns an array of all current players.
  24029. *
  24030. * @return {Array}
  24031. * An array of all players. The array will be in the order that
  24032. * `Object.keys` provides, which could potentially vary between
  24033. * JavaScript engines.
  24034. *
  24035. */
  24036. videojs$1.getAllPlayers = function () {
  24037. return (// Disposed players leave a key with a `null` value, so we need to make sure
  24038. // we filter those out.
  24039. Object.keys(Player.players).map(function (k) {
  24040. return Player.players[k];
  24041. }).filter(Boolean)
  24042. );
  24043. };
  24044. videojs$1.players = Player.players;
  24045. videojs$1.getComponent = Component.getComponent;
  24046. /**
  24047. * Register a component so it can referred to by name. Used when adding to other
  24048. * components, either through addChild `component.addChild('myComponent')` or through
  24049. * default children options `{ children: ['myComponent'] }`.
  24050. *
  24051. * > NOTE: You could also just initialize the component before adding.
  24052. * `component.addChild(new MyComponent());`
  24053. *
  24054. * @param {string} name
  24055. * The class name of the component
  24056. *
  24057. * @param {Component} comp
  24058. * The component class
  24059. *
  24060. * @return {Component}
  24061. * The newly registered component
  24062. */
  24063. videojs$1.registerComponent = function (name$$1, comp) {
  24064. if (Tech.isTech(comp)) {
  24065. log.warn("The " + name$$1 + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)");
  24066. }
  24067. Component.registerComponent.call(Component, name$$1, comp);
  24068. };
  24069. videojs$1.getTech = Tech.getTech;
  24070. videojs$1.registerTech = Tech.registerTech;
  24071. videojs$1.use = use;
  24072. /**
  24073. * An object that can be returned by a middleware to signify
  24074. * that the middleware is being terminated.
  24075. *
  24076. * @type {object}
  24077. * @property {object} middleware.TERMINATOR
  24078. */
  24079. Object.defineProperty(videojs$1, 'middleware', {
  24080. value: {},
  24081. writeable: false,
  24082. enumerable: true
  24083. });
  24084. Object.defineProperty(videojs$1.middleware, 'TERMINATOR', {
  24085. value: TERMINATOR,
  24086. writeable: false,
  24087. enumerable: true
  24088. });
  24089. /**
  24090. * A reference to the {@link module:browser|browser utility module} as an object.
  24091. *
  24092. * @type {Object}
  24093. * @see {@link module:browser|browser}
  24094. */
  24095. videojs$1.browser = browser;
  24096. /**
  24097. * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only
  24098. * included for backward-compatibility with 4.x.
  24099. *
  24100. * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead.
  24101. * @type {boolean}
  24102. */
  24103. videojs$1.TOUCH_ENABLED = TOUCH_ENABLED;
  24104. videojs$1.extend = extend$1;
  24105. videojs$1.mergeOptions = mergeOptions;
  24106. videojs$1.bind = bind;
  24107. videojs$1.registerPlugin = Plugin.registerPlugin;
  24108. videojs$1.deregisterPlugin = Plugin.deregisterPlugin;
  24109. /**
  24110. * Deprecated method to register a plugin with Video.js
  24111. *
  24112. * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead
  24113. *
  24114. * @param {string} name
  24115. * The plugin name
  24116. *
  24117. * @param {Plugin|Function} plugin
  24118. * The plugin sub-class or function
  24119. */
  24120. videojs$1.plugin = function (name$$1, plugin) {
  24121. log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
  24122. return Plugin.registerPlugin(name$$1, plugin);
  24123. };
  24124. videojs$1.getPlugins = Plugin.getPlugins;
  24125. videojs$1.getPlugin = Plugin.getPlugin;
  24126. videojs$1.getPluginVersion = Plugin.getPluginVersion;
  24127. /**
  24128. * Adding languages so that they're available to all players.
  24129. * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
  24130. *
  24131. * @param {string} code
  24132. * The language code or dictionary property
  24133. *
  24134. * @param {Object} data
  24135. * The data values to be translated
  24136. *
  24137. * @return {Object}
  24138. * The resulting language dictionary object
  24139. */
  24140. videojs$1.addLanguage = function (code, data) {
  24141. var _mergeOptions;
  24142. code = ('' + code).toLowerCase();
  24143. videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
  24144. return videojs$1.options.languages[code];
  24145. };
  24146. /**
  24147. * A reference to the {@link module:log|log utility module} as an object.
  24148. *
  24149. * @type {Function}
  24150. * @see {@link module:log|log}
  24151. */
  24152. videojs$1.log = log;
  24153. videojs$1.createLogger = createLogger$1;
  24154. videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges;
  24155. videojs$1.formatTime = formatTime;
  24156. videojs$1.setFormatTime = setFormatTime;
  24157. videojs$1.resetFormatTime = resetFormatTime;
  24158. videojs$1.parseUrl = parseUrl;
  24159. videojs$1.isCrossOrigin = isCrossOrigin;
  24160. videojs$1.EventTarget = EventTarget;
  24161. videojs$1.on = on;
  24162. videojs$1.one = one;
  24163. videojs$1.off = off;
  24164. videojs$1.trigger = trigger;
  24165. /**
  24166. * A cross-browser XMLHttpRequest wrapper.
  24167. *
  24168. * @function
  24169. * @param {Object} options
  24170. * Settings for the request.
  24171. *
  24172. * @return {XMLHttpRequest|XDomainRequest}
  24173. * The request object.
  24174. *
  24175. * @see https://github.com/Raynos/xhr
  24176. */
  24177. videojs$1.xhr = xhr;
  24178. videojs$1.TextTrack = TextTrack;
  24179. videojs$1.AudioTrack = AudioTrack;
  24180. videojs$1.VideoTrack = VideoTrack;
  24181. ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
  24182. videojs$1[k] = function () {
  24183. log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead");
  24184. return Dom[k].apply(null, arguments);
  24185. };
  24186. });
  24187. videojs$1.computedStyle = computedStyle;
  24188. /**
  24189. * A reference to the {@link module:dom|DOM utility module} as an object.
  24190. *
  24191. * @type {Object}
  24192. * @see {@link module:dom|dom}
  24193. */
  24194. videojs$1.dom = Dom;
  24195. /**
  24196. * A reference to the {@link module:url|URL utility module} as an object.
  24197. *
  24198. * @type {Object}
  24199. * @see {@link module:url|url}
  24200. */
  24201. videojs$1.url = Url;
  24202. var urlToolkit = createCommonjsModule(function (module, exports) {
  24203. // see https://tools.ietf.org/html/rfc1808
  24204. /* jshint ignore:start */
  24205. (function (root) {
  24206. /* jshint ignore:end */
  24207. var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
  24208. var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
  24209. var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
  24210. var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
  24211. var URLToolkit = {
  24212. // jshint ignore:line
  24213. // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
  24214. // E.g
  24215. // With opts.alwaysNormalize = false (default, spec compliant)
  24216. // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
  24217. // With opts.alwaysNormalize = true (not spec compliant)
  24218. // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
  24219. buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
  24220. opts = opts || {}; // remove any remaining space and CRLF
  24221. baseURL = baseURL.trim();
  24222. relativeURL = relativeURL.trim();
  24223. if (!relativeURL) {
  24224. // 2a) If the embedded URL is entirely empty, it inherits the
  24225. // entire base URL (i.e., is set equal to the base URL)
  24226. // and we are done.
  24227. if (!opts.alwaysNormalize) {
  24228. return baseURL;
  24229. }
  24230. var basePartsForNormalise = URLToolkit.parseURL(baseURL);
  24231. if (!basePartsForNormalise) {
  24232. throw new Error('Error trying to parse base URL.');
  24233. }
  24234. basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
  24235. return URLToolkit.buildURLFromParts(basePartsForNormalise);
  24236. }
  24237. var relativeParts = URLToolkit.parseURL(relativeURL);
  24238. if (!relativeParts) {
  24239. throw new Error('Error trying to parse relative URL.');
  24240. }
  24241. if (relativeParts.scheme) {
  24242. // 2b) If the embedded URL starts with a scheme name, it is
  24243. // interpreted as an absolute URL and we are done.
  24244. if (!opts.alwaysNormalize) {
  24245. return relativeURL;
  24246. }
  24247. relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
  24248. return URLToolkit.buildURLFromParts(relativeParts);
  24249. }
  24250. var baseParts = URLToolkit.parseURL(baseURL);
  24251. if (!baseParts) {
  24252. throw new Error('Error trying to parse base URL.');
  24253. }
  24254. if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
  24255. // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
  24256. // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
  24257. var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
  24258. baseParts.netLoc = pathParts[1];
  24259. baseParts.path = pathParts[2];
  24260. }
  24261. if (baseParts.netLoc && !baseParts.path) {
  24262. baseParts.path = '/';
  24263. }
  24264. var builtParts = {
  24265. // 2c) Otherwise, the embedded URL inherits the scheme of
  24266. // the base URL.
  24267. scheme: baseParts.scheme,
  24268. netLoc: relativeParts.netLoc,
  24269. path: null,
  24270. params: relativeParts.params,
  24271. query: relativeParts.query,
  24272. fragment: relativeParts.fragment
  24273. };
  24274. if (!relativeParts.netLoc) {
  24275. // 3) If the embedded URL's <net_loc> is non-empty, we skip to
  24276. // Step 7. Otherwise, the embedded URL inherits the <net_loc>
  24277. // (if any) of the base URL.
  24278. builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
  24279. // path is not relative and we skip to Step 7.
  24280. if (relativeParts.path[0] !== '/') {
  24281. if (!relativeParts.path) {
  24282. // 5) If the embedded URL path is empty (and not preceded by a
  24283. // slash), then the embedded URL inherits the base URL path
  24284. builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
  24285. // step 7; otherwise, it inherits the <params> of the base
  24286. // URL (if any) and
  24287. if (!relativeParts.params) {
  24288. builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
  24289. // step 7; otherwise, it inherits the <query> of the base
  24290. // URL (if any) and we skip to step 7.
  24291. if (!relativeParts.query) {
  24292. builtParts.query = baseParts.query;
  24293. }
  24294. }
  24295. } else {
  24296. // 6) The last segment of the base URL's path (anything
  24297. // following the rightmost slash "/", or the entire path if no
  24298. // slash is present) is removed and the embedded URL's path is
  24299. // appended in its place.
  24300. var baseURLPath = baseParts.path;
  24301. var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
  24302. builtParts.path = URLToolkit.normalizePath(newPath);
  24303. }
  24304. }
  24305. }
  24306. if (builtParts.path === null) {
  24307. builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
  24308. }
  24309. return URLToolkit.buildURLFromParts(builtParts);
  24310. },
  24311. parseURL: function parseURL(url) {
  24312. var parts = URL_REGEX.exec(url);
  24313. if (!parts) {
  24314. return null;
  24315. }
  24316. return {
  24317. scheme: parts[1] || '',
  24318. netLoc: parts[2] || '',
  24319. path: parts[3] || '',
  24320. params: parts[4] || '',
  24321. query: parts[5] || '',
  24322. fragment: parts[6] || ''
  24323. };
  24324. },
  24325. normalizePath: function normalizePath(path) {
  24326. // The following operations are
  24327. // then applied, in order, to the new path:
  24328. // 6a) All occurrences of "./", where "." is a complete path
  24329. // segment, are removed.
  24330. // 6b) If the path ends with "." as a complete path segment,
  24331. // that "." is removed.
  24332. path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
  24333. // complete path segment not equal to "..", are removed.
  24334. // Removal of these path segments is performed iteratively,
  24335. // removing the leftmost matching pattern on each iteration,
  24336. // until no matching pattern remains.
  24337. // 6d) If the path ends with "<segment>/..", where <segment> is a
  24338. // complete path segment not equal to "..", that
  24339. // "<segment>/.." is removed.
  24340. while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
  24341. return path.split('').reverse().join('');
  24342. },
  24343. buildURLFromParts: function buildURLFromParts(parts) {
  24344. return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
  24345. }
  24346. };
  24347. /* jshint ignore:start */
  24348. module.exports = URLToolkit;
  24349. })(commonjsGlobal);
  24350. /* jshint ignore:end */
  24351. });
  24352. /*! @name m3u8-parser @version 4.3.0 @license Apache-2.0 */
  24353. function _extends$1() {
  24354. _extends$1 = Object.assign || function (target) {
  24355. for (var i = 1; i < arguments.length; i++) {
  24356. var source = arguments[i];
  24357. for (var key in source) {
  24358. if (Object.prototype.hasOwnProperty.call(source, key)) {
  24359. target[key] = source[key];
  24360. }
  24361. }
  24362. }
  24363. return target;
  24364. };
  24365. return _extends$1.apply(this, arguments);
  24366. }
  24367. function _inheritsLoose$1(subClass, superClass) {
  24368. subClass.prototype = Object.create(superClass.prototype);
  24369. subClass.prototype.constructor = subClass;
  24370. subClass.__proto__ = superClass;
  24371. }
  24372. function _assertThisInitialized$1(self) {
  24373. if (self === void 0) {
  24374. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  24375. }
  24376. return self;
  24377. }
  24378. /**
  24379. * @file stream.js
  24380. */
  24381. /**
  24382. * A lightweight readable stream implementation that handles event dispatching.
  24383. *
  24384. * @class Stream
  24385. */
  24386. var Stream =
  24387. /*#__PURE__*/
  24388. function () {
  24389. function Stream() {
  24390. this.listeners = {};
  24391. }
  24392. /**
  24393. * Add a listener for a specified event type.
  24394. *
  24395. * @param {string} type the event name
  24396. * @param {Function} listener the callback to be invoked when an event of
  24397. * the specified type occurs
  24398. */
  24399. var _proto = Stream.prototype;
  24400. _proto.on = function on(type, listener) {
  24401. if (!this.listeners[type]) {
  24402. this.listeners[type] = [];
  24403. }
  24404. this.listeners[type].push(listener);
  24405. };
  24406. /**
  24407. * Remove a listener for a specified event type.
  24408. *
  24409. * @param {string} type the event name
  24410. * @param {Function} listener a function previously registered for this
  24411. * type of event through `on`
  24412. * @return {boolean} if we could turn it off or not
  24413. */
  24414. _proto.off = function off(type, listener) {
  24415. if (!this.listeners[type]) {
  24416. return false;
  24417. }
  24418. var index = this.listeners[type].indexOf(listener);
  24419. this.listeners[type].splice(index, 1);
  24420. return index > -1;
  24421. };
  24422. /**
  24423. * Trigger an event of the specified type on this stream. Any additional
  24424. * arguments to this function are passed as parameters to event listeners.
  24425. *
  24426. * @param {string} type the event name
  24427. */
  24428. _proto.trigger = function trigger(type) {
  24429. var callbacks = this.listeners[type];
  24430. var i;
  24431. var length;
  24432. var args;
  24433. if (!callbacks) {
  24434. return;
  24435. } // Slicing the arguments on every invocation of this method
  24436. // can add a significant amount of overhead. Avoid the
  24437. // intermediate object creation for the common case of a
  24438. // single callback argument
  24439. if (arguments.length === 2) {
  24440. length = callbacks.length;
  24441. for (i = 0; i < length; ++i) {
  24442. callbacks[i].call(this, arguments[1]);
  24443. }
  24444. } else {
  24445. args = Array.prototype.slice.call(arguments, 1);
  24446. length = callbacks.length;
  24447. for (i = 0; i < length; ++i) {
  24448. callbacks[i].apply(this, args);
  24449. }
  24450. }
  24451. };
  24452. /**
  24453. * Destroys the stream and cleans up.
  24454. */
  24455. _proto.dispose = function dispose() {
  24456. this.listeners = {};
  24457. };
  24458. /**
  24459. * Forwards all `data` events on this stream to the destination stream. The
  24460. * destination stream should provide a method `push` to receive the data
  24461. * events as they arrive.
  24462. *
  24463. * @param {Stream} destination the stream that will receive all `data` events
  24464. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  24465. */
  24466. _proto.pipe = function pipe(destination) {
  24467. this.on('data', function (data) {
  24468. destination.push(data);
  24469. });
  24470. };
  24471. return Stream;
  24472. }();
  24473. /**
  24474. * A stream that buffers string input and generates a `data` event for each
  24475. * line.
  24476. *
  24477. * @class LineStream
  24478. * @extends Stream
  24479. */
  24480. var LineStream =
  24481. /*#__PURE__*/
  24482. function (_Stream) {
  24483. _inheritsLoose$1(LineStream, _Stream);
  24484. function LineStream() {
  24485. var _this;
  24486. _this = _Stream.call(this) || this;
  24487. _this.buffer = '';
  24488. return _this;
  24489. }
  24490. /**
  24491. * Add new data to be parsed.
  24492. *
  24493. * @param {string} data the text to process
  24494. */
  24495. var _proto = LineStream.prototype;
  24496. _proto.push = function push(data) {
  24497. var nextNewline;
  24498. this.buffer += data;
  24499. nextNewline = this.buffer.indexOf('\n');
  24500. for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
  24501. this.trigger('data', this.buffer.substring(0, nextNewline));
  24502. this.buffer = this.buffer.substring(nextNewline + 1);
  24503. }
  24504. };
  24505. return LineStream;
  24506. }(Stream);
  24507. /**
  24508. * "forgiving" attribute list psuedo-grammar:
  24509. * attributes -> keyvalue (',' keyvalue)*
  24510. * keyvalue -> key '=' value
  24511. * key -> [^=]*
  24512. * value -> '"' [^"]* '"' | [^,]*
  24513. */
  24514. var attributeSeparator = function attributeSeparator() {
  24515. var key = '[^=]*';
  24516. var value = '"[^"]*"|[^,]*';
  24517. var keyvalue = '(?:' + key + ')=(?:' + value + ')';
  24518. return new RegExp('(?:^|,)(' + keyvalue + ')');
  24519. };
  24520. /**
  24521. * Parse attributes from a line given the separator
  24522. *
  24523. * @param {string} attributes the attribute line to parse
  24524. */
  24525. var parseAttributes = function parseAttributes(attributes) {
  24526. // split the string using attributes as the separator
  24527. var attrs = attributes.split(attributeSeparator());
  24528. var result = {};
  24529. var i = attrs.length;
  24530. var attr;
  24531. while (i--) {
  24532. // filter out unmatched portions of the string
  24533. if (attrs[i] === '') {
  24534. continue;
  24535. } // split the key and value
  24536. attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
  24537. attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
  24538. attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
  24539. attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
  24540. result[attr[0]] = attr[1];
  24541. }
  24542. return result;
  24543. };
  24544. /**
  24545. * A line-level M3U8 parser event stream. It expects to receive input one
  24546. * line at a time and performs a context-free parse of its contents. A stream
  24547. * interpretation of a manifest can be useful if the manifest is expected to
  24548. * be too large to fit comfortably into memory or the entirety of the input
  24549. * is not immediately available. Otherwise, it's probably much easier to work
  24550. * with a regular `Parser` object.
  24551. *
  24552. * Produces `data` events with an object that captures the parser's
  24553. * interpretation of the input. That object has a property `tag` that is one
  24554. * of `uri`, `comment`, or `tag`. URIs only have a single additional
  24555. * property, `line`, which captures the entirety of the input without
  24556. * interpretation. Comments similarly have a single additional property
  24557. * `text` which is the input without the leading `#`.
  24558. *
  24559. * Tags always have a property `tagType` which is the lower-cased version of
  24560. * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
  24561. * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
  24562. * tags are given the tag type `unknown` and a single additional property
  24563. * `data` with the remainder of the input.
  24564. *
  24565. * @class ParseStream
  24566. * @extends Stream
  24567. */
  24568. var ParseStream =
  24569. /*#__PURE__*/
  24570. function (_Stream) {
  24571. _inheritsLoose$1(ParseStream, _Stream);
  24572. function ParseStream() {
  24573. var _this;
  24574. _this = _Stream.call(this) || this;
  24575. _this.customParsers = [];
  24576. _this.tagMappers = [];
  24577. return _this;
  24578. }
  24579. /**
  24580. * Parses an additional line of input.
  24581. *
  24582. * @param {string} line a single line of an M3U8 file to parse
  24583. */
  24584. var _proto = ParseStream.prototype;
  24585. _proto.push = function push(line) {
  24586. var _this2 = this;
  24587. var match;
  24588. var event; // strip whitespace
  24589. line = line.trim();
  24590. if (line.length === 0) {
  24591. // ignore empty lines
  24592. return;
  24593. } // URIs
  24594. if (line[0] !== '#') {
  24595. this.trigger('data', {
  24596. type: 'uri',
  24597. uri: line
  24598. });
  24599. return;
  24600. } // map tags
  24601. var newLines = this.tagMappers.reduce(function (acc, mapper) {
  24602. var mappedLine = mapper(line); // skip if unchanged
  24603. if (mappedLine === line) {
  24604. return acc;
  24605. }
  24606. return acc.concat([mappedLine]);
  24607. }, [line]);
  24608. newLines.forEach(function (newLine) {
  24609. for (var i = 0; i < _this2.customParsers.length; i++) {
  24610. if (_this2.customParsers[i].call(_this2, newLine)) {
  24611. return;
  24612. }
  24613. } // Comments
  24614. if (newLine.indexOf('#EXT') !== 0) {
  24615. _this2.trigger('data', {
  24616. type: 'comment',
  24617. text: newLine.slice(1)
  24618. });
  24619. return;
  24620. } // strip off any carriage returns here so the regex matching
  24621. // doesn't have to account for them.
  24622. newLine = newLine.replace('\r', ''); // Tags
  24623. match = /^#EXTM3U/.exec(newLine);
  24624. if (match) {
  24625. _this2.trigger('data', {
  24626. type: 'tag',
  24627. tagType: 'm3u'
  24628. });
  24629. return;
  24630. }
  24631. match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine);
  24632. if (match) {
  24633. event = {
  24634. type: 'tag',
  24635. tagType: 'inf'
  24636. };
  24637. if (match[1]) {
  24638. event.duration = parseFloat(match[1]);
  24639. }
  24640. if (match[2]) {
  24641. event.title = match[2];
  24642. }
  24643. _this2.trigger('data', event);
  24644. return;
  24645. }
  24646. match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine);
  24647. if (match) {
  24648. event = {
  24649. type: 'tag',
  24650. tagType: 'targetduration'
  24651. };
  24652. if (match[1]) {
  24653. event.duration = parseInt(match[1], 10);
  24654. }
  24655. _this2.trigger('data', event);
  24656. return;
  24657. }
  24658. match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine);
  24659. if (match) {
  24660. event = {
  24661. type: 'tag',
  24662. tagType: 'totalduration'
  24663. };
  24664. if (match[1]) {
  24665. event.duration = parseInt(match[1], 10);
  24666. }
  24667. _this2.trigger('data', event);
  24668. return;
  24669. }
  24670. match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine);
  24671. if (match) {
  24672. event = {
  24673. type: 'tag',
  24674. tagType: 'version'
  24675. };
  24676. if (match[1]) {
  24677. event.version = parseInt(match[1], 10);
  24678. }
  24679. _this2.trigger('data', event);
  24680. return;
  24681. }
  24682. match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
  24683. if (match) {
  24684. event = {
  24685. type: 'tag',
  24686. tagType: 'media-sequence'
  24687. };
  24688. if (match[1]) {
  24689. event.number = parseInt(match[1], 10);
  24690. }
  24691. _this2.trigger('data', event);
  24692. return;
  24693. }
  24694. match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
  24695. if (match) {
  24696. event = {
  24697. type: 'tag',
  24698. tagType: 'discontinuity-sequence'
  24699. };
  24700. if (match[1]) {
  24701. event.number = parseInt(match[1], 10);
  24702. }
  24703. _this2.trigger('data', event);
  24704. return;
  24705. }
  24706. match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine);
  24707. if (match) {
  24708. event = {
  24709. type: 'tag',
  24710. tagType: 'playlist-type'
  24711. };
  24712. if (match[1]) {
  24713. event.playlistType = match[1];
  24714. }
  24715. _this2.trigger('data', event);
  24716. return;
  24717. }
  24718. match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine);
  24719. if (match) {
  24720. event = {
  24721. type: 'tag',
  24722. tagType: 'byterange'
  24723. };
  24724. if (match[1]) {
  24725. event.length = parseInt(match[1], 10);
  24726. }
  24727. if (match[2]) {
  24728. event.offset = parseInt(match[2], 10);
  24729. }
  24730. _this2.trigger('data', event);
  24731. return;
  24732. }
  24733. match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine);
  24734. if (match) {
  24735. event = {
  24736. type: 'tag',
  24737. tagType: 'allow-cache'
  24738. };
  24739. if (match[1]) {
  24740. event.allowed = !/NO/.test(match[1]);
  24741. }
  24742. _this2.trigger('data', event);
  24743. return;
  24744. }
  24745. match = /^#EXT-X-MAP:?(.*)$/.exec(newLine);
  24746. if (match) {
  24747. event = {
  24748. type: 'tag',
  24749. tagType: 'map'
  24750. };
  24751. if (match[1]) {
  24752. var attributes = parseAttributes(match[1]);
  24753. if (attributes.URI) {
  24754. event.uri = attributes.URI;
  24755. }
  24756. if (attributes.BYTERANGE) {
  24757. var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'),
  24758. length = _attributes$BYTERANGE[0],
  24759. offset = _attributes$BYTERANGE[1];
  24760. event.byterange = {};
  24761. if (length) {
  24762. event.byterange.length = parseInt(length, 10);
  24763. }
  24764. if (offset) {
  24765. event.byterange.offset = parseInt(offset, 10);
  24766. }
  24767. }
  24768. }
  24769. _this2.trigger('data', event);
  24770. return;
  24771. }
  24772. match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine);
  24773. if (match) {
  24774. event = {
  24775. type: 'tag',
  24776. tagType: 'stream-inf'
  24777. };
  24778. if (match[1]) {
  24779. event.attributes = parseAttributes(match[1]);
  24780. if (event.attributes.RESOLUTION) {
  24781. var split = event.attributes.RESOLUTION.split('x');
  24782. var resolution = {};
  24783. if (split[0]) {
  24784. resolution.width = parseInt(split[0], 10);
  24785. }
  24786. if (split[1]) {
  24787. resolution.height = parseInt(split[1], 10);
  24788. }
  24789. event.attributes.RESOLUTION = resolution;
  24790. }
  24791. if (event.attributes.BANDWIDTH) {
  24792. event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
  24793. }
  24794. if (event.attributes['PROGRAM-ID']) {
  24795. event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
  24796. }
  24797. }
  24798. _this2.trigger('data', event);
  24799. return;
  24800. }
  24801. match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine);
  24802. if (match) {
  24803. event = {
  24804. type: 'tag',
  24805. tagType: 'media'
  24806. };
  24807. if (match[1]) {
  24808. event.attributes = parseAttributes(match[1]);
  24809. }
  24810. _this2.trigger('data', event);
  24811. return;
  24812. }
  24813. match = /^#EXT-X-ENDLIST/.exec(newLine);
  24814. if (match) {
  24815. _this2.trigger('data', {
  24816. type: 'tag',
  24817. tagType: 'endlist'
  24818. });
  24819. return;
  24820. }
  24821. match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
  24822. if (match) {
  24823. _this2.trigger('data', {
  24824. type: 'tag',
  24825. tagType: 'discontinuity'
  24826. });
  24827. return;
  24828. }
  24829. match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine);
  24830. if (match) {
  24831. event = {
  24832. type: 'tag',
  24833. tagType: 'program-date-time'
  24834. };
  24835. if (match[1]) {
  24836. event.dateTimeString = match[1];
  24837. event.dateTimeObject = new Date(match[1]);
  24838. }
  24839. _this2.trigger('data', event);
  24840. return;
  24841. }
  24842. match = /^#EXT-X-KEY:?(.*)$/.exec(newLine);
  24843. if (match) {
  24844. event = {
  24845. type: 'tag',
  24846. tagType: 'key'
  24847. };
  24848. if (match[1]) {
  24849. event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array
  24850. if (event.attributes.IV) {
  24851. if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
  24852. event.attributes.IV = event.attributes.IV.substring(2);
  24853. }
  24854. event.attributes.IV = event.attributes.IV.match(/.{8}/g);
  24855. event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
  24856. event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
  24857. event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
  24858. event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
  24859. event.attributes.IV = new Uint32Array(event.attributes.IV);
  24860. }
  24861. }
  24862. _this2.trigger('data', event);
  24863. return;
  24864. }
  24865. match = /^#EXT-X-START:?(.*)$/.exec(newLine);
  24866. if (match) {
  24867. event = {
  24868. type: 'tag',
  24869. tagType: 'start'
  24870. };
  24871. if (match[1]) {
  24872. event.attributes = parseAttributes(match[1]);
  24873. event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
  24874. event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
  24875. }
  24876. _this2.trigger('data', event);
  24877. return;
  24878. }
  24879. match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine);
  24880. if (match) {
  24881. event = {
  24882. type: 'tag',
  24883. tagType: 'cue-out-cont'
  24884. };
  24885. if (match[1]) {
  24886. event.data = match[1];
  24887. } else {
  24888. event.data = '';
  24889. }
  24890. _this2.trigger('data', event);
  24891. return;
  24892. }
  24893. match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine);
  24894. if (match) {
  24895. event = {
  24896. type: 'tag',
  24897. tagType: 'cue-out'
  24898. };
  24899. if (match[1]) {
  24900. event.data = match[1];
  24901. } else {
  24902. event.data = '';
  24903. }
  24904. _this2.trigger('data', event);
  24905. return;
  24906. }
  24907. match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine);
  24908. if (match) {
  24909. event = {
  24910. type: 'tag',
  24911. tagType: 'cue-in'
  24912. };
  24913. if (match[1]) {
  24914. event.data = match[1];
  24915. } else {
  24916. event.data = '';
  24917. }
  24918. _this2.trigger('data', event);
  24919. return;
  24920. } // unknown tag type
  24921. _this2.trigger('data', {
  24922. type: 'tag',
  24923. data: newLine.slice(4)
  24924. });
  24925. });
  24926. };
  24927. /**
  24928. * Add a parser for custom headers
  24929. *
  24930. * @param {Object} options a map of options for the added parser
  24931. * @param {RegExp} options.expression a regular expression to match the custom header
  24932. * @param {string} options.customType the custom type to register to the output
  24933. * @param {Function} [options.dataParser] function to parse the line into an object
  24934. * @param {boolean} [options.segment] should tag data be attached to the segment object
  24935. */
  24936. _proto.addParser = function addParser(_ref) {
  24937. var _this3 = this;
  24938. var expression = _ref.expression,
  24939. customType = _ref.customType,
  24940. dataParser = _ref.dataParser,
  24941. segment = _ref.segment;
  24942. if (typeof dataParser !== 'function') {
  24943. dataParser = function dataParser(line) {
  24944. return line;
  24945. };
  24946. }
  24947. this.customParsers.push(function (line) {
  24948. var match = expression.exec(line);
  24949. if (match) {
  24950. _this3.trigger('data', {
  24951. type: 'custom',
  24952. data: dataParser(line),
  24953. customType: customType,
  24954. segment: segment
  24955. });
  24956. return true;
  24957. }
  24958. });
  24959. };
  24960. /**
  24961. * Add a custom header mapper
  24962. *
  24963. * @param {Object} options
  24964. * @param {RegExp} options.expression a regular expression to match the custom header
  24965. * @param {Function} options.map function to translate tag into a different tag
  24966. */
  24967. _proto.addTagMapper = function addTagMapper(_ref2) {
  24968. var expression = _ref2.expression,
  24969. map = _ref2.map;
  24970. var mapFn = function mapFn(line) {
  24971. if (expression.test(line)) {
  24972. return map(line);
  24973. }
  24974. return line;
  24975. };
  24976. this.tagMappers.push(mapFn);
  24977. };
  24978. return ParseStream;
  24979. }(Stream);
  24980. /**
  24981. * A parser for M3U8 files. The current interpretation of the input is
  24982. * exposed as a property `manifest` on parser objects. It's just two lines to
  24983. * create and parse a manifest once you have the contents available as a string:
  24984. *
  24985. * ```js
  24986. * var parser = new m3u8.Parser();
  24987. * parser.push(xhr.responseText);
  24988. * ```
  24989. *
  24990. * New input can later be applied to update the manifest object by calling
  24991. * `push` again.
  24992. *
  24993. * The parser attempts to create a usable manifest object even if the
  24994. * underlying input is somewhat nonsensical. It emits `info` and `warning`
  24995. * events during the parse if it encounters input that seems invalid or
  24996. * requires some property of the manifest object to be defaulted.
  24997. *
  24998. * @class Parser
  24999. * @extends Stream
  25000. */
  25001. var Parser =
  25002. /*#__PURE__*/
  25003. function (_Stream) {
  25004. _inheritsLoose$1(Parser, _Stream);
  25005. function Parser() {
  25006. var _this;
  25007. _this = _Stream.call(this) || this;
  25008. _this.lineStream = new LineStream();
  25009. _this.parseStream = new ParseStream();
  25010. _this.lineStream.pipe(_this.parseStream);
  25011. /* eslint-disable consistent-this */
  25012. var self = _assertThisInitialized$1(_assertThisInitialized$1(_this));
  25013. /* eslint-enable consistent-this */
  25014. var uris = [];
  25015. var currentUri = {}; // if specified, the active EXT-X-MAP definition
  25016. var currentMap; // if specified, the active decryption key
  25017. var _key;
  25018. var noop = function noop() {};
  25019. var defaultMediaGroups = {
  25020. 'AUDIO': {},
  25021. 'VIDEO': {},
  25022. 'CLOSED-CAPTIONS': {},
  25023. 'SUBTITLES': {}
  25024. }; // group segments into numbered timelines delineated by discontinuities
  25025. var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
  25026. _this.manifest = {
  25027. allowCache: true,
  25028. discontinuityStarts: [],
  25029. segments: []
  25030. }; // update the manifest with the m3u8 entry from the parse stream
  25031. _this.parseStream.on('data', function (entry) {
  25032. var mediaGroup;
  25033. var rendition;
  25034. ({
  25035. tag: function tag() {
  25036. // switch based on the tag type
  25037. (({
  25038. 'allow-cache': function allowCache() {
  25039. this.manifest.allowCache = entry.allowed;
  25040. if (!('allowed' in entry)) {
  25041. this.trigger('info', {
  25042. message: 'defaulting allowCache to YES'
  25043. });
  25044. this.manifest.allowCache = true;
  25045. }
  25046. },
  25047. byterange: function byterange() {
  25048. var byterange = {};
  25049. if ('length' in entry) {
  25050. currentUri.byterange = byterange;
  25051. byterange.length = entry.length;
  25052. if (!('offset' in entry)) {
  25053. this.trigger('info', {
  25054. message: 'defaulting offset to zero'
  25055. });
  25056. entry.offset = 0;
  25057. }
  25058. }
  25059. if ('offset' in entry) {
  25060. currentUri.byterange = byterange;
  25061. byterange.offset = entry.offset;
  25062. }
  25063. },
  25064. endlist: function endlist() {
  25065. this.manifest.endList = true;
  25066. },
  25067. inf: function inf() {
  25068. if (!('mediaSequence' in this.manifest)) {
  25069. this.manifest.mediaSequence = 0;
  25070. this.trigger('info', {
  25071. message: 'defaulting media sequence to zero'
  25072. });
  25073. }
  25074. if (!('discontinuitySequence' in this.manifest)) {
  25075. this.manifest.discontinuitySequence = 0;
  25076. this.trigger('info', {
  25077. message: 'defaulting discontinuity sequence to zero'
  25078. });
  25079. }
  25080. if (entry.duration > 0) {
  25081. currentUri.duration = entry.duration;
  25082. }
  25083. if (entry.duration === 0) {
  25084. currentUri.duration = 0.01;
  25085. this.trigger('info', {
  25086. message: 'updating zero segment duration to a small value'
  25087. });
  25088. }
  25089. this.manifest.segments = uris;
  25090. },
  25091. key: function key() {
  25092. if (!entry.attributes) {
  25093. this.trigger('warn', {
  25094. message: 'ignoring key declaration without attribute list'
  25095. });
  25096. return;
  25097. } // clear the active encryption key
  25098. if (entry.attributes.METHOD === 'NONE') {
  25099. _key = null;
  25100. return;
  25101. }
  25102. if (!entry.attributes.URI) {
  25103. this.trigger('warn', {
  25104. message: 'ignoring key declaration without URI'
  25105. });
  25106. return;
  25107. }
  25108. if (!entry.attributes.METHOD) {
  25109. this.trigger('warn', {
  25110. message: 'defaulting key method to AES-128'
  25111. });
  25112. } // setup an encryption key for upcoming segments
  25113. _key = {
  25114. method: entry.attributes.METHOD || 'AES-128',
  25115. uri: entry.attributes.URI
  25116. };
  25117. if (typeof entry.attributes.IV !== 'undefined') {
  25118. _key.iv = entry.attributes.IV;
  25119. }
  25120. },
  25121. 'media-sequence': function mediaSequence() {
  25122. if (!isFinite(entry.number)) {
  25123. this.trigger('warn', {
  25124. message: 'ignoring invalid media sequence: ' + entry.number
  25125. });
  25126. return;
  25127. }
  25128. this.manifest.mediaSequence = entry.number;
  25129. },
  25130. 'discontinuity-sequence': function discontinuitySequence() {
  25131. if (!isFinite(entry.number)) {
  25132. this.trigger('warn', {
  25133. message: 'ignoring invalid discontinuity sequence: ' + entry.number
  25134. });
  25135. return;
  25136. }
  25137. this.manifest.discontinuitySequence = entry.number;
  25138. currentTimeline = entry.number;
  25139. },
  25140. 'playlist-type': function playlistType() {
  25141. if (!/VOD|EVENT/.test(entry.playlistType)) {
  25142. this.trigger('warn', {
  25143. message: 'ignoring unknown playlist type: ' + entry.playlist
  25144. });
  25145. return;
  25146. }
  25147. this.manifest.playlistType = entry.playlistType;
  25148. },
  25149. map: function map() {
  25150. currentMap = {};
  25151. if (entry.uri) {
  25152. currentMap.uri = entry.uri;
  25153. }
  25154. if (entry.byterange) {
  25155. currentMap.byterange = entry.byterange;
  25156. }
  25157. },
  25158. 'stream-inf': function streamInf() {
  25159. this.manifest.playlists = uris;
  25160. this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
  25161. if (!entry.attributes) {
  25162. this.trigger('warn', {
  25163. message: 'ignoring empty stream-inf attributes'
  25164. });
  25165. return;
  25166. }
  25167. if (!currentUri.attributes) {
  25168. currentUri.attributes = {};
  25169. }
  25170. _extends$1(currentUri.attributes, entry.attributes);
  25171. },
  25172. media: function media() {
  25173. this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
  25174. if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
  25175. this.trigger('warn', {
  25176. message: 'ignoring incomplete or missing media group'
  25177. });
  25178. return;
  25179. } // find the media group, creating defaults as necessary
  25180. var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
  25181. mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
  25182. mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
  25183. rendition = {
  25184. default: /yes/i.test(entry.attributes.DEFAULT)
  25185. };
  25186. if (rendition.default) {
  25187. rendition.autoselect = true;
  25188. } else {
  25189. rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
  25190. }
  25191. if (entry.attributes.LANGUAGE) {
  25192. rendition.language = entry.attributes.LANGUAGE;
  25193. }
  25194. if (entry.attributes.URI) {
  25195. rendition.uri = entry.attributes.URI;
  25196. }
  25197. if (entry.attributes['INSTREAM-ID']) {
  25198. rendition.instreamId = entry.attributes['INSTREAM-ID'];
  25199. }
  25200. if (entry.attributes.CHARACTERISTICS) {
  25201. rendition.characteristics = entry.attributes.CHARACTERISTICS;
  25202. }
  25203. if (entry.attributes.FORCED) {
  25204. rendition.forced = /yes/i.test(entry.attributes.FORCED);
  25205. } // insert the new rendition
  25206. mediaGroup[entry.attributes.NAME] = rendition;
  25207. },
  25208. discontinuity: function discontinuity() {
  25209. currentTimeline += 1;
  25210. currentUri.discontinuity = true;
  25211. this.manifest.discontinuityStarts.push(uris.length);
  25212. },
  25213. 'program-date-time': function programDateTime() {
  25214. if (typeof this.manifest.dateTimeString === 'undefined') {
  25215. // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
  25216. // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
  25217. // to the manifest object
  25218. // TODO: Consider removing this in future major version
  25219. this.manifest.dateTimeString = entry.dateTimeString;
  25220. this.manifest.dateTimeObject = entry.dateTimeObject;
  25221. }
  25222. currentUri.dateTimeString = entry.dateTimeString;
  25223. currentUri.dateTimeObject = entry.dateTimeObject;
  25224. },
  25225. targetduration: function targetduration() {
  25226. if (!isFinite(entry.duration) || entry.duration < 0) {
  25227. this.trigger('warn', {
  25228. message: 'ignoring invalid target duration: ' + entry.duration
  25229. });
  25230. return;
  25231. }
  25232. this.manifest.targetDuration = entry.duration;
  25233. },
  25234. totalduration: function totalduration() {
  25235. if (!isFinite(entry.duration) || entry.duration < 0) {
  25236. this.trigger('warn', {
  25237. message: 'ignoring invalid total duration: ' + entry.duration
  25238. });
  25239. return;
  25240. }
  25241. this.manifest.totalDuration = entry.duration;
  25242. },
  25243. start: function start() {
  25244. if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
  25245. this.trigger('warn', {
  25246. message: 'ignoring start declaration without appropriate attribute list'
  25247. });
  25248. return;
  25249. }
  25250. this.manifest.start = {
  25251. timeOffset: entry.attributes['TIME-OFFSET'],
  25252. precise: entry.attributes.PRECISE
  25253. };
  25254. },
  25255. 'cue-out': function cueOut() {
  25256. currentUri.cueOut = entry.data;
  25257. },
  25258. 'cue-out-cont': function cueOutCont() {
  25259. currentUri.cueOutCont = entry.data;
  25260. },
  25261. 'cue-in': function cueIn() {
  25262. currentUri.cueIn = entry.data;
  25263. }
  25264. })[entry.tagType] || noop).call(self);
  25265. },
  25266. uri: function uri() {
  25267. currentUri.uri = entry.uri;
  25268. uris.push(currentUri); // if no explicit duration was declared, use the target duration
  25269. if (this.manifest.targetDuration && !('duration' in currentUri)) {
  25270. this.trigger('warn', {
  25271. message: 'defaulting segment duration to the target duration'
  25272. });
  25273. currentUri.duration = this.manifest.targetDuration;
  25274. } // annotate with encryption information, if necessary
  25275. if (_key) {
  25276. currentUri.key = _key;
  25277. }
  25278. currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
  25279. if (currentMap) {
  25280. currentUri.map = currentMap;
  25281. } // prepare for the next URI
  25282. currentUri = {};
  25283. },
  25284. comment: function comment() {// comments are not important for playback
  25285. },
  25286. custom: function custom() {
  25287. // if this is segment-level data attach the output to the segment
  25288. if (entry.segment) {
  25289. currentUri.custom = currentUri.custom || {};
  25290. currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
  25291. } else {
  25292. this.manifest.custom = this.manifest.custom || {};
  25293. this.manifest.custom[entry.customType] = entry.data;
  25294. }
  25295. }
  25296. })[entry.type].call(self);
  25297. });
  25298. return _this;
  25299. }
  25300. /**
  25301. * Parse the input string and update the manifest object.
  25302. *
  25303. * @param {string} chunk a potentially incomplete portion of the manifest
  25304. */
  25305. var _proto = Parser.prototype;
  25306. _proto.push = function push(chunk) {
  25307. this.lineStream.push(chunk);
  25308. };
  25309. /**
  25310. * Flush any remaining input. This can be handy if the last line of an M3U8
  25311. * manifest did not contain a trailing newline but the file has been
  25312. * completely received.
  25313. */
  25314. _proto.end = function end() {
  25315. // flush any buffered input
  25316. this.lineStream.push('\n');
  25317. };
  25318. /**
  25319. * Add an additional parser for non-standard tags
  25320. *
  25321. * @param {Object} options a map of options for the added parser
  25322. * @param {RegExp} options.expression a regular expression to match the custom header
  25323. * @param {string} options.type the type to register to the output
  25324. * @param {Function} [options.dataParser] function to parse the line into an object
  25325. * @param {boolean} [options.segment] should tag data be attached to the segment object
  25326. */
  25327. _proto.addParser = function addParser(options) {
  25328. this.parseStream.addParser(options);
  25329. };
  25330. /**
  25331. * Add a custom header mapper
  25332. *
  25333. * @param {Object} options
  25334. * @param {RegExp} options.expression a regular expression to match the custom header
  25335. * @param {Function} options.map function to translate tag into a different tag
  25336. */
  25337. _proto.addTagMapper = function addTagMapper(options) {
  25338. this.parseStream.addTagMapper(options);
  25339. };
  25340. return Parser;
  25341. }(Stream);
  25342. /*! @name mpd-parser @version 0.8.0 @license Apache-2.0 */
  25343. var isObject$1 = function isObject(obj) {
  25344. return !!obj && typeof obj === 'object';
  25345. };
  25346. var merge = function merge() {
  25347. for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) {
  25348. objects[_key] = arguments[_key];
  25349. }
  25350. return objects.reduce(function (result, source) {
  25351. Object.keys(source).forEach(function (key) {
  25352. if (Array.isArray(result[key]) && Array.isArray(source[key])) {
  25353. result[key] = result[key].concat(source[key]);
  25354. } else if (isObject$1(result[key]) && isObject$1(source[key])) {
  25355. result[key] = merge(result[key], source[key]);
  25356. } else {
  25357. result[key] = source[key];
  25358. }
  25359. });
  25360. return result;
  25361. }, {});
  25362. };
  25363. var values = function values(o) {
  25364. return Object.keys(o).map(function (k) {
  25365. return o[k];
  25366. });
  25367. };
  25368. var range = function range(start, end) {
  25369. var result = [];
  25370. for (var i = start; i < end; i++) {
  25371. result.push(i);
  25372. }
  25373. return result;
  25374. };
  25375. var flatten = function flatten(lists) {
  25376. return lists.reduce(function (x, y) {
  25377. return x.concat(y);
  25378. }, []);
  25379. };
  25380. var from = function from(list) {
  25381. if (!list.length) {
  25382. return [];
  25383. }
  25384. var result = [];
  25385. for (var i = 0; i < list.length; i++) {
  25386. result.push(list[i]);
  25387. }
  25388. return result;
  25389. };
  25390. var findIndexes = function findIndexes(l, key) {
  25391. return l.reduce(function (a, e, i) {
  25392. if (e[key]) {
  25393. a.push(i);
  25394. }
  25395. return a;
  25396. }, []);
  25397. };
  25398. var errors = {
  25399. INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
  25400. DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
  25401. DASH_INVALID_XML: 'DASH_INVALID_XML',
  25402. NO_BASE_URL: 'NO_BASE_URL',
  25403. MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
  25404. SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
  25405. UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
  25406. };
  25407. var commonjsGlobal$1 = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
  25408. function createCommonjsModule$1(fn, module) {
  25409. return module = {
  25410. exports: {}
  25411. }, fn(module, module.exports), module.exports;
  25412. }
  25413. var urlToolkit$1 = createCommonjsModule$1(function (module, exports) {
  25414. // see https://tools.ietf.org/html/rfc1808
  25415. /* jshint ignore:start */
  25416. (function (root) {
  25417. /* jshint ignore:end */
  25418. var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
  25419. var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
  25420. var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
  25421. var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
  25422. var URLToolkit = {
  25423. // jshint ignore:line
  25424. // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
  25425. // E.g
  25426. // With opts.alwaysNormalize = false (default, spec compliant)
  25427. // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
  25428. // With opts.alwaysNormalize = true (not spec compliant)
  25429. // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
  25430. buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
  25431. opts = opts || {}; // remove any remaining space and CRLF
  25432. baseURL = baseURL.trim();
  25433. relativeURL = relativeURL.trim();
  25434. if (!relativeURL) {
  25435. // 2a) If the embedded URL is entirely empty, it inherits the
  25436. // entire base URL (i.e., is set equal to the base URL)
  25437. // and we are done.
  25438. if (!opts.alwaysNormalize) {
  25439. return baseURL;
  25440. }
  25441. var basePartsForNormalise = URLToolkit.parseURL(baseURL);
  25442. if (!basePartsForNormalise) {
  25443. throw new Error('Error trying to parse base URL.');
  25444. }
  25445. basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
  25446. return URLToolkit.buildURLFromParts(basePartsForNormalise);
  25447. }
  25448. var relativeParts = URLToolkit.parseURL(relativeURL);
  25449. if (!relativeParts) {
  25450. throw new Error('Error trying to parse relative URL.');
  25451. }
  25452. if (relativeParts.scheme) {
  25453. // 2b) If the embedded URL starts with a scheme name, it is
  25454. // interpreted as an absolute URL and we are done.
  25455. if (!opts.alwaysNormalize) {
  25456. return relativeURL;
  25457. }
  25458. relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
  25459. return URLToolkit.buildURLFromParts(relativeParts);
  25460. }
  25461. var baseParts = URLToolkit.parseURL(baseURL);
  25462. if (!baseParts) {
  25463. throw new Error('Error trying to parse base URL.');
  25464. }
  25465. if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
  25466. // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
  25467. // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
  25468. var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
  25469. baseParts.netLoc = pathParts[1];
  25470. baseParts.path = pathParts[2];
  25471. }
  25472. if (baseParts.netLoc && !baseParts.path) {
  25473. baseParts.path = '/';
  25474. }
  25475. var builtParts = {
  25476. // 2c) Otherwise, the embedded URL inherits the scheme of
  25477. // the base URL.
  25478. scheme: baseParts.scheme,
  25479. netLoc: relativeParts.netLoc,
  25480. path: null,
  25481. params: relativeParts.params,
  25482. query: relativeParts.query,
  25483. fragment: relativeParts.fragment
  25484. };
  25485. if (!relativeParts.netLoc) {
  25486. // 3) If the embedded URL's <net_loc> is non-empty, we skip to
  25487. // Step 7. Otherwise, the embedded URL inherits the <net_loc>
  25488. // (if any) of the base URL.
  25489. builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
  25490. // path is not relative and we skip to Step 7.
  25491. if (relativeParts.path[0] !== '/') {
  25492. if (!relativeParts.path) {
  25493. // 5) If the embedded URL path is empty (and not preceded by a
  25494. // slash), then the embedded URL inherits the base URL path
  25495. builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
  25496. // step 7; otherwise, it inherits the <params> of the base
  25497. // URL (if any) and
  25498. if (!relativeParts.params) {
  25499. builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
  25500. // step 7; otherwise, it inherits the <query> of the base
  25501. // URL (if any) and we skip to step 7.
  25502. if (!relativeParts.query) {
  25503. builtParts.query = baseParts.query;
  25504. }
  25505. }
  25506. } else {
  25507. // 6) The last segment of the base URL's path (anything
  25508. // following the rightmost slash "/", or the entire path if no
  25509. // slash is present) is removed and the embedded URL's path is
  25510. // appended in its place.
  25511. var baseURLPath = baseParts.path;
  25512. var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
  25513. builtParts.path = URLToolkit.normalizePath(newPath);
  25514. }
  25515. }
  25516. }
  25517. if (builtParts.path === null) {
  25518. builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
  25519. }
  25520. return URLToolkit.buildURLFromParts(builtParts);
  25521. },
  25522. parseURL: function parseURL(url) {
  25523. var parts = URL_REGEX.exec(url);
  25524. if (!parts) {
  25525. return null;
  25526. }
  25527. return {
  25528. scheme: parts[1] || '',
  25529. netLoc: parts[2] || '',
  25530. path: parts[3] || '',
  25531. params: parts[4] || '',
  25532. query: parts[5] || '',
  25533. fragment: parts[6] || ''
  25534. };
  25535. },
  25536. normalizePath: function normalizePath(path) {
  25537. // The following operations are
  25538. // then applied, in order, to the new path:
  25539. // 6a) All occurrences of "./", where "." is a complete path
  25540. // segment, are removed.
  25541. // 6b) If the path ends with "." as a complete path segment,
  25542. // that "." is removed.
  25543. path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
  25544. // complete path segment not equal to "..", are removed.
  25545. // Removal of these path segments is performed iteratively,
  25546. // removing the leftmost matching pattern on each iteration,
  25547. // until no matching pattern remains.
  25548. // 6d) If the path ends with "<segment>/..", where <segment> is a
  25549. // complete path segment not equal to "..", that
  25550. // "<segment>/.." is removed.
  25551. while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
  25552. return path.split('').reverse().join('');
  25553. },
  25554. buildURLFromParts: function buildURLFromParts(parts) {
  25555. return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
  25556. }
  25557. };
  25558. /* jshint ignore:start */
  25559. module.exports = URLToolkit;
  25560. })(commonjsGlobal$1);
  25561. /* jshint ignore:end */
  25562. });
  25563. var resolveUrl = function resolveUrl(baseUrl, relativeUrl) {
  25564. // return early if we don't need to resolve
  25565. if (/^[a-z]+:/i.test(relativeUrl)) {
  25566. return relativeUrl;
  25567. } // if the base URL is relative then combine with the current location
  25568. if (!/\/\//i.test(baseUrl)) {
  25569. baseUrl = urlToolkit$1.buildAbsoluteURL(window$1.location.href, baseUrl);
  25570. }
  25571. return urlToolkit$1.buildAbsoluteURL(baseUrl, relativeUrl);
  25572. };
  25573. /**
  25574. * @typedef {Object} SingleUri
  25575. * @property {string} uri - relative location of segment
  25576. * @property {string} resolvedUri - resolved location of segment
  25577. * @property {Object} byterange - Object containing information on how to make byte range
  25578. * requests following byte-range-spec per RFC2616.
  25579. * @property {String} byterange.length - length of range request
  25580. * @property {String} byterange.offset - byte offset of range request
  25581. *
  25582. * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
  25583. */
  25584. /**
  25585. * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
  25586. * that conforms to how m3u8-parser is structured
  25587. *
  25588. * @see https://github.com/videojs/m3u8-parser
  25589. *
  25590. * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
  25591. * @param {string} source - source url for segment
  25592. * @param {string} range - optional range used for range calls,
  25593. * follows RFC 2616, Clause 14.35.1
  25594. * @return {SingleUri} full segment information transformed into a format similar
  25595. * to m3u8-parser
  25596. */
  25597. var urlTypeToSegment = function urlTypeToSegment(_ref) {
  25598. var _ref$baseUrl = _ref.baseUrl,
  25599. baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl,
  25600. _ref$source = _ref.source,
  25601. source = _ref$source === void 0 ? '' : _ref$source,
  25602. _ref$range = _ref.range,
  25603. range = _ref$range === void 0 ? '' : _ref$range,
  25604. _ref$indexRange = _ref.indexRange,
  25605. indexRange = _ref$indexRange === void 0 ? '' : _ref$indexRange;
  25606. var segment = {
  25607. uri: source,
  25608. resolvedUri: resolveUrl(baseUrl || '', source)
  25609. };
  25610. if (range || indexRange) {
  25611. var rangeStr = range ? range : indexRange;
  25612. var ranges = rangeStr.split('-');
  25613. var startRange = parseInt(ranges[0], 10);
  25614. var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to
  25615. // RFC 2616, Clause 14.35.1
  25616. segment.byterange = {
  25617. length: endRange - startRange + 1,
  25618. offset: startRange
  25619. };
  25620. }
  25621. return segment;
  25622. };
  25623. var byteRangeToString = function byteRangeToString(byterange) {
  25624. // `endRange` is one less than `offset + length` because the HTTP range
  25625. // header uses inclusive ranges
  25626. var endRange = byterange.offset + byterange.length - 1;
  25627. return byterange.offset + "-" + endRange;
  25628. };
  25629. /**
  25630. * Functions for calculating the range of available segments in static and dynamic
  25631. * manifests.
  25632. */
  25633. var segmentRange = {
  25634. /**
  25635. * Returns the entire range of available segments for a static MPD
  25636. *
  25637. * @param {Object} attributes
  25638. * Inheritied MPD attributes
  25639. * @return {{ start: number, end: number }}
  25640. * The start and end numbers for available segments
  25641. */
  25642. static: function _static(attributes) {
  25643. var duration = attributes.duration,
  25644. _attributes$timescale = attributes.timescale,
  25645. timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
  25646. sourceDuration = attributes.sourceDuration;
  25647. return {
  25648. start: 0,
  25649. end: Math.ceil(sourceDuration / (duration / timescale))
  25650. };
  25651. },
  25652. /**
  25653. * Returns the current live window range of available segments for a dynamic MPD
  25654. *
  25655. * @param {Object} attributes
  25656. * Inheritied MPD attributes
  25657. * @return {{ start: number, end: number }}
  25658. * The start and end numbers for available segments
  25659. */
  25660. dynamic: function dynamic(attributes) {
  25661. var NOW = attributes.NOW,
  25662. clientOffset = attributes.clientOffset,
  25663. availabilityStartTime = attributes.availabilityStartTime,
  25664. _attributes$timescale2 = attributes.timescale,
  25665. timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
  25666. duration = attributes.duration,
  25667. _attributes$start = attributes.start,
  25668. start = _attributes$start === void 0 ? 0 : _attributes$start,
  25669. _attributes$minimumUp = attributes.minimumUpdatePeriod,
  25670. minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp,
  25671. _attributes$timeShift = attributes.timeShiftBufferDepth,
  25672. timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift;
  25673. var now = (NOW + clientOffset) / 1000;
  25674. var periodStartWC = availabilityStartTime + start;
  25675. var periodEndWC = now + minimumUpdatePeriod;
  25676. var periodDuration = periodEndWC - periodStartWC;
  25677. var segmentCount = Math.ceil(periodDuration * timescale / duration);
  25678. var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
  25679. var availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
  25680. return {
  25681. start: Math.max(0, availableStart),
  25682. end: Math.min(segmentCount, availableEnd)
  25683. };
  25684. }
  25685. };
  25686. /**
  25687. * Maps a range of numbers to objects with information needed to build the corresponding
  25688. * segment list
  25689. *
  25690. * @name toSegmentsCallback
  25691. * @function
  25692. * @param {number} number
  25693. * Number of the segment
  25694. * @param {number} index
  25695. * Index of the number in the range list
  25696. * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
  25697. * Object with segment timing and duration info
  25698. */
  25699. /**
  25700. * Returns a callback for Array.prototype.map for mapping a range of numbers to
  25701. * information needed to build the segment list.
  25702. *
  25703. * @param {Object} attributes
  25704. * Inherited MPD attributes
  25705. * @return {toSegmentsCallback}
  25706. * Callback map function
  25707. */
  25708. var toSegments = function toSegments(attributes) {
  25709. return function (number, index) {
  25710. var duration = attributes.duration,
  25711. _attributes$timescale3 = attributes.timescale,
  25712. timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3,
  25713. periodIndex = attributes.periodIndex,
  25714. _attributes$startNumb = attributes.startNumber,
  25715. startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb;
  25716. return {
  25717. number: startNumber + number,
  25718. duration: duration / timescale,
  25719. timeline: periodIndex,
  25720. time: index * duration
  25721. };
  25722. };
  25723. };
  25724. /**
  25725. * Returns a list of objects containing segment timing and duration info used for
  25726. * building the list of segments. This uses the @duration attribute specified
  25727. * in the MPD manifest to derive the range of segments.
  25728. *
  25729. * @param {Object} attributes
  25730. * Inherited MPD attributes
  25731. * @return {{number: number, duration: number, time: number, timeline: number}[]}
  25732. * List of Objects with segment timing and duration info
  25733. */
  25734. var parseByDuration = function parseByDuration(attributes) {
  25735. var _attributes$type = attributes.type,
  25736. type = _attributes$type === void 0 ? 'static' : _attributes$type,
  25737. duration = attributes.duration,
  25738. _attributes$timescale4 = attributes.timescale,
  25739. timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4,
  25740. sourceDuration = attributes.sourceDuration;
  25741. var _segmentRange$type = segmentRange[type](attributes),
  25742. start = _segmentRange$type.start,
  25743. end = _segmentRange$type.end;
  25744. var segments = range(start, end).map(toSegments(attributes));
  25745. if (type === 'static') {
  25746. var index = segments.length - 1; // final segment may be less than full segment duration
  25747. segments[index].duration = sourceDuration - duration / timescale * index;
  25748. }
  25749. return segments;
  25750. };
  25751. /**
  25752. * Translates SegmentBase into a set of segments.
  25753. * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
  25754. * node should be translated into segment.
  25755. *
  25756. * @param {Object} attributes
  25757. * Object containing all inherited attributes from parent elements with attribute
  25758. * names as keys
  25759. * @return {Object.<Array>} list of segments
  25760. */
  25761. var segmentsFromBase = function segmentsFromBase(attributes) {
  25762. var baseUrl = attributes.baseUrl,
  25763. _attributes$initializ = attributes.initialization,
  25764. initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ,
  25765. sourceDuration = attributes.sourceDuration,
  25766. _attributes$timescale = attributes.timescale,
  25767. timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
  25768. _attributes$indexRang = attributes.indexRange,
  25769. indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang,
  25770. duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
  25771. if (!baseUrl) {
  25772. throw new Error(errors.NO_BASE_URL);
  25773. }
  25774. var initSegment = urlTypeToSegment({
  25775. baseUrl: baseUrl,
  25776. source: initialization.sourceURL,
  25777. range: initialization.range
  25778. });
  25779. var segment = urlTypeToSegment({
  25780. baseUrl: baseUrl,
  25781. source: baseUrl,
  25782. indexRange: indexRange
  25783. });
  25784. segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
  25785. // (since SegmentBase is only for one total segment)
  25786. if (duration) {
  25787. var segmentTimeInfo = parseByDuration(attributes);
  25788. if (segmentTimeInfo.length) {
  25789. segment.duration = segmentTimeInfo[0].duration;
  25790. segment.timeline = segmentTimeInfo[0].timeline;
  25791. }
  25792. } else if (sourceDuration) {
  25793. segment.duration = sourceDuration / timescale;
  25794. segment.timeline = 0;
  25795. } // This is used for mediaSequence
  25796. segment.number = 0;
  25797. return [segment];
  25798. };
  25799. /**
  25800. * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist
  25801. * according to the sidx information given.
  25802. *
  25803. * playlist.sidx has metadadata about the sidx where-as the sidx param
  25804. * is the parsed sidx box itself.
  25805. *
  25806. * @param {Object} playlist the playlist to update the sidx information for
  25807. * @param {Object} sidx the parsed sidx box
  25808. * @return {Object} the playlist object with the updated sidx information
  25809. */
  25810. var addSegmentsToPlaylist = function addSegmentsToPlaylist(playlist, sidx, baseUrl) {
  25811. // Retain init segment information
  25812. var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial master manifest parsing
  25813. var sourceDuration = playlist.sidx.duration; // Retain source timeline
  25814. var timeline = playlist.timeline || 0;
  25815. var sidxByteRange = playlist.sidx.byterange;
  25816. var sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx
  25817. var timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
  25818. var mediaReferences = sidx.references.filter(function (r) {
  25819. return r.referenceType !== 1;
  25820. });
  25821. var segments = []; // firstOffset is the offset from the end of the sidx box
  25822. var startIndex = sidxEnd + sidx.firstOffset;
  25823. for (var i = 0; i < mediaReferences.length; i++) {
  25824. var reference = sidx.references[i]; // size of the referenced (sub)segment
  25825. var size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale
  25826. // this will be converted to seconds when generating segments
  25827. var duration = reference.subsegmentDuration; // should be an inclusive range
  25828. var endIndex = startIndex + size - 1;
  25829. var indexRange = startIndex + "-" + endIndex;
  25830. var attributes = {
  25831. baseUrl: baseUrl,
  25832. timescale: timescale,
  25833. timeline: timeline,
  25834. // this is used in parseByDuration
  25835. periodIndex: timeline,
  25836. duration: duration,
  25837. sourceDuration: sourceDuration,
  25838. indexRange: indexRange
  25839. };
  25840. var segment = segmentsFromBase(attributes)[0];
  25841. if (initSegment) {
  25842. segment.map = initSegment;
  25843. }
  25844. segments.push(segment);
  25845. startIndex += size;
  25846. }
  25847. playlist.segments = segments;
  25848. return playlist;
  25849. };
  25850. var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) {
  25851. var mergedPlaylists = values(playlists.reduce(function (acc, playlist) {
  25852. // assuming playlist IDs are the same across periods
  25853. // TODO: handle multiperiod where representation sets are not the same
  25854. // across periods
  25855. var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first
  25856. if (acc[name]) {
  25857. var _acc$name$segments; // first segment of subsequent periods signal a discontinuity
  25858. if (playlist.segments[0]) {
  25859. playlist.segments[0].discontinuity = true;
  25860. }
  25861. (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content
  25862. // has the same contentProtection
  25863. if (playlist.attributes.contentProtection) {
  25864. acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
  25865. }
  25866. } else {
  25867. // first Period
  25868. acc[name] = playlist;
  25869. }
  25870. return acc;
  25871. }, {}));
  25872. return mergedPlaylists.map(function (playlist) {
  25873. playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity');
  25874. return playlist;
  25875. });
  25876. };
  25877. var addSegmentInfoFromSidx = function addSegmentInfoFromSidx(playlists, sidxMapping) {
  25878. if (sidxMapping === void 0) {
  25879. sidxMapping = {};
  25880. }
  25881. if (!Object.keys(sidxMapping).length) {
  25882. return playlists;
  25883. }
  25884. for (var i in playlists) {
  25885. var playlist = playlists[i];
  25886. var sidxKey = playlist.sidx.uri + '-' + byteRangeToString(playlist.sidx.byterange);
  25887. var sidxMatch = sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
  25888. if (playlist.sidx && sidxMatch) {
  25889. addSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri);
  25890. }
  25891. }
  25892. return playlists;
  25893. };
  25894. var formatAudioPlaylist = function formatAudioPlaylist(_ref) {
  25895. var _attributes;
  25896. var attributes = _ref.attributes,
  25897. segments = _ref.segments,
  25898. sidx = _ref.sidx;
  25899. var playlist = {
  25900. attributes: (_attributes = {
  25901. NAME: attributes.id,
  25902. BANDWIDTH: attributes.bandwidth,
  25903. CODECS: attributes.codecs
  25904. }, _attributes['PROGRAM-ID'] = 1, _attributes),
  25905. uri: '',
  25906. endList: (attributes.type || 'static') === 'static',
  25907. timeline: attributes.periodIndex,
  25908. resolvedUri: '',
  25909. targetDuration: attributes.duration,
  25910. segments: segments,
  25911. mediaSequence: segments.length ? segments[0].number : 1
  25912. };
  25913. if (attributes.contentProtection) {
  25914. playlist.contentProtection = attributes.contentProtection;
  25915. }
  25916. if (sidx) {
  25917. playlist.sidx = sidx;
  25918. }
  25919. return playlist;
  25920. };
  25921. var formatVttPlaylist = function formatVttPlaylist(_ref2) {
  25922. var _attributes2;
  25923. var attributes = _ref2.attributes,
  25924. segments = _ref2.segments;
  25925. if (typeof segments === 'undefined') {
  25926. // vtt tracks may use single file in BaseURL
  25927. segments = [{
  25928. uri: attributes.baseUrl,
  25929. timeline: attributes.periodIndex,
  25930. resolvedUri: attributes.baseUrl || '',
  25931. duration: attributes.sourceDuration,
  25932. number: 0
  25933. }]; // targetDuration should be the same duration as the only segment
  25934. attributes.duration = attributes.sourceDuration;
  25935. }
  25936. return {
  25937. attributes: (_attributes2 = {
  25938. NAME: attributes.id,
  25939. BANDWIDTH: attributes.bandwidth
  25940. }, _attributes2['PROGRAM-ID'] = 1, _attributes2),
  25941. uri: '',
  25942. endList: (attributes.type || 'static') === 'static',
  25943. timeline: attributes.periodIndex,
  25944. resolvedUri: attributes.baseUrl || '',
  25945. targetDuration: attributes.duration,
  25946. segments: segments,
  25947. mediaSequence: segments.length ? segments[0].number : 1
  25948. };
  25949. };
  25950. var organizeAudioPlaylists = function organizeAudioPlaylists(playlists, sidxMapping) {
  25951. if (sidxMapping === void 0) {
  25952. sidxMapping = {};
  25953. }
  25954. var mainPlaylist;
  25955. var formattedPlaylists = playlists.reduce(function (a, playlist) {
  25956. var role = playlist.attributes.role && playlist.attributes.role.value || '';
  25957. var language = playlist.attributes.lang || '';
  25958. var label = 'main';
  25959. if (language) {
  25960. var roleLabel = role ? " (" + role + ")" : '';
  25961. label = "" + playlist.attributes.lang + roleLabel;
  25962. } // skip if we already have the highest quality audio for a language
  25963. if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) {
  25964. return a;
  25965. }
  25966. a[label] = {
  25967. language: language,
  25968. autoselect: true,
  25969. default: role === 'main',
  25970. playlists: addSegmentInfoFromSidx([formatAudioPlaylist(playlist)], sidxMapping),
  25971. uri: ''
  25972. };
  25973. if (typeof mainPlaylist === 'undefined' && role === 'main') {
  25974. mainPlaylist = playlist;
  25975. mainPlaylist.default = true;
  25976. }
  25977. return a;
  25978. }, {}); // if no playlists have role "main", mark the first as main
  25979. if (!mainPlaylist) {
  25980. var firstLabel = Object.keys(formattedPlaylists)[0];
  25981. formattedPlaylists[firstLabel].default = true;
  25982. }
  25983. return formattedPlaylists;
  25984. };
  25985. var organizeVttPlaylists = function organizeVttPlaylists(playlists, sidxMapping) {
  25986. if (sidxMapping === void 0) {
  25987. sidxMapping = {};
  25988. }
  25989. return playlists.reduce(function (a, playlist) {
  25990. var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles
  25991. if (a[label]) {
  25992. return a;
  25993. }
  25994. a[label] = {
  25995. language: label,
  25996. default: false,
  25997. autoselect: false,
  25998. playlists: addSegmentInfoFromSidx([formatVttPlaylist(playlist)], sidxMapping),
  25999. uri: ''
  26000. };
  26001. return a;
  26002. }, {});
  26003. };
  26004. var formatVideoPlaylist = function formatVideoPlaylist(_ref3) {
  26005. var _attributes3;
  26006. var attributes = _ref3.attributes,
  26007. segments = _ref3.segments,
  26008. sidx = _ref3.sidx;
  26009. var playlist = {
  26010. attributes: (_attributes3 = {
  26011. NAME: attributes.id,
  26012. AUDIO: 'audio',
  26013. SUBTITLES: 'subs',
  26014. RESOLUTION: {
  26015. width: attributes.width,
  26016. height: attributes.height
  26017. },
  26018. CODECS: attributes.codecs,
  26019. BANDWIDTH: attributes.bandwidth
  26020. }, _attributes3['PROGRAM-ID'] = 1, _attributes3),
  26021. uri: '',
  26022. endList: (attributes.type || 'static') === 'static',
  26023. timeline: attributes.periodIndex,
  26024. resolvedUri: '',
  26025. targetDuration: attributes.duration,
  26026. segments: segments,
  26027. mediaSequence: segments.length ? segments[0].number : 1
  26028. };
  26029. if (attributes.contentProtection) {
  26030. playlist.contentProtection = attributes.contentProtection;
  26031. }
  26032. if (sidx) {
  26033. playlist.sidx = sidx;
  26034. }
  26035. return playlist;
  26036. };
  26037. var toM3u8 = function toM3u8(dashPlaylists, sidxMapping) {
  26038. var _mediaGroups;
  26039. if (sidxMapping === void 0) {
  26040. sidxMapping = {};
  26041. }
  26042. if (!dashPlaylists.length) {
  26043. return {};
  26044. } // grab all master attributes
  26045. var _dashPlaylists$0$attr = dashPlaylists[0].attributes,
  26046. duration = _dashPlaylists$0$attr.sourceDuration,
  26047. _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.minimumUpdatePeriod,
  26048. minimumUpdatePeriod = _dashPlaylists$0$attr2 === void 0 ? 0 : _dashPlaylists$0$attr2;
  26049. var videoOnly = function videoOnly(_ref4) {
  26050. var attributes = _ref4.attributes;
  26051. return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video';
  26052. };
  26053. var audioOnly = function audioOnly(_ref5) {
  26054. var attributes = _ref5.attributes;
  26055. return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio';
  26056. };
  26057. var vttOnly = function vttOnly(_ref6) {
  26058. var attributes = _ref6.attributes;
  26059. return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
  26060. };
  26061. var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
  26062. var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
  26063. var vttPlaylists = dashPlaylists.filter(vttOnly);
  26064. var master = {
  26065. allowCache: true,
  26066. discontinuityStarts: [],
  26067. segments: [],
  26068. endList: true,
  26069. mediaGroups: (_mediaGroups = {
  26070. AUDIO: {},
  26071. VIDEO: {}
  26072. }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups),
  26073. uri: '',
  26074. duration: duration,
  26075. playlists: addSegmentInfoFromSidx(videoPlaylists, sidxMapping),
  26076. minimumUpdatePeriod: minimumUpdatePeriod * 1000
  26077. };
  26078. if (audioPlaylists.length) {
  26079. master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping);
  26080. }
  26081. if (vttPlaylists.length) {
  26082. master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping);
  26083. }
  26084. return master;
  26085. };
  26086. /**
  26087. * Calculates the R (repetition) value for a live stream (for the final segment
  26088. * in a manifest where the r value is negative 1)
  26089. *
  26090. * @param {Object} attributes
  26091. * Object containing all inherited attributes from parent elements with attribute
  26092. * names as keys
  26093. * @param {number} time
  26094. * current time (typically the total time up until the final segment)
  26095. * @param {number} duration
  26096. * duration property for the given <S />
  26097. *
  26098. * @return {number}
  26099. * R value to reach the end of the given period
  26100. */
  26101. var getLiveRValue = function getLiveRValue(attributes, time, duration) {
  26102. var NOW = attributes.NOW,
  26103. clientOffset = attributes.clientOffset,
  26104. availabilityStartTime = attributes.availabilityStartTime,
  26105. _attributes$timescale = attributes.timescale,
  26106. timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
  26107. _attributes$start = attributes.start,
  26108. start = _attributes$start === void 0 ? 0 : _attributes$start,
  26109. _attributes$minimumUp = attributes.minimumUpdatePeriod,
  26110. minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp;
  26111. var now = (NOW + clientOffset) / 1000;
  26112. var periodStartWC = availabilityStartTime + start;
  26113. var periodEndWC = now + minimumUpdatePeriod;
  26114. var periodDuration = periodEndWC - periodStartWC;
  26115. return Math.ceil((periodDuration * timescale - time) / duration);
  26116. };
  26117. /**
  26118. * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
  26119. * timing and duration
  26120. *
  26121. * @param {Object} attributes
  26122. * Object containing all inherited attributes from parent elements with attribute
  26123. * names as keys
  26124. * @param {Object[]} segmentTimeline
  26125. * List of objects representing the attributes of each S element contained within
  26126. *
  26127. * @return {{number: number, duration: number, time: number, timeline: number}[]}
  26128. * List of Objects with segment timing and duration info
  26129. */
  26130. var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) {
  26131. var _attributes$type = attributes.type,
  26132. type = _attributes$type === void 0 ? 'static' : _attributes$type,
  26133. _attributes$minimumUp2 = attributes.minimumUpdatePeriod,
  26134. minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2,
  26135. _attributes$media = attributes.media,
  26136. media = _attributes$media === void 0 ? '' : _attributes$media,
  26137. sourceDuration = attributes.sourceDuration,
  26138. _attributes$timescale2 = attributes.timescale,
  26139. timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
  26140. _attributes$startNumb = attributes.startNumber,
  26141. startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb,
  26142. timeline = attributes.periodIndex;
  26143. var segments = [];
  26144. var time = -1;
  26145. for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
  26146. var S = segmentTimeline[sIndex];
  26147. var duration = S.d;
  26148. var repeat = S.r || 0;
  26149. var segmentTime = S.t || 0;
  26150. if (time < 0) {
  26151. // first segment
  26152. time = segmentTime;
  26153. }
  26154. if (segmentTime && segmentTime > time) {
  26155. // discontinuity
  26156. // TODO: How to handle this type of discontinuity
  26157. // timeline++ here would treat it like HLS discontuity and content would
  26158. // get appended without gap
  26159. // E.G.
  26160. // <S t="0" d="1" />
  26161. // <S d="1" />
  26162. // <S d="1" />
  26163. // <S t="5" d="1" />
  26164. // would have $Time$ values of [0, 1, 2, 5]
  26165. // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
  26166. // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
  26167. // does the value of sourceDuration consider this when calculating arbitrary
  26168. // negative @r repeat value?
  26169. // E.G. Same elements as above with this added at the end
  26170. // <S d="1" r="-1" />
  26171. // with a sourceDuration of 10
  26172. // Would the 2 gaps be included in the time duration calculations resulting in
  26173. // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
  26174. // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
  26175. time = segmentTime;
  26176. }
  26177. var count = void 0;
  26178. if (repeat < 0) {
  26179. var nextS = sIndex + 1;
  26180. if (nextS === segmentTimeline.length) {
  26181. // last segment
  26182. if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
  26183. count = getLiveRValue(attributes, time, duration);
  26184. } else {
  26185. // TODO: This may be incorrect depending on conclusion of TODO above
  26186. count = (sourceDuration * timescale - time) / duration;
  26187. }
  26188. } else {
  26189. count = (segmentTimeline[nextS].t - time) / duration;
  26190. }
  26191. } else {
  26192. count = repeat + 1;
  26193. }
  26194. var end = startNumber + segments.length + count;
  26195. var number = startNumber + segments.length;
  26196. while (number < end) {
  26197. segments.push({
  26198. number: number,
  26199. duration: duration / timescale,
  26200. time: time,
  26201. timeline: timeline
  26202. });
  26203. time += duration;
  26204. number++;
  26205. }
  26206. }
  26207. return segments;
  26208. };
  26209. var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
  26210. /**
  26211. * Replaces template identifiers with corresponding values. To be used as the callback
  26212. * for String.prototype.replace
  26213. *
  26214. * @name replaceCallback
  26215. * @function
  26216. * @param {string} match
  26217. * Entire match of identifier
  26218. * @param {string} identifier
  26219. * Name of matched identifier
  26220. * @param {string} format
  26221. * Format tag string. Its presence indicates that padding is expected
  26222. * @param {string} width
  26223. * Desired length of the replaced value. Values less than this width shall be left
  26224. * zero padded
  26225. * @return {string}
  26226. * Replacement for the matched identifier
  26227. */
  26228. /**
  26229. * Returns a function to be used as a callback for String.prototype.replace to replace
  26230. * template identifiers
  26231. *
  26232. * @param {Obect} values
  26233. * Object containing values that shall be used to replace known identifiers
  26234. * @param {number} values.RepresentationID
  26235. * Value of the Representation@id attribute
  26236. * @param {number} values.Number
  26237. * Number of the corresponding segment
  26238. * @param {number} values.Bandwidth
  26239. * Value of the Representation@bandwidth attribute.
  26240. * @param {number} values.Time
  26241. * Timestamp value of the corresponding segment
  26242. * @return {replaceCallback}
  26243. * Callback to be used with String.prototype.replace to replace identifiers
  26244. */
  26245. var identifierReplacement = function identifierReplacement(values) {
  26246. return function (match, identifier, format, width) {
  26247. if (match === '$$') {
  26248. // escape sequence
  26249. return '$';
  26250. }
  26251. if (typeof values[identifier] === 'undefined') {
  26252. return match;
  26253. }
  26254. var value = '' + values[identifier];
  26255. if (identifier === 'RepresentationID') {
  26256. // Format tag shall not be present with RepresentationID
  26257. return value;
  26258. }
  26259. if (!format) {
  26260. width = 1;
  26261. } else {
  26262. width = parseInt(width, 10);
  26263. }
  26264. if (value.length >= width) {
  26265. return value;
  26266. }
  26267. return "" + new Array(width - value.length + 1).join('0') + value;
  26268. };
  26269. };
  26270. /**
  26271. * Constructs a segment url from a template string
  26272. *
  26273. * @param {string} url
  26274. * Template string to construct url from
  26275. * @param {Obect} values
  26276. * Object containing values that shall be used to replace known identifiers
  26277. * @param {number} values.RepresentationID
  26278. * Value of the Representation@id attribute
  26279. * @param {number} values.Number
  26280. * Number of the corresponding segment
  26281. * @param {number} values.Bandwidth
  26282. * Value of the Representation@bandwidth attribute.
  26283. * @param {number} values.Time
  26284. * Timestamp value of the corresponding segment
  26285. * @return {string}
  26286. * Segment url with identifiers replaced
  26287. */
  26288. var constructTemplateUrl = function constructTemplateUrl(url, values) {
  26289. return url.replace(identifierPattern, identifierReplacement(values));
  26290. };
  26291. /**
  26292. * Generates a list of objects containing timing and duration information about each
  26293. * segment needed to generate segment uris and the complete segment object
  26294. *
  26295. * @param {Object} attributes
  26296. * Object containing all inherited attributes from parent elements with attribute
  26297. * names as keys
  26298. * @param {Object[]|undefined} segmentTimeline
  26299. * List of objects representing the attributes of each S element contained within
  26300. * the SegmentTimeline element
  26301. * @return {{number: number, duration: number, time: number, timeline: number}[]}
  26302. * List of Objects with segment timing and duration info
  26303. */
  26304. var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) {
  26305. if (!attributes.duration && !segmentTimeline) {
  26306. // if neither @duration or SegmentTimeline are present, then there shall be exactly
  26307. // one media segment
  26308. return [{
  26309. number: attributes.startNumber || 1,
  26310. duration: attributes.sourceDuration,
  26311. time: 0,
  26312. timeline: attributes.periodIndex
  26313. }];
  26314. }
  26315. if (attributes.duration) {
  26316. return parseByDuration(attributes);
  26317. }
  26318. return parseByTimeline(attributes, segmentTimeline);
  26319. };
  26320. /**
  26321. * Generates a list of segments using information provided by the SegmentTemplate element
  26322. *
  26323. * @param {Object} attributes
  26324. * Object containing all inherited attributes from parent elements with attribute
  26325. * names as keys
  26326. * @param {Object[]|undefined} segmentTimeline
  26327. * List of objects representing the attributes of each S element contained within
  26328. * the SegmentTimeline element
  26329. * @return {Object[]}
  26330. * List of segment objects
  26331. */
  26332. var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) {
  26333. var templateValues = {
  26334. RepresentationID: attributes.id,
  26335. Bandwidth: attributes.bandwidth || 0
  26336. };
  26337. var _attributes$initializ = attributes.initialization,
  26338. initialization = _attributes$initializ === void 0 ? {
  26339. sourceURL: '',
  26340. range: ''
  26341. } : _attributes$initializ;
  26342. var mapSegment = urlTypeToSegment({
  26343. baseUrl: attributes.baseUrl,
  26344. source: constructTemplateUrl(initialization.sourceURL, templateValues),
  26345. range: initialization.range
  26346. });
  26347. var segments = parseTemplateInfo(attributes, segmentTimeline);
  26348. return segments.map(function (segment) {
  26349. templateValues.Number = segment.number;
  26350. templateValues.Time = segment.time;
  26351. var uri = constructTemplateUrl(attributes.media || '', templateValues);
  26352. return {
  26353. uri: uri,
  26354. timeline: segment.timeline,
  26355. duration: segment.duration,
  26356. resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
  26357. map: mapSegment,
  26358. number: segment.number
  26359. };
  26360. });
  26361. };
  26362. /**
  26363. * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
  26364. * to an object that matches the output of a segment in videojs/mpd-parser
  26365. *
  26366. * @param {Object} attributes
  26367. * Object containing all inherited attributes from parent elements with attribute
  26368. * names as keys
  26369. * @param {Object} segmentUrl
  26370. * <SegmentURL> node to translate into a segment object
  26371. * @return {Object} translated segment object
  26372. */
  26373. var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) {
  26374. var baseUrl = attributes.baseUrl,
  26375. _attributes$initializ = attributes.initialization,
  26376. initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ;
  26377. var initSegment = urlTypeToSegment({
  26378. baseUrl: baseUrl,
  26379. source: initialization.sourceURL,
  26380. range: initialization.range
  26381. });
  26382. var segment = urlTypeToSegment({
  26383. baseUrl: baseUrl,
  26384. source: segmentUrl.media,
  26385. range: segmentUrl.mediaRange
  26386. });
  26387. segment.map = initSegment;
  26388. return segment;
  26389. };
  26390. /**
  26391. * Generates a list of segments using information provided by the SegmentList element
  26392. * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
  26393. * node should be translated into segment.
  26394. *
  26395. * @param {Object} attributes
  26396. * Object containing all inherited attributes from parent elements with attribute
  26397. * names as keys
  26398. * @param {Object[]|undefined} segmentTimeline
  26399. * List of objects representing the attributes of each S element contained within
  26400. * the SegmentTimeline element
  26401. * @return {Object.<Array>} list of segments
  26402. */
  26403. var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) {
  26404. var duration = attributes.duration,
  26405. _attributes$segmentUr = attributes.segmentUrls,
  26406. segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR
  26407. // if both SegmentTimeline and @duration are defined, it is outside of spec.
  26408. if (!duration && !segmentTimeline || duration && segmentTimeline) {
  26409. throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
  26410. }
  26411. var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) {
  26412. return SegmentURLToSegmentObject(attributes, segmentUrlObject);
  26413. });
  26414. var segmentTimeInfo;
  26415. if (duration) {
  26416. segmentTimeInfo = parseByDuration(attributes);
  26417. }
  26418. if (segmentTimeline) {
  26419. segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
  26420. }
  26421. var segments = segmentTimeInfo.map(function (segmentTime, index) {
  26422. if (segmentUrlMap[index]) {
  26423. var segment = segmentUrlMap[index];
  26424. segment.timeline = segmentTime.timeline;
  26425. segment.duration = segmentTime.duration;
  26426. segment.number = segmentTime.number;
  26427. return segment;
  26428. } // Since we're mapping we should get rid of any blank segments (in case
  26429. // the given SegmentTimeline is handling for more elements than we have
  26430. // SegmentURLs for).
  26431. }).filter(function (segment) {
  26432. return segment;
  26433. });
  26434. return segments;
  26435. };
  26436. var generateSegments = function generateSegments(_ref) {
  26437. var attributes = _ref.attributes,
  26438. segmentInfo = _ref.segmentInfo;
  26439. var segmentAttributes;
  26440. var segmentsFn;
  26441. if (segmentInfo.template) {
  26442. segmentsFn = segmentsFromTemplate;
  26443. segmentAttributes = merge(attributes, segmentInfo.template);
  26444. } else if (segmentInfo.base) {
  26445. segmentsFn = segmentsFromBase;
  26446. segmentAttributes = merge(attributes, segmentInfo.base);
  26447. } else if (segmentInfo.list) {
  26448. segmentsFn = segmentsFromList;
  26449. segmentAttributes = merge(attributes, segmentInfo.list);
  26450. }
  26451. var segmentsInfo = {
  26452. attributes: attributes
  26453. };
  26454. if (!segmentsFn) {
  26455. return segmentsInfo;
  26456. }
  26457. var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which
  26458. // must be in seconds. Since we've generated the segment list, we no longer need
  26459. // @duration to be in @timescale units, so we can convert it here.
  26460. if (segmentAttributes.duration) {
  26461. var _segmentAttributes = segmentAttributes,
  26462. duration = _segmentAttributes.duration,
  26463. _segmentAttributes$ti = _segmentAttributes.timescale,
  26464. timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti;
  26465. segmentAttributes.duration = duration / timescale;
  26466. } else if (segments.length) {
  26467. // if there is no @duration attribute, use the largest segment duration as
  26468. // as target duration
  26469. segmentAttributes.duration = segments.reduce(function (max, segment) {
  26470. return Math.max(max, Math.ceil(segment.duration));
  26471. }, 0);
  26472. } else {
  26473. segmentAttributes.duration = 0;
  26474. }
  26475. segmentsInfo.attributes = segmentAttributes;
  26476. segmentsInfo.segments = segments; // This is a sidx box without actual segment information
  26477. if (segmentInfo.base && segmentAttributes.indexRange) {
  26478. segmentsInfo.sidx = segments[0];
  26479. segmentsInfo.segments = [];
  26480. }
  26481. return segmentsInfo;
  26482. };
  26483. var toPlaylists = function toPlaylists(representations) {
  26484. return representations.map(generateSegments);
  26485. };
  26486. var findChildren = function findChildren(element, name) {
  26487. return from(element.childNodes).filter(function (_ref) {
  26488. var tagName = _ref.tagName;
  26489. return tagName === name;
  26490. });
  26491. };
  26492. var getContent = function getContent(element) {
  26493. return element.textContent.trim();
  26494. };
  26495. var parseDuration = function parseDuration(str) {
  26496. var SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
  26497. var SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
  26498. var SECONDS_IN_DAY = 24 * 60 * 60;
  26499. var SECONDS_IN_HOUR = 60 * 60;
  26500. var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
  26501. var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
  26502. var match = durationRegex.exec(str);
  26503. if (!match) {
  26504. return 0;
  26505. }
  26506. var _match$slice = match.slice(1),
  26507. year = _match$slice[0],
  26508. month = _match$slice[1],
  26509. day = _match$slice[2],
  26510. hour = _match$slice[3],
  26511. minute = _match$slice[4],
  26512. second = _match$slice[5];
  26513. return parseFloat(year || 0) * SECONDS_IN_YEAR + parseFloat(month || 0) * SECONDS_IN_MONTH + parseFloat(day || 0) * SECONDS_IN_DAY + parseFloat(hour || 0) * SECONDS_IN_HOUR + parseFloat(minute || 0) * SECONDS_IN_MIN + parseFloat(second || 0);
  26514. };
  26515. var parseDate = function parseDate(str) {
  26516. // Date format without timezone according to ISO 8601
  26517. // YYY-MM-DDThh:mm:ss.ssssss
  26518. var dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/; // If the date string does not specifiy a timezone, we must specifiy UTC. This is
  26519. // expressed by ending with 'Z'
  26520. if (dateRegex.test(str)) {
  26521. str += 'Z';
  26522. }
  26523. return Date.parse(str);
  26524. };
  26525. var parsers = {
  26526. /**
  26527. * Specifies the duration of the entire Media Presentation. Format is a duration string
  26528. * as specified in ISO 8601
  26529. *
  26530. * @param {string} value
  26531. * value of attribute as a string
  26532. * @return {number}
  26533. * The duration in seconds
  26534. */
  26535. mediaPresentationDuration: function mediaPresentationDuration(value) {
  26536. return parseDuration(value);
  26537. },
  26538. /**
  26539. * Specifies the Segment availability start time for all Segments referred to in this
  26540. * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
  26541. * time. Format is a date string as specified in ISO 8601
  26542. *
  26543. * @param {string} value
  26544. * value of attribute as a string
  26545. * @return {number}
  26546. * The date as seconds from unix epoch
  26547. */
  26548. availabilityStartTime: function availabilityStartTime(value) {
  26549. return parseDate(value) / 1000;
  26550. },
  26551. /**
  26552. * Specifies the smallest period between potential changes to the MPD. Format is a
  26553. * duration string as specified in ISO 8601
  26554. *
  26555. * @param {string} value
  26556. * value of attribute as a string
  26557. * @return {number}
  26558. * The duration in seconds
  26559. */
  26560. minimumUpdatePeriod: function minimumUpdatePeriod(value) {
  26561. return parseDuration(value);
  26562. },
  26563. /**
  26564. * Specifies the duration of the smallest time shifting buffer for any Representation
  26565. * in the MPD. Format is a duration string as specified in ISO 8601
  26566. *
  26567. * @param {string} value
  26568. * value of attribute as a string
  26569. * @return {number}
  26570. * The duration in seconds
  26571. */
  26572. timeShiftBufferDepth: function timeShiftBufferDepth(value) {
  26573. return parseDuration(value);
  26574. },
  26575. /**
  26576. * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
  26577. * Format is a duration string as specified in ISO 8601
  26578. *
  26579. * @param {string} value
  26580. * value of attribute as a string
  26581. * @return {number}
  26582. * The duration in seconds
  26583. */
  26584. start: function start(value) {
  26585. return parseDuration(value);
  26586. },
  26587. /**
  26588. * Specifies the width of the visual presentation
  26589. *
  26590. * @param {string} value
  26591. * value of attribute as a string
  26592. * @return {number}
  26593. * The parsed width
  26594. */
  26595. width: function width(value) {
  26596. return parseInt(value, 10);
  26597. },
  26598. /**
  26599. * Specifies the height of the visual presentation
  26600. *
  26601. * @param {string} value
  26602. * value of attribute as a string
  26603. * @return {number}
  26604. * The parsed height
  26605. */
  26606. height: function height(value) {
  26607. return parseInt(value, 10);
  26608. },
  26609. /**
  26610. * Specifies the bitrate of the representation
  26611. *
  26612. * @param {string} value
  26613. * value of attribute as a string
  26614. * @return {number}
  26615. * The parsed bandwidth
  26616. */
  26617. bandwidth: function bandwidth(value) {
  26618. return parseInt(value, 10);
  26619. },
  26620. /**
  26621. * Specifies the number of the first Media Segment in this Representation in the Period
  26622. *
  26623. * @param {string} value
  26624. * value of attribute as a string
  26625. * @return {number}
  26626. * The parsed number
  26627. */
  26628. startNumber: function startNumber(value) {
  26629. return parseInt(value, 10);
  26630. },
  26631. /**
  26632. * Specifies the timescale in units per seconds
  26633. *
  26634. * @param {string} value
  26635. * value of attribute as a string
  26636. * @return {number}
  26637. * The aprsed timescale
  26638. */
  26639. timescale: function timescale(value) {
  26640. return parseInt(value, 10);
  26641. },
  26642. /**
  26643. * Specifies the constant approximate Segment duration
  26644. * NOTE: The <Period> element also contains an @duration attribute. This duration
  26645. * specifies the duration of the Period. This attribute is currently not
  26646. * supported by the rest of the parser, however we still check for it to prevent
  26647. * errors.
  26648. *
  26649. * @param {string} value
  26650. * value of attribute as a string
  26651. * @return {number}
  26652. * The parsed duration
  26653. */
  26654. duration: function duration(value) {
  26655. var parsedValue = parseInt(value, 10);
  26656. if (isNaN(parsedValue)) {
  26657. return parseDuration(value);
  26658. }
  26659. return parsedValue;
  26660. },
  26661. /**
  26662. * Specifies the Segment duration, in units of the value of the @timescale.
  26663. *
  26664. * @param {string} value
  26665. * value of attribute as a string
  26666. * @return {number}
  26667. * The parsed duration
  26668. */
  26669. d: function d(value) {
  26670. return parseInt(value, 10);
  26671. },
  26672. /**
  26673. * Specifies the MPD start time, in @timescale units, the first Segment in the series
  26674. * starts relative to the beginning of the Period
  26675. *
  26676. * @param {string} value
  26677. * value of attribute as a string
  26678. * @return {number}
  26679. * The parsed time
  26680. */
  26681. t: function t(value) {
  26682. return parseInt(value, 10);
  26683. },
  26684. /**
  26685. * Specifies the repeat count of the number of following contiguous Segments with the
  26686. * same duration expressed by the value of @d
  26687. *
  26688. * @param {string} value
  26689. * value of attribute as a string
  26690. * @return {number}
  26691. * The parsed number
  26692. */
  26693. r: function r(value) {
  26694. return parseInt(value, 10);
  26695. },
  26696. /**
  26697. * Default parser for all other attributes. Acts as a no-op and just returns the value
  26698. * as a string
  26699. *
  26700. * @param {string} value
  26701. * value of attribute as a string
  26702. * @return {string}
  26703. * Unparsed value
  26704. */
  26705. DEFAULT: function DEFAULT(value) {
  26706. return value;
  26707. }
  26708. };
  26709. /**
  26710. * Gets all the attributes and values of the provided node, parses attributes with known
  26711. * types, and returns an object with attribute names mapped to values.
  26712. *
  26713. * @param {Node} el
  26714. * The node to parse attributes from
  26715. * @return {Object}
  26716. * Object with all attributes of el parsed
  26717. */
  26718. var parseAttributes$1 = function parseAttributes(el) {
  26719. if (!(el && el.attributes)) {
  26720. return {};
  26721. }
  26722. return from(el.attributes).reduce(function (a, e) {
  26723. var parseFn = parsers[e.name] || parsers.DEFAULT;
  26724. a[e.name] = parseFn(e.value);
  26725. return a;
  26726. }, {});
  26727. };
  26728. function decodeB64ToUint8Array(b64Text) {
  26729. var decodedString = window$1.atob(b64Text);
  26730. var array = new Uint8Array(decodedString.length);
  26731. for (var i = 0; i < decodedString.length; i++) {
  26732. array[i] = decodedString.charCodeAt(i);
  26733. }
  26734. return array;
  26735. }
  26736. var keySystemsMap = {
  26737. 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
  26738. 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
  26739. 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
  26740. 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
  26741. };
  26742. /**
  26743. * Builds a list of urls that is the product of the reference urls and BaseURL values
  26744. *
  26745. * @param {string[]} referenceUrls
  26746. * List of reference urls to resolve to
  26747. * @param {Node[]} baseUrlElements
  26748. * List of BaseURL nodes from the mpd
  26749. * @return {string[]}
  26750. * List of resolved urls
  26751. */
  26752. var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) {
  26753. if (!baseUrlElements.length) {
  26754. return referenceUrls;
  26755. }
  26756. return flatten(referenceUrls.map(function (reference) {
  26757. return baseUrlElements.map(function (baseUrlElement) {
  26758. return resolveUrl(reference, getContent(baseUrlElement));
  26759. });
  26760. }));
  26761. };
  26762. /**
  26763. * Contains all Segment information for its containing AdaptationSet
  26764. *
  26765. * @typedef {Object} SegmentInformation
  26766. * @property {Object|undefined} template
  26767. * Contains the attributes for the SegmentTemplate node
  26768. * @property {Object[]|undefined} timeline
  26769. * Contains a list of atrributes for each S node within the SegmentTimeline node
  26770. * @property {Object|undefined} list
  26771. * Contains the attributes for the SegmentList node
  26772. * @property {Object|undefined} base
  26773. * Contains the attributes for the SegmentBase node
  26774. */
  26775. /**
  26776. * Returns all available Segment information contained within the AdaptationSet node
  26777. *
  26778. * @param {Node} adaptationSet
  26779. * The AdaptationSet node to get Segment information from
  26780. * @return {SegmentInformation}
  26781. * The Segment information contained within the provided AdaptationSet
  26782. */
  26783. var getSegmentInformation = function getSegmentInformation(adaptationSet) {
  26784. var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
  26785. var segmentList = findChildren(adaptationSet, 'SegmentList')[0];
  26786. var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) {
  26787. return merge({
  26788. tag: 'SegmentURL'
  26789. }, parseAttributes$1(s));
  26790. });
  26791. var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
  26792. var segmentTimelineParentNode = segmentList || segmentTemplate;
  26793. var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
  26794. var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
  26795. var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
  26796. // @initialization and an <Initialization> node. @initialization can be templated,
  26797. // while the node can have a url and range specified. If the <SegmentTemplate> has
  26798. // both @initialization and an <Initialization> subelement we opt to override with
  26799. // the node, as this interaction is not defined in the spec.
  26800. var template = segmentTemplate && parseAttributes$1(segmentTemplate);
  26801. if (template && segmentInitialization) {
  26802. template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization);
  26803. } else if (template && template.initialization) {
  26804. // If it is @initialization we convert it to an object since this is the format that
  26805. // later functions will rely on for the initialization segment. This is only valid
  26806. // for <SegmentTemplate>
  26807. template.initialization = {
  26808. sourceURL: template.initialization
  26809. };
  26810. }
  26811. var segmentInfo = {
  26812. template: template,
  26813. timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) {
  26814. return parseAttributes$1(s);
  26815. }),
  26816. list: segmentList && merge(parseAttributes$1(segmentList), {
  26817. segmentUrls: segmentUrls,
  26818. initialization: parseAttributes$1(segmentInitialization)
  26819. }),
  26820. base: segmentBase && merge(parseAttributes$1(segmentBase), {
  26821. initialization: parseAttributes$1(segmentInitialization)
  26822. })
  26823. };
  26824. Object.keys(segmentInfo).forEach(function (key) {
  26825. if (!segmentInfo[key]) {
  26826. delete segmentInfo[key];
  26827. }
  26828. });
  26829. return segmentInfo;
  26830. };
  26831. /**
  26832. * Contains Segment information and attributes needed to construct a Playlist object
  26833. * from a Representation
  26834. *
  26835. * @typedef {Object} RepresentationInformation
  26836. * @property {SegmentInformation} segmentInfo
  26837. * Segment information for this Representation
  26838. * @property {Object} attributes
  26839. * Inherited attributes for this Representation
  26840. */
  26841. /**
  26842. * Maps a Representation node to an object containing Segment information and attributes
  26843. *
  26844. * @name inheritBaseUrlsCallback
  26845. * @function
  26846. * @param {Node} representation
  26847. * Representation node from the mpd
  26848. * @return {RepresentationInformation}
  26849. * Representation information needed to construct a Playlist object
  26850. */
  26851. /**
  26852. * Returns a callback for Array.prototype.map for mapping Representation nodes to
  26853. * Segment information and attributes using inherited BaseURL nodes.
  26854. *
  26855. * @param {Object} adaptationSetAttributes
  26856. * Contains attributes inherited by the AdaptationSet
  26857. * @param {string[]} adaptationSetBaseUrls
  26858. * Contains list of resolved base urls inherited by the AdaptationSet
  26859. * @param {SegmentInformation} adaptationSetSegmentInfo
  26860. * Contains Segment information for the AdaptationSet
  26861. * @return {inheritBaseUrlsCallback}
  26862. * Callback map function
  26863. */
  26864. var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) {
  26865. return function (representation) {
  26866. var repBaseUrlElements = findChildren(representation, 'BaseURL');
  26867. var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
  26868. var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation));
  26869. var representationSegmentInfo = getSegmentInformation(representation);
  26870. return repBaseUrls.map(function (baseUrl) {
  26871. return {
  26872. segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
  26873. attributes: merge(attributes, {
  26874. baseUrl: baseUrl
  26875. })
  26876. };
  26877. });
  26878. };
  26879. };
  26880. /**
  26881. * Tranforms a series of content protection nodes to
  26882. * an object containing pssh data by key system
  26883. *
  26884. * @param {Node[]} contentProtectionNodes
  26885. * Content protection nodes
  26886. * @return {Object}
  26887. * Object containing pssh data by key system
  26888. */
  26889. var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) {
  26890. return contentProtectionNodes.reduce(function (acc, node) {
  26891. var attributes = parseAttributes$1(node);
  26892. var keySystem = keySystemsMap[attributes.schemeIdUri];
  26893. if (keySystem) {
  26894. acc[keySystem] = {
  26895. attributes: attributes
  26896. };
  26897. var psshNode = findChildren(node, 'cenc:pssh')[0];
  26898. if (psshNode) {
  26899. var pssh = getContent(psshNode);
  26900. var psshBuffer = pssh && decodeB64ToUint8Array(pssh);
  26901. acc[keySystem].pssh = psshBuffer;
  26902. }
  26903. }
  26904. return acc;
  26905. }, {});
  26906. };
  26907. /**
  26908. * Maps an AdaptationSet node to a list of Representation information objects
  26909. *
  26910. * @name toRepresentationsCallback
  26911. * @function
  26912. * @param {Node} adaptationSet
  26913. * AdaptationSet node from the mpd
  26914. * @return {RepresentationInformation[]}
  26915. * List of objects containing Representaion information
  26916. */
  26917. /**
  26918. * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
  26919. * Representation information objects
  26920. *
  26921. * @param {Object} periodAttributes
  26922. * Contains attributes inherited by the Period
  26923. * @param {string[]} periodBaseUrls
  26924. * Contains list of resolved base urls inherited by the Period
  26925. * @param {string[]} periodSegmentInfo
  26926. * Contains Segment Information at the period level
  26927. * @return {toRepresentationsCallback}
  26928. * Callback map function
  26929. */
  26930. var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) {
  26931. return function (adaptationSet) {
  26932. var adaptationSetAttributes = parseAttributes$1(adaptationSet);
  26933. var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
  26934. var role = findChildren(adaptationSet, 'Role')[0];
  26935. var roleAttributes = {
  26936. role: parseAttributes$1(role)
  26937. };
  26938. var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
  26939. var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
  26940. if (Object.keys(contentProtection).length) {
  26941. attrs = merge(attrs, {
  26942. contentProtection: contentProtection
  26943. });
  26944. }
  26945. var segmentInfo = getSegmentInformation(adaptationSet);
  26946. var representations = findChildren(adaptationSet, 'Representation');
  26947. var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
  26948. return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
  26949. };
  26950. };
  26951. /**
  26952. * Maps an Period node to a list of Representation inforamtion objects for all
  26953. * AdaptationSet nodes contained within the Period
  26954. *
  26955. * @name toAdaptationSetsCallback
  26956. * @function
  26957. * @param {Node} period
  26958. * Period node from the mpd
  26959. * @param {number} periodIndex
  26960. * Index of the Period within the mpd
  26961. * @return {RepresentationInformation[]}
  26962. * List of objects containing Representaion information
  26963. */
  26964. /**
  26965. * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
  26966. * Representation information objects
  26967. *
  26968. * @param {Object} mpdAttributes
  26969. * Contains attributes inherited by the mpd
  26970. * @param {string[]} mpdBaseUrls
  26971. * Contains list of resolved base urls inherited by the mpd
  26972. * @return {toAdaptationSetsCallback}
  26973. * Callback map function
  26974. */
  26975. var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) {
  26976. return function (period, index) {
  26977. var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL'));
  26978. var periodAtt = parseAttributes$1(period);
  26979. var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number
  26980. var periodIndex = window$1.isNaN(parsedPeriodId) ? index : parsedPeriodId;
  26981. var periodAttributes = merge(mpdAttributes, {
  26982. periodIndex: periodIndex
  26983. });
  26984. var adaptationSets = findChildren(period, 'AdaptationSet');
  26985. var periodSegmentInfo = getSegmentInformation(period);
  26986. return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
  26987. };
  26988. };
  26989. /**
  26990. * Traverses the mpd xml tree to generate a list of Representation information objects
  26991. * that have inherited attributes from parent nodes
  26992. *
  26993. * @param {Node} mpd
  26994. * The root node of the mpd
  26995. * @param {Object} options
  26996. * Available options for inheritAttributes
  26997. * @param {string} options.manifestUri
  26998. * The uri source of the mpd
  26999. * @param {number} options.NOW
  27000. * Current time per DASH IOP. Default is current time in ms since epoch
  27001. * @param {number} options.clientOffset
  27002. * Client time difference from NOW (in milliseconds)
  27003. * @return {RepresentationInformation[]}
  27004. * List of objects containing Representation information
  27005. */
  27006. var inheritAttributes = function inheritAttributes(mpd, options) {
  27007. if (options === void 0) {
  27008. options = {};
  27009. }
  27010. var _options = options,
  27011. _options$manifestUri = _options.manifestUri,
  27012. manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri,
  27013. _options$NOW = _options.NOW,
  27014. NOW = _options$NOW === void 0 ? Date.now() : _options$NOW,
  27015. _options$clientOffset = _options.clientOffset,
  27016. clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset;
  27017. var periods = findChildren(mpd, 'Period');
  27018. if (!periods.length) {
  27019. throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
  27020. }
  27021. var mpdAttributes = parseAttributes$1(mpd);
  27022. var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL'));
  27023. mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
  27024. mpdAttributes.NOW = NOW;
  27025. mpdAttributes.clientOffset = clientOffset;
  27026. return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)));
  27027. };
  27028. var stringToMpdXml = function stringToMpdXml(manifestString) {
  27029. if (manifestString === '') {
  27030. throw new Error(errors.DASH_EMPTY_MANIFEST);
  27031. }
  27032. var parser = new window$1.DOMParser();
  27033. var xml = parser.parseFromString(manifestString, 'application/xml');
  27034. var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
  27035. if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
  27036. throw new Error(errors.DASH_INVALID_XML);
  27037. }
  27038. return mpd;
  27039. };
  27040. /**
  27041. * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
  27042. *
  27043. * @param {string} mpd
  27044. * XML string of the MPD manifest
  27045. * @return {Object|null}
  27046. * Attributes of UTCTiming node specified in the manifest. Null if none found
  27047. */
  27048. var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) {
  27049. var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
  27050. if (!UTCTimingNode) {
  27051. return null;
  27052. }
  27053. var attributes = parseAttributes$1(UTCTimingNode);
  27054. switch (attributes.schemeIdUri) {
  27055. case 'urn:mpeg:dash:utc:http-head:2014':
  27056. case 'urn:mpeg:dash:utc:http-head:2012':
  27057. attributes.method = 'HEAD';
  27058. break;
  27059. case 'urn:mpeg:dash:utc:http-xsdate:2014':
  27060. case 'urn:mpeg:dash:utc:http-iso:2014':
  27061. case 'urn:mpeg:dash:utc:http-xsdate:2012':
  27062. case 'urn:mpeg:dash:utc:http-iso:2012':
  27063. attributes.method = 'GET';
  27064. break;
  27065. case 'urn:mpeg:dash:utc:direct:2014':
  27066. case 'urn:mpeg:dash:utc:direct:2012':
  27067. attributes.method = 'DIRECT';
  27068. attributes.value = Date.parse(attributes.value);
  27069. break;
  27070. case 'urn:mpeg:dash:utc:http-ntp:2014':
  27071. case 'urn:mpeg:dash:utc:ntp:2014':
  27072. case 'urn:mpeg:dash:utc:sntp:2014':
  27073. default:
  27074. throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
  27075. }
  27076. return attributes;
  27077. };
  27078. var parse = function parse(manifestString, options) {
  27079. if (options === void 0) {
  27080. options = {};
  27081. }
  27082. return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)), options.sidxMapping);
  27083. };
  27084. /**
  27085. * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
  27086. *
  27087. * @param {string} manifestString
  27088. * XML string of the MPD manifest
  27089. * @return {Object|null}
  27090. * Attributes of UTCTiming node specified in the manifest. Null if none found
  27091. */
  27092. var parseUTCTiming = function parseUTCTiming(manifestString) {
  27093. return parseUTCTimingScheme(stringToMpdXml(manifestString));
  27094. };
  27095. var toUnsigned = function toUnsigned(value) {
  27096. return value >>> 0;
  27097. };
  27098. var bin = {
  27099. toUnsigned: toUnsigned
  27100. };
  27101. var toUnsigned$1 = bin.toUnsigned;
  27102. var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path
  27103. _findBox = function findBox(data, path) {
  27104. var results = [],
  27105. i,
  27106. size,
  27107. type,
  27108. end,
  27109. subresults;
  27110. if (!path.length) {
  27111. // short-circuit the search for empty paths
  27112. return null;
  27113. }
  27114. for (i = 0; i < data.byteLength;) {
  27115. size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
  27116. type = parseType(data.subarray(i + 4, i + 8));
  27117. end = size > 1 ? i + size : data.byteLength;
  27118. if (type === path[0]) {
  27119. if (path.length === 1) {
  27120. // this is the end of the path and we've found the box we were
  27121. // looking for
  27122. results.push(data.subarray(i + 8, end));
  27123. } else {
  27124. // recursively search for the next box along the path
  27125. subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
  27126. if (subresults.length) {
  27127. results = results.concat(subresults);
  27128. }
  27129. }
  27130. }
  27131. i = end;
  27132. } // we've finished searching all of data
  27133. return results;
  27134. };
  27135. /**
  27136. * Returns the string representation of an ASCII encoded four byte buffer.
  27137. * @param buffer {Uint8Array} a four-byte buffer to translate
  27138. * @return {string} the corresponding string
  27139. */
  27140. parseType = function parseType(buffer) {
  27141. var result = '';
  27142. result += String.fromCharCode(buffer[0]);
  27143. result += String.fromCharCode(buffer[1]);
  27144. result += String.fromCharCode(buffer[2]);
  27145. result += String.fromCharCode(buffer[3]);
  27146. return result;
  27147. };
  27148. /**
  27149. * Parses an MP4 initialization segment and extracts the timescale
  27150. * values for any declared tracks. Timescale values indicate the
  27151. * number of clock ticks per second to assume for time-based values
  27152. * elsewhere in the MP4.
  27153. *
  27154. * To determine the start time of an MP4, you need two pieces of
  27155. * information: the timescale unit and the earliest base media decode
  27156. * time. Multiple timescales can be specified within an MP4 but the
  27157. * base media decode time is always expressed in the timescale from
  27158. * the media header box for the track:
  27159. * ```
  27160. * moov > trak > mdia > mdhd.timescale
  27161. * ```
  27162. * @param init {Uint8Array} the bytes of the init segment
  27163. * @return {object} a hash of track ids to timescale values or null if
  27164. * the init segment is malformed.
  27165. */
  27166. timescale = function timescale(init) {
  27167. var result = {},
  27168. traks = _findBox(init, ['moov', 'trak']); // mdhd timescale
  27169. return traks.reduce(function (result, trak) {
  27170. var tkhd, version, index, id, mdhd;
  27171. tkhd = _findBox(trak, ['tkhd'])[0];
  27172. if (!tkhd) {
  27173. return null;
  27174. }
  27175. version = tkhd[0];
  27176. index = version === 0 ? 12 : 20;
  27177. id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
  27178. mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
  27179. if (!mdhd) {
  27180. return null;
  27181. }
  27182. version = mdhd[0];
  27183. index = version === 0 ? 12 : 20;
  27184. result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
  27185. return result;
  27186. }, result);
  27187. };
  27188. /**
  27189. * Determine the base media decode start time, in seconds, for an MP4
  27190. * fragment. If multiple fragments are specified, the earliest time is
  27191. * returned.
  27192. *
  27193. * The base media decode time can be parsed from track fragment
  27194. * metadata:
  27195. * ```
  27196. * moof > traf > tfdt.baseMediaDecodeTime
  27197. * ```
  27198. * It requires the timescale value from the mdhd to interpret.
  27199. *
  27200. * @param timescale {object} a hash of track ids to timescale values.
  27201. * @return {number} the earliest base media decode start time for the
  27202. * fragment, in seconds
  27203. */
  27204. startTime = function startTime(timescale, fragment) {
  27205. var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
  27206. trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track
  27207. baseTimes = [].concat.apply([], trafs.map(function (traf) {
  27208. return _findBox(traf, ['tfhd']).map(function (tfhd) {
  27209. var id, scale, baseTime; // get the track id from the tfhd
  27210. id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
  27211. scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
  27212. baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
  27213. var version, result;
  27214. version = tfdt[0];
  27215. result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
  27216. if (version === 1) {
  27217. result *= Math.pow(2, 32);
  27218. result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
  27219. }
  27220. return result;
  27221. })[0];
  27222. baseTime = baseTime || Infinity; // convert base time to seconds
  27223. return baseTime / scale;
  27224. });
  27225. })); // return the minimum
  27226. result = Math.min.apply(null, baseTimes);
  27227. return isFinite(result) ? result : 0;
  27228. };
  27229. /**
  27230. * Find the trackIds of the video tracks in this source.
  27231. * Found by parsing the Handler Reference and Track Header Boxes:
  27232. * moov > trak > mdia > hdlr
  27233. * moov > trak > tkhd
  27234. *
  27235. * @param {Uint8Array} init - The bytes of the init segment for this source
  27236. * @return {Number[]} A list of trackIds
  27237. *
  27238. * @see ISO-BMFF-12/2015, Section 8.4.3
  27239. **/
  27240. getVideoTrackIds = function getVideoTrackIds(init) {
  27241. var traks = _findBox(init, ['moov', 'trak']);
  27242. var videoTrackIds = [];
  27243. traks.forEach(function (trak) {
  27244. var hdlrs = _findBox(trak, ['mdia', 'hdlr']);
  27245. var tkhds = _findBox(trak, ['tkhd']);
  27246. hdlrs.forEach(function (hdlr, index) {
  27247. var handlerType = parseType(hdlr.subarray(8, 12));
  27248. var tkhd = tkhds[index];
  27249. var view;
  27250. var version;
  27251. var trackId;
  27252. if (handlerType === 'vide') {
  27253. view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
  27254. version = view.getUint8(0);
  27255. trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
  27256. videoTrackIds.push(trackId);
  27257. }
  27258. });
  27259. });
  27260. return videoTrackIds;
  27261. };
  27262. var probe = {
  27263. findBox: _findBox,
  27264. parseType: parseType,
  27265. timescale: timescale,
  27266. startTime: startTime,
  27267. videoTrackIds: getVideoTrackIds
  27268. };
  27269. var inspectMp4,
  27270. _textifyMp,
  27271. parseType$1 = probe.parseType,
  27272. parseMp4Date = function parseMp4Date(seconds) {
  27273. return new Date(seconds * 1000 - 2082844800000);
  27274. },
  27275. parseSampleFlags = function parseSampleFlags(flags) {
  27276. return {
  27277. isLeading: (flags[0] & 0x0c) >>> 2,
  27278. dependsOn: flags[0] & 0x03,
  27279. isDependedOn: (flags[1] & 0xc0) >>> 6,
  27280. hasRedundancy: (flags[1] & 0x30) >>> 4,
  27281. paddingValue: (flags[1] & 0x0e) >>> 1,
  27282. isNonSyncSample: flags[1] & 0x01,
  27283. degradationPriority: flags[2] << 8 | flags[3]
  27284. };
  27285. },
  27286. nalParse = function nalParse(avcStream) {
  27287. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  27288. result = [],
  27289. i,
  27290. length;
  27291. for (i = 0; i + 4 < avcStream.length; i += length) {
  27292. length = avcView.getUint32(i);
  27293. i += 4; // bail if this doesn't appear to be an H264 stream
  27294. if (length <= 0) {
  27295. result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
  27296. continue;
  27297. }
  27298. switch (avcStream[i] & 0x1F) {
  27299. case 0x01:
  27300. result.push('slice_layer_without_partitioning_rbsp');
  27301. break;
  27302. case 0x05:
  27303. result.push('slice_layer_without_partitioning_rbsp_idr');
  27304. break;
  27305. case 0x06:
  27306. result.push('sei_rbsp');
  27307. break;
  27308. case 0x07:
  27309. result.push('seq_parameter_set_rbsp');
  27310. break;
  27311. case 0x08:
  27312. result.push('pic_parameter_set_rbsp');
  27313. break;
  27314. case 0x09:
  27315. result.push('access_unit_delimiter_rbsp');
  27316. break;
  27317. default:
  27318. result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
  27319. break;
  27320. }
  27321. }
  27322. return result;
  27323. },
  27324. // registry of handlers for individual mp4 box types
  27325. parse$1 = {
  27326. // codingname, not a first-class box type. stsd entries share the
  27327. // same format as real boxes so the parsing infrastructure can be
  27328. // shared
  27329. avc1: function avc1(data) {
  27330. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  27331. return {
  27332. dataReferenceIndex: view.getUint16(6),
  27333. width: view.getUint16(24),
  27334. height: view.getUint16(26),
  27335. horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
  27336. vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
  27337. frameCount: view.getUint16(40),
  27338. depth: view.getUint16(74),
  27339. config: inspectMp4(data.subarray(78, data.byteLength))
  27340. };
  27341. },
  27342. avcC: function avcC(data) {
  27343. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27344. result = {
  27345. configurationVersion: data[0],
  27346. avcProfileIndication: data[1],
  27347. profileCompatibility: data[2],
  27348. avcLevelIndication: data[3],
  27349. lengthSizeMinusOne: data[4] & 0x03,
  27350. sps: [],
  27351. pps: []
  27352. },
  27353. numOfSequenceParameterSets = data[5] & 0x1f,
  27354. numOfPictureParameterSets,
  27355. nalSize,
  27356. offset,
  27357. i; // iterate past any SPSs
  27358. offset = 6;
  27359. for (i = 0; i < numOfSequenceParameterSets; i++) {
  27360. nalSize = view.getUint16(offset);
  27361. offset += 2;
  27362. result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  27363. offset += nalSize;
  27364. } // iterate past any PPSs
  27365. numOfPictureParameterSets = data[offset];
  27366. offset++;
  27367. for (i = 0; i < numOfPictureParameterSets; i++) {
  27368. nalSize = view.getUint16(offset);
  27369. offset += 2;
  27370. result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  27371. offset += nalSize;
  27372. }
  27373. return result;
  27374. },
  27375. btrt: function btrt(data) {
  27376. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  27377. return {
  27378. bufferSizeDB: view.getUint32(0),
  27379. maxBitrate: view.getUint32(4),
  27380. avgBitrate: view.getUint32(8)
  27381. };
  27382. },
  27383. esds: function esds(data) {
  27384. return {
  27385. version: data[0],
  27386. flags: new Uint8Array(data.subarray(1, 4)),
  27387. esId: data[6] << 8 | data[7],
  27388. streamPriority: data[8] & 0x1f,
  27389. decoderConfig: {
  27390. objectProfileIndication: data[11],
  27391. streamType: data[12] >>> 2 & 0x3f,
  27392. bufferSize: data[13] << 16 | data[14] << 8 | data[15],
  27393. maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
  27394. avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
  27395. decoderConfigDescriptor: {
  27396. tag: data[24],
  27397. length: data[25],
  27398. audioObjectType: data[26] >>> 3 & 0x1f,
  27399. samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
  27400. channelConfiguration: data[27] >>> 3 & 0x0f
  27401. }
  27402. }
  27403. };
  27404. },
  27405. ftyp: function ftyp(data) {
  27406. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27407. result = {
  27408. majorBrand: parseType$1(data.subarray(0, 4)),
  27409. minorVersion: view.getUint32(4),
  27410. compatibleBrands: []
  27411. },
  27412. i = 8;
  27413. while (i < data.byteLength) {
  27414. result.compatibleBrands.push(parseType$1(data.subarray(i, i + 4)));
  27415. i += 4;
  27416. }
  27417. return result;
  27418. },
  27419. dinf: function dinf(data) {
  27420. return {
  27421. boxes: inspectMp4(data)
  27422. };
  27423. },
  27424. dref: function dref(data) {
  27425. return {
  27426. version: data[0],
  27427. flags: new Uint8Array(data.subarray(1, 4)),
  27428. dataReferences: inspectMp4(data.subarray(8))
  27429. };
  27430. },
  27431. hdlr: function hdlr(data) {
  27432. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27433. result = {
  27434. version: view.getUint8(0),
  27435. flags: new Uint8Array(data.subarray(1, 4)),
  27436. handlerType: parseType$1(data.subarray(8, 12)),
  27437. name: ''
  27438. },
  27439. i = 8; // parse out the name field
  27440. for (i = 24; i < data.byteLength; i++) {
  27441. if (data[i] === 0x00) {
  27442. // the name field is null-terminated
  27443. i++;
  27444. break;
  27445. }
  27446. result.name += String.fromCharCode(data[i]);
  27447. } // decode UTF-8 to javascript's internal representation
  27448. // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
  27449. result.name = decodeURIComponent(escape(result.name));
  27450. return result;
  27451. },
  27452. mdat: function mdat(data) {
  27453. return {
  27454. byteLength: data.byteLength,
  27455. nals: nalParse(data)
  27456. };
  27457. },
  27458. mdhd: function mdhd(data) {
  27459. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27460. i = 4,
  27461. language,
  27462. result = {
  27463. version: view.getUint8(0),
  27464. flags: new Uint8Array(data.subarray(1, 4)),
  27465. language: ''
  27466. };
  27467. if (result.version === 1) {
  27468. i += 4;
  27469. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  27470. i += 8;
  27471. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  27472. i += 4;
  27473. result.timescale = view.getUint32(i);
  27474. i += 8;
  27475. result.duration = view.getUint32(i); // truncating top 4 bytes
  27476. } else {
  27477. result.creationTime = parseMp4Date(view.getUint32(i));
  27478. i += 4;
  27479. result.modificationTime = parseMp4Date(view.getUint32(i));
  27480. i += 4;
  27481. result.timescale = view.getUint32(i);
  27482. i += 4;
  27483. result.duration = view.getUint32(i);
  27484. }
  27485. i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
  27486. // each field is the packed difference between its ASCII value and 0x60
  27487. language = view.getUint16(i);
  27488. result.language += String.fromCharCode((language >> 10) + 0x60);
  27489. result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
  27490. result.language += String.fromCharCode((language & 0x1f) + 0x60);
  27491. return result;
  27492. },
  27493. mdia: function mdia(data) {
  27494. return {
  27495. boxes: inspectMp4(data)
  27496. };
  27497. },
  27498. mfhd: function mfhd(data) {
  27499. return {
  27500. version: data[0],
  27501. flags: new Uint8Array(data.subarray(1, 4)),
  27502. sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  27503. };
  27504. },
  27505. minf: function minf(data) {
  27506. return {
  27507. boxes: inspectMp4(data)
  27508. };
  27509. },
  27510. // codingname, not a first-class box type. stsd entries share the
  27511. // same format as real boxes so the parsing infrastructure can be
  27512. // shared
  27513. mp4a: function mp4a(data) {
  27514. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27515. result = {
  27516. // 6 bytes reserved
  27517. dataReferenceIndex: view.getUint16(6),
  27518. // 4 + 4 bytes reserved
  27519. channelcount: view.getUint16(16),
  27520. samplesize: view.getUint16(18),
  27521. // 2 bytes pre_defined
  27522. // 2 bytes reserved
  27523. samplerate: view.getUint16(24) + view.getUint16(26) / 65536
  27524. }; // if there are more bytes to process, assume this is an ISO/IEC
  27525. // 14496-14 MP4AudioSampleEntry and parse the ESDBox
  27526. if (data.byteLength > 28) {
  27527. result.streamDescriptor = inspectMp4(data.subarray(28))[0];
  27528. }
  27529. return result;
  27530. },
  27531. moof: function moof(data) {
  27532. return {
  27533. boxes: inspectMp4(data)
  27534. };
  27535. },
  27536. moov: function moov(data) {
  27537. return {
  27538. boxes: inspectMp4(data)
  27539. };
  27540. },
  27541. mvex: function mvex(data) {
  27542. return {
  27543. boxes: inspectMp4(data)
  27544. };
  27545. },
  27546. mvhd: function mvhd(data) {
  27547. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27548. i = 4,
  27549. result = {
  27550. version: view.getUint8(0),
  27551. flags: new Uint8Array(data.subarray(1, 4))
  27552. };
  27553. if (result.version === 1) {
  27554. i += 4;
  27555. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  27556. i += 8;
  27557. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  27558. i += 4;
  27559. result.timescale = view.getUint32(i);
  27560. i += 8;
  27561. result.duration = view.getUint32(i); // truncating top 4 bytes
  27562. } else {
  27563. result.creationTime = parseMp4Date(view.getUint32(i));
  27564. i += 4;
  27565. result.modificationTime = parseMp4Date(view.getUint32(i));
  27566. i += 4;
  27567. result.timescale = view.getUint32(i);
  27568. i += 4;
  27569. result.duration = view.getUint32(i);
  27570. }
  27571. i += 4; // convert fixed-point, base 16 back to a number
  27572. result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
  27573. i += 4;
  27574. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  27575. i += 2;
  27576. i += 2;
  27577. i += 2 * 4;
  27578. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  27579. i += 9 * 4;
  27580. i += 6 * 4;
  27581. result.nextTrackId = view.getUint32(i);
  27582. return result;
  27583. },
  27584. pdin: function pdin(data) {
  27585. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  27586. return {
  27587. version: view.getUint8(0),
  27588. flags: new Uint8Array(data.subarray(1, 4)),
  27589. rate: view.getUint32(4),
  27590. initialDelay: view.getUint32(8)
  27591. };
  27592. },
  27593. sdtp: function sdtp(data) {
  27594. var result = {
  27595. version: data[0],
  27596. flags: new Uint8Array(data.subarray(1, 4)),
  27597. samples: []
  27598. },
  27599. i;
  27600. for (i = 4; i < data.byteLength; i++) {
  27601. result.samples.push({
  27602. dependsOn: (data[i] & 0x30) >> 4,
  27603. isDependedOn: (data[i] & 0x0c) >> 2,
  27604. hasRedundancy: data[i] & 0x03
  27605. });
  27606. }
  27607. return result;
  27608. },
  27609. sidx: function sidx(data) {
  27610. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27611. result = {
  27612. version: data[0],
  27613. flags: new Uint8Array(data.subarray(1, 4)),
  27614. references: [],
  27615. referenceId: view.getUint32(4),
  27616. timescale: view.getUint32(8),
  27617. earliestPresentationTime: view.getUint32(12),
  27618. firstOffset: view.getUint32(16)
  27619. },
  27620. referenceCount = view.getUint16(22),
  27621. i;
  27622. for (i = 24; referenceCount; i += 12, referenceCount--) {
  27623. result.references.push({
  27624. referenceType: (data[i] & 0x80) >>> 7,
  27625. referencedSize: view.getUint32(i) & 0x7FFFFFFF,
  27626. subsegmentDuration: view.getUint32(i + 4),
  27627. startsWithSap: !!(data[i + 8] & 0x80),
  27628. sapType: (data[i + 8] & 0x70) >>> 4,
  27629. sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
  27630. });
  27631. }
  27632. return result;
  27633. },
  27634. smhd: function smhd(data) {
  27635. return {
  27636. version: data[0],
  27637. flags: new Uint8Array(data.subarray(1, 4)),
  27638. balance: data[4] + data[5] / 256
  27639. };
  27640. },
  27641. stbl: function stbl(data) {
  27642. return {
  27643. boxes: inspectMp4(data)
  27644. };
  27645. },
  27646. stco: function stco(data) {
  27647. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27648. result = {
  27649. version: data[0],
  27650. flags: new Uint8Array(data.subarray(1, 4)),
  27651. chunkOffsets: []
  27652. },
  27653. entryCount = view.getUint32(4),
  27654. i;
  27655. for (i = 8; entryCount; i += 4, entryCount--) {
  27656. result.chunkOffsets.push(view.getUint32(i));
  27657. }
  27658. return result;
  27659. },
  27660. stsc: function stsc(data) {
  27661. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27662. entryCount = view.getUint32(4),
  27663. result = {
  27664. version: data[0],
  27665. flags: new Uint8Array(data.subarray(1, 4)),
  27666. sampleToChunks: []
  27667. },
  27668. i;
  27669. for (i = 8; entryCount; i += 12, entryCount--) {
  27670. result.sampleToChunks.push({
  27671. firstChunk: view.getUint32(i),
  27672. samplesPerChunk: view.getUint32(i + 4),
  27673. sampleDescriptionIndex: view.getUint32(i + 8)
  27674. });
  27675. }
  27676. return result;
  27677. },
  27678. stsd: function stsd(data) {
  27679. return {
  27680. version: data[0],
  27681. flags: new Uint8Array(data.subarray(1, 4)),
  27682. sampleDescriptions: inspectMp4(data.subarray(8))
  27683. };
  27684. },
  27685. stsz: function stsz(data) {
  27686. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27687. result = {
  27688. version: data[0],
  27689. flags: new Uint8Array(data.subarray(1, 4)),
  27690. sampleSize: view.getUint32(4),
  27691. entries: []
  27692. },
  27693. i;
  27694. for (i = 12; i < data.byteLength; i += 4) {
  27695. result.entries.push(view.getUint32(i));
  27696. }
  27697. return result;
  27698. },
  27699. stts: function stts(data) {
  27700. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27701. result = {
  27702. version: data[0],
  27703. flags: new Uint8Array(data.subarray(1, 4)),
  27704. timeToSamples: []
  27705. },
  27706. entryCount = view.getUint32(4),
  27707. i;
  27708. for (i = 8; entryCount; i += 8, entryCount--) {
  27709. result.timeToSamples.push({
  27710. sampleCount: view.getUint32(i),
  27711. sampleDelta: view.getUint32(i + 4)
  27712. });
  27713. }
  27714. return result;
  27715. },
  27716. styp: function styp(data) {
  27717. return parse$1.ftyp(data);
  27718. },
  27719. tfdt: function tfdt(data) {
  27720. var result = {
  27721. version: data[0],
  27722. flags: new Uint8Array(data.subarray(1, 4)),
  27723. baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  27724. };
  27725. if (result.version === 1) {
  27726. result.baseMediaDecodeTime *= Math.pow(2, 32);
  27727. result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11];
  27728. }
  27729. return result;
  27730. },
  27731. tfhd: function tfhd(data) {
  27732. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27733. result = {
  27734. version: data[0],
  27735. flags: new Uint8Array(data.subarray(1, 4)),
  27736. trackId: view.getUint32(4)
  27737. },
  27738. baseDataOffsetPresent = result.flags[2] & 0x01,
  27739. sampleDescriptionIndexPresent = result.flags[2] & 0x02,
  27740. defaultSampleDurationPresent = result.flags[2] & 0x08,
  27741. defaultSampleSizePresent = result.flags[2] & 0x10,
  27742. defaultSampleFlagsPresent = result.flags[2] & 0x20,
  27743. durationIsEmpty = result.flags[0] & 0x010000,
  27744. defaultBaseIsMoof = result.flags[0] & 0x020000,
  27745. i;
  27746. i = 8;
  27747. if (baseDataOffsetPresent) {
  27748. i += 4; // truncate top 4 bytes
  27749. // FIXME: should we read the full 64 bits?
  27750. result.baseDataOffset = view.getUint32(12);
  27751. i += 4;
  27752. }
  27753. if (sampleDescriptionIndexPresent) {
  27754. result.sampleDescriptionIndex = view.getUint32(i);
  27755. i += 4;
  27756. }
  27757. if (defaultSampleDurationPresent) {
  27758. result.defaultSampleDuration = view.getUint32(i);
  27759. i += 4;
  27760. }
  27761. if (defaultSampleSizePresent) {
  27762. result.defaultSampleSize = view.getUint32(i);
  27763. i += 4;
  27764. }
  27765. if (defaultSampleFlagsPresent) {
  27766. result.defaultSampleFlags = view.getUint32(i);
  27767. }
  27768. if (durationIsEmpty) {
  27769. result.durationIsEmpty = true;
  27770. }
  27771. if (!baseDataOffsetPresent && defaultBaseIsMoof) {
  27772. result.baseDataOffsetIsMoof = true;
  27773. }
  27774. return result;
  27775. },
  27776. tkhd: function tkhd(data) {
  27777. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27778. i = 4,
  27779. result = {
  27780. version: view.getUint8(0),
  27781. flags: new Uint8Array(data.subarray(1, 4))
  27782. };
  27783. if (result.version === 1) {
  27784. i += 4;
  27785. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  27786. i += 8;
  27787. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  27788. i += 4;
  27789. result.trackId = view.getUint32(i);
  27790. i += 4;
  27791. i += 8;
  27792. result.duration = view.getUint32(i); // truncating top 4 bytes
  27793. } else {
  27794. result.creationTime = parseMp4Date(view.getUint32(i));
  27795. i += 4;
  27796. result.modificationTime = parseMp4Date(view.getUint32(i));
  27797. i += 4;
  27798. result.trackId = view.getUint32(i);
  27799. i += 4;
  27800. i += 4;
  27801. result.duration = view.getUint32(i);
  27802. }
  27803. i += 4;
  27804. i += 2 * 4;
  27805. result.layer = view.getUint16(i);
  27806. i += 2;
  27807. result.alternateGroup = view.getUint16(i);
  27808. i += 2; // convert fixed-point, base 16 back to a number
  27809. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  27810. i += 2;
  27811. i += 2;
  27812. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  27813. i += 9 * 4;
  27814. result.width = view.getUint16(i) + view.getUint16(i + 2) / 16;
  27815. i += 4;
  27816. result.height = view.getUint16(i) + view.getUint16(i + 2) / 16;
  27817. return result;
  27818. },
  27819. traf: function traf(data) {
  27820. return {
  27821. boxes: inspectMp4(data)
  27822. };
  27823. },
  27824. trak: function trak(data) {
  27825. return {
  27826. boxes: inspectMp4(data)
  27827. };
  27828. },
  27829. trex: function trex(data) {
  27830. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  27831. return {
  27832. version: data[0],
  27833. flags: new Uint8Array(data.subarray(1, 4)),
  27834. trackId: view.getUint32(4),
  27835. defaultSampleDescriptionIndex: view.getUint32(8),
  27836. defaultSampleDuration: view.getUint32(12),
  27837. defaultSampleSize: view.getUint32(16),
  27838. sampleDependsOn: data[20] & 0x03,
  27839. sampleIsDependedOn: (data[21] & 0xc0) >> 6,
  27840. sampleHasRedundancy: (data[21] & 0x30) >> 4,
  27841. samplePaddingValue: (data[21] & 0x0e) >> 1,
  27842. sampleIsDifferenceSample: !!(data[21] & 0x01),
  27843. sampleDegradationPriority: view.getUint16(22)
  27844. };
  27845. },
  27846. trun: function trun(data) {
  27847. var result = {
  27848. version: data[0],
  27849. flags: new Uint8Array(data.subarray(1, 4)),
  27850. samples: []
  27851. },
  27852. view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  27853. // Flag interpretation
  27854. dataOffsetPresent = result.flags[2] & 0x01,
  27855. // compare with 2nd byte of 0x1
  27856. firstSampleFlagsPresent = result.flags[2] & 0x04,
  27857. // compare with 2nd byte of 0x4
  27858. sampleDurationPresent = result.flags[1] & 0x01,
  27859. // compare with 2nd byte of 0x100
  27860. sampleSizePresent = result.flags[1] & 0x02,
  27861. // compare with 2nd byte of 0x200
  27862. sampleFlagsPresent = result.flags[1] & 0x04,
  27863. // compare with 2nd byte of 0x400
  27864. sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
  27865. // compare with 2nd byte of 0x800
  27866. sampleCount = view.getUint32(4),
  27867. offset = 8,
  27868. sample;
  27869. if (dataOffsetPresent) {
  27870. // 32 bit signed integer
  27871. result.dataOffset = view.getInt32(offset);
  27872. offset += 4;
  27873. } // Overrides the flags for the first sample only. The order of
  27874. // optional values will be: duration, size, compositionTimeOffset
  27875. if (firstSampleFlagsPresent && sampleCount) {
  27876. sample = {
  27877. flags: parseSampleFlags(data.subarray(offset, offset + 4))
  27878. };
  27879. offset += 4;
  27880. if (sampleDurationPresent) {
  27881. sample.duration = view.getUint32(offset);
  27882. offset += 4;
  27883. }
  27884. if (sampleSizePresent) {
  27885. sample.size = view.getUint32(offset);
  27886. offset += 4;
  27887. }
  27888. if (sampleCompositionTimeOffsetPresent) {
  27889. // Note: this should be a signed int if version is 1
  27890. sample.compositionTimeOffset = view.getUint32(offset);
  27891. offset += 4;
  27892. }
  27893. result.samples.push(sample);
  27894. sampleCount--;
  27895. }
  27896. while (sampleCount--) {
  27897. sample = {};
  27898. if (sampleDurationPresent) {
  27899. sample.duration = view.getUint32(offset);
  27900. offset += 4;
  27901. }
  27902. if (sampleSizePresent) {
  27903. sample.size = view.getUint32(offset);
  27904. offset += 4;
  27905. }
  27906. if (sampleFlagsPresent) {
  27907. sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
  27908. offset += 4;
  27909. }
  27910. if (sampleCompositionTimeOffsetPresent) {
  27911. // Note: this should be a signed int if version is 1
  27912. sample.compositionTimeOffset = view.getUint32(offset);
  27913. offset += 4;
  27914. }
  27915. result.samples.push(sample);
  27916. }
  27917. return result;
  27918. },
  27919. 'url ': function url(data) {
  27920. return {
  27921. version: data[0],
  27922. flags: new Uint8Array(data.subarray(1, 4))
  27923. };
  27924. },
  27925. vmhd: function vmhd(data) {
  27926. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  27927. return {
  27928. version: data[0],
  27929. flags: new Uint8Array(data.subarray(1, 4)),
  27930. graphicsmode: view.getUint16(4),
  27931. opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
  27932. };
  27933. }
  27934. };
  27935. /**
  27936. * Return a javascript array of box objects parsed from an ISO base
  27937. * media file.
  27938. * @param data {Uint8Array} the binary data of the media to be inspected
  27939. * @return {array} a javascript array of potentially nested box objects
  27940. */
  27941. inspectMp4 = function inspectMp4(data) {
  27942. var i = 0,
  27943. result = [],
  27944. view,
  27945. size,
  27946. type,
  27947. end,
  27948. box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
  27949. var ab = new ArrayBuffer(data.length);
  27950. var v = new Uint8Array(ab);
  27951. for (var z = 0; z < data.length; ++z) {
  27952. v[z] = data[z];
  27953. }
  27954. view = new DataView(ab);
  27955. while (i < data.byteLength) {
  27956. // parse box data
  27957. size = view.getUint32(i);
  27958. type = parseType$1(data.subarray(i + 4, i + 8));
  27959. end = size > 1 ? i + size : data.byteLength; // parse type-specific data
  27960. box = (parse$1[type] || function (data) {
  27961. return {
  27962. data: data
  27963. };
  27964. })(data.subarray(i + 8, end));
  27965. box.size = size;
  27966. box.type = type; // store this box and move to the next
  27967. result.push(box);
  27968. i = end;
  27969. }
  27970. return result;
  27971. };
  27972. /**
  27973. * Returns a textual representation of the javascript represtentation
  27974. * of an MP4 file. You can use it as an alternative to
  27975. * JSON.stringify() to compare inspected MP4s.
  27976. * @param inspectedMp4 {array} the parsed array of boxes in an MP4
  27977. * file
  27978. * @param depth {number} (optional) the number of ancestor boxes of
  27979. * the elements of inspectedMp4. Assumed to be zero if unspecified.
  27980. * @return {string} a text representation of the parsed MP4
  27981. */
  27982. _textifyMp = function textifyMp4(inspectedMp4, depth) {
  27983. var indent;
  27984. depth = depth || 0;
  27985. indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
  27986. return inspectedMp4.map(function (box, index) {
  27987. // list the box type first at the current indentation level
  27988. return indent + box.type + '\n' + // the type is already included and handle child boxes separately
  27989. Object.keys(box).filter(function (key) {
  27990. return key !== 'type' && key !== 'boxes'; // output all the box properties
  27991. }).map(function (key) {
  27992. var prefix = indent + ' ' + key + ': ',
  27993. value = box[key]; // print out raw bytes as hexademical
  27994. if (value instanceof Uint8Array || value instanceof Uint32Array) {
  27995. var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) {
  27996. return ' ' + ('00' + byte.toString(16)).slice(-2);
  27997. }).join('').match(/.{1,24}/g);
  27998. if (!bytes) {
  27999. return prefix + '<>';
  28000. }
  28001. if (bytes.length === 1) {
  28002. return prefix + '<' + bytes.join('').slice(1) + '>';
  28003. }
  28004. return prefix + '<\n' + bytes.map(function (line) {
  28005. return indent + ' ' + line;
  28006. }).join('\n') + '\n' + indent + ' >';
  28007. } // stringify generic objects
  28008. return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
  28009. if (index === 0) {
  28010. return line;
  28011. }
  28012. return indent + ' ' + line;
  28013. }).join('\n');
  28014. }).join('\n') + ( // recursively textify the child boxes
  28015. box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
  28016. }).join('\n');
  28017. };
  28018. var mp4Inspector = {
  28019. inspect: inspectMp4,
  28020. textify: _textifyMp,
  28021. parseTfdt: parse$1.tfdt,
  28022. parseHdlr: parse$1.hdlr,
  28023. parseTfhd: parse$1.tfhd,
  28024. parseTrun: parse$1.trun,
  28025. parseSidx: parse$1.sidx
  28026. };
  28027. /**
  28028. * mux.js
  28029. *
  28030. * Copyright (c) 2015 Brightcove
  28031. * All rights reserved.
  28032. *
  28033. * Functions that generate fragmented MP4s suitable for use with Media
  28034. * Source Extensions.
  28035. */
  28036. var UINT32_MAX = Math.pow(2, 32) - 1;
  28037. var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants
  28038. (function () {
  28039. var i;
  28040. types = {
  28041. avc1: [],
  28042. // codingname
  28043. avcC: [],
  28044. btrt: [],
  28045. dinf: [],
  28046. dref: [],
  28047. esds: [],
  28048. ftyp: [],
  28049. hdlr: [],
  28050. mdat: [],
  28051. mdhd: [],
  28052. mdia: [],
  28053. mfhd: [],
  28054. minf: [],
  28055. moof: [],
  28056. moov: [],
  28057. mp4a: [],
  28058. // codingname
  28059. mvex: [],
  28060. mvhd: [],
  28061. sdtp: [],
  28062. smhd: [],
  28063. stbl: [],
  28064. stco: [],
  28065. stsc: [],
  28066. stsd: [],
  28067. stsz: [],
  28068. stts: [],
  28069. styp: [],
  28070. tfdt: [],
  28071. tfhd: [],
  28072. traf: [],
  28073. trak: [],
  28074. trun: [],
  28075. trex: [],
  28076. tkhd: [],
  28077. vmhd: []
  28078. }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
  28079. // don't throw an error
  28080. if (typeof Uint8Array === 'undefined') {
  28081. return;
  28082. }
  28083. for (i in types) {
  28084. if (types.hasOwnProperty(i)) {
  28085. types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
  28086. }
  28087. }
  28088. MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
  28089. AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
  28090. MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
  28091. VIDEO_HDLR = new Uint8Array([0x00, // version 0
  28092. 0x00, 0x00, 0x00, // flags
  28093. 0x00, 0x00, 0x00, 0x00, // pre_defined
  28094. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  28095. 0x00, 0x00, 0x00, 0x00, // reserved
  28096. 0x00, 0x00, 0x00, 0x00, // reserved
  28097. 0x00, 0x00, 0x00, 0x00, // reserved
  28098. 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  28099. ]);
  28100. AUDIO_HDLR = new Uint8Array([0x00, // version 0
  28101. 0x00, 0x00, 0x00, // flags
  28102. 0x00, 0x00, 0x00, 0x00, // pre_defined
  28103. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  28104. 0x00, 0x00, 0x00, 0x00, // reserved
  28105. 0x00, 0x00, 0x00, 0x00, // reserved
  28106. 0x00, 0x00, 0x00, 0x00, // reserved
  28107. 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  28108. ]);
  28109. HDLR_TYPES = {
  28110. video: VIDEO_HDLR,
  28111. audio: AUDIO_HDLR
  28112. };
  28113. DREF = new Uint8Array([0x00, // version 0
  28114. 0x00, 0x00, 0x00, // flags
  28115. 0x00, 0x00, 0x00, 0x01, // entry_count
  28116. 0x00, 0x00, 0x00, 0x0c, // entry_size
  28117. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  28118. 0x00, // version 0
  28119. 0x00, 0x00, 0x01 // entry_flags
  28120. ]);
  28121. SMHD = new Uint8Array([0x00, // version
  28122. 0x00, 0x00, 0x00, // flags
  28123. 0x00, 0x00, // balance, 0 means centered
  28124. 0x00, 0x00 // reserved
  28125. ]);
  28126. STCO = new Uint8Array([0x00, // version
  28127. 0x00, 0x00, 0x00, // flags
  28128. 0x00, 0x00, 0x00, 0x00 // entry_count
  28129. ]);
  28130. STSC = STCO;
  28131. STSZ = new Uint8Array([0x00, // version
  28132. 0x00, 0x00, 0x00, // flags
  28133. 0x00, 0x00, 0x00, 0x00, // sample_size
  28134. 0x00, 0x00, 0x00, 0x00 // sample_count
  28135. ]);
  28136. STTS = STCO;
  28137. VMHD = new Uint8Array([0x00, // version
  28138. 0x00, 0x00, 0x01, // flags
  28139. 0x00, 0x00, // graphicsmode
  28140. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
  28141. ]);
  28142. })();
  28143. box = function box(type) {
  28144. var payload = [],
  28145. size = 0,
  28146. i,
  28147. result,
  28148. view;
  28149. for (i = 1; i < arguments.length; i++) {
  28150. payload.push(arguments[i]);
  28151. }
  28152. i = payload.length; // calculate the total size we need to allocate
  28153. while (i--) {
  28154. size += payload[i].byteLength;
  28155. }
  28156. result = new Uint8Array(size + 8);
  28157. view = new DataView(result.buffer, result.byteOffset, result.byteLength);
  28158. view.setUint32(0, result.byteLength);
  28159. result.set(type, 4); // copy the payload into the result
  28160. for (i = 0, size = 8; i < payload.length; i++) {
  28161. result.set(payload[i], size);
  28162. size += payload[i].byteLength;
  28163. }
  28164. return result;
  28165. };
  28166. dinf = function dinf() {
  28167. return box(types.dinf, box(types.dref, DREF));
  28168. };
  28169. esds = function esds(track) {
  28170. return box(types.esds, new Uint8Array([0x00, // version
  28171. 0x00, 0x00, 0x00, // flags
  28172. // ES_Descriptor
  28173. 0x03, // tag, ES_DescrTag
  28174. 0x19, // length
  28175. 0x00, 0x00, // ES_ID
  28176. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  28177. // DecoderConfigDescriptor
  28178. 0x04, // tag, DecoderConfigDescrTag
  28179. 0x11, // length
  28180. 0x40, // object type
  28181. 0x15, // streamType
  28182. 0x00, 0x06, 0x00, // bufferSizeDB
  28183. 0x00, 0x00, 0xda, 0xc0, // maxBitrate
  28184. 0x00, 0x00, 0xda, 0xc0, // avgBitrate
  28185. // DecoderSpecificInfo
  28186. 0x05, // tag, DecoderSpecificInfoTag
  28187. 0x02, // length
  28188. // ISO/IEC 14496-3, AudioSpecificConfig
  28189. // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  28190. track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
  28191. ]));
  28192. };
  28193. ftyp = function ftyp() {
  28194. return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
  28195. };
  28196. hdlr = function hdlr(type) {
  28197. return box(types.hdlr, HDLR_TYPES[type]);
  28198. };
  28199. mdat = function mdat(data) {
  28200. return box(types.mdat, data);
  28201. };
  28202. mdhd = function mdhd(track) {
  28203. var result = new Uint8Array([0x00, // version 0
  28204. 0x00, 0x00, 0x00, // flags
  28205. 0x00, 0x00, 0x00, 0x02, // creation_time
  28206. 0x00, 0x00, 0x00, 0x03, // modification_time
  28207. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  28208. track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
  28209. 0x55, 0xc4, // 'und' language (undetermined)
  28210. 0x00, 0x00]); // Use the sample rate from the track metadata, when it is
  28211. // defined. The sample rate can be parsed out of an ADTS header, for
  28212. // instance.
  28213. if (track.samplerate) {
  28214. result[12] = track.samplerate >>> 24 & 0xFF;
  28215. result[13] = track.samplerate >>> 16 & 0xFF;
  28216. result[14] = track.samplerate >>> 8 & 0xFF;
  28217. result[15] = track.samplerate & 0xFF;
  28218. }
  28219. return box(types.mdhd, result);
  28220. };
  28221. mdia = function mdia(track) {
  28222. return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
  28223. };
  28224. mfhd = function mfhd(sequenceNumber) {
  28225. return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
  28226. (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
  28227. ]));
  28228. };
  28229. minf = function minf(track) {
  28230. return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
  28231. };
  28232. moof = function moof(sequenceNumber, tracks) {
  28233. var trackFragments = [],
  28234. i = tracks.length; // build traf boxes for each track fragment
  28235. while (i--) {
  28236. trackFragments[i] = traf(tracks[i]);
  28237. }
  28238. return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
  28239. };
  28240. /**
  28241. * Returns a movie box.
  28242. * @param tracks {array} the tracks associated with this movie
  28243. * @see ISO/IEC 14496-12:2012(E), section 8.2.1
  28244. */
  28245. moov = function moov(tracks) {
  28246. var i = tracks.length,
  28247. boxes = [];
  28248. while (i--) {
  28249. boxes[i] = trak(tracks[i]);
  28250. }
  28251. return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
  28252. };
  28253. mvex = function mvex(tracks) {
  28254. var i = tracks.length,
  28255. boxes = [];
  28256. while (i--) {
  28257. boxes[i] = trex(tracks[i]);
  28258. }
  28259. return box.apply(null, [types.mvex].concat(boxes));
  28260. };
  28261. mvhd = function mvhd(duration) {
  28262. var bytes = new Uint8Array([0x00, // version 0
  28263. 0x00, 0x00, 0x00, // flags
  28264. 0x00, 0x00, 0x00, 0x01, // creation_time
  28265. 0x00, 0x00, 0x00, 0x02, // modification_time
  28266. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  28267. (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
  28268. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  28269. 0x01, 0x00, // 1.0 volume
  28270. 0x00, 0x00, // reserved
  28271. 0x00, 0x00, 0x00, 0x00, // reserved
  28272. 0x00, 0x00, 0x00, 0x00, // reserved
  28273. 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
  28274. 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
  28275. 0xff, 0xff, 0xff, 0xff // next_track_ID
  28276. ]);
  28277. return box(types.mvhd, bytes);
  28278. };
  28279. sdtp = function sdtp(track) {
  28280. var samples = track.samples || [],
  28281. bytes = new Uint8Array(4 + samples.length),
  28282. flags,
  28283. i; // leave the full box header (4 bytes) all zero
  28284. // write the sample table
  28285. for (i = 0; i < samples.length; i++) {
  28286. flags = samples[i].flags;
  28287. bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
  28288. }
  28289. return box(types.sdtp, bytes);
  28290. };
  28291. stbl = function stbl(track) {
  28292. return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
  28293. };
  28294. (function () {
  28295. var videoSample, audioSample;
  28296. stsd = function stsd(track) {
  28297. return box(types.stsd, new Uint8Array([0x00, // version 0
  28298. 0x00, 0x00, 0x00, // flags
  28299. 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
  28300. };
  28301. videoSample = function videoSample(track) {
  28302. var sps = track.sps || [],
  28303. pps = track.pps || [],
  28304. sequenceParameterSets = [],
  28305. pictureParameterSets = [],
  28306. i; // assemble the SPSs
  28307. for (i = 0; i < sps.length; i++) {
  28308. sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
  28309. sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
  28310. sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
  28311. } // assemble the PPSs
  28312. for (i = 0; i < pps.length; i++) {
  28313. pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
  28314. pictureParameterSets.push(pps[i].byteLength & 0xFF);
  28315. pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
  28316. }
  28317. return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  28318. 0x00, 0x01, // data_reference_index
  28319. 0x00, 0x00, // pre_defined
  28320. 0x00, 0x00, // reserved
  28321. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  28322. (track.width & 0xff00) >> 8, track.width & 0xff, // width
  28323. (track.height & 0xff00) >> 8, track.height & 0xff, // height
  28324. 0x00, 0x48, 0x00, 0x00, // horizresolution
  28325. 0x00, 0x48, 0x00, 0x00, // vertresolution
  28326. 0x00, 0x00, 0x00, 0x00, // reserved
  28327. 0x00, 0x01, // frame_count
  28328. 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
  28329. 0x00, 0x18, // depth = 24
  28330. 0x11, 0x11 // pre_defined = -1
  28331. ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
  28332. track.profileIdc, // AVCProfileIndication
  28333. track.profileCompatibility, // profile_compatibility
  28334. track.levelIdc, // AVCLevelIndication
  28335. 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
  28336. ].concat([sps.length // numOfSequenceParameterSets
  28337. ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
  28338. ]).concat(pictureParameterSets))), // "PPS"
  28339. box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  28340. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  28341. 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
  28342. );
  28343. };
  28344. audioSample = function audioSample(track) {
  28345. return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
  28346. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  28347. 0x00, 0x01, // data_reference_index
  28348. // AudioSampleEntry, ISO/IEC 14496-12
  28349. 0x00, 0x00, 0x00, 0x00, // reserved
  28350. 0x00, 0x00, 0x00, 0x00, // reserved
  28351. (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
  28352. (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
  28353. 0x00, 0x00, // pre_defined
  28354. 0x00, 0x00, // reserved
  28355. (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
  28356. // MP4AudioSampleEntry, ISO/IEC 14496-14
  28357. ]), esds(track));
  28358. };
  28359. })();
  28360. tkhd = function tkhd(track) {
  28361. var result = new Uint8Array([0x00, // version 0
  28362. 0x00, 0x00, 0x07, // flags
  28363. 0x00, 0x00, 0x00, 0x00, // creation_time
  28364. 0x00, 0x00, 0x00, 0x00, // modification_time
  28365. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  28366. 0x00, 0x00, 0x00, 0x00, // reserved
  28367. (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
  28368. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  28369. 0x00, 0x00, // layer
  28370. 0x00, 0x00, // alternate_group
  28371. 0x01, 0x00, // non-audio track volume
  28372. 0x00, 0x00, // reserved
  28373. 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
  28374. (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
  28375. (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
  28376. ]);
  28377. return box(types.tkhd, result);
  28378. };
  28379. /**
  28380. * Generate a track fragment (traf) box. A traf box collects metadata
  28381. * about tracks in a movie fragment (moof) box.
  28382. */
  28383. traf = function traf(track) {
  28384. var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
  28385. trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
  28386. 0x00, 0x00, 0x3a, // flags
  28387. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  28388. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  28389. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  28390. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  28391. 0x00, 0x00, 0x00, 0x00 // default_sample_flags
  28392. ]));
  28393. upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
  28394. lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
  28395. trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
  28396. 0x00, 0x00, 0x00, // flags
  28397. // baseMediaDecodeTime
  28398. 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
  28399. // the containing moof to the first payload byte of the associated
  28400. // mdat
  28401. dataOffset = 32 + // tfhd
  28402. 20 + // tfdt
  28403. 8 + // traf header
  28404. 16 + // mfhd
  28405. 8 + // moof header
  28406. 8; // mdat header
  28407. // audio tracks require less metadata
  28408. if (track.type === 'audio') {
  28409. trackFragmentRun = trun(track, dataOffset);
  28410. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
  28411. } // video tracks should contain an independent and disposable samples
  28412. // box (sdtp)
  28413. // generate one and adjust offsets to match
  28414. sampleDependencyTable = sdtp(track);
  28415. trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
  28416. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
  28417. };
  28418. /**
  28419. * Generate a track box.
  28420. * @param track {object} a track definition
  28421. * @return {Uint8Array} the track box
  28422. */
  28423. trak = function trak(track) {
  28424. track.duration = track.duration || 0xffffffff;
  28425. return box(types.trak, tkhd(track), mdia(track));
  28426. };
  28427. trex = function trex(track) {
  28428. var result = new Uint8Array([0x00, // version 0
  28429. 0x00, 0x00, 0x00, // flags
  28430. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  28431. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  28432. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  28433. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  28434. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  28435. ]); // the last two bytes of default_sample_flags is the sample
  28436. // degradation priority, a hint about the importance of this sample
  28437. // relative to others. Lower the degradation priority for all sample
  28438. // types other than video.
  28439. if (track.type !== 'video') {
  28440. result[result.length - 1] = 0x00;
  28441. }
  28442. return box(types.trex, result);
  28443. };
  28444. (function () {
  28445. var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
  28446. // duration is present for the first sample, it will be present for
  28447. // all subsequent samples.
  28448. // see ISO/IEC 14496-12:2012, Section 8.8.8.1
  28449. trunHeader = function trunHeader(samples, offset) {
  28450. var durationPresent = 0,
  28451. sizePresent = 0,
  28452. flagsPresent = 0,
  28453. compositionTimeOffset = 0; // trun flag constants
  28454. if (samples.length) {
  28455. if (samples[0].duration !== undefined) {
  28456. durationPresent = 0x1;
  28457. }
  28458. if (samples[0].size !== undefined) {
  28459. sizePresent = 0x2;
  28460. }
  28461. if (samples[0].flags !== undefined) {
  28462. flagsPresent = 0x4;
  28463. }
  28464. if (samples[0].compositionTimeOffset !== undefined) {
  28465. compositionTimeOffset = 0x8;
  28466. }
  28467. }
  28468. return [0x00, // version 0
  28469. 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
  28470. (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
  28471. (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
  28472. ];
  28473. };
  28474. videoTrun = function videoTrun(track, offset) {
  28475. var bytes, samples, sample, i;
  28476. samples = track.samples || [];
  28477. offset += 8 + 12 + 16 * samples.length;
  28478. bytes = trunHeader(samples, offset);
  28479. for (i = 0; i < samples.length; i++) {
  28480. sample = samples[i];
  28481. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  28482. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
  28483. sample.flags.isLeading << 2 | sample.flags.dependsOn, sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample, sample.flags.degradationPriority & 0xF0 << 8, sample.flags.degradationPriority & 0x0F, // sample_flags
  28484. (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
  28485. ]);
  28486. }
  28487. return box(types.trun, new Uint8Array(bytes));
  28488. };
  28489. audioTrun = function audioTrun(track, offset) {
  28490. var bytes, samples, sample, i;
  28491. samples = track.samples || [];
  28492. offset += 8 + 12 + 8 * samples.length;
  28493. bytes = trunHeader(samples, offset);
  28494. for (i = 0; i < samples.length; i++) {
  28495. sample = samples[i];
  28496. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  28497. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
  28498. }
  28499. return box(types.trun, new Uint8Array(bytes));
  28500. };
  28501. trun = function trun(track, offset) {
  28502. if (track.type === 'audio') {
  28503. return audioTrun(track, offset);
  28504. }
  28505. return videoTrun(track, offset);
  28506. };
  28507. })();
  28508. var mp4Generator = {
  28509. ftyp: ftyp,
  28510. mdat: mdat,
  28511. moof: moof,
  28512. moov: moov,
  28513. initSegment: function initSegment(tracks) {
  28514. var fileType = ftyp(),
  28515. movie = moov(tracks),
  28516. result;
  28517. result = new Uint8Array(fileType.byteLength + movie.byteLength);
  28518. result.set(fileType);
  28519. result.set(movie, fileType.byteLength);
  28520. return result;
  28521. }
  28522. };
  28523. /**
  28524. * mux.js
  28525. *
  28526. * Copyright (c) 2014 Brightcove
  28527. * All rights reserved.
  28528. *
  28529. * A lightweight readable stream implemention that handles event dispatching.
  28530. * Objects that inherit from streams should call init in their constructors.
  28531. */
  28532. var Stream$1 = function Stream() {
  28533. this.init = function () {
  28534. var listeners = {};
  28535. /**
  28536. * Add a listener for a specified event type.
  28537. * @param type {string} the event name
  28538. * @param listener {function} the callback to be invoked when an event of
  28539. * the specified type occurs
  28540. */
  28541. this.on = function (type, listener) {
  28542. if (!listeners[type]) {
  28543. listeners[type] = [];
  28544. }
  28545. listeners[type] = listeners[type].concat(listener);
  28546. };
  28547. /**
  28548. * Remove a listener for a specified event type.
  28549. * @param type {string} the event name
  28550. * @param listener {function} a function previously registered for this
  28551. * type of event through `on`
  28552. */
  28553. this.off = function (type, listener) {
  28554. var index;
  28555. if (!listeners[type]) {
  28556. return false;
  28557. }
  28558. index = listeners[type].indexOf(listener);
  28559. listeners[type] = listeners[type].slice();
  28560. listeners[type].splice(index, 1);
  28561. return index > -1;
  28562. };
  28563. /**
  28564. * Trigger an event of the specified type on this stream. Any additional
  28565. * arguments to this function are passed as parameters to event listeners.
  28566. * @param type {string} the event name
  28567. */
  28568. this.trigger = function (type) {
  28569. var callbacks, i, length, args;
  28570. callbacks = listeners[type];
  28571. if (!callbacks) {
  28572. return;
  28573. } // Slicing the arguments on every invocation of this method
  28574. // can add a significant amount of overhead. Avoid the
  28575. // intermediate object creation for the common case of a
  28576. // single callback argument
  28577. if (arguments.length === 2) {
  28578. length = callbacks.length;
  28579. for (i = 0; i < length; ++i) {
  28580. callbacks[i].call(this, arguments[1]);
  28581. }
  28582. } else {
  28583. args = [];
  28584. i = arguments.length;
  28585. for (i = 1; i < arguments.length; ++i) {
  28586. args.push(arguments[i]);
  28587. }
  28588. length = callbacks.length;
  28589. for (i = 0; i < length; ++i) {
  28590. callbacks[i].apply(this, args);
  28591. }
  28592. }
  28593. };
  28594. /**
  28595. * Destroys the stream and cleans up.
  28596. */
  28597. this.dispose = function () {
  28598. listeners = {};
  28599. };
  28600. };
  28601. };
  28602. /**
  28603. * Forwards all `data` events on this stream to the destination stream. The
  28604. * destination stream should provide a method `push` to receive the data
  28605. * events as they arrive.
  28606. * @param destination {stream} the stream that will receive all `data` events
  28607. * @param autoFlush {boolean} if false, we will not call `flush` on the destination
  28608. * when the current stream emits a 'done' event
  28609. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  28610. */
  28611. Stream$1.prototype.pipe = function (destination) {
  28612. this.on('data', function (data) {
  28613. destination.push(data);
  28614. });
  28615. this.on('done', function (flushSource) {
  28616. destination.flush(flushSource);
  28617. });
  28618. return destination;
  28619. }; // Default stream functions that are expected to be overridden to perform
  28620. // actual work. These are provided by the prototype as a sort of no-op
  28621. // implementation so that we don't have to check for their existence in the
  28622. // `pipe` function above.
  28623. Stream$1.prototype.push = function (data) {
  28624. this.trigger('data', data);
  28625. };
  28626. Stream$1.prototype.flush = function (flushSource) {
  28627. this.trigger('done', flushSource);
  28628. };
  28629. var stream = Stream$1;
  28630. // Convert an array of nal units into an array of frames with each frame being
  28631. // composed of the nal units that make up that frame
  28632. // Also keep track of cummulative data about the frame from the nal units such
  28633. // as the frame duration, starting pts, etc.
  28634. var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
  28635. var i,
  28636. currentNal,
  28637. currentFrame = [],
  28638. frames = [];
  28639. currentFrame.byteLength = 0;
  28640. for (i = 0; i < nalUnits.length; i++) {
  28641. currentNal = nalUnits[i]; // Split on 'aud'-type nal units
  28642. if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  28643. // Since the very first nal unit is expected to be an AUD
  28644. // only push to the frames array when currentFrame is not empty
  28645. if (currentFrame.length) {
  28646. currentFrame.duration = currentNal.dts - currentFrame.dts;
  28647. frames.push(currentFrame);
  28648. }
  28649. currentFrame = [currentNal];
  28650. currentFrame.byteLength = currentNal.data.byteLength;
  28651. currentFrame.pts = currentNal.pts;
  28652. currentFrame.dts = currentNal.dts;
  28653. } else {
  28654. // Specifically flag key frames for ease of use later
  28655. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  28656. currentFrame.keyFrame = true;
  28657. }
  28658. currentFrame.duration = currentNal.dts - currentFrame.dts;
  28659. currentFrame.byteLength += currentNal.data.byteLength;
  28660. currentFrame.push(currentNal);
  28661. }
  28662. } // For the last frame, use the duration of the previous frame if we
  28663. // have nothing better to go on
  28664. if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
  28665. currentFrame.duration = frames[frames.length - 1].duration;
  28666. } // Push the final frame
  28667. frames.push(currentFrame);
  28668. return frames;
  28669. }; // Convert an array of frames into an array of Gop with each Gop being composed
  28670. // of the frames that make up that Gop
  28671. // Also keep track of cummulative data about the Gop from the frames such as the
  28672. // Gop duration, starting pts, etc.
  28673. var groupFramesIntoGops = function groupFramesIntoGops(frames) {
  28674. var i,
  28675. currentFrame,
  28676. currentGop = [],
  28677. gops = []; // We must pre-set some of the values on the Gop since we
  28678. // keep running totals of these values
  28679. currentGop.byteLength = 0;
  28680. currentGop.nalCount = 0;
  28681. currentGop.duration = 0;
  28682. currentGop.pts = frames[0].pts;
  28683. currentGop.dts = frames[0].dts; // store some metadata about all the Gops
  28684. gops.byteLength = 0;
  28685. gops.nalCount = 0;
  28686. gops.duration = 0;
  28687. gops.pts = frames[0].pts;
  28688. gops.dts = frames[0].dts;
  28689. for (i = 0; i < frames.length; i++) {
  28690. currentFrame = frames[i];
  28691. if (currentFrame.keyFrame) {
  28692. // Since the very first frame is expected to be an keyframe
  28693. // only push to the gops array when currentGop is not empty
  28694. if (currentGop.length) {
  28695. gops.push(currentGop);
  28696. gops.byteLength += currentGop.byteLength;
  28697. gops.nalCount += currentGop.nalCount;
  28698. gops.duration += currentGop.duration;
  28699. }
  28700. currentGop = [currentFrame];
  28701. currentGop.nalCount = currentFrame.length;
  28702. currentGop.byteLength = currentFrame.byteLength;
  28703. currentGop.pts = currentFrame.pts;
  28704. currentGop.dts = currentFrame.dts;
  28705. currentGop.duration = currentFrame.duration;
  28706. } else {
  28707. currentGop.duration += currentFrame.duration;
  28708. currentGop.nalCount += currentFrame.length;
  28709. currentGop.byteLength += currentFrame.byteLength;
  28710. currentGop.push(currentFrame);
  28711. }
  28712. }
  28713. if (gops.length && currentGop.duration <= 0) {
  28714. currentGop.duration = gops[gops.length - 1].duration;
  28715. }
  28716. gops.byteLength += currentGop.byteLength;
  28717. gops.nalCount += currentGop.nalCount;
  28718. gops.duration += currentGop.duration; // push the final Gop
  28719. gops.push(currentGop);
  28720. return gops;
  28721. };
  28722. /*
  28723. * Search for the first keyframe in the GOPs and throw away all frames
  28724. * until that keyframe. Then extend the duration of the pulled keyframe
  28725. * and pull the PTS and DTS of the keyframe so that it covers the time
  28726. * range of the frames that were disposed.
  28727. *
  28728. * @param {Array} gops video GOPs
  28729. * @returns {Array} modified video GOPs
  28730. */
  28731. var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
  28732. var currentGop;
  28733. if (!gops[0][0].keyFrame && gops.length > 1) {
  28734. // Remove the first GOP
  28735. currentGop = gops.shift();
  28736. gops.byteLength -= currentGop.byteLength;
  28737. gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
  28738. // first gop to cover the time period of the
  28739. // frames we just removed
  28740. gops[0][0].dts = currentGop.dts;
  28741. gops[0][0].pts = currentGop.pts;
  28742. gops[0][0].duration += currentGop.duration;
  28743. }
  28744. return gops;
  28745. };
  28746. /**
  28747. * Default sample object
  28748. * see ISO/IEC 14496-12:2012, section 8.6.4.3
  28749. */
  28750. var createDefaultSample = function createDefaultSample() {
  28751. return {
  28752. size: 0,
  28753. flags: {
  28754. isLeading: 0,
  28755. dependsOn: 1,
  28756. isDependedOn: 0,
  28757. hasRedundancy: 0,
  28758. degradationPriority: 0,
  28759. isNonSyncSample: 1
  28760. }
  28761. };
  28762. };
  28763. /*
  28764. * Collates information from a video frame into an object for eventual
  28765. * entry into an MP4 sample table.
  28766. *
  28767. * @param {Object} frame the video frame
  28768. * @param {Number} dataOffset the byte offset to position the sample
  28769. * @return {Object} object containing sample table info for a frame
  28770. */
  28771. var sampleForFrame = function sampleForFrame(frame, dataOffset) {
  28772. var sample = createDefaultSample();
  28773. sample.dataOffset = dataOffset;
  28774. sample.compositionTimeOffset = frame.pts - frame.dts;
  28775. sample.duration = frame.duration;
  28776. sample.size = 4 * frame.length; // Space for nal unit size
  28777. sample.size += frame.byteLength;
  28778. if (frame.keyFrame) {
  28779. sample.flags.dependsOn = 2;
  28780. sample.flags.isNonSyncSample = 0;
  28781. }
  28782. return sample;
  28783. }; // generate the track's sample table from an array of gops
  28784. var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
  28785. var h,
  28786. i,
  28787. sample,
  28788. currentGop,
  28789. currentFrame,
  28790. dataOffset = baseDataOffset || 0,
  28791. samples = [];
  28792. for (h = 0; h < gops.length; h++) {
  28793. currentGop = gops[h];
  28794. for (i = 0; i < currentGop.length; i++) {
  28795. currentFrame = currentGop[i];
  28796. sample = sampleForFrame(currentFrame, dataOffset);
  28797. dataOffset += sample.size;
  28798. samples.push(sample);
  28799. }
  28800. }
  28801. return samples;
  28802. }; // generate the track's raw mdat data from an array of gops
  28803. var concatenateNalData = function concatenateNalData(gops) {
  28804. var h,
  28805. i,
  28806. j,
  28807. currentGop,
  28808. currentFrame,
  28809. currentNal,
  28810. dataOffset = 0,
  28811. nalsByteLength = gops.byteLength,
  28812. numberOfNals = gops.nalCount,
  28813. totalByteLength = nalsByteLength + 4 * numberOfNals,
  28814. data = new Uint8Array(totalByteLength),
  28815. view = new DataView(data.buffer); // For each Gop..
  28816. for (h = 0; h < gops.length; h++) {
  28817. currentGop = gops[h]; // For each Frame..
  28818. for (i = 0; i < currentGop.length; i++) {
  28819. currentFrame = currentGop[i]; // For each NAL..
  28820. for (j = 0; j < currentFrame.length; j++) {
  28821. currentNal = currentFrame[j];
  28822. view.setUint32(dataOffset, currentNal.data.byteLength);
  28823. dataOffset += 4;
  28824. data.set(currentNal.data, dataOffset);
  28825. dataOffset += currentNal.data.byteLength;
  28826. }
  28827. }
  28828. }
  28829. return data;
  28830. };
  28831. var frameUtils = {
  28832. groupNalsIntoFrames: groupNalsIntoFrames,
  28833. groupFramesIntoGops: groupFramesIntoGops,
  28834. extendFirstKeyFrame: extendFirstKeyFrame,
  28835. generateSampleTable: generateSampleTable,
  28836. concatenateNalData: concatenateNalData
  28837. };
  28838. var highPrefix = [33, 16, 5, 32, 164, 27];
  28839. var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
  28840. var zeroFill = function zeroFill(count) {
  28841. var a = [];
  28842. while (count--) {
  28843. a.push(0);
  28844. }
  28845. return a;
  28846. };
  28847. var makeTable = function makeTable(metaTable) {
  28848. return Object.keys(metaTable).reduce(function (obj, key) {
  28849. obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
  28850. return arr.concat(part);
  28851. }, []));
  28852. return obj;
  28853. }, {});
  28854. }; // Frames-of-silence to use for filling in missing AAC frames
  28855. var coneOfSilence = {
  28856. 96000: [highPrefix, [227, 64], zeroFill(154), [56]],
  28857. 88200: [highPrefix, [231], zeroFill(170), [56]],
  28858. 64000: [highPrefix, [248, 192], zeroFill(240), [56]],
  28859. 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
  28860. 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
  28861. 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
  28862. 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
  28863. 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
  28864. 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]],
  28865. 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]],
  28866. 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
  28867. };
  28868. var silence = makeTable(coneOfSilence);
  28869. var ONE_SECOND_IN_TS = 90000,
  28870. // 90kHz clock
  28871. secondsToVideoTs,
  28872. secondsToAudioTs,
  28873. videoTsToSeconds,
  28874. audioTsToSeconds,
  28875. audioTsToVideoTs,
  28876. videoTsToAudioTs;
  28877. secondsToVideoTs = function secondsToVideoTs(seconds) {
  28878. return seconds * ONE_SECOND_IN_TS;
  28879. };
  28880. secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
  28881. return seconds * sampleRate;
  28882. };
  28883. videoTsToSeconds = function videoTsToSeconds(timestamp) {
  28884. return timestamp / ONE_SECOND_IN_TS;
  28885. };
  28886. audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
  28887. return timestamp / sampleRate;
  28888. };
  28889. audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
  28890. return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
  28891. };
  28892. videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
  28893. return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
  28894. };
  28895. var clock = {
  28896. secondsToVideoTs: secondsToVideoTs,
  28897. secondsToAudioTs: secondsToAudioTs,
  28898. videoTsToSeconds: videoTsToSeconds,
  28899. audioTsToSeconds: audioTsToSeconds,
  28900. audioTsToVideoTs: audioTsToVideoTs,
  28901. videoTsToAudioTs: videoTsToAudioTs
  28902. };
  28903. var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
  28904. /**
  28905. * Sum the `byteLength` properties of the data in each AAC frame
  28906. */
  28907. var sumFrameByteLengths = function sumFrameByteLengths(array) {
  28908. var i,
  28909. currentObj,
  28910. sum = 0; // sum the byteLength's all each nal unit in the frame
  28911. for (i = 0; i < array.length; i++) {
  28912. currentObj = array[i];
  28913. sum += currentObj.data.byteLength;
  28914. }
  28915. return sum;
  28916. }; // Possibly pad (prefix) the audio track with silence if appending this track
  28917. // would lead to the introduction of a gap in the audio buffer
  28918. var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
  28919. var baseMediaDecodeTimeTs,
  28920. frameDuration = 0,
  28921. audioGapDuration = 0,
  28922. audioFillFrameCount = 0,
  28923. audioFillDuration = 0,
  28924. silentFrame,
  28925. i;
  28926. if (!frames.length) {
  28927. return;
  28928. }
  28929. baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
  28930. frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
  28931. if (audioAppendStartTs && videoBaseMediaDecodeTime) {
  28932. // insert the shortest possible amount (audio gap or audio to video gap)
  28933. audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
  28934. audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
  28935. audioFillDuration = audioFillFrameCount * frameDuration;
  28936. } // don't attempt to fill gaps smaller than a single frame or larger
  28937. // than a half second
  28938. if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
  28939. return;
  28940. }
  28941. silentFrame = silence[track.samplerate];
  28942. if (!silentFrame) {
  28943. // we don't have a silent frame pregenerated for the sample rate, so use a frame
  28944. // from the content instead
  28945. silentFrame = frames[0].data;
  28946. }
  28947. for (i = 0; i < audioFillFrameCount; i++) {
  28948. frames.splice(i, 0, {
  28949. data: silentFrame
  28950. });
  28951. }
  28952. track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
  28953. }; // If the audio segment extends before the earliest allowed dts
  28954. // value, remove AAC frames until starts at or after the earliest
  28955. // allowed DTS so that we don't end up with a negative baseMedia-
  28956. // DecodeTime for the audio track
  28957. var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
  28958. if (track.minSegmentDts >= earliestAllowedDts) {
  28959. return adtsFrames;
  28960. } // We will need to recalculate the earliest segment Dts
  28961. track.minSegmentDts = Infinity;
  28962. return adtsFrames.filter(function (currentFrame) {
  28963. // If this is an allowed frame, keep it and record it's Dts
  28964. if (currentFrame.dts >= earliestAllowedDts) {
  28965. track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
  28966. track.minSegmentPts = track.minSegmentDts;
  28967. return true;
  28968. } // Otherwise, discard it
  28969. return false;
  28970. });
  28971. }; // generate the track's raw mdat data from an array of frames
  28972. var generateSampleTable$1 = function generateSampleTable(frames) {
  28973. var i,
  28974. currentFrame,
  28975. samples = [];
  28976. for (i = 0; i < frames.length; i++) {
  28977. currentFrame = frames[i];
  28978. samples.push({
  28979. size: currentFrame.data.byteLength,
  28980. duration: 1024 // For AAC audio, all samples contain 1024 samples
  28981. });
  28982. }
  28983. return samples;
  28984. }; // generate the track's sample table from an array of frames
  28985. var concatenateFrameData = function concatenateFrameData(frames) {
  28986. var i,
  28987. currentFrame,
  28988. dataOffset = 0,
  28989. data = new Uint8Array(sumFrameByteLengths(frames));
  28990. for (i = 0; i < frames.length; i++) {
  28991. currentFrame = frames[i];
  28992. data.set(currentFrame.data, dataOffset);
  28993. dataOffset += currentFrame.data.byteLength;
  28994. }
  28995. return data;
  28996. };
  28997. var audioFrameUtils = {
  28998. prefixWithSilence: prefixWithSilence,
  28999. trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
  29000. generateSampleTable: generateSampleTable$1,
  29001. concatenateFrameData: concatenateFrameData
  29002. };
  29003. var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock
  29004. /**
  29005. * Store information about the start and end of the track and the
  29006. * duration for each frame/sample we process in order to calculate
  29007. * the baseMediaDecodeTime
  29008. */
  29009. var collectDtsInfo = function collectDtsInfo(track, data) {
  29010. if (typeof data.pts === 'number') {
  29011. if (track.timelineStartInfo.pts === undefined) {
  29012. track.timelineStartInfo.pts = data.pts;
  29013. }
  29014. if (track.minSegmentPts === undefined) {
  29015. track.minSegmentPts = data.pts;
  29016. } else {
  29017. track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
  29018. }
  29019. if (track.maxSegmentPts === undefined) {
  29020. track.maxSegmentPts = data.pts;
  29021. } else {
  29022. track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
  29023. }
  29024. }
  29025. if (typeof data.dts === 'number') {
  29026. if (track.timelineStartInfo.dts === undefined) {
  29027. track.timelineStartInfo.dts = data.dts;
  29028. }
  29029. if (track.minSegmentDts === undefined) {
  29030. track.minSegmentDts = data.dts;
  29031. } else {
  29032. track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
  29033. }
  29034. if (track.maxSegmentDts === undefined) {
  29035. track.maxSegmentDts = data.dts;
  29036. } else {
  29037. track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
  29038. }
  29039. }
  29040. };
  29041. /**
  29042. * Clear values used to calculate the baseMediaDecodeTime between
  29043. * tracks
  29044. */
  29045. var clearDtsInfo = function clearDtsInfo(track) {
  29046. delete track.minSegmentDts;
  29047. delete track.maxSegmentDts;
  29048. delete track.minSegmentPts;
  29049. delete track.maxSegmentPts;
  29050. };
  29051. /**
  29052. * Calculate the track's baseMediaDecodeTime based on the earliest
  29053. * DTS the transmuxer has ever seen and the minimum DTS for the
  29054. * current track
  29055. * @param track {object} track metadata configuration
  29056. * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
  29057. * in the source; false to adjust the first segment to start at 0.
  29058. */
  29059. var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
  29060. var baseMediaDecodeTime,
  29061. scale,
  29062. minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
  29063. if (!keepOriginalTimestamps) {
  29064. minSegmentDts -= track.timelineStartInfo.dts;
  29065. } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
  29066. // we want the start of the first segment to be placed
  29067. baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
  29068. baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
  29069. baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
  29070. if (track.type === 'audio') {
  29071. // Audio has a different clock equal to the sampling_rate so we need to
  29072. // scale the PTS values into the clock rate of the track
  29073. scale = track.samplerate / ONE_SECOND_IN_TS$2;
  29074. baseMediaDecodeTime *= scale;
  29075. baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
  29076. }
  29077. return baseMediaDecodeTime;
  29078. };
  29079. var trackDecodeInfo = {
  29080. clearDtsInfo: clearDtsInfo,
  29081. calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
  29082. collectDtsInfo: collectDtsInfo
  29083. };
  29084. /**
  29085. * mux.js
  29086. *
  29087. * Copyright (c) 2015 Brightcove
  29088. * All rights reserved.
  29089. *
  29090. * Reads in-band caption information from a video elementary
  29091. * stream. Captions must follow the CEA-708 standard for injection
  29092. * into an MPEG-2 transport streams.
  29093. * @see https://en.wikipedia.org/wiki/CEA-708
  29094. * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
  29095. */
  29096. // payload type field to indicate how they are to be
  29097. // interpreted. CEAS-708 caption content is always transmitted with
  29098. // payload type 0x04.
  29099. var USER_DATA_REGISTERED_ITU_T_T35 = 4,
  29100. RBSP_TRAILING_BITS = 128;
  29101. /**
  29102. * Parse a supplemental enhancement information (SEI) NAL unit.
  29103. * Stops parsing once a message of type ITU T T35 has been found.
  29104. *
  29105. * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  29106. * @return {object} the parsed SEI payload
  29107. * @see Rec. ITU-T H.264, 7.3.2.3.1
  29108. */
  29109. var parseSei = function parseSei(bytes) {
  29110. var i = 0,
  29111. result = {
  29112. payloadType: -1,
  29113. payloadSize: 0
  29114. },
  29115. payloadType = 0,
  29116. payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
  29117. while (i < bytes.byteLength) {
  29118. // stop once we have hit the end of the sei_rbsp
  29119. if (bytes[i] === RBSP_TRAILING_BITS) {
  29120. break;
  29121. } // Parse payload type
  29122. while (bytes[i] === 0xFF) {
  29123. payloadType += 255;
  29124. i++;
  29125. }
  29126. payloadType += bytes[i++]; // Parse payload size
  29127. while (bytes[i] === 0xFF) {
  29128. payloadSize += 255;
  29129. i++;
  29130. }
  29131. payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
  29132. // there can only ever be one caption message in a frame's sei
  29133. if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
  29134. result.payloadType = payloadType;
  29135. result.payloadSize = payloadSize;
  29136. result.payload = bytes.subarray(i, i + payloadSize);
  29137. break;
  29138. } // skip the payload and parse the next message
  29139. i += payloadSize;
  29140. payloadType = 0;
  29141. payloadSize = 0;
  29142. }
  29143. return result;
  29144. }; // see ANSI/SCTE 128-1 (2013), section 8.1
  29145. var parseUserData = function parseUserData(sei) {
  29146. // itu_t_t35_contry_code must be 181 (United States) for
  29147. // captions
  29148. if (sei.payload[0] !== 181) {
  29149. return null;
  29150. } // itu_t_t35_provider_code should be 49 (ATSC) for captions
  29151. if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
  29152. return null;
  29153. } // the user_identifier should be "GA94" to indicate ATSC1 data
  29154. if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
  29155. return null;
  29156. } // finally, user_data_type_code should be 0x03 for caption data
  29157. if (sei.payload[7] !== 0x03) {
  29158. return null;
  29159. } // return the user_data_type_structure and strip the trailing
  29160. // marker bits
  29161. return sei.payload.subarray(8, sei.payload.length - 1);
  29162. }; // see CEA-708-D, section 4.4
  29163. var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  29164. var results = [],
  29165. i,
  29166. count,
  29167. offset,
  29168. data; // if this is just filler, return immediately
  29169. if (!(userData[0] & 0x40)) {
  29170. return results;
  29171. } // parse out the cc_data_1 and cc_data_2 fields
  29172. count = userData[0] & 0x1f;
  29173. for (i = 0; i < count; i++) {
  29174. offset = i * 3;
  29175. data = {
  29176. type: userData[offset + 2] & 0x03,
  29177. pts: pts
  29178. }; // capture cc data when cc_valid is 1
  29179. if (userData[offset + 2] & 0x04) {
  29180. data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
  29181. results.push(data);
  29182. }
  29183. }
  29184. return results;
  29185. };
  29186. var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  29187. var length = data.byteLength,
  29188. emulationPreventionBytesPositions = [],
  29189. i = 1,
  29190. newLength,
  29191. newData; // Find all `Emulation Prevention Bytes`
  29192. while (i < length - 2) {
  29193. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  29194. emulationPreventionBytesPositions.push(i + 2);
  29195. i += 2;
  29196. } else {
  29197. i++;
  29198. }
  29199. } // If no Emulation Prevention Bytes were found just return the original
  29200. // array
  29201. if (emulationPreventionBytesPositions.length === 0) {
  29202. return data;
  29203. } // Create a new array to hold the NAL unit data
  29204. newLength = length - emulationPreventionBytesPositions.length;
  29205. newData = new Uint8Array(newLength);
  29206. var sourceIndex = 0;
  29207. for (i = 0; i < newLength; sourceIndex++, i++) {
  29208. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  29209. // Skip this byte
  29210. sourceIndex++; // Remove this position index
  29211. emulationPreventionBytesPositions.shift();
  29212. }
  29213. newData[i] = data[sourceIndex];
  29214. }
  29215. return newData;
  29216. }; // exports
  29217. var captionPacketParser = {
  29218. parseSei: parseSei,
  29219. parseUserData: parseUserData,
  29220. parseCaptionPackets: parseCaptionPackets,
  29221. discardEmulationPreventionBytes: discardEmulationPreventionBytes,
  29222. USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
  29223. };
  29224. // Link To Transport
  29225. // -----------------
  29226. var CaptionStream = function CaptionStream() {
  29227. CaptionStream.prototype.init.call(this);
  29228. this.captionPackets_ = [];
  29229. this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
  29230. new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
  29231. new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
  29232. new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
  29233. ];
  29234. this.reset(); // forward data and done events from CCs to this CaptionStream
  29235. this.ccStreams_.forEach(function (cc) {
  29236. cc.on('data', this.trigger.bind(this, 'data'));
  29237. cc.on('done', this.trigger.bind(this, 'done'));
  29238. }, this);
  29239. };
  29240. CaptionStream.prototype = new stream();
  29241. CaptionStream.prototype.push = function (event) {
  29242. var sei, userData, newCaptionPackets; // only examine SEI NALs
  29243. if (event.nalUnitType !== 'sei_rbsp') {
  29244. return;
  29245. } // parse the sei
  29246. sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
  29247. if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
  29248. return;
  29249. } // parse out the user data payload
  29250. userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
  29251. if (!userData) {
  29252. return;
  29253. } // Sometimes, the same segment # will be downloaded twice. To stop the
  29254. // caption data from being processed twice, we track the latest dts we've
  29255. // received and ignore everything with a dts before that. However, since
  29256. // data for a specific dts can be split across packets on either side of
  29257. // a segment boundary, we need to make sure we *don't* ignore the packets
  29258. // from the *next* segment that have dts === this.latestDts_. By constantly
  29259. // tracking the number of packets received with dts === this.latestDts_, we
  29260. // know how many should be ignored once we start receiving duplicates.
  29261. if (event.dts < this.latestDts_) {
  29262. // We've started getting older data, so set the flag.
  29263. this.ignoreNextEqualDts_ = true;
  29264. return;
  29265. } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
  29266. this.numSameDts_--;
  29267. if (!this.numSameDts_) {
  29268. // We've received the last duplicate packet, time to start processing again
  29269. this.ignoreNextEqualDts_ = false;
  29270. }
  29271. return;
  29272. } // parse out CC data packets and save them for later
  29273. newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
  29274. this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
  29275. if (this.latestDts_ !== event.dts) {
  29276. this.numSameDts_ = 0;
  29277. }
  29278. this.numSameDts_++;
  29279. this.latestDts_ = event.dts;
  29280. };
  29281. CaptionStream.prototype.flush = function () {
  29282. // make sure we actually parsed captions before proceeding
  29283. if (!this.captionPackets_.length) {
  29284. this.ccStreams_.forEach(function (cc) {
  29285. cc.flush();
  29286. }, this);
  29287. return;
  29288. } // In Chrome, the Array#sort function is not stable so add a
  29289. // presortIndex that we can use to ensure we get a stable-sort
  29290. this.captionPackets_.forEach(function (elem, idx) {
  29291. elem.presortIndex = idx;
  29292. }); // sort caption byte-pairs based on their PTS values
  29293. this.captionPackets_.sort(function (a, b) {
  29294. if (a.pts === b.pts) {
  29295. return a.presortIndex - b.presortIndex;
  29296. }
  29297. return a.pts - b.pts;
  29298. });
  29299. this.captionPackets_.forEach(function (packet) {
  29300. if (packet.type < 2) {
  29301. // Dispatch packet to the right Cea608Stream
  29302. this.dispatchCea608Packet(packet);
  29303. } // this is where an 'else' would go for a dispatching packets
  29304. // to a theoretical Cea708Stream that handles SERVICEn data
  29305. }, this);
  29306. this.captionPackets_.length = 0;
  29307. this.ccStreams_.forEach(function (cc) {
  29308. cc.flush();
  29309. }, this);
  29310. return;
  29311. };
  29312. CaptionStream.prototype.reset = function () {
  29313. this.latestDts_ = null;
  29314. this.ignoreNextEqualDts_ = false;
  29315. this.numSameDts_ = 0;
  29316. this.activeCea608Channel_ = [null, null];
  29317. this.ccStreams_.forEach(function (ccStream) {
  29318. ccStream.reset();
  29319. });
  29320. };
  29321. CaptionStream.prototype.dispatchCea608Packet = function (packet) {
  29322. // NOTE: packet.type is the CEA608 field
  29323. if (this.setsChannel1Active(packet)) {
  29324. this.activeCea608Channel_[packet.type] = 0;
  29325. } else if (this.setsChannel2Active(packet)) {
  29326. this.activeCea608Channel_[packet.type] = 1;
  29327. }
  29328. if (this.activeCea608Channel_[packet.type] === null) {
  29329. // If we haven't received anything to set the active channel, discard the
  29330. // data; we don't want jumbled captions
  29331. return;
  29332. }
  29333. this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
  29334. };
  29335. CaptionStream.prototype.setsChannel1Active = function (packet) {
  29336. return (packet.ccData & 0x7800) === 0x1000;
  29337. };
  29338. CaptionStream.prototype.setsChannel2Active = function (packet) {
  29339. return (packet.ccData & 0x7800) === 0x1800;
  29340. }; // ----------------------
  29341. // Session to Application
  29342. // ----------------------
  29343. // This hash maps non-ASCII, special, and extended character codes to their
  29344. // proper Unicode equivalent. The first keys that are only a single byte
  29345. // are the non-standard ASCII characters, which simply map the CEA608 byte
  29346. // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
  29347. // character codes, but have their MSB bitmasked with 0x03 so that a lookup
  29348. // can be performed regardless of the field and data channel on which the
  29349. // character code was received.
  29350. var CHARACTER_TRANSLATION = {
  29351. 0x2a: 0xe1,
  29352. // á
  29353. 0x5c: 0xe9,
  29354. // é
  29355. 0x5e: 0xed,
  29356. // í
  29357. 0x5f: 0xf3,
  29358. // ó
  29359. 0x60: 0xfa,
  29360. // ú
  29361. 0x7b: 0xe7,
  29362. // ç
  29363. 0x7c: 0xf7,
  29364. // ÷
  29365. 0x7d: 0xd1,
  29366. // Ñ
  29367. 0x7e: 0xf1,
  29368. // ñ
  29369. 0x7f: 0x2588,
  29370. // █
  29371. 0x0130: 0xae,
  29372. // ®
  29373. 0x0131: 0xb0,
  29374. // °
  29375. 0x0132: 0xbd,
  29376. // ½
  29377. 0x0133: 0xbf,
  29378. // ¿
  29379. 0x0134: 0x2122,
  29380. // ™
  29381. 0x0135: 0xa2,
  29382. // ¢
  29383. 0x0136: 0xa3,
  29384. // £
  29385. 0x0137: 0x266a,
  29386. // ♪
  29387. 0x0138: 0xe0,
  29388. // à
  29389. 0x0139: 0xa0,
  29390. //
  29391. 0x013a: 0xe8,
  29392. // è
  29393. 0x013b: 0xe2,
  29394. // â
  29395. 0x013c: 0xea,
  29396. // ê
  29397. 0x013d: 0xee,
  29398. // î
  29399. 0x013e: 0xf4,
  29400. // ô
  29401. 0x013f: 0xfb,
  29402. // û
  29403. 0x0220: 0xc1,
  29404. // Á
  29405. 0x0221: 0xc9,
  29406. // É
  29407. 0x0222: 0xd3,
  29408. // Ó
  29409. 0x0223: 0xda,
  29410. // Ú
  29411. 0x0224: 0xdc,
  29412. // Ü
  29413. 0x0225: 0xfc,
  29414. // ü
  29415. 0x0226: 0x2018,
  29416. // ‘
  29417. 0x0227: 0xa1,
  29418. // ¡
  29419. 0x0228: 0x2a,
  29420. // *
  29421. 0x0229: 0x27,
  29422. // '
  29423. 0x022a: 0x2014,
  29424. // —
  29425. 0x022b: 0xa9,
  29426. // ©
  29427. 0x022c: 0x2120,
  29428. // ℠
  29429. 0x022d: 0x2022,
  29430. // •
  29431. 0x022e: 0x201c,
  29432. // “
  29433. 0x022f: 0x201d,
  29434. // ”
  29435. 0x0230: 0xc0,
  29436. // À
  29437. 0x0231: 0xc2,
  29438. // Â
  29439. 0x0232: 0xc7,
  29440. // Ç
  29441. 0x0233: 0xc8,
  29442. // È
  29443. 0x0234: 0xca,
  29444. // Ê
  29445. 0x0235: 0xcb,
  29446. // Ë
  29447. 0x0236: 0xeb,
  29448. // ë
  29449. 0x0237: 0xce,
  29450. // Î
  29451. 0x0238: 0xcf,
  29452. // Ï
  29453. 0x0239: 0xef,
  29454. // ï
  29455. 0x023a: 0xd4,
  29456. // Ô
  29457. 0x023b: 0xd9,
  29458. // Ù
  29459. 0x023c: 0xf9,
  29460. // ù
  29461. 0x023d: 0xdb,
  29462. // Û
  29463. 0x023e: 0xab,
  29464. // «
  29465. 0x023f: 0xbb,
  29466. // »
  29467. 0x0320: 0xc3,
  29468. // Ã
  29469. 0x0321: 0xe3,
  29470. // ã
  29471. 0x0322: 0xcd,
  29472. // Í
  29473. 0x0323: 0xcc,
  29474. // Ì
  29475. 0x0324: 0xec,
  29476. // ì
  29477. 0x0325: 0xd2,
  29478. // Ò
  29479. 0x0326: 0xf2,
  29480. // ò
  29481. 0x0327: 0xd5,
  29482. // Õ
  29483. 0x0328: 0xf5,
  29484. // õ
  29485. 0x0329: 0x7b,
  29486. // {
  29487. 0x032a: 0x7d,
  29488. // }
  29489. 0x032b: 0x5c,
  29490. // \
  29491. 0x032c: 0x5e,
  29492. // ^
  29493. 0x032d: 0x5f,
  29494. // _
  29495. 0x032e: 0x7c,
  29496. // |
  29497. 0x032f: 0x7e,
  29498. // ~
  29499. 0x0330: 0xc4,
  29500. // Ä
  29501. 0x0331: 0xe4,
  29502. // ä
  29503. 0x0332: 0xd6,
  29504. // Ö
  29505. 0x0333: 0xf6,
  29506. // ö
  29507. 0x0334: 0xdf,
  29508. // ß
  29509. 0x0335: 0xa5,
  29510. // ¥
  29511. 0x0336: 0xa4,
  29512. // ¤
  29513. 0x0337: 0x2502,
  29514. // │
  29515. 0x0338: 0xc5,
  29516. // Å
  29517. 0x0339: 0xe5,
  29518. // å
  29519. 0x033a: 0xd8,
  29520. // Ø
  29521. 0x033b: 0xf8,
  29522. // ø
  29523. 0x033c: 0x250c,
  29524. // ┌
  29525. 0x033d: 0x2510,
  29526. // ┐
  29527. 0x033e: 0x2514,
  29528. // └
  29529. 0x033f: 0x2518 // ┘
  29530. };
  29531. var getCharFromCode = function getCharFromCode(code) {
  29532. if (code === null) {
  29533. return '';
  29534. }
  29535. code = CHARACTER_TRANSLATION[code] || code;
  29536. return String.fromCharCode(code);
  29537. }; // the index of the last row in a CEA-608 display buffer
  29538. var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
  29539. // getting it through bit logic.
  29540. 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
  29541. // cells. The "bottom" row is the last element in the outer array.
  29542. var createDisplayBuffer = function createDisplayBuffer() {
  29543. var result = [],
  29544. i = BOTTOM_ROW + 1;
  29545. while (i--) {
  29546. result.push('');
  29547. }
  29548. return result;
  29549. };
  29550. var Cea608Stream = function Cea608Stream(field, dataChannel) {
  29551. Cea608Stream.prototype.init.call(this);
  29552. this.field_ = field || 0;
  29553. this.dataChannel_ = dataChannel || 0;
  29554. this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
  29555. this.setConstants();
  29556. this.reset();
  29557. this.push = function (packet) {
  29558. var data, swap, char0, char1, text; // remove the parity bits
  29559. data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
  29560. if (data === this.lastControlCode_) {
  29561. this.lastControlCode_ = null;
  29562. return;
  29563. } // Store control codes
  29564. if ((data & 0xf000) === 0x1000) {
  29565. this.lastControlCode_ = data;
  29566. } else if (data !== this.PADDING_) {
  29567. this.lastControlCode_ = null;
  29568. }
  29569. char0 = data >>> 8;
  29570. char1 = data & 0xff;
  29571. if (data === this.PADDING_) {
  29572. return;
  29573. } else if (data === this.RESUME_CAPTION_LOADING_) {
  29574. this.mode_ = 'popOn';
  29575. } else if (data === this.END_OF_CAPTION_) {
  29576. // If an EOC is received while in paint-on mode, the displayed caption
  29577. // text should be swapped to non-displayed memory as if it was a pop-on
  29578. // caption. Because of that, we should explicitly switch back to pop-on
  29579. // mode
  29580. this.mode_ = 'popOn';
  29581. this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
  29582. this.flushDisplayed(packet.pts); // flip memory
  29583. swap = this.displayed_;
  29584. this.displayed_ = this.nonDisplayed_;
  29585. this.nonDisplayed_ = swap; // start measuring the time to display the caption
  29586. this.startPts_ = packet.pts;
  29587. } else if (data === this.ROLL_UP_2_ROWS_) {
  29588. this.rollUpRows_ = 2;
  29589. this.setRollUp(packet.pts);
  29590. } else if (data === this.ROLL_UP_3_ROWS_) {
  29591. this.rollUpRows_ = 3;
  29592. this.setRollUp(packet.pts);
  29593. } else if (data === this.ROLL_UP_4_ROWS_) {
  29594. this.rollUpRows_ = 4;
  29595. this.setRollUp(packet.pts);
  29596. } else if (data === this.CARRIAGE_RETURN_) {
  29597. this.clearFormatting(packet.pts);
  29598. this.flushDisplayed(packet.pts);
  29599. this.shiftRowsUp_();
  29600. this.startPts_ = packet.pts;
  29601. } else if (data === this.BACKSPACE_) {
  29602. if (this.mode_ === 'popOn') {
  29603. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  29604. } else {
  29605. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  29606. }
  29607. } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
  29608. this.flushDisplayed(packet.pts);
  29609. this.displayed_ = createDisplayBuffer();
  29610. } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
  29611. this.nonDisplayed_ = createDisplayBuffer();
  29612. } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
  29613. if (this.mode_ !== 'paintOn') {
  29614. // NOTE: This should be removed when proper caption positioning is
  29615. // implemented
  29616. this.flushDisplayed(packet.pts);
  29617. this.displayed_ = createDisplayBuffer();
  29618. }
  29619. this.mode_ = 'paintOn';
  29620. this.startPts_ = packet.pts; // Append special characters to caption text
  29621. } else if (this.isSpecialCharacter(char0, char1)) {
  29622. // Bitmask char0 so that we can apply character transformations
  29623. // regardless of field and data channel.
  29624. // Then byte-shift to the left and OR with char1 so we can pass the
  29625. // entire character code to `getCharFromCode`.
  29626. char0 = (char0 & 0x03) << 8;
  29627. text = getCharFromCode(char0 | char1);
  29628. this[this.mode_](packet.pts, text);
  29629. this.column_++; // Append extended characters to caption text
  29630. } else if (this.isExtCharacter(char0, char1)) {
  29631. // Extended characters always follow their "non-extended" equivalents.
  29632. // IE if a "è" is desired, you'll always receive "eè"; non-compliant
  29633. // decoders are supposed to drop the "è", while compliant decoders
  29634. // backspace the "e" and insert "è".
  29635. // Delete the previous character
  29636. if (this.mode_ === 'popOn') {
  29637. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  29638. } else {
  29639. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  29640. } // Bitmask char0 so that we can apply character transformations
  29641. // regardless of field and data channel.
  29642. // Then byte-shift to the left and OR with char1 so we can pass the
  29643. // entire character code to `getCharFromCode`.
  29644. char0 = (char0 & 0x03) << 8;
  29645. text = getCharFromCode(char0 | char1);
  29646. this[this.mode_](packet.pts, text);
  29647. this.column_++; // Process mid-row codes
  29648. } else if (this.isMidRowCode(char0, char1)) {
  29649. // Attributes are not additive, so clear all formatting
  29650. this.clearFormatting(packet.pts); // According to the standard, mid-row codes
  29651. // should be replaced with spaces, so add one now
  29652. this[this.mode_](packet.pts, ' ');
  29653. this.column_++;
  29654. if ((char1 & 0xe) === 0xe) {
  29655. this.addFormatting(packet.pts, ['i']);
  29656. }
  29657. if ((char1 & 0x1) === 0x1) {
  29658. this.addFormatting(packet.pts, ['u']);
  29659. } // Detect offset control codes and adjust cursor
  29660. } else if (this.isOffsetControlCode(char0, char1)) {
  29661. // Cursor position is set by indent PAC (see below) in 4-column
  29662. // increments, with an additional offset code of 1-3 to reach any
  29663. // of the 32 columns specified by CEA-608. So all we need to do
  29664. // here is increment the column cursor by the given offset.
  29665. this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
  29666. } else if (this.isPAC(char0, char1)) {
  29667. // There's no logic for PAC -> row mapping, so we have to just
  29668. // find the row code in an array and use its index :(
  29669. var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
  29670. if (this.mode_ === 'rollUp') {
  29671. // This implies that the base row is incorrectly set.
  29672. // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
  29673. // of roll-up rows set.
  29674. if (row - this.rollUpRows_ + 1 < 0) {
  29675. row = this.rollUpRows_ - 1;
  29676. }
  29677. this.setRollUp(packet.pts, row);
  29678. }
  29679. if (row !== this.row_) {
  29680. // formatting is only persistent for current row
  29681. this.clearFormatting(packet.pts);
  29682. this.row_ = row;
  29683. } // All PACs can apply underline, so detect and apply
  29684. // (All odd-numbered second bytes set underline)
  29685. if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
  29686. this.addFormatting(packet.pts, ['u']);
  29687. }
  29688. if ((data & 0x10) === 0x10) {
  29689. // We've got an indent level code. Each successive even number
  29690. // increments the column cursor by 4, so we can get the desired
  29691. // column position by bit-shifting to the right (to get n/2)
  29692. // and multiplying by 4.
  29693. this.column_ = ((data & 0xe) >> 1) * 4;
  29694. }
  29695. if (this.isColorPAC(char1)) {
  29696. // it's a color code, though we only support white, which
  29697. // can be either normal or italicized. white italics can be
  29698. // either 0x4e or 0x6e depending on the row, so we just
  29699. // bitwise-and with 0xe to see if italics should be turned on
  29700. if ((char1 & 0xe) === 0xe) {
  29701. this.addFormatting(packet.pts, ['i']);
  29702. }
  29703. } // We have a normal character in char0, and possibly one in char1
  29704. } else if (this.isNormalChar(char0)) {
  29705. if (char1 === 0x00) {
  29706. char1 = null;
  29707. }
  29708. text = getCharFromCode(char0);
  29709. text += getCharFromCode(char1);
  29710. this[this.mode_](packet.pts, text);
  29711. this.column_ += text.length;
  29712. } // finish data processing
  29713. };
  29714. };
  29715. Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
  29716. // display buffer
  29717. Cea608Stream.prototype.flushDisplayed = function (pts) {
  29718. var content = this.displayed_ // remove spaces from the start and end of the string
  29719. .map(function (row) {
  29720. try {
  29721. return row.trim();
  29722. } catch (e) {
  29723. // Ordinarily, this shouldn't happen. However, caption
  29724. // parsing errors should not throw exceptions and
  29725. // break playback.
  29726. // eslint-disable-next-line no-console
  29727. console.error('Skipping malformed caption.');
  29728. return '';
  29729. }
  29730. }) // combine all text rows to display in one cue
  29731. .join('\n') // and remove blank rows from the start and end, but not the middle
  29732. .replace(/^\n+|\n+$/g, '');
  29733. if (content.length) {
  29734. this.trigger('data', {
  29735. startPts: this.startPts_,
  29736. endPts: pts,
  29737. text: content,
  29738. stream: this.name_
  29739. });
  29740. }
  29741. };
  29742. /**
  29743. * Zero out the data, used for startup and on seek
  29744. */
  29745. Cea608Stream.prototype.reset = function () {
  29746. this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
  29747. // actually display captions. If a caption is shifted to a row
  29748. // with a lower index than this, it is cleared from the display
  29749. // buffer
  29750. this.topRow_ = 0;
  29751. this.startPts_ = 0;
  29752. this.displayed_ = createDisplayBuffer();
  29753. this.nonDisplayed_ = createDisplayBuffer();
  29754. this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
  29755. this.column_ = 0;
  29756. this.row_ = BOTTOM_ROW;
  29757. this.rollUpRows_ = 2; // This variable holds currently-applied formatting
  29758. this.formatting_ = [];
  29759. };
  29760. /**
  29761. * Sets up control code and related constants for this instance
  29762. */
  29763. Cea608Stream.prototype.setConstants = function () {
  29764. // The following attributes have these uses:
  29765. // ext_ : char0 for mid-row codes, and the base for extended
  29766. // chars (ext_+0, ext_+1, and ext_+2 are char0s for
  29767. // extended codes)
  29768. // control_: char0 for control codes, except byte-shifted to the
  29769. // left so that we can do this.control_ | CONTROL_CODE
  29770. // offset_: char0 for tab offset codes
  29771. //
  29772. // It's also worth noting that control codes, and _only_ control codes,
  29773. // differ between field 1 and field2. Field 2 control codes are always
  29774. // their field 1 value plus 1. That's why there's the "| field" on the
  29775. // control value.
  29776. if (this.dataChannel_ === 0) {
  29777. this.BASE_ = 0x10;
  29778. this.EXT_ = 0x11;
  29779. this.CONTROL_ = (0x14 | this.field_) << 8;
  29780. this.OFFSET_ = 0x17;
  29781. } else if (this.dataChannel_ === 1) {
  29782. this.BASE_ = 0x18;
  29783. this.EXT_ = 0x19;
  29784. this.CONTROL_ = (0x1c | this.field_) << 8;
  29785. this.OFFSET_ = 0x1f;
  29786. } // Constants for the LSByte command codes recognized by Cea608Stream. This
  29787. // list is not exhaustive. For a more comprehensive listing and semantics see
  29788. // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
  29789. // Padding
  29790. this.PADDING_ = 0x0000; // Pop-on Mode
  29791. this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
  29792. this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
  29793. this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
  29794. this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
  29795. this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
  29796. this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
  29797. this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
  29798. this.BACKSPACE_ = this.CONTROL_ | 0x21;
  29799. this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
  29800. this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
  29801. };
  29802. /**
  29803. * Detects if the 2-byte packet data is a special character
  29804. *
  29805. * Special characters have a second byte in the range 0x30 to 0x3f,
  29806. * with the first byte being 0x11 (for data channel 1) or 0x19 (for
  29807. * data channel 2).
  29808. *
  29809. * @param {Integer} char0 The first byte
  29810. * @param {Integer} char1 The second byte
  29811. * @return {Boolean} Whether the 2 bytes are an special character
  29812. */
  29813. Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
  29814. return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
  29815. };
  29816. /**
  29817. * Detects if the 2-byte packet data is an extended character
  29818. *
  29819. * Extended characters have a second byte in the range 0x20 to 0x3f,
  29820. * with the first byte being 0x12 or 0x13 (for data channel 1) or
  29821. * 0x1a or 0x1b (for data channel 2).
  29822. *
  29823. * @param {Integer} char0 The first byte
  29824. * @param {Integer} char1 The second byte
  29825. * @return {Boolean} Whether the 2 bytes are an extended character
  29826. */
  29827. Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
  29828. return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
  29829. };
  29830. /**
  29831. * Detects if the 2-byte packet is a mid-row code
  29832. *
  29833. * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
  29834. * the first byte being 0x11 (for data channel 1) or 0x19 (for data
  29835. * channel 2).
  29836. *
  29837. * @param {Integer} char0 The first byte
  29838. * @param {Integer} char1 The second byte
  29839. * @return {Boolean} Whether the 2 bytes are a mid-row code
  29840. */
  29841. Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
  29842. return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
  29843. };
  29844. /**
  29845. * Detects if the 2-byte packet is an offset control code
  29846. *
  29847. * Offset control codes have a second byte in the range 0x21 to 0x23,
  29848. * with the first byte being 0x17 (for data channel 1) or 0x1f (for
  29849. * data channel 2).
  29850. *
  29851. * @param {Integer} char0 The first byte
  29852. * @param {Integer} char1 The second byte
  29853. * @return {Boolean} Whether the 2 bytes are an offset control code
  29854. */
  29855. Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
  29856. return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
  29857. };
  29858. /**
  29859. * Detects if the 2-byte packet is a Preamble Address Code
  29860. *
  29861. * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
  29862. * or 0x18 to 0x1f (for data channel 2), with the second byte in the
  29863. * range 0x40 to 0x7f.
  29864. *
  29865. * @param {Integer} char0 The first byte
  29866. * @param {Integer} char1 The second byte
  29867. * @return {Boolean} Whether the 2 bytes are a PAC
  29868. */
  29869. Cea608Stream.prototype.isPAC = function (char0, char1) {
  29870. return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
  29871. };
  29872. /**
  29873. * Detects if a packet's second byte is in the range of a PAC color code
  29874. *
  29875. * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
  29876. * 0x60 to 0x6f.
  29877. *
  29878. * @param {Integer} char1 The second byte
  29879. * @return {Boolean} Whether the byte is a color PAC
  29880. */
  29881. Cea608Stream.prototype.isColorPAC = function (char1) {
  29882. return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
  29883. };
  29884. /**
  29885. * Detects if a single byte is in the range of a normal character
  29886. *
  29887. * Normal text bytes are in the range 0x20 to 0x7f.
  29888. *
  29889. * @param {Integer} char The byte
  29890. * @return {Boolean} Whether the byte is a normal character
  29891. */
  29892. Cea608Stream.prototype.isNormalChar = function (char) {
  29893. return char >= 0x20 && char <= 0x7f;
  29894. };
  29895. /**
  29896. * Configures roll-up
  29897. *
  29898. * @param {Integer} pts Current PTS
  29899. * @param {Integer} newBaseRow Used by PACs to slide the current window to
  29900. * a new position
  29901. */
  29902. Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
  29903. // Reset the base row to the bottom row when switching modes
  29904. if (this.mode_ !== 'rollUp') {
  29905. this.row_ = BOTTOM_ROW;
  29906. this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
  29907. this.flushDisplayed(pts);
  29908. this.nonDisplayed_ = createDisplayBuffer();
  29909. this.displayed_ = createDisplayBuffer();
  29910. }
  29911. if (newBaseRow !== undefined && newBaseRow !== this.row_) {
  29912. // move currently displayed captions (up or down) to the new base row
  29913. for (var i = 0; i < this.rollUpRows_; i++) {
  29914. this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
  29915. this.displayed_[this.row_ - i] = '';
  29916. }
  29917. }
  29918. if (newBaseRow === undefined) {
  29919. newBaseRow = this.row_;
  29920. }
  29921. this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
  29922. }; // Adds the opening HTML tag for the passed character to the caption text,
  29923. // and keeps track of it for later closing
  29924. Cea608Stream.prototype.addFormatting = function (pts, format) {
  29925. this.formatting_ = this.formatting_.concat(format);
  29926. var text = format.reduce(function (text, format) {
  29927. return text + '<' + format + '>';
  29928. }, '');
  29929. this[this.mode_](pts, text);
  29930. }; // Adds HTML closing tags for current formatting to caption text and
  29931. // clears remembered formatting
  29932. Cea608Stream.prototype.clearFormatting = function (pts) {
  29933. if (!this.formatting_.length) {
  29934. return;
  29935. }
  29936. var text = this.formatting_.reverse().reduce(function (text, format) {
  29937. return text + '</' + format + '>';
  29938. }, '');
  29939. this.formatting_ = [];
  29940. this[this.mode_](pts, text);
  29941. }; // Mode Implementations
  29942. Cea608Stream.prototype.popOn = function (pts, text) {
  29943. var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
  29944. baseRow += text;
  29945. this.nonDisplayed_[this.row_] = baseRow;
  29946. };
  29947. Cea608Stream.prototype.rollUp = function (pts, text) {
  29948. var baseRow = this.displayed_[this.row_];
  29949. baseRow += text;
  29950. this.displayed_[this.row_] = baseRow;
  29951. };
  29952. Cea608Stream.prototype.shiftRowsUp_ = function () {
  29953. var i; // clear out inactive rows
  29954. for (i = 0; i < this.topRow_; i++) {
  29955. this.displayed_[i] = '';
  29956. }
  29957. for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
  29958. this.displayed_[i] = '';
  29959. } // shift displayed rows up
  29960. for (i = this.topRow_; i < this.row_; i++) {
  29961. this.displayed_[i] = this.displayed_[i + 1];
  29962. } // clear out the bottom row
  29963. this.displayed_[this.row_] = '';
  29964. };
  29965. Cea608Stream.prototype.paintOn = function (pts, text) {
  29966. var baseRow = this.displayed_[this.row_];
  29967. baseRow += text;
  29968. this.displayed_[this.row_] = baseRow;
  29969. }; // exports
  29970. var captionStream = {
  29971. CaptionStream: CaptionStream,
  29972. Cea608Stream: Cea608Stream
  29973. };
  29974. var streamTypes = {
  29975. H264_STREAM_TYPE: 0x1B,
  29976. ADTS_STREAM_TYPE: 0x0F,
  29977. METADATA_STREAM_TYPE: 0x15
  29978. };
  29979. var MAX_TS = 8589934592;
  29980. var RO_THRESH = 4294967296;
  29981. var handleRollover = function handleRollover(value, reference) {
  29982. var direction = 1;
  29983. if (value > reference) {
  29984. // If the current timestamp value is greater than our reference timestamp and we detect a
  29985. // timestamp rollover, this means the roll over is happening in the opposite direction.
  29986. // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
  29987. // point will be set to a small number, e.g. 1. The user then seeks backwards over the
  29988. // rollover point. In loading this segment, the timestamp values will be very large,
  29989. // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
  29990. // the time stamp to be `value - 2^33`.
  29991. direction = -1;
  29992. } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
  29993. // cause an incorrect adjustment.
  29994. while (Math.abs(reference - value) > RO_THRESH) {
  29995. value += direction * MAX_TS;
  29996. }
  29997. return value;
  29998. };
  29999. var TimestampRolloverStream = function TimestampRolloverStream(type) {
  30000. var lastDTS, referenceDTS;
  30001. TimestampRolloverStream.prototype.init.call(this);
  30002. this.type_ = type;
  30003. this.push = function (data) {
  30004. if (data.type !== this.type_) {
  30005. return;
  30006. }
  30007. if (referenceDTS === undefined) {
  30008. referenceDTS = data.dts;
  30009. }
  30010. data.dts = handleRollover(data.dts, referenceDTS);
  30011. data.pts = handleRollover(data.pts, referenceDTS);
  30012. lastDTS = data.dts;
  30013. this.trigger('data', data);
  30014. };
  30015. this.flush = function () {
  30016. referenceDTS = lastDTS;
  30017. this.trigger('done');
  30018. };
  30019. this.discontinuity = function () {
  30020. referenceDTS = void 0;
  30021. lastDTS = void 0;
  30022. };
  30023. };
  30024. TimestampRolloverStream.prototype = new stream();
  30025. var timestampRolloverStream = {
  30026. TimestampRolloverStream: TimestampRolloverStream,
  30027. handleRollover: handleRollover
  30028. };
  30029. var percentEncode = function percentEncode(bytes, start, end) {
  30030. var i,
  30031. result = '';
  30032. for (i = start; i < end; i++) {
  30033. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  30034. }
  30035. return result;
  30036. },
  30037. // return the string representation of the specified byte range,
  30038. // interpreted as UTf-8.
  30039. parseUtf8 = function parseUtf8(bytes, start, end) {
  30040. return decodeURIComponent(percentEncode(bytes, start, end));
  30041. },
  30042. // return the string representation of the specified byte range,
  30043. // interpreted as ISO-8859-1.
  30044. parseIso88591 = function parseIso88591(bytes, start, end) {
  30045. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  30046. },
  30047. parseSyncSafeInteger = function parseSyncSafeInteger(data) {
  30048. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  30049. },
  30050. tagParsers = {
  30051. TXXX: function TXXX(tag) {
  30052. var i;
  30053. if (tag.data[0] !== 3) {
  30054. // ignore frames with unrecognized character encodings
  30055. return;
  30056. }
  30057. for (i = 1; i < tag.data.length; i++) {
  30058. if (tag.data[i] === 0) {
  30059. // parse the text fields
  30060. tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
  30061. tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
  30062. break;
  30063. }
  30064. }
  30065. tag.data = tag.value;
  30066. },
  30067. WXXX: function WXXX(tag) {
  30068. var i;
  30069. if (tag.data[0] !== 3) {
  30070. // ignore frames with unrecognized character encodings
  30071. return;
  30072. }
  30073. for (i = 1; i < tag.data.length; i++) {
  30074. if (tag.data[i] === 0) {
  30075. // parse the description and URL fields
  30076. tag.description = parseUtf8(tag.data, 1, i);
  30077. tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
  30078. break;
  30079. }
  30080. }
  30081. },
  30082. PRIV: function PRIV(tag) {
  30083. var i;
  30084. for (i = 0; i < tag.data.length; i++) {
  30085. if (tag.data[i] === 0) {
  30086. // parse the description and URL fields
  30087. tag.owner = parseIso88591(tag.data, 0, i);
  30088. break;
  30089. }
  30090. }
  30091. tag.privateData = tag.data.subarray(i + 1);
  30092. tag.data = tag.privateData;
  30093. }
  30094. },
  30095. _MetadataStream;
  30096. _MetadataStream = function MetadataStream(options) {
  30097. var settings = {
  30098. debug: !!(options && options.debug),
  30099. // the bytes of the program-level descriptor field in MP2T
  30100. // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
  30101. // program element descriptors"
  30102. descriptor: options && options.descriptor
  30103. },
  30104. // the total size in bytes of the ID3 tag being parsed
  30105. tagSize = 0,
  30106. // tag data that is not complete enough to be parsed
  30107. buffer = [],
  30108. // the total number of bytes currently in the buffer
  30109. bufferSize = 0,
  30110. i;
  30111. _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
  30112. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  30113. this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
  30114. if (settings.descriptor) {
  30115. for (i = 0; i < settings.descriptor.length; i++) {
  30116. this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
  30117. }
  30118. }
  30119. this.push = function (chunk) {
  30120. var tag, frameStart, frameSize, frame, i, frameHeader;
  30121. if (chunk.type !== 'timed-metadata') {
  30122. return;
  30123. } // if data_alignment_indicator is set in the PES header,
  30124. // we must have the start of a new ID3 tag. Assume anything
  30125. // remaining in the buffer was malformed and throw it out
  30126. if (chunk.dataAlignmentIndicator) {
  30127. bufferSize = 0;
  30128. buffer.length = 0;
  30129. } // ignore events that don't look like ID3 data
  30130. 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))) {
  30131. if (settings.debug) {
  30132. // eslint-disable-next-line no-console
  30133. console.log('Skipping unrecognized metadata packet');
  30134. }
  30135. return;
  30136. } // add this chunk to the data we've collected so far
  30137. buffer.push(chunk);
  30138. bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
  30139. if (buffer.length === 1) {
  30140. // the frame size is transmitted as a 28-bit integer in the
  30141. // last four bytes of the ID3 header.
  30142. // The most significant bit of each byte is dropped and the
  30143. // results concatenated to recover the actual value.
  30144. tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  30145. // convenient for our comparisons to include it
  30146. tagSize += 10;
  30147. } // if the entire frame has not arrived, wait for more data
  30148. if (bufferSize < tagSize) {
  30149. return;
  30150. } // collect the entire frame so it can be parsed
  30151. tag = {
  30152. data: new Uint8Array(tagSize),
  30153. frames: [],
  30154. pts: buffer[0].pts,
  30155. dts: buffer[0].dts
  30156. };
  30157. for (i = 0; i < tagSize;) {
  30158. tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
  30159. i += buffer[0].data.byteLength;
  30160. bufferSize -= buffer[0].data.byteLength;
  30161. buffer.shift();
  30162. } // find the start of the first frame and the end of the tag
  30163. frameStart = 10;
  30164. if (tag.data[5] & 0x40) {
  30165. // advance the frame start past the extended header
  30166. frameStart += 4; // header size field
  30167. frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
  30168. tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
  30169. } // parse one or more ID3 frames
  30170. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  30171. do {
  30172. // determine the number of bytes in this frame
  30173. frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
  30174. if (frameSize < 1) {
  30175. // eslint-disable-next-line no-console
  30176. return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
  30177. }
  30178. frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
  30179. frame = {
  30180. id: frameHeader,
  30181. data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
  30182. };
  30183. frame.key = frame.id;
  30184. if (tagParsers[frame.id]) {
  30185. tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
  30186. // time for raw AAC data
  30187. if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
  30188. var d = frame.data,
  30189. size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  30190. size *= 4;
  30191. size += d[7] & 0x03;
  30192. frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
  30193. // on the value of this frame
  30194. // we couldn't have known the appropriate pts and dts before
  30195. // parsing this ID3 tag so set those values now
  30196. if (tag.pts === undefined && tag.dts === undefined) {
  30197. tag.pts = frame.timeStamp;
  30198. tag.dts = frame.timeStamp;
  30199. }
  30200. this.trigger('timestamp', frame);
  30201. }
  30202. }
  30203. tag.frames.push(frame);
  30204. frameStart += 10; // advance past the frame header
  30205. frameStart += frameSize; // advance past the frame body
  30206. } while (frameStart < tagSize);
  30207. this.trigger('data', tag);
  30208. };
  30209. };
  30210. _MetadataStream.prototype = new stream();
  30211. var metadataStream = _MetadataStream;
  30212. var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
  30213. var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
  30214. var MP2T_PACKET_LENGTH = 188,
  30215. // bytes
  30216. SYNC_BYTE = 0x47;
  30217. /**
  30218. * Splits an incoming stream of binary data into MPEG-2 Transport
  30219. * Stream packets.
  30220. */
  30221. _TransportPacketStream = function TransportPacketStream() {
  30222. var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
  30223. bytesInBuffer = 0;
  30224. _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
  30225. /**
  30226. * Split a stream of data into M2TS packets
  30227. **/
  30228. this.push = function (bytes) {
  30229. var startIndex = 0,
  30230. endIndex = MP2T_PACKET_LENGTH,
  30231. everything; // If there are bytes remaining from the last segment, prepend them to the
  30232. // bytes that were pushed in
  30233. if (bytesInBuffer) {
  30234. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  30235. everything.set(buffer.subarray(0, bytesInBuffer));
  30236. everything.set(bytes, bytesInBuffer);
  30237. bytesInBuffer = 0;
  30238. } else {
  30239. everything = bytes;
  30240. } // While we have enough data for a packet
  30241. while (endIndex < everything.byteLength) {
  30242. // Look for a pair of start and end sync bytes in the data..
  30243. if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
  30244. // We found a packet so emit it and jump one whole packet forward in
  30245. // the stream
  30246. this.trigger('data', everything.subarray(startIndex, endIndex));
  30247. startIndex += MP2T_PACKET_LENGTH;
  30248. endIndex += MP2T_PACKET_LENGTH;
  30249. continue;
  30250. } // If we get here, we have somehow become de-synchronized and we need to step
  30251. // forward one byte at a time until we find a pair of sync bytes that denote
  30252. // a packet
  30253. startIndex++;
  30254. endIndex++;
  30255. } // If there was some data left over at the end of the segment that couldn't
  30256. // possibly be a whole packet, keep it because it might be the start of a packet
  30257. // that continues in the next segment
  30258. if (startIndex < everything.byteLength) {
  30259. buffer.set(everything.subarray(startIndex), 0);
  30260. bytesInBuffer = everything.byteLength - startIndex;
  30261. }
  30262. };
  30263. /**
  30264. * Passes identified M2TS packets to the TransportParseStream to be parsed
  30265. **/
  30266. this.flush = function () {
  30267. // If the buffer contains a whole packet when we are being flushed, emit it
  30268. // and empty the buffer. Otherwise hold onto the data because it may be
  30269. // important for decoding the next segment
  30270. if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
  30271. this.trigger('data', buffer);
  30272. bytesInBuffer = 0;
  30273. }
  30274. this.trigger('done');
  30275. };
  30276. };
  30277. _TransportPacketStream.prototype = new stream();
  30278. /**
  30279. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  30280. * forms of the individual transport stream packets.
  30281. */
  30282. _TransportParseStream = function TransportParseStream() {
  30283. var parsePsi, parsePat, parsePmt, self;
  30284. _TransportParseStream.prototype.init.call(this);
  30285. self = this;
  30286. this.packetsWaitingForPmt = [];
  30287. this.programMapTable = undefined;
  30288. parsePsi = function parsePsi(payload, psi) {
  30289. var offset = 0; // PSI packets may be split into multiple sections and those
  30290. // sections may be split into multiple packets. If a PSI
  30291. // section starts in this packet, the payload_unit_start_indicator
  30292. // will be true and the first byte of the payload will indicate
  30293. // the offset from the current position to the start of the
  30294. // section.
  30295. if (psi.payloadUnitStartIndicator) {
  30296. offset += payload[offset] + 1;
  30297. }
  30298. if (psi.type === 'pat') {
  30299. parsePat(payload.subarray(offset), psi);
  30300. } else {
  30301. parsePmt(payload.subarray(offset), psi);
  30302. }
  30303. };
  30304. parsePat = function parsePat(payload, pat) {
  30305. pat.section_number = payload[7]; // eslint-disable-line camelcase
  30306. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  30307. // skip the PSI header and parse the first PMT entry
  30308. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  30309. pat.pmtPid = self.pmtPid;
  30310. };
  30311. /**
  30312. * Parse out the relevant fields of a Program Map Table (PMT).
  30313. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  30314. * packet. The first byte in this array should be the table_id
  30315. * field.
  30316. * @param pmt {object} the object that should be decorated with
  30317. * fields parsed from the PMT.
  30318. */
  30319. parsePmt = function parsePmt(payload, pmt) {
  30320. var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
  30321. // take effect. We don't believe this should ever be the case
  30322. // for HLS but we'll ignore "forward" PMT declarations if we see
  30323. // them. Future PMT declarations have the current_next_indicator
  30324. // set to zero.
  30325. if (!(payload[5] & 0x01)) {
  30326. return;
  30327. } // overwrite any existing program map table
  30328. self.programMapTable = {
  30329. video: null,
  30330. audio: null,
  30331. 'timed-metadata': {}
  30332. }; // the mapping table ends at the end of the current section
  30333. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  30334. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  30335. // long the program info descriptors are
  30336. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
  30337. offset = 12 + programInfoLength;
  30338. while (offset < tableEnd) {
  30339. var streamType = payload[offset];
  30340. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
  30341. // TODO: should this be done for metadata too? for now maintain behavior of
  30342. // multiple metadata streams
  30343. if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
  30344. self.programMapTable.video = pid;
  30345. } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
  30346. self.programMapTable.audio = pid;
  30347. } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
  30348. // map pid to stream type for metadata streams
  30349. self.programMapTable['timed-metadata'][pid] = streamType;
  30350. } // move to the next table entry
  30351. // skip past the elementary stream descriptors, if present
  30352. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  30353. } // record the map on the packet as well
  30354. pmt.programMapTable = self.programMapTable;
  30355. };
  30356. /**
  30357. * Deliver a new MP2T packet to the next stream in the pipeline.
  30358. */
  30359. this.push = function (packet) {
  30360. var result = {},
  30361. offset = 4;
  30362. result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
  30363. result.pid = packet[1] & 0x1f;
  30364. result.pid <<= 8;
  30365. result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
  30366. // fifth byte of the TS packet header. The adaptation field is
  30367. // used to add stuffing to PES packets that don't fill a complete
  30368. // TS packet, and to specify some forms of timing and control data
  30369. // that we do not currently use.
  30370. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  30371. offset += packet[offset] + 1;
  30372. } // parse the rest of the packet based on the type
  30373. if (result.pid === 0) {
  30374. result.type = 'pat';
  30375. parsePsi(packet.subarray(offset), result);
  30376. this.trigger('data', result);
  30377. } else if (result.pid === this.pmtPid) {
  30378. result.type = 'pmt';
  30379. parsePsi(packet.subarray(offset), result);
  30380. this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
  30381. while (this.packetsWaitingForPmt.length) {
  30382. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  30383. }
  30384. } else if (this.programMapTable === undefined) {
  30385. // When we have not seen a PMT yet, defer further processing of
  30386. // PES packets until one has been parsed
  30387. this.packetsWaitingForPmt.push([packet, offset, result]);
  30388. } else {
  30389. this.processPes_(packet, offset, result);
  30390. }
  30391. };
  30392. this.processPes_ = function (packet, offset, result) {
  30393. // set the appropriate stream type
  30394. if (result.pid === this.programMapTable.video) {
  30395. result.streamType = streamTypes.H264_STREAM_TYPE;
  30396. } else if (result.pid === this.programMapTable.audio) {
  30397. result.streamType = streamTypes.ADTS_STREAM_TYPE;
  30398. } else {
  30399. // if not video or audio, it is timed-metadata or unknown
  30400. // if unknown, streamType will be undefined
  30401. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  30402. }
  30403. result.type = 'pes';
  30404. result.data = packet.subarray(offset);
  30405. this.trigger('data', result);
  30406. };
  30407. };
  30408. _TransportParseStream.prototype = new stream();
  30409. _TransportParseStream.STREAM_TYPES = {
  30410. h264: 0x1b,
  30411. adts: 0x0f
  30412. };
  30413. /**
  30414. * Reconsistutes program elementary stream (PES) packets from parsed
  30415. * transport stream packets. That is, if you pipe an
  30416. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  30417. * events will be events which capture the bytes for individual PES
  30418. * packets plus relevant metadata that has been extracted from the
  30419. * container.
  30420. */
  30421. _ElementaryStream = function ElementaryStream() {
  30422. var self = this,
  30423. // PES packet fragments
  30424. video = {
  30425. data: [],
  30426. size: 0
  30427. },
  30428. audio = {
  30429. data: [],
  30430. size: 0
  30431. },
  30432. timedMetadata = {
  30433. data: [],
  30434. size: 0
  30435. },
  30436. parsePes = function parsePes(payload, pes) {
  30437. var ptsDtsFlags; // get the packet length, this will be 0 for video
  30438. pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
  30439. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
  30440. // and a DTS value. Determine what combination of values is
  30441. // available to work with.
  30442. ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  30443. // performs all bitwise operations on 32-bit integers but javascript
  30444. // supports a much greater range (52-bits) of integer using standard
  30445. // mathematical operations.
  30446. // We construct a 31-bit value using bitwise operators over the 31
  30447. // most significant bits and then multiply by 4 (equal to a left-shift
  30448. // of 2) before we add the final 2 least significant bits of the
  30449. // timestamp (equal to an OR.)
  30450. if (ptsDtsFlags & 0xC0) {
  30451. // the PTS and DTS are not written out directly. For information
  30452. // on how they are encoded, see
  30453. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  30454. pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
  30455. pes.pts *= 4; // Left shift by 2
  30456. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  30457. pes.dts = pes.pts;
  30458. if (ptsDtsFlags & 0x40) {
  30459. pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
  30460. pes.dts *= 4; // Left shift by 2
  30461. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  30462. }
  30463. } // the data section starts immediately after the PES header.
  30464. // pes_header_data_length specifies the number of header bytes
  30465. // that follow the last byte of the field.
  30466. pes.data = payload.subarray(9 + payload[8]);
  30467. },
  30468. /**
  30469. * Pass completely parsed PES packets to the next stream in the pipeline
  30470. **/
  30471. flushStream = function flushStream(stream$$1, type, forceFlush) {
  30472. var packetData = new Uint8Array(stream$$1.size),
  30473. event = {
  30474. type: type
  30475. },
  30476. i = 0,
  30477. offset = 0,
  30478. packetFlushable = false,
  30479. fragment; // do nothing if there is not enough buffered data for a complete
  30480. // PES header
  30481. if (!stream$$1.data.length || stream$$1.size < 9) {
  30482. return;
  30483. }
  30484. event.trackId = stream$$1.data[0].pid; // reassemble the packet
  30485. for (i = 0; i < stream$$1.data.length; i++) {
  30486. fragment = stream$$1.data[i];
  30487. packetData.set(fragment.data, offset);
  30488. offset += fragment.data.byteLength;
  30489. } // parse assembled packet's PES header
  30490. parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
  30491. // check that there is enough stream data to fill the packet
  30492. packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right
  30493. if (forceFlush || packetFlushable) {
  30494. stream$$1.size = 0;
  30495. stream$$1.data.length = 0;
  30496. } // only emit packets that are complete. this is to avoid assembling
  30497. // incomplete PES packets due to poor segmentation
  30498. if (packetFlushable) {
  30499. self.trigger('data', event);
  30500. }
  30501. };
  30502. _ElementaryStream.prototype.init.call(this);
  30503. /**
  30504. * Identifies M2TS packet types and parses PES packets using metadata
  30505. * parsed from the PMT
  30506. **/
  30507. this.push = function (data) {
  30508. ({
  30509. pat: function pat() {// we have to wait for the PMT to arrive as well before we
  30510. // have any meaningful metadata
  30511. },
  30512. pes: function pes() {
  30513. var stream$$1, streamType;
  30514. switch (data.streamType) {
  30515. case streamTypes.H264_STREAM_TYPE:
  30516. case streamTypes.H264_STREAM_TYPE:
  30517. stream$$1 = video;
  30518. streamType = 'video';
  30519. break;
  30520. case streamTypes.ADTS_STREAM_TYPE:
  30521. stream$$1 = audio;
  30522. streamType = 'audio';
  30523. break;
  30524. case streamTypes.METADATA_STREAM_TYPE:
  30525. stream$$1 = timedMetadata;
  30526. streamType = 'timed-metadata';
  30527. break;
  30528. default:
  30529. // ignore unknown stream types
  30530. return;
  30531. } // if a new packet is starting, we can flush the completed
  30532. // packet
  30533. if (data.payloadUnitStartIndicator) {
  30534. flushStream(stream$$1, streamType, true);
  30535. } // buffer this fragment until we are sure we've received the
  30536. // complete payload
  30537. stream$$1.data.push(data);
  30538. stream$$1.size += data.data.byteLength;
  30539. },
  30540. pmt: function pmt() {
  30541. var event = {
  30542. type: 'metadata',
  30543. tracks: []
  30544. },
  30545. programMapTable = data.programMapTable; // translate audio and video streams to tracks
  30546. if (programMapTable.video !== null) {
  30547. event.tracks.push({
  30548. timelineStartInfo: {
  30549. baseMediaDecodeTime: 0
  30550. },
  30551. id: +programMapTable.video,
  30552. codec: 'avc',
  30553. type: 'video'
  30554. });
  30555. }
  30556. if (programMapTable.audio !== null) {
  30557. event.tracks.push({
  30558. timelineStartInfo: {
  30559. baseMediaDecodeTime: 0
  30560. },
  30561. id: +programMapTable.audio,
  30562. codec: 'adts',
  30563. type: 'audio'
  30564. });
  30565. }
  30566. self.trigger('data', event);
  30567. }
  30568. })[data.type]();
  30569. };
  30570. /**
  30571. * Flush any remaining input. Video PES packets may be of variable
  30572. * length. Normally, the start of a new video packet can trigger the
  30573. * finalization of the previous packet. That is not possible if no
  30574. * more video is forthcoming, however. In that case, some other
  30575. * mechanism (like the end of the file) has to be employed. When it is
  30576. * clear that no additional data is forthcoming, calling this method
  30577. * will flush the buffered packets.
  30578. */
  30579. this.flush = function () {
  30580. // !!THIS ORDER IS IMPORTANT!!
  30581. // video first then audio
  30582. flushStream(video, 'video');
  30583. flushStream(audio, 'audio');
  30584. flushStream(timedMetadata, 'timed-metadata');
  30585. this.trigger('done');
  30586. };
  30587. };
  30588. _ElementaryStream.prototype = new stream();
  30589. var m2ts = {
  30590. PAT_PID: 0x0000,
  30591. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
  30592. TransportPacketStream: _TransportPacketStream,
  30593. TransportParseStream: _TransportParseStream,
  30594. ElementaryStream: _ElementaryStream,
  30595. TimestampRolloverStream: TimestampRolloverStream$1,
  30596. CaptionStream: captionStream.CaptionStream,
  30597. Cea608Stream: captionStream.Cea608Stream,
  30598. MetadataStream: metadataStream
  30599. };
  30600. for (var type$1 in streamTypes) {
  30601. if (streamTypes.hasOwnProperty(type$1)) {
  30602. m2ts[type$1] = streamTypes[type$1];
  30603. }
  30604. }
  30605. var m2ts_1 = m2ts;
  30606. var _AdtsStream;
  30607. var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  30608. /*
  30609. * Accepts a ElementaryStream and emits data events with parsed
  30610. * AAC Audio Frames of the individual packets. Input audio in ADTS
  30611. * format is unpacked and re-emitted as AAC frames.
  30612. *
  30613. * @see http://wiki.multimedia.cx/index.php?title=ADTS
  30614. * @see http://wiki.multimedia.cx/?title=Understanding_AAC
  30615. */
  30616. _AdtsStream = function AdtsStream() {
  30617. var buffer;
  30618. _AdtsStream.prototype.init.call(this);
  30619. this.push = function (packet) {
  30620. var i = 0,
  30621. frameNum = 0,
  30622. frameLength,
  30623. protectionSkipBytes,
  30624. frameEnd,
  30625. oldBuffer,
  30626. sampleCount,
  30627. adtsFrameDuration;
  30628. if (packet.type !== 'audio') {
  30629. // ignore non-audio data
  30630. return;
  30631. } // Prepend any data in the buffer to the input data so that we can parse
  30632. // aac frames the cross a PES packet boundary
  30633. if (buffer) {
  30634. oldBuffer = buffer;
  30635. buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
  30636. buffer.set(oldBuffer);
  30637. buffer.set(packet.data, oldBuffer.byteLength);
  30638. } else {
  30639. buffer = packet.data;
  30640. } // unpack any ADTS frames which have been fully received
  30641. // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
  30642. while (i + 5 < buffer.length) {
  30643. // Loook for the start of an ADTS header..
  30644. if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
  30645. // If a valid header was not found, jump one forward and attempt to
  30646. // find a valid ADTS header starting at the next byte
  30647. i++;
  30648. continue;
  30649. } // The protection skip bit tells us if we have 2 bytes of CRC data at the
  30650. // end of the ADTS header
  30651. protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
  30652. // end of the sync sequence
  30653. frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
  30654. sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
  30655. adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
  30656. frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
  30657. // and wait for more data
  30658. if (buffer.byteLength < frameEnd) {
  30659. return;
  30660. } // Otherwise, deliver the complete AAC frame
  30661. this.trigger('data', {
  30662. pts: packet.pts + frameNum * adtsFrameDuration,
  30663. dts: packet.dts + frameNum * adtsFrameDuration,
  30664. sampleCount: sampleCount,
  30665. audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
  30666. channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
  30667. samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
  30668. samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
  30669. // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
  30670. samplesize: 16,
  30671. data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
  30672. }); // If the buffer is empty, clear it and return
  30673. if (buffer.byteLength === frameEnd) {
  30674. buffer = undefined;
  30675. return;
  30676. }
  30677. frameNum++; // Remove the finished frame from the buffer and start the process again
  30678. buffer = buffer.subarray(frameEnd);
  30679. }
  30680. };
  30681. this.flush = function () {
  30682. this.trigger('done');
  30683. };
  30684. };
  30685. _AdtsStream.prototype = new stream();
  30686. var adts = _AdtsStream;
  30687. var ExpGolomb;
  30688. /**
  30689. * Parser for exponential Golomb codes, a variable-bitwidth number encoding
  30690. * scheme used by h264.
  30691. */
  30692. ExpGolomb = function ExpGolomb(workingData) {
  30693. var // the number of bytes left to examine in workingData
  30694. workingBytesAvailable = workingData.byteLength,
  30695. // the current word being examined
  30696. workingWord = 0,
  30697. // :uint
  30698. // the number of bits left to examine in the current word
  30699. workingBitsAvailable = 0; // :uint;
  30700. // ():uint
  30701. this.length = function () {
  30702. return 8 * workingBytesAvailable;
  30703. }; // ():uint
  30704. this.bitsAvailable = function () {
  30705. return 8 * workingBytesAvailable + workingBitsAvailable;
  30706. }; // ():void
  30707. this.loadWord = function () {
  30708. var position = workingData.byteLength - workingBytesAvailable,
  30709. workingBytes = new Uint8Array(4),
  30710. availableBytes = Math.min(4, workingBytesAvailable);
  30711. if (availableBytes === 0) {
  30712. throw new Error('no bytes available');
  30713. }
  30714. workingBytes.set(workingData.subarray(position, position + availableBytes));
  30715. workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
  30716. workingBitsAvailable = availableBytes * 8;
  30717. workingBytesAvailable -= availableBytes;
  30718. }; // (count:int):void
  30719. this.skipBits = function (count) {
  30720. var skipBytes; // :int
  30721. if (workingBitsAvailable > count) {
  30722. workingWord <<= count;
  30723. workingBitsAvailable -= count;
  30724. } else {
  30725. count -= workingBitsAvailable;
  30726. skipBytes = Math.floor(count / 8);
  30727. count -= skipBytes * 8;
  30728. workingBytesAvailable -= skipBytes;
  30729. this.loadWord();
  30730. workingWord <<= count;
  30731. workingBitsAvailable -= count;
  30732. }
  30733. }; // (size:int):uint
  30734. this.readBits = function (size) {
  30735. var bits = Math.min(workingBitsAvailable, size),
  30736. // :uint
  30737. valu = workingWord >>> 32 - bits; // :uint
  30738. // if size > 31, handle error
  30739. workingBitsAvailable -= bits;
  30740. if (workingBitsAvailable > 0) {
  30741. workingWord <<= bits;
  30742. } else if (workingBytesAvailable > 0) {
  30743. this.loadWord();
  30744. }
  30745. bits = size - bits;
  30746. if (bits > 0) {
  30747. return valu << bits | this.readBits(bits);
  30748. }
  30749. return valu;
  30750. }; // ():uint
  30751. this.skipLeadingZeros = function () {
  30752. var leadingZeroCount; // :uint
  30753. for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
  30754. if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
  30755. // the first bit of working word is 1
  30756. workingWord <<= leadingZeroCount;
  30757. workingBitsAvailable -= leadingZeroCount;
  30758. return leadingZeroCount;
  30759. }
  30760. } // we exhausted workingWord and still have not found a 1
  30761. this.loadWord();
  30762. return leadingZeroCount + this.skipLeadingZeros();
  30763. }; // ():void
  30764. this.skipUnsignedExpGolomb = function () {
  30765. this.skipBits(1 + this.skipLeadingZeros());
  30766. }; // ():void
  30767. this.skipExpGolomb = function () {
  30768. this.skipBits(1 + this.skipLeadingZeros());
  30769. }; // ():uint
  30770. this.readUnsignedExpGolomb = function () {
  30771. var clz = this.skipLeadingZeros(); // :uint
  30772. return this.readBits(clz + 1) - 1;
  30773. }; // ():int
  30774. this.readExpGolomb = function () {
  30775. var valu = this.readUnsignedExpGolomb(); // :int
  30776. if (0x01 & valu) {
  30777. // the number is odd if the low order bit is set
  30778. return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
  30779. }
  30780. return -1 * (valu >>> 1); // divide by two then make it negative
  30781. }; // Some convenience functions
  30782. // :Boolean
  30783. this.readBoolean = function () {
  30784. return this.readBits(1) === 1;
  30785. }; // ():int
  30786. this.readUnsignedByte = function () {
  30787. return this.readBits(8);
  30788. };
  30789. this.loadWord();
  30790. };
  30791. var expGolomb = ExpGolomb;
  30792. var _H264Stream, _NalByteStream;
  30793. var PROFILES_WITH_OPTIONAL_SPS_DATA;
  30794. /**
  30795. * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
  30796. */
  30797. _NalByteStream = function NalByteStream() {
  30798. var syncPoint = 0,
  30799. i,
  30800. buffer;
  30801. _NalByteStream.prototype.init.call(this);
  30802. /*
  30803. * Scans a byte stream and triggers a data event with the NAL units found.
  30804. * @param {Object} data Event received from H264Stream
  30805. * @param {Uint8Array} data.data The h264 byte stream to be scanned
  30806. *
  30807. * @see H264Stream.push
  30808. */
  30809. this.push = function (data) {
  30810. var swapBuffer;
  30811. if (!buffer) {
  30812. buffer = data.data;
  30813. } else {
  30814. swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
  30815. swapBuffer.set(buffer);
  30816. swapBuffer.set(data.data, buffer.byteLength);
  30817. buffer = swapBuffer;
  30818. } // Rec. ITU-T H.264, Annex B
  30819. // scan for NAL unit boundaries
  30820. // a match looks like this:
  30821. // 0 0 1 .. NAL .. 0 0 1
  30822. // ^ sync point ^ i
  30823. // or this:
  30824. // 0 0 1 .. NAL .. 0 0 0
  30825. // ^ sync point ^ i
  30826. // advance the sync point to a NAL start, if necessary
  30827. for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
  30828. if (buffer[syncPoint + 2] === 1) {
  30829. // the sync point is properly aligned
  30830. i = syncPoint + 5;
  30831. break;
  30832. }
  30833. }
  30834. while (i < buffer.byteLength) {
  30835. // look at the current byte to determine if we've hit the end of
  30836. // a NAL unit boundary
  30837. switch (buffer[i]) {
  30838. case 0:
  30839. // skip past non-sync sequences
  30840. if (buffer[i - 1] !== 0) {
  30841. i += 2;
  30842. break;
  30843. } else if (buffer[i - 2] !== 0) {
  30844. i++;
  30845. break;
  30846. } // deliver the NAL unit if it isn't empty
  30847. if (syncPoint + 3 !== i - 2) {
  30848. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  30849. } // drop trailing zeroes
  30850. do {
  30851. i++;
  30852. } while (buffer[i] !== 1 && i < buffer.length);
  30853. syncPoint = i - 2;
  30854. i += 3;
  30855. break;
  30856. case 1:
  30857. // skip past non-sync sequences
  30858. if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
  30859. i += 3;
  30860. break;
  30861. } // deliver the NAL unit
  30862. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  30863. syncPoint = i - 2;
  30864. i += 3;
  30865. break;
  30866. default:
  30867. // the current byte isn't a one or zero, so it cannot be part
  30868. // of a sync sequence
  30869. i += 3;
  30870. break;
  30871. }
  30872. } // filter out the NAL units that were delivered
  30873. buffer = buffer.subarray(syncPoint);
  30874. i -= syncPoint;
  30875. syncPoint = 0;
  30876. };
  30877. this.flush = function () {
  30878. // deliver the last buffered NAL unit
  30879. if (buffer && buffer.byteLength > 3) {
  30880. this.trigger('data', buffer.subarray(syncPoint + 3));
  30881. } // reset the stream state
  30882. buffer = null;
  30883. syncPoint = 0;
  30884. this.trigger('done');
  30885. };
  30886. };
  30887. _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
  30888. // see Recommendation ITU-T H.264 (4/2013),
  30889. // 7.3.2.1.1 Sequence parameter set data syntax
  30890. PROFILES_WITH_OPTIONAL_SPS_DATA = {
  30891. 100: true,
  30892. 110: true,
  30893. 122: true,
  30894. 244: true,
  30895. 44: true,
  30896. 83: true,
  30897. 86: true,
  30898. 118: true,
  30899. 128: true,
  30900. 138: true,
  30901. 139: true,
  30902. 134: true
  30903. };
  30904. /**
  30905. * Accepts input from a ElementaryStream and produces H.264 NAL unit data
  30906. * events.
  30907. */
  30908. _H264Stream = function H264Stream() {
  30909. var nalByteStream = new _NalByteStream(),
  30910. self,
  30911. trackId,
  30912. currentPts,
  30913. currentDts,
  30914. discardEmulationPreventionBytes,
  30915. readSequenceParameterSet,
  30916. skipScalingList;
  30917. _H264Stream.prototype.init.call(this);
  30918. self = this;
  30919. /*
  30920. * Pushes a packet from a stream onto the NalByteStream
  30921. *
  30922. * @param {Object} packet - A packet received from a stream
  30923. * @param {Uint8Array} packet.data - The raw bytes of the packet
  30924. * @param {Number} packet.dts - Decode timestamp of the packet
  30925. * @param {Number} packet.pts - Presentation timestamp of the packet
  30926. * @param {Number} packet.trackId - The id of the h264 track this packet came from
  30927. * @param {('video'|'audio')} packet.type - The type of packet
  30928. *
  30929. */
  30930. this.push = function (packet) {
  30931. if (packet.type !== 'video') {
  30932. return;
  30933. }
  30934. trackId = packet.trackId;
  30935. currentPts = packet.pts;
  30936. currentDts = packet.dts;
  30937. nalByteStream.push(packet);
  30938. };
  30939. /*
  30940. * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
  30941. * for the NALUs to the next stream component.
  30942. * Also, preprocess caption and sequence parameter NALUs.
  30943. *
  30944. * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
  30945. * @see NalByteStream.push
  30946. */
  30947. nalByteStream.on('data', function (data) {
  30948. var event = {
  30949. trackId: trackId,
  30950. pts: currentPts,
  30951. dts: currentDts,
  30952. data: data
  30953. };
  30954. switch (data[0] & 0x1f) {
  30955. case 0x05:
  30956. event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
  30957. break;
  30958. case 0x06:
  30959. event.nalUnitType = 'sei_rbsp';
  30960. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  30961. break;
  30962. case 0x07:
  30963. event.nalUnitType = 'seq_parameter_set_rbsp';
  30964. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  30965. event.config = readSequenceParameterSet(event.escapedRBSP);
  30966. break;
  30967. case 0x08:
  30968. event.nalUnitType = 'pic_parameter_set_rbsp';
  30969. break;
  30970. case 0x09:
  30971. event.nalUnitType = 'access_unit_delimiter_rbsp';
  30972. break;
  30973. default:
  30974. break;
  30975. } // This triggers data on the H264Stream
  30976. self.trigger('data', event);
  30977. });
  30978. nalByteStream.on('done', function () {
  30979. self.trigger('done');
  30980. });
  30981. this.flush = function () {
  30982. nalByteStream.flush();
  30983. };
  30984. /**
  30985. * Advance the ExpGolomb decoder past a scaling list. The scaling
  30986. * list is optionally transmitted as part of a sequence parameter
  30987. * set and is not relevant to transmuxing.
  30988. * @param count {number} the number of entries in this scaling list
  30989. * @param expGolombDecoder {object} an ExpGolomb pointed to the
  30990. * start of a scaling list
  30991. * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  30992. */
  30993. skipScalingList = function skipScalingList(count, expGolombDecoder) {
  30994. var lastScale = 8,
  30995. nextScale = 8,
  30996. j,
  30997. deltaScale;
  30998. for (j = 0; j < count; j++) {
  30999. if (nextScale !== 0) {
  31000. deltaScale = expGolombDecoder.readExpGolomb();
  31001. nextScale = (lastScale + deltaScale + 256) % 256;
  31002. }
  31003. lastScale = nextScale === 0 ? lastScale : nextScale;
  31004. }
  31005. };
  31006. /**
  31007. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  31008. * Sequence Payload"
  31009. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  31010. * unit
  31011. * @return {Uint8Array} the RBSP without any Emulation
  31012. * Prevention Bytes
  31013. */
  31014. discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  31015. var length = data.byteLength,
  31016. emulationPreventionBytesPositions = [],
  31017. i = 1,
  31018. newLength,
  31019. newData; // Find all `Emulation Prevention Bytes`
  31020. while (i < length - 2) {
  31021. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  31022. emulationPreventionBytesPositions.push(i + 2);
  31023. i += 2;
  31024. } else {
  31025. i++;
  31026. }
  31027. } // If no Emulation Prevention Bytes were found just return the original
  31028. // array
  31029. if (emulationPreventionBytesPositions.length === 0) {
  31030. return data;
  31031. } // Create a new array to hold the NAL unit data
  31032. newLength = length - emulationPreventionBytesPositions.length;
  31033. newData = new Uint8Array(newLength);
  31034. var sourceIndex = 0;
  31035. for (i = 0; i < newLength; sourceIndex++, i++) {
  31036. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  31037. // Skip this byte
  31038. sourceIndex++; // Remove this position index
  31039. emulationPreventionBytesPositions.shift();
  31040. }
  31041. newData[i] = data[sourceIndex];
  31042. }
  31043. return newData;
  31044. };
  31045. /**
  31046. * Read a sequence parameter set and return some interesting video
  31047. * properties. A sequence parameter set is the H264 metadata that
  31048. * describes the properties of upcoming video frames.
  31049. * @param data {Uint8Array} the bytes of a sequence parameter set
  31050. * @return {object} an object with configuration parsed from the
  31051. * sequence parameter set, including the dimensions of the
  31052. * associated video frames.
  31053. */
  31054. readSequenceParameterSet = function readSequenceParameterSet(data) {
  31055. var frameCropLeftOffset = 0,
  31056. frameCropRightOffset = 0,
  31057. frameCropTopOffset = 0,
  31058. frameCropBottomOffset = 0,
  31059. sarScale = 1,
  31060. expGolombDecoder,
  31061. profileIdc,
  31062. levelIdc,
  31063. profileCompatibility,
  31064. chromaFormatIdc,
  31065. picOrderCntType,
  31066. numRefFramesInPicOrderCntCycle,
  31067. picWidthInMbsMinus1,
  31068. picHeightInMapUnitsMinus1,
  31069. frameMbsOnlyFlag,
  31070. scalingListCount,
  31071. sarRatio,
  31072. aspectRatioIdc,
  31073. i;
  31074. expGolombDecoder = new expGolomb(data);
  31075. profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
  31076. profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
  31077. levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
  31078. expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
  31079. // some profiles have more optional data we don't need
  31080. if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
  31081. chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  31082. if (chromaFormatIdc === 3) {
  31083. expGolombDecoder.skipBits(1); // separate_colour_plane_flag
  31084. }
  31085. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
  31086. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
  31087. expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  31088. if (expGolombDecoder.readBoolean()) {
  31089. // seq_scaling_matrix_present_flag
  31090. scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
  31091. for (i = 0; i < scalingListCount; i++) {
  31092. if (expGolombDecoder.readBoolean()) {
  31093. // seq_scaling_list_present_flag[ i ]
  31094. if (i < 6) {
  31095. skipScalingList(16, expGolombDecoder);
  31096. } else {
  31097. skipScalingList(64, expGolombDecoder);
  31098. }
  31099. }
  31100. }
  31101. }
  31102. }
  31103. expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
  31104. picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  31105. if (picOrderCntType === 0) {
  31106. expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
  31107. } else if (picOrderCntType === 1) {
  31108. expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
  31109. expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
  31110. expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
  31111. numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
  31112. for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  31113. expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
  31114. }
  31115. }
  31116. expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
  31117. expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  31118. picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  31119. picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  31120. frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  31121. if (frameMbsOnlyFlag === 0) {
  31122. expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
  31123. }
  31124. expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
  31125. if (expGolombDecoder.readBoolean()) {
  31126. // frame_cropping_flag
  31127. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  31128. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  31129. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  31130. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  31131. }
  31132. if (expGolombDecoder.readBoolean()) {
  31133. // vui_parameters_present_flag
  31134. if (expGolombDecoder.readBoolean()) {
  31135. // aspect_ratio_info_present_flag
  31136. aspectRatioIdc = expGolombDecoder.readUnsignedByte();
  31137. switch (aspectRatioIdc) {
  31138. case 1:
  31139. sarRatio = [1, 1];
  31140. break;
  31141. case 2:
  31142. sarRatio = [12, 11];
  31143. break;
  31144. case 3:
  31145. sarRatio = [10, 11];
  31146. break;
  31147. case 4:
  31148. sarRatio = [16, 11];
  31149. break;
  31150. case 5:
  31151. sarRatio = [40, 33];
  31152. break;
  31153. case 6:
  31154. sarRatio = [24, 11];
  31155. break;
  31156. case 7:
  31157. sarRatio = [20, 11];
  31158. break;
  31159. case 8:
  31160. sarRatio = [32, 11];
  31161. break;
  31162. case 9:
  31163. sarRatio = [80, 33];
  31164. break;
  31165. case 10:
  31166. sarRatio = [18, 11];
  31167. break;
  31168. case 11:
  31169. sarRatio = [15, 11];
  31170. break;
  31171. case 12:
  31172. sarRatio = [64, 33];
  31173. break;
  31174. case 13:
  31175. sarRatio = [160, 99];
  31176. break;
  31177. case 14:
  31178. sarRatio = [4, 3];
  31179. break;
  31180. case 15:
  31181. sarRatio = [3, 2];
  31182. break;
  31183. case 16:
  31184. sarRatio = [2, 1];
  31185. break;
  31186. case 255:
  31187. {
  31188. sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
  31189. break;
  31190. }
  31191. }
  31192. if (sarRatio) {
  31193. sarScale = sarRatio[0] / sarRatio[1];
  31194. }
  31195. }
  31196. }
  31197. return {
  31198. profileIdc: profileIdc,
  31199. levelIdc: levelIdc,
  31200. profileCompatibility: profileCompatibility,
  31201. width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
  31202. height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
  31203. };
  31204. };
  31205. };
  31206. _H264Stream.prototype = new stream();
  31207. var h264 = {
  31208. H264Stream: _H264Stream,
  31209. NalByteStream: _NalByteStream
  31210. };
  31211. /**
  31212. * mux.js
  31213. *
  31214. * Copyright (c) 2016 Brightcove
  31215. * All rights reserved.
  31216. *
  31217. * Utilities to detect basic properties and metadata about Aac data.
  31218. */
  31219. var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  31220. var isLikelyAacData = function isLikelyAacData(data) {
  31221. if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
  31222. return true;
  31223. }
  31224. return false;
  31225. };
  31226. var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
  31227. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  31228. }; // return a percent-encoded representation of the specified byte range
  31229. // @see http://en.wikipedia.org/wiki/Percent-encoding
  31230. var percentEncode$1 = function percentEncode(bytes, start, end) {
  31231. var i,
  31232. result = '';
  31233. for (i = start; i < end; i++) {
  31234. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  31235. }
  31236. return result;
  31237. }; // return the string representation of the specified byte range,
  31238. // interpreted as ISO-8859-1.
  31239. var parseIso88591$1 = function parseIso88591(bytes, start, end) {
  31240. return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
  31241. };
  31242. var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
  31243. var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
  31244. flags = header[byteIndex + 5],
  31245. footerPresent = (flags & 16) >> 4;
  31246. if (footerPresent) {
  31247. return returnSize + 20;
  31248. }
  31249. return returnSize + 10;
  31250. };
  31251. var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
  31252. var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
  31253. middle = header[byteIndex + 4] << 3,
  31254. highTwo = header[byteIndex + 3] & 0x3 << 11;
  31255. return highTwo | middle | lowThree;
  31256. };
  31257. var parseType$2 = function parseType(header, byteIndex) {
  31258. if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
  31259. return 'timed-metadata';
  31260. } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
  31261. return 'audio';
  31262. }
  31263. return null;
  31264. };
  31265. var parseSampleRate = function parseSampleRate(packet) {
  31266. var i = 0;
  31267. while (i + 5 < packet.length) {
  31268. if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
  31269. // If a valid header was not found, jump one forward and attempt to
  31270. // find a valid ADTS header starting at the next byte
  31271. i++;
  31272. continue;
  31273. }
  31274. return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
  31275. }
  31276. return null;
  31277. };
  31278. var parseAacTimestamp = function parseAacTimestamp(packet) {
  31279. var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
  31280. frameStart = 10;
  31281. if (packet[5] & 0x40) {
  31282. // advance the frame start past the extended header
  31283. frameStart += 4; // header size field
  31284. frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
  31285. } // parse one or more ID3 frames
  31286. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  31287. do {
  31288. // determine the number of bytes in this frame
  31289. frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
  31290. if (frameSize < 1) {
  31291. return null;
  31292. }
  31293. frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
  31294. if (frameHeader === 'PRIV') {
  31295. frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
  31296. for (var i = 0; i < frame.byteLength; i++) {
  31297. if (frame[i] === 0) {
  31298. var owner = parseIso88591$1(frame, 0, i);
  31299. if (owner === 'com.apple.streaming.transportStreamTimestamp') {
  31300. var d = frame.subarray(i + 1);
  31301. var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  31302. size *= 4;
  31303. size += d[7] & 0x03;
  31304. return size;
  31305. }
  31306. break;
  31307. }
  31308. }
  31309. }
  31310. frameStart += 10; // advance past the frame header
  31311. frameStart += frameSize; // advance past the frame body
  31312. } while (frameStart < packet.byteLength);
  31313. return null;
  31314. };
  31315. var utils = {
  31316. isLikelyAacData: isLikelyAacData,
  31317. parseId3TagSize: parseId3TagSize,
  31318. parseAdtsSize: parseAdtsSize,
  31319. parseType: parseType$2,
  31320. parseSampleRate: parseSampleRate,
  31321. parseAacTimestamp: parseAacTimestamp
  31322. };
  31323. var _AacStream;
  31324. /**
  31325. * Splits an incoming stream of binary data into ADTS and ID3 Frames.
  31326. */
  31327. _AacStream = function AacStream() {
  31328. var everything = new Uint8Array(),
  31329. timeStamp = 0;
  31330. _AacStream.prototype.init.call(this);
  31331. this.setTimestamp = function (timestamp) {
  31332. timeStamp = timestamp;
  31333. };
  31334. this.push = function (bytes) {
  31335. var frameSize = 0,
  31336. byteIndex = 0,
  31337. bytesLeft,
  31338. chunk,
  31339. packet,
  31340. tempLength; // If there are bytes remaining from the last segment, prepend them to the
  31341. // bytes that were pushed in
  31342. if (everything.length) {
  31343. tempLength = everything.length;
  31344. everything = new Uint8Array(bytes.byteLength + tempLength);
  31345. everything.set(everything.subarray(0, tempLength));
  31346. everything.set(bytes, tempLength);
  31347. } else {
  31348. everything = bytes;
  31349. }
  31350. while (everything.length - byteIndex >= 3) {
  31351. if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
  31352. // Exit early because we don't have enough to parse
  31353. // the ID3 tag header
  31354. if (everything.length - byteIndex < 10) {
  31355. break;
  31356. } // check framesize
  31357. frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  31358. // to emit a full packet
  31359. // Add to byteIndex to support multiple ID3 tags in sequence
  31360. if (byteIndex + frameSize > everything.length) {
  31361. break;
  31362. }
  31363. chunk = {
  31364. type: 'timed-metadata',
  31365. data: everything.subarray(byteIndex, byteIndex + frameSize)
  31366. };
  31367. this.trigger('data', chunk);
  31368. byteIndex += frameSize;
  31369. continue;
  31370. } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
  31371. // Exit early because we don't have enough to parse
  31372. // the ADTS frame header
  31373. if (everything.length - byteIndex < 7) {
  31374. break;
  31375. }
  31376. frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  31377. // to emit a full packet
  31378. if (byteIndex + frameSize > everything.length) {
  31379. break;
  31380. }
  31381. packet = {
  31382. type: 'audio',
  31383. data: everything.subarray(byteIndex, byteIndex + frameSize),
  31384. pts: timeStamp,
  31385. dts: timeStamp
  31386. };
  31387. this.trigger('data', packet);
  31388. byteIndex += frameSize;
  31389. continue;
  31390. }
  31391. byteIndex++;
  31392. }
  31393. bytesLeft = everything.length - byteIndex;
  31394. if (bytesLeft > 0) {
  31395. everything = everything.subarray(byteIndex);
  31396. } else {
  31397. everything = new Uint8Array();
  31398. }
  31399. };
  31400. };
  31401. _AacStream.prototype = new stream();
  31402. var aac = _AacStream;
  31403. var H264Stream = h264.H264Stream;
  31404. var isLikelyAacData$1 = utils.isLikelyAacData; // constants
  31405. var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
  31406. var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types
  31407. var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
  31408. /**
  31409. * Compare two arrays (even typed) for same-ness
  31410. */
  31411. var arrayEquals = function arrayEquals(a, b) {
  31412. var i;
  31413. if (a.length !== b.length) {
  31414. return false;
  31415. } // compare the value of each element in the array
  31416. for (i = 0; i < a.length; i++) {
  31417. if (a[i] !== b[i]) {
  31418. return false;
  31419. }
  31420. }
  31421. return true;
  31422. };
  31423. var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
  31424. var ptsOffsetFromDts = startPts - startDts,
  31425. decodeDuration = endDts - startDts,
  31426. presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
  31427. // however, the player time values will reflect a start from the baseMediaDecodeTime.
  31428. // In order to provide relevant values for the player times, base timing info on the
  31429. // baseMediaDecodeTime and the DTS and PTS durations of the segment.
  31430. return {
  31431. start: {
  31432. dts: baseMediaDecodeTime,
  31433. pts: baseMediaDecodeTime + ptsOffsetFromDts
  31434. },
  31435. end: {
  31436. dts: baseMediaDecodeTime + decodeDuration,
  31437. pts: baseMediaDecodeTime + presentationDuration
  31438. },
  31439. prependedContentDuration: prependedContentDuration,
  31440. baseMediaDecodeTime: baseMediaDecodeTime
  31441. };
  31442. };
  31443. /**
  31444. * Constructs a single-track, ISO BMFF media segment from AAC data
  31445. * events. The output of this stream can be fed to a SourceBuffer
  31446. * configured with a suitable initialization segment.
  31447. * @param track {object} track metadata configuration
  31448. * @param options {object} transmuxer options object
  31449. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  31450. * in the source; false to adjust the first segment to start at 0.
  31451. */
  31452. _AudioSegmentStream = function AudioSegmentStream(track, options) {
  31453. var adtsFrames = [],
  31454. sequenceNumber = 0,
  31455. earliestAllowedDts = 0,
  31456. audioAppendStartTs = 0,
  31457. videoBaseMediaDecodeTime = Infinity;
  31458. options = options || {};
  31459. _AudioSegmentStream.prototype.init.call(this);
  31460. this.push = function (data) {
  31461. trackDecodeInfo.collectDtsInfo(track, data);
  31462. if (track) {
  31463. AUDIO_PROPERTIES.forEach(function (prop) {
  31464. track[prop] = data[prop];
  31465. });
  31466. } // buffer audio data until end() is called
  31467. adtsFrames.push(data);
  31468. };
  31469. this.setEarliestDts = function (earliestDts) {
  31470. earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
  31471. };
  31472. this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  31473. videoBaseMediaDecodeTime = baseMediaDecodeTime;
  31474. };
  31475. this.setAudioAppendStart = function (timestamp) {
  31476. audioAppendStartTs = timestamp;
  31477. };
  31478. this.flush = function () {
  31479. var frames, moof, mdat, boxes; // return early if no audio data has been observed
  31480. if (adtsFrames.length === 0) {
  31481. this.trigger('done', 'AudioSegmentStream');
  31482. return;
  31483. }
  31484. frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
  31485. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  31486. audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
  31487. // samples (that is, adts frames) in the audio data
  31488. track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
  31489. mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
  31490. adtsFrames = [];
  31491. moof = mp4Generator.moof(sequenceNumber, [track]);
  31492. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
  31493. sequenceNumber++;
  31494. boxes.set(moof);
  31495. boxes.set(mdat, moof.byteLength);
  31496. trackDecodeInfo.clearDtsInfo(track);
  31497. this.trigger('data', {
  31498. track: track,
  31499. boxes: boxes
  31500. });
  31501. this.trigger('done', 'AudioSegmentStream');
  31502. };
  31503. };
  31504. _AudioSegmentStream.prototype = new stream();
  31505. /**
  31506. * Constructs a single-track, ISO BMFF media segment from H264 data
  31507. * events. The output of this stream can be fed to a SourceBuffer
  31508. * configured with a suitable initialization segment.
  31509. * @param track {object} track metadata configuration
  31510. * @param options {object} transmuxer options object
  31511. * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
  31512. * gopsToAlignWith list when attempting to align gop pts
  31513. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  31514. * in the source; false to adjust the first segment to start at 0.
  31515. */
  31516. _VideoSegmentStream = function VideoSegmentStream(track, options) {
  31517. var sequenceNumber = 0,
  31518. nalUnits = [],
  31519. gopsToAlignWith = [],
  31520. config,
  31521. pps;
  31522. options = options || {};
  31523. _VideoSegmentStream.prototype.init.call(this);
  31524. delete track.minPTS;
  31525. this.gopCache_ = [];
  31526. /**
  31527. * Constructs a ISO BMFF segment given H264 nalUnits
  31528. * @param {Object} nalUnit A data event representing a nalUnit
  31529. * @param {String} nalUnit.nalUnitType
  31530. * @param {Object} nalUnit.config Properties for a mp4 track
  31531. * @param {Uint8Array} nalUnit.data The nalUnit bytes
  31532. * @see lib/codecs/h264.js
  31533. **/
  31534. this.push = function (nalUnit) {
  31535. trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
  31536. if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
  31537. config = nalUnit.config;
  31538. track.sps = [nalUnit.data];
  31539. VIDEO_PROPERTIES.forEach(function (prop) {
  31540. track[prop] = config[prop];
  31541. }, this);
  31542. }
  31543. if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
  31544. pps = nalUnit.data;
  31545. track.pps = [nalUnit.data];
  31546. } // buffer video until flush() is called
  31547. nalUnits.push(nalUnit);
  31548. };
  31549. /**
  31550. * Pass constructed ISO BMFF track and boxes on to the
  31551. * next stream in the pipeline
  31552. **/
  31553. this.flush = function () {
  31554. var frames,
  31555. gopForFusion,
  31556. gops,
  31557. moof,
  31558. mdat,
  31559. boxes,
  31560. prependedContentDuration = 0,
  31561. firstGop,
  31562. lastGop; // Throw away nalUnits at the start of the byte stream until
  31563. // we find the first AUD
  31564. while (nalUnits.length) {
  31565. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  31566. break;
  31567. }
  31568. nalUnits.shift();
  31569. } // Return early if no video data has been observed
  31570. if (nalUnits.length === 0) {
  31571. this.resetStream_();
  31572. this.trigger('done', 'VideoSegmentStream');
  31573. return;
  31574. } // Organize the raw nal-units into arrays that represent
  31575. // higher-level constructs such as frames and gops
  31576. // (group-of-pictures)
  31577. frames = frameUtils.groupNalsIntoFrames(nalUnits);
  31578. gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
  31579. // a problem since MSE (on Chrome) requires a leading keyframe.
  31580. //
  31581. // We have two approaches to repairing this situation:
  31582. // 1) GOP-FUSION:
  31583. // This is where we keep track of the GOPS (group-of-pictures)
  31584. // from previous fragments and attempt to find one that we can
  31585. // prepend to the current fragment in order to create a valid
  31586. // fragment.
  31587. // 2) KEYFRAME-PULLING:
  31588. // Here we search for the first keyframe in the fragment and
  31589. // throw away all the frames between the start of the fragment
  31590. // and that keyframe. We then extend the duration and pull the
  31591. // PTS of the keyframe forward so that it covers the time range
  31592. // of the frames that were disposed of.
  31593. //
  31594. // #1 is far prefereable over #2 which can cause "stuttering" but
  31595. // requires more things to be just right.
  31596. if (!gops[0][0].keyFrame) {
  31597. // Search for a gop for fusion from our gopCache
  31598. gopForFusion = this.getGopForFusion_(nalUnits[0], track);
  31599. if (gopForFusion) {
  31600. // in order to provide more accurate timing information about the segment, save
  31601. // the number of seconds prepended to the original segment due to GOP fusion
  31602. prependedContentDuration = gopForFusion.duration;
  31603. gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
  31604. // new gop at the beginning
  31605. gops.byteLength += gopForFusion.byteLength;
  31606. gops.nalCount += gopForFusion.nalCount;
  31607. gops.pts = gopForFusion.pts;
  31608. gops.dts = gopForFusion.dts;
  31609. gops.duration += gopForFusion.duration;
  31610. } else {
  31611. // If we didn't find a candidate gop fall back to keyframe-pulling
  31612. gops = frameUtils.extendFirstKeyFrame(gops);
  31613. }
  31614. } // Trim gops to align with gopsToAlignWith
  31615. if (gopsToAlignWith.length) {
  31616. var alignedGops;
  31617. if (options.alignGopsAtEnd) {
  31618. alignedGops = this.alignGopsAtEnd_(gops);
  31619. } else {
  31620. alignedGops = this.alignGopsAtStart_(gops);
  31621. }
  31622. if (!alignedGops) {
  31623. // save all the nals in the last GOP into the gop cache
  31624. this.gopCache_.unshift({
  31625. gop: gops.pop(),
  31626. pps: track.pps,
  31627. sps: track.sps
  31628. }); // Keep a maximum of 6 GOPs in the cache
  31629. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  31630. nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
  31631. this.resetStream_();
  31632. this.trigger('done', 'VideoSegmentStream');
  31633. return;
  31634. } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
  31635. // when recalculated before sending off to CoalesceStream
  31636. trackDecodeInfo.clearDtsInfo(track);
  31637. gops = alignedGops;
  31638. }
  31639. trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
  31640. // samples (that is, frames) in the video data
  31641. track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
  31642. mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
  31643. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  31644. this.trigger('processedGopsInfo', gops.map(function (gop) {
  31645. return {
  31646. pts: gop.pts,
  31647. dts: gop.dts,
  31648. byteLength: gop.byteLength
  31649. };
  31650. }));
  31651. firstGop = gops[0];
  31652. lastGop = gops[gops.length - 1];
  31653. this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); // save all the nals in the last GOP into the gop cache
  31654. this.gopCache_.unshift({
  31655. gop: gops.pop(),
  31656. pps: track.pps,
  31657. sps: track.sps
  31658. }); // Keep a maximum of 6 GOPs in the cache
  31659. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  31660. nalUnits = [];
  31661. this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
  31662. this.trigger('timelineStartInfo', track.timelineStartInfo);
  31663. moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
  31664. // throwing away hundreds of media segment fragments
  31665. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
  31666. sequenceNumber++;
  31667. boxes.set(moof);
  31668. boxes.set(mdat, moof.byteLength);
  31669. this.trigger('data', {
  31670. track: track,
  31671. boxes: boxes
  31672. });
  31673. this.resetStream_(); // Continue with the flush process now
  31674. this.trigger('done', 'VideoSegmentStream');
  31675. };
  31676. this.resetStream_ = function () {
  31677. trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
  31678. // for instance, when we are rendition switching
  31679. config = undefined;
  31680. pps = undefined;
  31681. }; // Search for a candidate Gop for gop-fusion from the gop cache and
  31682. // return it or return null if no good candidate was found
  31683. this.getGopForFusion_ = function (nalUnit) {
  31684. var halfSecond = 45000,
  31685. // Half-a-second in a 90khz clock
  31686. allowableOverlap = 10000,
  31687. // About 3 frames @ 30fps
  31688. nearestDistance = Infinity,
  31689. dtsDistance,
  31690. nearestGopObj,
  31691. currentGop,
  31692. currentGopObj,
  31693. i; // Search for the GOP nearest to the beginning of this nal unit
  31694. for (i = 0; i < this.gopCache_.length; i++) {
  31695. currentGopObj = this.gopCache_[i];
  31696. currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
  31697. if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
  31698. continue;
  31699. } // Reject Gops that would require a negative baseMediaDecodeTime
  31700. if (currentGop.dts < track.timelineStartInfo.dts) {
  31701. continue;
  31702. } // The distance between the end of the gop and the start of the nalUnit
  31703. dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
  31704. // a half-second of the nal unit
  31705. if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
  31706. // Always use the closest GOP we found if there is more than
  31707. // one candidate
  31708. if (!nearestGopObj || nearestDistance > dtsDistance) {
  31709. nearestGopObj = currentGopObj;
  31710. nearestDistance = dtsDistance;
  31711. }
  31712. }
  31713. }
  31714. if (nearestGopObj) {
  31715. return nearestGopObj.gop;
  31716. }
  31717. return null;
  31718. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  31719. // of gopsToAlignWith starting from the START of the list
  31720. this.alignGopsAtStart_ = function (gops) {
  31721. var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
  31722. byteLength = gops.byteLength;
  31723. nalCount = gops.nalCount;
  31724. duration = gops.duration;
  31725. alignIndex = gopIndex = 0;
  31726. while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
  31727. align = gopsToAlignWith[alignIndex];
  31728. gop = gops[gopIndex];
  31729. if (align.pts === gop.pts) {
  31730. break;
  31731. }
  31732. if (gop.pts > align.pts) {
  31733. // this current gop starts after the current gop we want to align on, so increment
  31734. // align index
  31735. alignIndex++;
  31736. continue;
  31737. } // current gop starts before the current gop we want to align on. so increment gop
  31738. // index
  31739. gopIndex++;
  31740. byteLength -= gop.byteLength;
  31741. nalCount -= gop.nalCount;
  31742. duration -= gop.duration;
  31743. }
  31744. if (gopIndex === 0) {
  31745. // no gops to trim
  31746. return gops;
  31747. }
  31748. if (gopIndex === gops.length) {
  31749. // all gops trimmed, skip appending all gops
  31750. return null;
  31751. }
  31752. alignedGops = gops.slice(gopIndex);
  31753. alignedGops.byteLength = byteLength;
  31754. alignedGops.duration = duration;
  31755. alignedGops.nalCount = nalCount;
  31756. alignedGops.pts = alignedGops[0].pts;
  31757. alignedGops.dts = alignedGops[0].dts;
  31758. return alignedGops;
  31759. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  31760. // of gopsToAlignWith starting from the END of the list
  31761. this.alignGopsAtEnd_ = function (gops) {
  31762. var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
  31763. alignIndex = gopsToAlignWith.length - 1;
  31764. gopIndex = gops.length - 1;
  31765. alignEndIndex = null;
  31766. matchFound = false;
  31767. while (alignIndex >= 0 && gopIndex >= 0) {
  31768. align = gopsToAlignWith[alignIndex];
  31769. gop = gops[gopIndex];
  31770. if (align.pts === gop.pts) {
  31771. matchFound = true;
  31772. break;
  31773. }
  31774. if (align.pts > gop.pts) {
  31775. alignIndex--;
  31776. continue;
  31777. }
  31778. if (alignIndex === gopsToAlignWith.length - 1) {
  31779. // gop.pts is greater than the last alignment candidate. If no match is found
  31780. // by the end of this loop, we still want to append gops that come after this
  31781. // point
  31782. alignEndIndex = gopIndex;
  31783. }
  31784. gopIndex--;
  31785. }
  31786. if (!matchFound && alignEndIndex === null) {
  31787. return null;
  31788. }
  31789. var trimIndex;
  31790. if (matchFound) {
  31791. trimIndex = gopIndex;
  31792. } else {
  31793. trimIndex = alignEndIndex;
  31794. }
  31795. if (trimIndex === 0) {
  31796. return gops;
  31797. }
  31798. var alignedGops = gops.slice(trimIndex);
  31799. var metadata = alignedGops.reduce(function (total, gop) {
  31800. total.byteLength += gop.byteLength;
  31801. total.duration += gop.duration;
  31802. total.nalCount += gop.nalCount;
  31803. return total;
  31804. }, {
  31805. byteLength: 0,
  31806. duration: 0,
  31807. nalCount: 0
  31808. });
  31809. alignedGops.byteLength = metadata.byteLength;
  31810. alignedGops.duration = metadata.duration;
  31811. alignedGops.nalCount = metadata.nalCount;
  31812. alignedGops.pts = alignedGops[0].pts;
  31813. alignedGops.dts = alignedGops[0].dts;
  31814. return alignedGops;
  31815. };
  31816. this.alignGopsWith = function (newGopsToAlignWith) {
  31817. gopsToAlignWith = newGopsToAlignWith;
  31818. };
  31819. };
  31820. _VideoSegmentStream.prototype = new stream();
  31821. /**
  31822. * A Stream that can combine multiple streams (ie. audio & video)
  31823. * into a single output segment for MSE. Also supports audio-only
  31824. * and video-only streams.
  31825. * @param options {object} transmuxer options object
  31826. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  31827. * in the source; false to adjust the first segment to start at media timeline start.
  31828. */
  31829. _CoalesceStream = function CoalesceStream(options, metadataStream) {
  31830. // Number of Tracks per output segment
  31831. // If greater than 1, we combine multiple
  31832. // tracks into a single segment
  31833. this.numberOfTracks = 0;
  31834. this.metadataStream = metadataStream;
  31835. options = options || {};
  31836. if (typeof options.remux !== 'undefined') {
  31837. this.remuxTracks = !!options.remux;
  31838. } else {
  31839. this.remuxTracks = true;
  31840. }
  31841. if (typeof options.keepOriginalTimestamps === 'boolean') {
  31842. this.keepOriginalTimestamps = options.keepOriginalTimestamps;
  31843. }
  31844. this.pendingTracks = [];
  31845. this.videoTrack = null;
  31846. this.pendingBoxes = [];
  31847. this.pendingCaptions = [];
  31848. this.pendingMetadata = [];
  31849. this.pendingBytes = 0;
  31850. this.emittedTracks = 0;
  31851. _CoalesceStream.prototype.init.call(this); // Take output from multiple
  31852. this.push = function (output) {
  31853. // buffer incoming captions until the associated video segment
  31854. // finishes
  31855. if (output.text) {
  31856. return this.pendingCaptions.push(output);
  31857. } // buffer incoming id3 tags until the final flush
  31858. if (output.frames) {
  31859. return this.pendingMetadata.push(output);
  31860. } // Add this track to the list of pending tracks and store
  31861. // important information required for the construction of
  31862. // the final segment
  31863. this.pendingTracks.push(output.track);
  31864. this.pendingBoxes.push(output.boxes);
  31865. this.pendingBytes += output.boxes.byteLength;
  31866. if (output.track.type === 'video') {
  31867. this.videoTrack = output.track;
  31868. }
  31869. if (output.track.type === 'audio') {
  31870. this.audioTrack = output.track;
  31871. }
  31872. };
  31873. };
  31874. _CoalesceStream.prototype = new stream();
  31875. _CoalesceStream.prototype.flush = function (flushSource) {
  31876. var offset = 0,
  31877. event = {
  31878. captions: [],
  31879. captionStreams: {},
  31880. metadata: [],
  31881. info: {}
  31882. },
  31883. caption,
  31884. id3,
  31885. initSegment,
  31886. timelineStartPts = 0,
  31887. i;
  31888. if (this.pendingTracks.length < this.numberOfTracks) {
  31889. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  31890. // Return because we haven't received a flush from a data-generating
  31891. // portion of the segment (meaning that we have only recieved meta-data
  31892. // or captions.)
  31893. return;
  31894. } else if (this.remuxTracks) {
  31895. // Return until we have enough tracks from the pipeline to remux (if we
  31896. // are remuxing audio and video into a single MP4)
  31897. return;
  31898. } else if (this.pendingTracks.length === 0) {
  31899. // In the case where we receive a flush without any data having been
  31900. // received we consider it an emitted track for the purposes of coalescing
  31901. // `done` events.
  31902. // We do this for the case where there is an audio and video track in the
  31903. // segment but no audio data. (seen in several playlists with alternate
  31904. // audio tracks and no audio present in the main TS segments.)
  31905. this.emittedTracks++;
  31906. if (this.emittedTracks >= this.numberOfTracks) {
  31907. this.trigger('done');
  31908. this.emittedTracks = 0;
  31909. }
  31910. return;
  31911. }
  31912. }
  31913. if (this.videoTrack) {
  31914. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  31915. VIDEO_PROPERTIES.forEach(function (prop) {
  31916. event.info[prop] = this.videoTrack[prop];
  31917. }, this);
  31918. } else if (this.audioTrack) {
  31919. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  31920. AUDIO_PROPERTIES.forEach(function (prop) {
  31921. event.info[prop] = this.audioTrack[prop];
  31922. }, this);
  31923. }
  31924. if (this.pendingTracks.length === 1) {
  31925. event.type = this.pendingTracks[0].type;
  31926. } else {
  31927. event.type = 'combined';
  31928. }
  31929. this.emittedTracks += this.pendingTracks.length;
  31930. initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
  31931. event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
  31932. // and track definitions
  31933. event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
  31934. event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
  31935. for (i = 0; i < this.pendingBoxes.length; i++) {
  31936. event.data.set(this.pendingBoxes[i], offset);
  31937. offset += this.pendingBoxes[i].byteLength;
  31938. } // Translate caption PTS times into second offsets to match the
  31939. // video timeline for the segment, and add track info
  31940. for (i = 0; i < this.pendingCaptions.length; i++) {
  31941. caption = this.pendingCaptions[i];
  31942. caption.startTime = caption.startPts;
  31943. if (!this.keepOriginalTimestamps) {
  31944. caption.startTime -= timelineStartPts;
  31945. }
  31946. caption.startTime /= 90e3;
  31947. caption.endTime = caption.endPts;
  31948. if (!this.keepOriginalTimestamps) {
  31949. caption.endTime -= timelineStartPts;
  31950. }
  31951. caption.endTime /= 90e3;
  31952. event.captionStreams[caption.stream] = true;
  31953. event.captions.push(caption);
  31954. } // Translate ID3 frame PTS times into second offsets to match the
  31955. // video timeline for the segment
  31956. for (i = 0; i < this.pendingMetadata.length; i++) {
  31957. id3 = this.pendingMetadata[i];
  31958. id3.cueTime = id3.pts;
  31959. if (!this.keepOriginalTimestamps) {
  31960. id3.cueTime -= timelineStartPts;
  31961. }
  31962. id3.cueTime /= 90e3;
  31963. event.metadata.push(id3);
  31964. } // We add this to every single emitted segment even though we only need
  31965. // it for the first
  31966. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  31967. this.pendingTracks.length = 0;
  31968. this.videoTrack = null;
  31969. this.pendingBoxes.length = 0;
  31970. this.pendingCaptions.length = 0;
  31971. this.pendingBytes = 0;
  31972. this.pendingMetadata.length = 0; // Emit the built segment
  31973. this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted
  31974. if (this.emittedTracks >= this.numberOfTracks) {
  31975. this.trigger('done');
  31976. this.emittedTracks = 0;
  31977. }
  31978. };
  31979. /**
  31980. * A Stream that expects MP2T binary data as input and produces
  31981. * corresponding media segments, suitable for use with Media Source
  31982. * Extension (MSE) implementations that support the ISO BMFF byte
  31983. * stream format, like Chrome.
  31984. */
  31985. _Transmuxer = function Transmuxer(options) {
  31986. var self = this,
  31987. hasFlushed = true,
  31988. videoTrack,
  31989. audioTrack;
  31990. _Transmuxer.prototype.init.call(this);
  31991. options = options || {};
  31992. this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
  31993. this.transmuxPipeline_ = {};
  31994. this.setupAacPipeline = function () {
  31995. var pipeline = {};
  31996. this.transmuxPipeline_ = pipeline;
  31997. pipeline.type = 'aac';
  31998. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  31999. pipeline.aacStream = new aac();
  32000. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  32001. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  32002. pipeline.adtsStream = new adts();
  32003. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  32004. pipeline.headOfPipeline = pipeline.aacStream;
  32005. pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  32006. pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
  32007. pipeline.metadataStream.on('timestamp', function (frame) {
  32008. pipeline.aacStream.setTimestamp(frame.timeStamp);
  32009. });
  32010. pipeline.aacStream.on('data', function (data) {
  32011. if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
  32012. audioTrack = audioTrack || {
  32013. timelineStartInfo: {
  32014. baseMediaDecodeTime: self.baseMediaDecodeTime
  32015. },
  32016. codec: 'adts',
  32017. type: 'audio'
  32018. }; // hook up the audio segment stream to the first track with aac data
  32019. pipeline.coalesceStream.numberOfTracks++;
  32020. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  32021. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  32022. }
  32023. }); // Re-emit any data coming from the coalesce stream to the outside world
  32024. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  32025. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  32026. };
  32027. this.setupTsPipeline = function () {
  32028. var pipeline = {};
  32029. this.transmuxPipeline_ = pipeline;
  32030. pipeline.type = 'ts';
  32031. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  32032. pipeline.packetStream = new m2ts_1.TransportPacketStream();
  32033. pipeline.parseStream = new m2ts_1.TransportParseStream();
  32034. pipeline.elementaryStream = new m2ts_1.ElementaryStream();
  32035. pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
  32036. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  32037. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  32038. pipeline.adtsStream = new adts();
  32039. pipeline.h264Stream = new H264Stream();
  32040. pipeline.captionStream = new m2ts_1.CaptionStream();
  32041. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  32042. pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
  32043. pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!!
  32044. // demux the streams
  32045. pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
  32046. pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  32047. pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
  32048. pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
  32049. pipeline.elementaryStream.on('data', function (data) {
  32050. var i;
  32051. if (data.type === 'metadata') {
  32052. i = data.tracks.length; // scan the tracks listed in the metadata
  32053. while (i--) {
  32054. if (!videoTrack && data.tracks[i].type === 'video') {
  32055. videoTrack = data.tracks[i];
  32056. videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  32057. } else if (!audioTrack && data.tracks[i].type === 'audio') {
  32058. audioTrack = data.tracks[i];
  32059. audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  32060. }
  32061. } // hook up the video segment stream to the first track with h264 data
  32062. if (videoTrack && !pipeline.videoSegmentStream) {
  32063. pipeline.coalesceStream.numberOfTracks++;
  32064. pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
  32065. pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
  32066. // When video emits timelineStartInfo data after a flush, we forward that
  32067. // info to the AudioSegmentStream, if it exists, because video timeline
  32068. // data takes precedence.
  32069. if (audioTrack) {
  32070. audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
  32071. // very earliest DTS we have seen in video because Chrome will
  32072. // interpret any video track with a baseMediaDecodeTime that is
  32073. // non-zero as a gap.
  32074. pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
  32075. }
  32076. });
  32077. pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
  32078. pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
  32079. pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
  32080. if (audioTrack) {
  32081. pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
  32082. }
  32083. }); // Set up the final part of the video pipeline
  32084. pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
  32085. }
  32086. if (audioTrack && !pipeline.audioSegmentStream) {
  32087. // hook up the audio segment stream to the first track with aac data
  32088. pipeline.coalesceStream.numberOfTracks++;
  32089. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  32090. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  32091. }
  32092. }
  32093. }); // Re-emit any data coming from the coalesce stream to the outside world
  32094. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  32095. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  32096. }; // hook up the segment streams once track metadata is delivered
  32097. this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  32098. var pipeline = this.transmuxPipeline_;
  32099. if (!options.keepOriginalTimestamps) {
  32100. this.baseMediaDecodeTime = baseMediaDecodeTime;
  32101. }
  32102. if (audioTrack) {
  32103. audioTrack.timelineStartInfo.dts = undefined;
  32104. audioTrack.timelineStartInfo.pts = undefined;
  32105. trackDecodeInfo.clearDtsInfo(audioTrack);
  32106. if (!options.keepOriginalTimestamps) {
  32107. audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  32108. }
  32109. if (pipeline.audioTimestampRolloverStream) {
  32110. pipeline.audioTimestampRolloverStream.discontinuity();
  32111. }
  32112. }
  32113. if (videoTrack) {
  32114. if (pipeline.videoSegmentStream) {
  32115. pipeline.videoSegmentStream.gopCache_ = [];
  32116. pipeline.videoTimestampRolloverStream.discontinuity();
  32117. }
  32118. videoTrack.timelineStartInfo.dts = undefined;
  32119. videoTrack.timelineStartInfo.pts = undefined;
  32120. trackDecodeInfo.clearDtsInfo(videoTrack);
  32121. pipeline.captionStream.reset();
  32122. if (!options.keepOriginalTimestamps) {
  32123. videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  32124. }
  32125. }
  32126. if (pipeline.timedMetadataTimestampRolloverStream) {
  32127. pipeline.timedMetadataTimestampRolloverStream.discontinuity();
  32128. }
  32129. };
  32130. this.setAudioAppendStart = function (timestamp) {
  32131. if (audioTrack) {
  32132. this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
  32133. }
  32134. };
  32135. this.alignGopsWith = function (gopsToAlignWith) {
  32136. if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
  32137. this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
  32138. }
  32139. }; // feed incoming data to the front of the parsing pipeline
  32140. this.push = function (data) {
  32141. if (hasFlushed) {
  32142. var isAac = isLikelyAacData$1(data);
  32143. if (isAac && this.transmuxPipeline_.type !== 'aac') {
  32144. this.setupAacPipeline();
  32145. } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
  32146. this.setupTsPipeline();
  32147. }
  32148. hasFlushed = false;
  32149. }
  32150. this.transmuxPipeline_.headOfPipeline.push(data);
  32151. }; // flush any buffered data
  32152. this.flush = function () {
  32153. hasFlushed = true; // Start at the top of the pipeline and flush all pending work
  32154. this.transmuxPipeline_.headOfPipeline.flush();
  32155. }; // Caption data has to be reset when seeking outside buffered range
  32156. this.resetCaptions = function () {
  32157. if (this.transmuxPipeline_.captionStream) {
  32158. this.transmuxPipeline_.captionStream.reset();
  32159. }
  32160. };
  32161. };
  32162. _Transmuxer.prototype = new stream();
  32163. var transmuxer = {
  32164. Transmuxer: _Transmuxer,
  32165. VideoSegmentStream: _VideoSegmentStream,
  32166. AudioSegmentStream: _AudioSegmentStream,
  32167. AUDIO_PROPERTIES: AUDIO_PROPERTIES,
  32168. VIDEO_PROPERTIES: VIDEO_PROPERTIES,
  32169. // exported for testing
  32170. generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
  32171. };
  32172. var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
  32173. var CaptionStream$1 = captionStream.CaptionStream;
  32174. /**
  32175. * Maps an offset in the mdat to a sample based on the the size of the samples.
  32176. * Assumes that `parseSamples` has been called first.
  32177. *
  32178. * @param {Number} offset - The offset into the mdat
  32179. * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
  32180. * @return {?Object} The matching sample, or null if no match was found.
  32181. *
  32182. * @see ISO-BMFF-12/2015, Section 8.8.8
  32183. **/
  32184. var mapToSample = function mapToSample(offset, samples) {
  32185. var approximateOffset = offset;
  32186. for (var i = 0; i < samples.length; i++) {
  32187. var sample = samples[i];
  32188. if (approximateOffset < sample.size) {
  32189. return sample;
  32190. }
  32191. approximateOffset -= sample.size;
  32192. }
  32193. return null;
  32194. };
  32195. /**
  32196. * Finds SEI nal units contained in a Media Data Box.
  32197. * Assumes that `parseSamples` has been called first.
  32198. *
  32199. * @param {Uint8Array} avcStream - The bytes of the mdat
  32200. * @param {Object[]} samples - The samples parsed out by `parseSamples`
  32201. * @param {Number} trackId - The trackId of this video track
  32202. * @return {Object[]} seiNals - the parsed SEI NALUs found.
  32203. * The contents of the seiNal should match what is expected by
  32204. * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
  32205. *
  32206. * @see ISO-BMFF-12/2015, Section 8.1.1
  32207. * @see Rec. ITU-T H.264, 7.3.2.3.1
  32208. **/
  32209. var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
  32210. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  32211. result = [],
  32212. seiNal,
  32213. i,
  32214. length,
  32215. lastMatchedSample;
  32216. for (i = 0; i + 4 < avcStream.length; i += length) {
  32217. length = avcView.getUint32(i);
  32218. i += 4; // Bail if this doesn't appear to be an H264 stream
  32219. if (length <= 0) {
  32220. continue;
  32221. }
  32222. switch (avcStream[i] & 0x1F) {
  32223. case 0x06:
  32224. var data = avcStream.subarray(i + 1, i + 1 + length);
  32225. var matchingSample = mapToSample(i, samples);
  32226. seiNal = {
  32227. nalUnitType: 'sei_rbsp',
  32228. size: length,
  32229. data: data,
  32230. escapedRBSP: discardEmulationPreventionBytes$1(data),
  32231. trackId: trackId
  32232. };
  32233. if (matchingSample) {
  32234. seiNal.pts = matchingSample.pts;
  32235. seiNal.dts = matchingSample.dts;
  32236. lastMatchedSample = matchingSample;
  32237. } else {
  32238. // If a matching sample cannot be found, use the last
  32239. // sample's values as they should be as close as possible
  32240. seiNal.pts = lastMatchedSample.pts;
  32241. seiNal.dts = lastMatchedSample.dts;
  32242. }
  32243. result.push(seiNal);
  32244. break;
  32245. default:
  32246. break;
  32247. }
  32248. }
  32249. return result;
  32250. };
  32251. /**
  32252. * Parses sample information out of Track Run Boxes and calculates
  32253. * the absolute presentation and decode timestamps of each sample.
  32254. *
  32255. * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
  32256. * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
  32257. @see ISO-BMFF-12/2015, Section 8.8.12
  32258. * @param {Object} tfhd - The parsed Track Fragment Header
  32259. * @see inspect.parseTfhd
  32260. * @return {Object[]} the parsed samples
  32261. *
  32262. * @see ISO-BMFF-12/2015, Section 8.8.8
  32263. **/
  32264. var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
  32265. var currentDts = baseMediaDecodeTime;
  32266. var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
  32267. var defaultSampleSize = tfhd.defaultSampleSize || 0;
  32268. var trackId = tfhd.trackId;
  32269. var allSamples = [];
  32270. truns.forEach(function (trun) {
  32271. // Note: We currently do not parse the sample table as well
  32272. // as the trun. It's possible some sources will require this.
  32273. // moov > trak > mdia > minf > stbl
  32274. var trackRun = mp4Inspector.parseTrun(trun);
  32275. var samples = trackRun.samples;
  32276. samples.forEach(function (sample) {
  32277. if (sample.duration === undefined) {
  32278. sample.duration = defaultSampleDuration;
  32279. }
  32280. if (sample.size === undefined) {
  32281. sample.size = defaultSampleSize;
  32282. }
  32283. sample.trackId = trackId;
  32284. sample.dts = currentDts;
  32285. if (sample.compositionTimeOffset === undefined) {
  32286. sample.compositionTimeOffset = 0;
  32287. }
  32288. sample.pts = currentDts + sample.compositionTimeOffset;
  32289. currentDts += sample.duration;
  32290. });
  32291. allSamples = allSamples.concat(samples);
  32292. });
  32293. return allSamples;
  32294. };
  32295. /**
  32296. * Parses out caption nals from an FMP4 segment's video tracks.
  32297. *
  32298. * @param {Uint8Array} segment - The bytes of a single segment
  32299. * @param {Number} videoTrackId - The trackId of a video track in the segment
  32300. * @return {Object.<Number, Object[]>} A mapping of video trackId to
  32301. * a list of seiNals found in that track
  32302. **/
  32303. var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
  32304. // To get the samples
  32305. var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units
  32306. var mdats = probe.findBox(segment, ['mdat']);
  32307. var captionNals = {};
  32308. var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
  32309. mdats.forEach(function (mdat, index) {
  32310. var matchingTraf = trafs[index];
  32311. mdatTrafPairs.push({
  32312. mdat: mdat,
  32313. traf: matchingTraf
  32314. });
  32315. });
  32316. mdatTrafPairs.forEach(function (pair) {
  32317. var mdat = pair.mdat;
  32318. var traf = pair.traf;
  32319. var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
  32320. var headerInfo = mp4Inspector.parseTfhd(tfhd[0]);
  32321. var trackId = headerInfo.trackId;
  32322. var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
  32323. var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
  32324. var truns = probe.findBox(traf, ['trun']);
  32325. var samples;
  32326. var seiNals; // Only parse video data for the chosen video track
  32327. if (videoTrackId === trackId && truns.length > 0) {
  32328. samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
  32329. seiNals = findSeiNals(mdat, samples, trackId);
  32330. if (!captionNals[trackId]) {
  32331. captionNals[trackId] = [];
  32332. }
  32333. captionNals[trackId] = captionNals[trackId].concat(seiNals);
  32334. }
  32335. });
  32336. return captionNals;
  32337. };
  32338. /**
  32339. * Parses out inband captions from an MP4 container and returns
  32340. * caption objects that can be used by WebVTT and the TextTrack API.
  32341. * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
  32342. * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
  32343. * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
  32344. *
  32345. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  32346. * @param {Number} trackId - The id of the video track to parse
  32347. * @param {Number} timescale - The timescale for the video track from the init segment
  32348. *
  32349. * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
  32350. * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
  32351. * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
  32352. * @return {String} parsedCaptions[].text - The visible content of the caption
  32353. **/
  32354. var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
  32355. var seiNals;
  32356. if (!trackId) {
  32357. return null;
  32358. }
  32359. seiNals = parseCaptionNals(segment, trackId);
  32360. return {
  32361. seiNals: seiNals[trackId],
  32362. timescale: timescale
  32363. };
  32364. };
  32365. /**
  32366. * Converts SEI NALUs into captions that can be used by video.js
  32367. **/
  32368. var CaptionParser = function CaptionParser() {
  32369. var isInitialized = false;
  32370. var captionStream$$1; // Stores segments seen before trackId and timescale are set
  32371. var segmentCache; // Stores video track ID of the track being parsed
  32372. var trackId; // Stores the timescale of the track being parsed
  32373. var timescale; // Stores captions parsed so far
  32374. var parsedCaptions;
  32375. /**
  32376. * A method to indicate whether a CaptionParser has been initalized
  32377. * @returns {Boolean}
  32378. **/
  32379. this.isInitialized = function () {
  32380. return isInitialized;
  32381. };
  32382. /**
  32383. * Initializes the underlying CaptionStream, SEI NAL parsing
  32384. * and management, and caption collection
  32385. **/
  32386. this.init = function () {
  32387. captionStream$$1 = new CaptionStream$1();
  32388. isInitialized = true; // Collect dispatched captions
  32389. captionStream$$1.on('data', function (event) {
  32390. // Convert to seconds in the source's timescale
  32391. event.startTime = event.startPts / timescale;
  32392. event.endTime = event.endPts / timescale;
  32393. parsedCaptions.captions.push(event);
  32394. parsedCaptions.captionStreams[event.stream] = true;
  32395. });
  32396. };
  32397. /**
  32398. * Determines if a new video track will be selected
  32399. * or if the timescale changed
  32400. * @return {Boolean}
  32401. **/
  32402. this.isNewInit = function (videoTrackIds, timescales) {
  32403. if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
  32404. return false;
  32405. }
  32406. return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
  32407. };
  32408. /**
  32409. * Parses out SEI captions and interacts with underlying
  32410. * CaptionStream to return dispatched captions
  32411. *
  32412. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  32413. * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
  32414. * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
  32415. * @see parseEmbeddedCaptions
  32416. * @see m2ts/caption-stream.js
  32417. **/
  32418. this.parse = function (segment, videoTrackIds, timescales) {
  32419. var parsedData;
  32420. if (!this.isInitialized()) {
  32421. return null; // This is not likely to be a video segment
  32422. } else if (!videoTrackIds || !timescales) {
  32423. return null;
  32424. } else if (this.isNewInit(videoTrackIds, timescales)) {
  32425. // Use the first video track only as there is no
  32426. // mechanism to switch to other video tracks
  32427. trackId = videoTrackIds[0];
  32428. timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
  32429. // data until we have one
  32430. } else if (!trackId || !timescale) {
  32431. segmentCache.push(segment);
  32432. return null;
  32433. } // Now that a timescale and trackId is set, parse cached segments
  32434. while (segmentCache.length > 0) {
  32435. var cachedSegment = segmentCache.shift();
  32436. this.parse(cachedSegment, videoTrackIds, timescales);
  32437. }
  32438. parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
  32439. if (parsedData === null || !parsedData.seiNals) {
  32440. return null;
  32441. }
  32442. this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
  32443. this.flushStream();
  32444. return parsedCaptions;
  32445. };
  32446. /**
  32447. * Pushes SEI NALUs onto CaptionStream
  32448. * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
  32449. * Assumes that `parseCaptionNals` has been called first
  32450. * @see m2ts/caption-stream.js
  32451. **/
  32452. this.pushNals = function (nals) {
  32453. if (!this.isInitialized() || !nals || nals.length === 0) {
  32454. return null;
  32455. }
  32456. nals.forEach(function (nal) {
  32457. captionStream$$1.push(nal);
  32458. });
  32459. };
  32460. /**
  32461. * Flushes underlying CaptionStream to dispatch processed, displayable captions
  32462. * @see m2ts/caption-stream.js
  32463. **/
  32464. this.flushStream = function () {
  32465. if (!this.isInitialized()) {
  32466. return null;
  32467. }
  32468. captionStream$$1.flush();
  32469. };
  32470. /**
  32471. * Reset caption buckets for new data
  32472. **/
  32473. this.clearParsedCaptions = function () {
  32474. parsedCaptions.captions = [];
  32475. parsedCaptions.captionStreams = {};
  32476. };
  32477. /**
  32478. * Resets underlying CaptionStream
  32479. * @see m2ts/caption-stream.js
  32480. **/
  32481. this.resetCaptionStream = function () {
  32482. if (!this.isInitialized()) {
  32483. return null;
  32484. }
  32485. captionStream$$1.reset();
  32486. };
  32487. /**
  32488. * Convenience method to clear all captions flushed from the
  32489. * CaptionStream and still being parsed
  32490. * @see m2ts/caption-stream.js
  32491. **/
  32492. this.clearAllCaptions = function () {
  32493. this.clearParsedCaptions();
  32494. this.resetCaptionStream();
  32495. };
  32496. /**
  32497. * Reset caption parser
  32498. **/
  32499. this.reset = function () {
  32500. segmentCache = [];
  32501. trackId = null;
  32502. timescale = null;
  32503. if (!parsedCaptions) {
  32504. parsedCaptions = {
  32505. captions: [],
  32506. // CC1, CC2, CC3, CC4
  32507. captionStreams: {}
  32508. };
  32509. } else {
  32510. this.clearParsedCaptions();
  32511. }
  32512. this.resetCaptionStream();
  32513. };
  32514. this.reset();
  32515. };
  32516. var captionParser = CaptionParser;
  32517. var mp4 = {
  32518. generator: mp4Generator,
  32519. probe: probe,
  32520. Transmuxer: transmuxer.Transmuxer,
  32521. AudioSegmentStream: transmuxer.AudioSegmentStream,
  32522. VideoSegmentStream: transmuxer.VideoSegmentStream,
  32523. CaptionParser: captionParser
  32524. };
  32525. var mp4_6 = mp4.CaptionParser;
  32526. var parsePid = function parsePid(packet) {
  32527. var pid = packet[1] & 0x1f;
  32528. pid <<= 8;
  32529. pid |= packet[2];
  32530. return pid;
  32531. };
  32532. var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
  32533. return !!(packet[1] & 0x40);
  32534. };
  32535. var parseAdaptionField = function parseAdaptionField(packet) {
  32536. var offset = 0; // if an adaption field is present, its length is specified by the
  32537. // fifth byte of the TS packet header. The adaptation field is
  32538. // used to add stuffing to PES packets that don't fill a complete
  32539. // TS packet, and to specify some forms of timing and control data
  32540. // that we do not currently use.
  32541. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  32542. offset += packet[4] + 1;
  32543. }
  32544. return offset;
  32545. };
  32546. var parseType$3 = function parseType(packet, pmtPid) {
  32547. var pid = parsePid(packet);
  32548. if (pid === 0) {
  32549. return 'pat';
  32550. } else if (pid === pmtPid) {
  32551. return 'pmt';
  32552. } else if (pmtPid) {
  32553. return 'pes';
  32554. }
  32555. return null;
  32556. };
  32557. var parsePat = function parsePat(packet) {
  32558. var pusi = parsePayloadUnitStartIndicator(packet);
  32559. var offset = 4 + parseAdaptionField(packet);
  32560. if (pusi) {
  32561. offset += packet[offset] + 1;
  32562. }
  32563. return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
  32564. };
  32565. var parsePmt = function parsePmt(packet) {
  32566. var programMapTable = {};
  32567. var pusi = parsePayloadUnitStartIndicator(packet);
  32568. var payloadOffset = 4 + parseAdaptionField(packet);
  32569. if (pusi) {
  32570. payloadOffset += packet[payloadOffset] + 1;
  32571. } // PMTs can be sent ahead of the time when they should actually
  32572. // take effect. We don't believe this should ever be the case
  32573. // for HLS but we'll ignore "forward" PMT declarations if we see
  32574. // them. Future PMT declarations have the current_next_indicator
  32575. // set to zero.
  32576. if (!(packet[payloadOffset + 5] & 0x01)) {
  32577. return;
  32578. }
  32579. var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
  32580. sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
  32581. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  32582. // long the program info descriptors are
  32583. programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
  32584. var offset = 12 + programInfoLength;
  32585. while (offset < tableEnd) {
  32586. var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
  32587. programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
  32588. // skip past the elementary stream descriptors, if present
  32589. offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
  32590. }
  32591. return programMapTable;
  32592. };
  32593. var parsePesType = function parsePesType(packet, programMapTable) {
  32594. var pid = parsePid(packet);
  32595. var type = programMapTable[pid];
  32596. switch (type) {
  32597. case streamTypes.H264_STREAM_TYPE:
  32598. return 'video';
  32599. case streamTypes.ADTS_STREAM_TYPE:
  32600. return 'audio';
  32601. case streamTypes.METADATA_STREAM_TYPE:
  32602. return 'timed-metadata';
  32603. default:
  32604. return null;
  32605. }
  32606. };
  32607. var parsePesTime = function parsePesTime(packet) {
  32608. var pusi = parsePayloadUnitStartIndicator(packet);
  32609. if (!pusi) {
  32610. return null;
  32611. }
  32612. var offset = 4 + parseAdaptionField(packet);
  32613. if (offset >= packet.byteLength) {
  32614. // From the H 222.0 MPEG-TS spec
  32615. // "For transport stream packets carrying PES packets, stuffing is needed when there
  32616. // is insufficient PES packet data to completely fill the transport stream packet
  32617. // payload bytes. Stuffing is accomplished by defining an adaptation field longer than
  32618. // the sum of the lengths of the data elements in it, so that the payload bytes
  32619. // remaining after the adaptation field exactly accommodates the available PES packet
  32620. // data."
  32621. //
  32622. // If the offset is >= the length of the packet, then the packet contains no data
  32623. // and instead is just adaption field stuffing bytes
  32624. return null;
  32625. }
  32626. var pes = null;
  32627. var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
  32628. // and a DTS value. Determine what combination of values is
  32629. // available to work with.
  32630. ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  32631. // performs all bitwise operations on 32-bit integers but javascript
  32632. // supports a much greater range (52-bits) of integer using standard
  32633. // mathematical operations.
  32634. // We construct a 31-bit value using bitwise operators over the 31
  32635. // most significant bits and then multiply by 4 (equal to a left-shift
  32636. // of 2) before we add the final 2 least significant bits of the
  32637. // timestamp (equal to an OR.)
  32638. if (ptsDtsFlags & 0xC0) {
  32639. pes = {}; // the PTS and DTS are not written out directly. For information
  32640. // on how they are encoded, see
  32641. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  32642. 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;
  32643. pes.pts *= 4; // Left shift by 2
  32644. pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
  32645. pes.dts = pes.pts;
  32646. if (ptsDtsFlags & 0x40) {
  32647. 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;
  32648. pes.dts *= 4; // Left shift by 2
  32649. pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
  32650. }
  32651. }
  32652. return pes;
  32653. };
  32654. var parseNalUnitType = function parseNalUnitType(type) {
  32655. switch (type) {
  32656. case 0x05:
  32657. return 'slice_layer_without_partitioning_rbsp_idr';
  32658. case 0x06:
  32659. return 'sei_rbsp';
  32660. case 0x07:
  32661. return 'seq_parameter_set_rbsp';
  32662. case 0x08:
  32663. return 'pic_parameter_set_rbsp';
  32664. case 0x09:
  32665. return 'access_unit_delimiter_rbsp';
  32666. default:
  32667. return null;
  32668. }
  32669. };
  32670. var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
  32671. var offset = 4 + parseAdaptionField(packet);
  32672. var frameBuffer = packet.subarray(offset);
  32673. var frameI = 0;
  32674. var frameSyncPoint = 0;
  32675. var foundKeyFrame = false;
  32676. var nalType; // advance the sync point to a NAL start, if necessary
  32677. for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
  32678. if (frameBuffer[frameSyncPoint + 2] === 1) {
  32679. // the sync point is properly aligned
  32680. frameI = frameSyncPoint + 5;
  32681. break;
  32682. }
  32683. }
  32684. while (frameI < frameBuffer.byteLength) {
  32685. // look at the current byte to determine if we've hit the end of
  32686. // a NAL unit boundary
  32687. switch (frameBuffer[frameI]) {
  32688. case 0:
  32689. // skip past non-sync sequences
  32690. if (frameBuffer[frameI - 1] !== 0) {
  32691. frameI += 2;
  32692. break;
  32693. } else if (frameBuffer[frameI - 2] !== 0) {
  32694. frameI++;
  32695. break;
  32696. }
  32697. if (frameSyncPoint + 3 !== frameI - 2) {
  32698. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  32699. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  32700. foundKeyFrame = true;
  32701. }
  32702. } // drop trailing zeroes
  32703. do {
  32704. frameI++;
  32705. } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
  32706. frameSyncPoint = frameI - 2;
  32707. frameI += 3;
  32708. break;
  32709. case 1:
  32710. // skip past non-sync sequences
  32711. if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
  32712. frameI += 3;
  32713. break;
  32714. }
  32715. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  32716. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  32717. foundKeyFrame = true;
  32718. }
  32719. frameSyncPoint = frameI - 2;
  32720. frameI += 3;
  32721. break;
  32722. default:
  32723. // the current byte isn't a one or zero, so it cannot be part
  32724. // of a sync sequence
  32725. frameI += 3;
  32726. break;
  32727. }
  32728. }
  32729. frameBuffer = frameBuffer.subarray(frameSyncPoint);
  32730. frameI -= frameSyncPoint;
  32731. frameSyncPoint = 0; // parse the final nal
  32732. if (frameBuffer && frameBuffer.byteLength > 3) {
  32733. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  32734. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  32735. foundKeyFrame = true;
  32736. }
  32737. }
  32738. return foundKeyFrame;
  32739. };
  32740. var probe$1 = {
  32741. parseType: parseType$3,
  32742. parsePat: parsePat,
  32743. parsePmt: parsePmt,
  32744. parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
  32745. parsePesType: parsePesType,
  32746. parsePesTime: parsePesTime,
  32747. videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
  32748. };
  32749. var handleRollover$1 = timestampRolloverStream.handleRollover;
  32750. var probe$2 = {};
  32751. probe$2.ts = probe$1;
  32752. probe$2.aac = utils;
  32753. var PES_TIMESCALE = 90000,
  32754. MP2T_PACKET_LENGTH$1 = 188,
  32755. // bytes
  32756. SYNC_BYTE$1 = 0x47;
  32757. /**
  32758. * walks through segment data looking for pat and pmt packets to parse out
  32759. * program map table information
  32760. */
  32761. var parsePsi_ = function parsePsi_(bytes, pmt) {
  32762. var startIndex = 0,
  32763. endIndex = MP2T_PACKET_LENGTH$1,
  32764. packet,
  32765. type;
  32766. while (endIndex < bytes.byteLength) {
  32767. // Look for a pair of start and end sync bytes in the data..
  32768. if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
  32769. // We found a packet
  32770. packet = bytes.subarray(startIndex, endIndex);
  32771. type = probe$2.ts.parseType(packet, pmt.pid);
  32772. switch (type) {
  32773. case 'pat':
  32774. if (!pmt.pid) {
  32775. pmt.pid = probe$2.ts.parsePat(packet);
  32776. }
  32777. break;
  32778. case 'pmt':
  32779. if (!pmt.table) {
  32780. pmt.table = probe$2.ts.parsePmt(packet);
  32781. }
  32782. break;
  32783. default:
  32784. break;
  32785. } // Found the pat and pmt, we can stop walking the segment
  32786. if (pmt.pid && pmt.table) {
  32787. return;
  32788. }
  32789. startIndex += MP2T_PACKET_LENGTH$1;
  32790. endIndex += MP2T_PACKET_LENGTH$1;
  32791. continue;
  32792. } // If we get here, we have somehow become de-synchronized and we need to step
  32793. // forward one byte at a time until we find a pair of sync bytes that denote
  32794. // a packet
  32795. startIndex++;
  32796. endIndex++;
  32797. }
  32798. };
  32799. /**
  32800. * walks through the segment data from the start and end to get timing information
  32801. * for the first and last audio pes packets
  32802. */
  32803. var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) {
  32804. var startIndex = 0,
  32805. endIndex = MP2T_PACKET_LENGTH$1,
  32806. packet,
  32807. type,
  32808. pesType,
  32809. pusi,
  32810. parsed;
  32811. var endLoop = false; // Start walking from start of segment to get first audio packet
  32812. while (endIndex <= bytes.byteLength) {
  32813. // Look for a pair of start and end sync bytes in the data..
  32814. if (bytes[startIndex] === SYNC_BYTE$1 && (bytes[endIndex] === SYNC_BYTE$1 || endIndex === bytes.byteLength)) {
  32815. // We found a packet
  32816. packet = bytes.subarray(startIndex, endIndex);
  32817. type = probe$2.ts.parseType(packet, pmt.pid);
  32818. switch (type) {
  32819. case 'pes':
  32820. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  32821. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  32822. if (pesType === 'audio' && pusi) {
  32823. parsed = probe$2.ts.parsePesTime(packet);
  32824. if (parsed) {
  32825. parsed.type = 'audio';
  32826. result.audio.push(parsed);
  32827. endLoop = true;
  32828. }
  32829. }
  32830. break;
  32831. default:
  32832. break;
  32833. }
  32834. if (endLoop) {
  32835. break;
  32836. }
  32837. startIndex += MP2T_PACKET_LENGTH$1;
  32838. endIndex += MP2T_PACKET_LENGTH$1;
  32839. continue;
  32840. } // If we get here, we have somehow become de-synchronized and we need to step
  32841. // forward one byte at a time until we find a pair of sync bytes that denote
  32842. // a packet
  32843. startIndex++;
  32844. endIndex++;
  32845. } // Start walking from end of segment to get last audio packet
  32846. endIndex = bytes.byteLength;
  32847. startIndex = endIndex - MP2T_PACKET_LENGTH$1;
  32848. endLoop = false;
  32849. while (startIndex >= 0) {
  32850. // Look for a pair of start and end sync bytes in the data..
  32851. if (bytes[startIndex] === SYNC_BYTE$1 && (bytes[endIndex] === SYNC_BYTE$1 || endIndex === bytes.byteLength)) {
  32852. // We found a packet
  32853. packet = bytes.subarray(startIndex, endIndex);
  32854. type = probe$2.ts.parseType(packet, pmt.pid);
  32855. switch (type) {
  32856. case 'pes':
  32857. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  32858. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  32859. if (pesType === 'audio' && pusi) {
  32860. parsed = probe$2.ts.parsePesTime(packet);
  32861. if (parsed) {
  32862. parsed.type = 'audio';
  32863. result.audio.push(parsed);
  32864. endLoop = true;
  32865. }
  32866. }
  32867. break;
  32868. default:
  32869. break;
  32870. }
  32871. if (endLoop) {
  32872. break;
  32873. }
  32874. startIndex -= MP2T_PACKET_LENGTH$1;
  32875. endIndex -= MP2T_PACKET_LENGTH$1;
  32876. continue;
  32877. } // If we get here, we have somehow become de-synchronized and we need to step
  32878. // forward one byte at a time until we find a pair of sync bytes that denote
  32879. // a packet
  32880. startIndex--;
  32881. endIndex--;
  32882. }
  32883. };
  32884. /**
  32885. * walks through the segment data from the start and end to get timing information
  32886. * for the first and last video pes packets as well as timing information for the first
  32887. * key frame.
  32888. */
  32889. var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) {
  32890. var startIndex = 0,
  32891. endIndex = MP2T_PACKET_LENGTH$1,
  32892. packet,
  32893. type,
  32894. pesType,
  32895. pusi,
  32896. parsed,
  32897. frame,
  32898. i,
  32899. pes;
  32900. var endLoop = false;
  32901. var currentFrame = {
  32902. data: [],
  32903. size: 0
  32904. }; // Start walking from start of segment to get first video packet
  32905. while (endIndex < bytes.byteLength) {
  32906. // Look for a pair of start and end sync bytes in the data..
  32907. if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
  32908. // We found a packet
  32909. packet = bytes.subarray(startIndex, endIndex);
  32910. type = probe$2.ts.parseType(packet, pmt.pid);
  32911. switch (type) {
  32912. case 'pes':
  32913. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  32914. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  32915. if (pesType === 'video') {
  32916. if (pusi && !endLoop) {
  32917. parsed = probe$2.ts.parsePesTime(packet);
  32918. if (parsed) {
  32919. parsed.type = 'video';
  32920. result.video.push(parsed);
  32921. endLoop = true;
  32922. }
  32923. }
  32924. if (!result.firstKeyFrame) {
  32925. if (pusi) {
  32926. if (currentFrame.size !== 0) {
  32927. frame = new Uint8Array(currentFrame.size);
  32928. i = 0;
  32929. while (currentFrame.data.length) {
  32930. pes = currentFrame.data.shift();
  32931. frame.set(pes, i);
  32932. i += pes.byteLength;
  32933. }
  32934. if (probe$2.ts.videoPacketContainsKeyFrame(frame)) {
  32935. result.firstKeyFrame = probe$2.ts.parsePesTime(frame);
  32936. result.firstKeyFrame.type = 'video';
  32937. }
  32938. currentFrame.size = 0;
  32939. }
  32940. }
  32941. currentFrame.data.push(packet);
  32942. currentFrame.size += packet.byteLength;
  32943. }
  32944. }
  32945. break;
  32946. default:
  32947. break;
  32948. }
  32949. if (endLoop && result.firstKeyFrame) {
  32950. break;
  32951. }
  32952. startIndex += MP2T_PACKET_LENGTH$1;
  32953. endIndex += MP2T_PACKET_LENGTH$1;
  32954. continue;
  32955. } // If we get here, we have somehow become de-synchronized and we need to step
  32956. // forward one byte at a time until we find a pair of sync bytes that denote
  32957. // a packet
  32958. startIndex++;
  32959. endIndex++;
  32960. } // Start walking from end of segment to get last video packet
  32961. endIndex = bytes.byteLength;
  32962. startIndex = endIndex - MP2T_PACKET_LENGTH$1;
  32963. endLoop = false;
  32964. while (startIndex >= 0) {
  32965. // Look for a pair of start and end sync bytes in the data..
  32966. if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
  32967. // We found a packet
  32968. packet = bytes.subarray(startIndex, endIndex);
  32969. type = probe$2.ts.parseType(packet, pmt.pid);
  32970. switch (type) {
  32971. case 'pes':
  32972. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  32973. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  32974. if (pesType === 'video' && pusi) {
  32975. parsed = probe$2.ts.parsePesTime(packet);
  32976. if (parsed) {
  32977. parsed.type = 'video';
  32978. result.video.push(parsed);
  32979. endLoop = true;
  32980. }
  32981. }
  32982. break;
  32983. default:
  32984. break;
  32985. }
  32986. if (endLoop) {
  32987. break;
  32988. }
  32989. startIndex -= MP2T_PACKET_LENGTH$1;
  32990. endIndex -= MP2T_PACKET_LENGTH$1;
  32991. continue;
  32992. } // If we get here, we have somehow become de-synchronized and we need to step
  32993. // forward one byte at a time until we find a pair of sync bytes that denote
  32994. // a packet
  32995. startIndex--;
  32996. endIndex--;
  32997. }
  32998. };
  32999. /**
  33000. * Adjusts the timestamp information for the segment to account for
  33001. * rollover and convert to seconds based on pes packet timescale (90khz clock)
  33002. */
  33003. var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) {
  33004. if (segmentInfo.audio && segmentInfo.audio.length) {
  33005. var audioBaseTimestamp = baseTimestamp;
  33006. if (typeof audioBaseTimestamp === 'undefined') {
  33007. audioBaseTimestamp = segmentInfo.audio[0].dts;
  33008. }
  33009. segmentInfo.audio.forEach(function (info) {
  33010. info.dts = handleRollover$1(info.dts, audioBaseTimestamp);
  33011. info.pts = handleRollover$1(info.pts, audioBaseTimestamp); // time in seconds
  33012. info.dtsTime = info.dts / PES_TIMESCALE;
  33013. info.ptsTime = info.pts / PES_TIMESCALE;
  33014. });
  33015. }
  33016. if (segmentInfo.video && segmentInfo.video.length) {
  33017. var videoBaseTimestamp = baseTimestamp;
  33018. if (typeof videoBaseTimestamp === 'undefined') {
  33019. videoBaseTimestamp = segmentInfo.video[0].dts;
  33020. }
  33021. segmentInfo.video.forEach(function (info) {
  33022. info.dts = handleRollover$1(info.dts, videoBaseTimestamp);
  33023. info.pts = handleRollover$1(info.pts, videoBaseTimestamp); // time in seconds
  33024. info.dtsTime = info.dts / PES_TIMESCALE;
  33025. info.ptsTime = info.pts / PES_TIMESCALE;
  33026. });
  33027. if (segmentInfo.firstKeyFrame) {
  33028. var frame = segmentInfo.firstKeyFrame;
  33029. frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp);
  33030. frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); // time in seconds
  33031. frame.dtsTime = frame.dts / PES_TIMESCALE;
  33032. frame.ptsTime = frame.dts / PES_TIMESCALE;
  33033. }
  33034. }
  33035. };
  33036. /**
  33037. * inspects the aac data stream for start and end time information
  33038. */
  33039. var inspectAac_ = function inspectAac_(bytes) {
  33040. var endLoop = false,
  33041. audioCount = 0,
  33042. sampleRate = null,
  33043. timestamp = null,
  33044. frameSize = 0,
  33045. byteIndex = 0,
  33046. packet;
  33047. while (bytes.length - byteIndex >= 3) {
  33048. var type = probe$2.aac.parseType(bytes, byteIndex);
  33049. switch (type) {
  33050. case 'timed-metadata':
  33051. // Exit early because we don't have enough to parse
  33052. // the ID3 tag header
  33053. if (bytes.length - byteIndex < 10) {
  33054. endLoop = true;
  33055. break;
  33056. }
  33057. frameSize = probe$2.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
  33058. // to emit a full packet
  33059. if (frameSize > bytes.length) {
  33060. endLoop = true;
  33061. break;
  33062. }
  33063. if (timestamp === null) {
  33064. packet = bytes.subarray(byteIndex, byteIndex + frameSize);
  33065. timestamp = probe$2.aac.parseAacTimestamp(packet);
  33066. }
  33067. byteIndex += frameSize;
  33068. break;
  33069. case 'audio':
  33070. // Exit early because we don't have enough to parse
  33071. // the ADTS frame header
  33072. if (bytes.length - byteIndex < 7) {
  33073. endLoop = true;
  33074. break;
  33075. }
  33076. frameSize = probe$2.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
  33077. // to emit a full packet
  33078. if (frameSize > bytes.length) {
  33079. endLoop = true;
  33080. break;
  33081. }
  33082. if (sampleRate === null) {
  33083. packet = bytes.subarray(byteIndex, byteIndex + frameSize);
  33084. sampleRate = probe$2.aac.parseSampleRate(packet);
  33085. }
  33086. audioCount++;
  33087. byteIndex += frameSize;
  33088. break;
  33089. default:
  33090. byteIndex++;
  33091. break;
  33092. }
  33093. if (endLoop) {
  33094. return null;
  33095. }
  33096. }
  33097. if (sampleRate === null || timestamp === null) {
  33098. return null;
  33099. }
  33100. var audioTimescale = PES_TIMESCALE / sampleRate;
  33101. var result = {
  33102. audio: [{
  33103. type: 'audio',
  33104. dts: timestamp,
  33105. pts: timestamp
  33106. }, {
  33107. type: 'audio',
  33108. dts: timestamp + audioCount * 1024 * audioTimescale,
  33109. pts: timestamp + audioCount * 1024 * audioTimescale
  33110. }]
  33111. };
  33112. return result;
  33113. };
  33114. /**
  33115. * inspects the transport stream segment data for start and end time information
  33116. * of the audio and video tracks (when present) as well as the first key frame's
  33117. * start time.
  33118. */
  33119. var inspectTs_ = function inspectTs_(bytes) {
  33120. var pmt = {
  33121. pid: null,
  33122. table: null
  33123. };
  33124. var result = {};
  33125. parsePsi_(bytes, pmt);
  33126. for (var pid in pmt.table) {
  33127. if (pmt.table.hasOwnProperty(pid)) {
  33128. var type = pmt.table[pid];
  33129. switch (type) {
  33130. case streamTypes.H264_STREAM_TYPE:
  33131. result.video = [];
  33132. parseVideoPes_(bytes, pmt, result);
  33133. if (result.video.length === 0) {
  33134. delete result.video;
  33135. }
  33136. break;
  33137. case streamTypes.ADTS_STREAM_TYPE:
  33138. result.audio = [];
  33139. parseAudioPes_(bytes, pmt, result);
  33140. if (result.audio.length === 0) {
  33141. delete result.audio;
  33142. }
  33143. break;
  33144. default:
  33145. break;
  33146. }
  33147. }
  33148. }
  33149. return result;
  33150. };
  33151. /**
  33152. * Inspects segment byte data and returns an object with start and end timing information
  33153. *
  33154. * @param {Uint8Array} bytes The segment byte data
  33155. * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
  33156. * timestamps for rollover. This value must be in 90khz clock.
  33157. * @return {Object} Object containing start and end frame timing info of segment.
  33158. */
  33159. var inspect = function inspect(bytes, baseTimestamp) {
  33160. var isAacData = probe$2.aac.isLikelyAacData(bytes);
  33161. var result;
  33162. if (isAacData) {
  33163. result = inspectAac_(bytes);
  33164. } else {
  33165. result = inspectTs_(bytes);
  33166. }
  33167. if (!result || !result.audio && !result.video) {
  33168. return null;
  33169. }
  33170. adjustTimestamp_(result, baseTimestamp);
  33171. return result;
  33172. };
  33173. var tsInspector = {
  33174. inspect: inspect,
  33175. parseAudioPes_: parseAudioPes_
  33176. };
  33177. /*
  33178. * pkcs7.pad
  33179. * https://github.com/brightcove/pkcs7
  33180. *
  33181. * Copyright (c) 2014 Brightcove
  33182. * Licensed under the apache2 license.
  33183. */
  33184. /**
  33185. * Returns the subarray of a Uint8Array without PKCS#7 padding.
  33186. * @param padded {Uint8Array} unencrypted bytes that have been padded
  33187. * @return {Uint8Array} the unpadded bytes
  33188. * @see http://tools.ietf.org/html/rfc5652
  33189. */
  33190. function unpad(padded) {
  33191. return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
  33192. }
  33193. var classCallCheck = function classCallCheck(instance, Constructor) {
  33194. if (!(instance instanceof Constructor)) {
  33195. throw new TypeError("Cannot call a class as a function");
  33196. }
  33197. };
  33198. var createClass = function () {
  33199. function defineProperties(target, props) {
  33200. for (var i = 0; i < props.length; i++) {
  33201. var descriptor = props[i];
  33202. descriptor.enumerable = descriptor.enumerable || false;
  33203. descriptor.configurable = true;
  33204. if ("value" in descriptor) descriptor.writable = true;
  33205. Object.defineProperty(target, descriptor.key, descriptor);
  33206. }
  33207. }
  33208. return function (Constructor, protoProps, staticProps) {
  33209. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  33210. if (staticProps) defineProperties(Constructor, staticProps);
  33211. return Constructor;
  33212. };
  33213. }();
  33214. var inherits = function inherits(subClass, superClass) {
  33215. if (typeof superClass !== "function" && superClass !== null) {
  33216. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  33217. }
  33218. subClass.prototype = Object.create(superClass && superClass.prototype, {
  33219. constructor: {
  33220. value: subClass,
  33221. enumerable: false,
  33222. writable: true,
  33223. configurable: true
  33224. }
  33225. });
  33226. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  33227. };
  33228. var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
  33229. if (!self) {
  33230. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  33231. }
  33232. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  33233. };
  33234. /**
  33235. * @file aes.js
  33236. *
  33237. * This file contains an adaptation of the AES decryption algorithm
  33238. * from the Standford Javascript Cryptography Library. That work is
  33239. * covered by the following copyright and permissions notice:
  33240. *
  33241. * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
  33242. * All rights reserved.
  33243. *
  33244. * Redistribution and use in source and binary forms, with or without
  33245. * modification, are permitted provided that the following conditions are
  33246. * met:
  33247. *
  33248. * 1. Redistributions of source code must retain the above copyright
  33249. * notice, this list of conditions and the following disclaimer.
  33250. *
  33251. * 2. Redistributions in binary form must reproduce the above
  33252. * copyright notice, this list of conditions and the following
  33253. * disclaimer in the documentation and/or other materials provided
  33254. * with the distribution.
  33255. *
  33256. * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
  33257. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  33258. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  33259. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
  33260. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  33261. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  33262. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  33263. * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  33264. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  33265. * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
  33266. * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33267. *
  33268. * The views and conclusions contained in the software and documentation
  33269. * are those of the authors and should not be interpreted as representing
  33270. * official policies, either expressed or implied, of the authors.
  33271. */
  33272. /**
  33273. * Expand the S-box tables.
  33274. *
  33275. * @private
  33276. */
  33277. var precompute = function precompute() {
  33278. var tables = [[[], [], [], [], []], [[], [], [], [], []]];
  33279. var encTable = tables[0];
  33280. var decTable = tables[1];
  33281. var sbox = encTable[4];
  33282. var sboxInv = decTable[4];
  33283. var i = void 0;
  33284. var x = void 0;
  33285. var xInv = void 0;
  33286. var d = [];
  33287. var th = [];
  33288. var x2 = void 0;
  33289. var x4 = void 0;
  33290. var x8 = void 0;
  33291. var s = void 0;
  33292. var tEnc = void 0;
  33293. var tDec = void 0; // Compute double and third tables
  33294. for (i = 0; i < 256; i++) {
  33295. th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
  33296. }
  33297. for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
  33298. // Compute sbox
  33299. s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
  33300. s = s >> 8 ^ s & 255 ^ 99;
  33301. sbox[x] = s;
  33302. sboxInv[s] = x; // Compute MixColumns
  33303. x8 = d[x4 = d[x2 = d[x]]];
  33304. tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
  33305. tEnc = d[s] * 0x101 ^ s * 0x1010100;
  33306. for (i = 0; i < 4; i++) {
  33307. encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
  33308. decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
  33309. }
  33310. } // Compactify. Considerable speedup on Firefox.
  33311. for (i = 0; i < 5; i++) {
  33312. encTable[i] = encTable[i].slice(0);
  33313. decTable[i] = decTable[i].slice(0);
  33314. }
  33315. return tables;
  33316. };
  33317. var aesTables = null;
  33318. /**
  33319. * Schedule out an AES key for both encryption and decryption. This
  33320. * is a low-level class. Use a cipher mode to do bulk encryption.
  33321. *
  33322. * @class AES
  33323. * @param key {Array} The key as an array of 4, 6 or 8 words.
  33324. */
  33325. var AES = function () {
  33326. function AES(key) {
  33327. classCallCheck(this, AES);
  33328. /**
  33329. * The expanded S-box and inverse S-box tables. These will be computed
  33330. * on the client so that we don't have to send them down the wire.
  33331. *
  33332. * There are two tables, _tables[0] is for encryption and
  33333. * _tables[1] is for decryption.
  33334. *
  33335. * The first 4 sub-tables are the expanded S-box with MixColumns. The
  33336. * last (_tables[01][4]) is the S-box itself.
  33337. *
  33338. * @private
  33339. */
  33340. // if we have yet to precompute the S-box tables
  33341. // do so now
  33342. if (!aesTables) {
  33343. aesTables = precompute();
  33344. } // then make a copy of that object for use
  33345. 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()]];
  33346. var i = void 0;
  33347. var j = void 0;
  33348. var tmp = void 0;
  33349. var encKey = void 0;
  33350. var decKey = void 0;
  33351. var sbox = this._tables[0][4];
  33352. var decTable = this._tables[1];
  33353. var keyLen = key.length;
  33354. var rcon = 1;
  33355. if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
  33356. throw new Error('Invalid aes key size');
  33357. }
  33358. encKey = key.slice(0);
  33359. decKey = [];
  33360. this._key = [encKey, decKey]; // schedule encryption keys
  33361. for (i = keyLen; i < 4 * keyLen + 28; i++) {
  33362. tmp = encKey[i - 1]; // apply sbox
  33363. if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
  33364. tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
  33365. if (i % keyLen === 0) {
  33366. tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
  33367. rcon = rcon << 1 ^ (rcon >> 7) * 283;
  33368. }
  33369. }
  33370. encKey[i] = encKey[i - keyLen] ^ tmp;
  33371. } // schedule decryption keys
  33372. for (j = 0; i; j++, i--) {
  33373. tmp = encKey[j & 3 ? i : i - 4];
  33374. if (i <= 4 || j < 4) {
  33375. decKey[j] = tmp;
  33376. } else {
  33377. decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
  33378. }
  33379. }
  33380. }
  33381. /**
  33382. * Decrypt 16 bytes, specified as four 32-bit words.
  33383. *
  33384. * @param {Number} encrypted0 the first word to decrypt
  33385. * @param {Number} encrypted1 the second word to decrypt
  33386. * @param {Number} encrypted2 the third word to decrypt
  33387. * @param {Number} encrypted3 the fourth word to decrypt
  33388. * @param {Int32Array} out the array to write the decrypted words
  33389. * into
  33390. * @param {Number} offset the offset into the output array to start
  33391. * writing results
  33392. * @return {Array} The plaintext.
  33393. */
  33394. AES.prototype.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
  33395. var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
  33396. var a = encrypted0 ^ key[0];
  33397. var b = encrypted3 ^ key[1];
  33398. var c = encrypted2 ^ key[2];
  33399. var d = encrypted1 ^ key[3];
  33400. var a2 = void 0;
  33401. var b2 = void 0;
  33402. var c2 = void 0; // key.length === 2 ?
  33403. var nInnerRounds = key.length / 4 - 2;
  33404. var i = void 0;
  33405. var kIndex = 4;
  33406. var table = this._tables[1]; // load up the tables
  33407. var table0 = table[0];
  33408. var table1 = table[1];
  33409. var table2 = table[2];
  33410. var table3 = table[3];
  33411. var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
  33412. for (i = 0; i < nInnerRounds; i++) {
  33413. a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
  33414. b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
  33415. c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
  33416. d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
  33417. kIndex += 4;
  33418. a = a2;
  33419. b = b2;
  33420. c = c2;
  33421. } // Last round.
  33422. for (i = 0; i < 4; i++) {
  33423. out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
  33424. a2 = a;
  33425. a = b;
  33426. b = c;
  33427. c = d;
  33428. d = a2;
  33429. }
  33430. };
  33431. return AES;
  33432. }();
  33433. /**
  33434. * @file stream.js
  33435. */
  33436. /**
  33437. * A lightweight readable stream implemention that handles event dispatching.
  33438. *
  33439. * @class Stream
  33440. */
  33441. var Stream$2 = function () {
  33442. function Stream() {
  33443. classCallCheck(this, Stream);
  33444. this.listeners = {};
  33445. }
  33446. /**
  33447. * Add a listener for a specified event type.
  33448. *
  33449. * @param {String} type the event name
  33450. * @param {Function} listener the callback to be invoked when an event of
  33451. * the specified type occurs
  33452. */
  33453. Stream.prototype.on = function on(type, listener) {
  33454. if (!this.listeners[type]) {
  33455. this.listeners[type] = [];
  33456. }
  33457. this.listeners[type].push(listener);
  33458. };
  33459. /**
  33460. * Remove a listener for a specified event type.
  33461. *
  33462. * @param {String} type the event name
  33463. * @param {Function} listener a function previously registered for this
  33464. * type of event through `on`
  33465. * @return {Boolean} if we could turn it off or not
  33466. */
  33467. Stream.prototype.off = function off(type, listener) {
  33468. if (!this.listeners[type]) {
  33469. return false;
  33470. }
  33471. var index = this.listeners[type].indexOf(listener);
  33472. this.listeners[type].splice(index, 1);
  33473. return index > -1;
  33474. };
  33475. /**
  33476. * Trigger an event of the specified type on this stream. Any additional
  33477. * arguments to this function are passed as parameters to event listeners.
  33478. *
  33479. * @param {String} type the event name
  33480. */
  33481. Stream.prototype.trigger = function trigger(type) {
  33482. var callbacks = this.listeners[type];
  33483. if (!callbacks) {
  33484. return;
  33485. } // Slicing the arguments on every invocation of this method
  33486. // can add a significant amount of overhead. Avoid the
  33487. // intermediate object creation for the common case of a
  33488. // single callback argument
  33489. if (arguments.length === 2) {
  33490. var length = callbacks.length;
  33491. for (var i = 0; i < length; ++i) {
  33492. callbacks[i].call(this, arguments[1]);
  33493. }
  33494. } else {
  33495. var args = Array.prototype.slice.call(arguments, 1);
  33496. var _length = callbacks.length;
  33497. for (var _i = 0; _i < _length; ++_i) {
  33498. callbacks[_i].apply(this, args);
  33499. }
  33500. }
  33501. };
  33502. /**
  33503. * Destroys the stream and cleans up.
  33504. */
  33505. Stream.prototype.dispose = function dispose() {
  33506. this.listeners = {};
  33507. };
  33508. /**
  33509. * Forwards all `data` events on this stream to the destination stream. The
  33510. * destination stream should provide a method `push` to receive the data
  33511. * events as they arrive.
  33512. *
  33513. * @param {Stream} destination the stream that will receive all `data` events
  33514. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  33515. */
  33516. Stream.prototype.pipe = function pipe(destination) {
  33517. this.on('data', function (data) {
  33518. destination.push(data);
  33519. });
  33520. };
  33521. return Stream;
  33522. }();
  33523. /**
  33524. * @file async-stream.js
  33525. */
  33526. /**
  33527. * A wrapper around the Stream class to use setTiemout
  33528. * and run stream "jobs" Asynchronously
  33529. *
  33530. * @class AsyncStream
  33531. * @extends Stream
  33532. */
  33533. var AsyncStream = function (_Stream) {
  33534. inherits(AsyncStream, _Stream);
  33535. function AsyncStream() {
  33536. classCallCheck(this, AsyncStream);
  33537. var _this = possibleConstructorReturn(this, _Stream.call(this, Stream$2));
  33538. _this.jobs = [];
  33539. _this.delay = 1;
  33540. _this.timeout_ = null;
  33541. return _this;
  33542. }
  33543. /**
  33544. * process an async job
  33545. *
  33546. * @private
  33547. */
  33548. AsyncStream.prototype.processJob_ = function processJob_() {
  33549. this.jobs.shift()();
  33550. if (this.jobs.length) {
  33551. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  33552. } else {
  33553. this.timeout_ = null;
  33554. }
  33555. };
  33556. /**
  33557. * push a job into the stream
  33558. *
  33559. * @param {Function} job the job to push into the stream
  33560. */
  33561. AsyncStream.prototype.push = function push(job) {
  33562. this.jobs.push(job);
  33563. if (!this.timeout_) {
  33564. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  33565. }
  33566. };
  33567. return AsyncStream;
  33568. }(Stream$2);
  33569. /**
  33570. * @file decrypter.js
  33571. *
  33572. * An asynchronous implementation of AES-128 CBC decryption with
  33573. * PKCS#7 padding.
  33574. */
  33575. /**
  33576. * Convert network-order (big-endian) bytes into their little-endian
  33577. * representation.
  33578. */
  33579. var ntoh = function ntoh(word) {
  33580. return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
  33581. };
  33582. /**
  33583. * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
  33584. *
  33585. * @param {Uint8Array} encrypted the encrypted bytes
  33586. * @param {Uint32Array} key the bytes of the decryption key
  33587. * @param {Uint32Array} initVector the initialization vector (IV) to
  33588. * use for the first round of CBC.
  33589. * @return {Uint8Array} the decrypted bytes
  33590. *
  33591. * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
  33592. * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
  33593. * @see https://tools.ietf.org/html/rfc2315
  33594. */
  33595. var decrypt = function decrypt(encrypted, key, initVector) {
  33596. // word-level access to the encrypted bytes
  33597. var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
  33598. var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
  33599. var decrypted = new Uint8Array(encrypted.byteLength);
  33600. var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
  33601. // decrypted data
  33602. var init0 = void 0;
  33603. var init1 = void 0;
  33604. var init2 = void 0;
  33605. var init3 = void 0;
  33606. var encrypted0 = void 0;
  33607. var encrypted1 = void 0;
  33608. var encrypted2 = void 0;
  33609. var encrypted3 = void 0; // iteration variable
  33610. var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
  33611. // passed-in reference and easier access
  33612. init0 = initVector[0];
  33613. init1 = initVector[1];
  33614. init2 = initVector[2];
  33615. init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
  33616. // to each decrypted block
  33617. for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
  33618. // convert big-endian (network order) words into little-endian
  33619. // (javascript order)
  33620. encrypted0 = ntoh(encrypted32[wordIx]);
  33621. encrypted1 = ntoh(encrypted32[wordIx + 1]);
  33622. encrypted2 = ntoh(encrypted32[wordIx + 2]);
  33623. encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
  33624. decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
  33625. // plaintext
  33626. decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
  33627. decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
  33628. decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
  33629. decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
  33630. init0 = encrypted0;
  33631. init1 = encrypted1;
  33632. init2 = encrypted2;
  33633. init3 = encrypted3;
  33634. }
  33635. return decrypted;
  33636. };
  33637. /**
  33638. * The `Decrypter` class that manages decryption of AES
  33639. * data through `AsyncStream` objects and the `decrypt`
  33640. * function
  33641. *
  33642. * @param {Uint8Array} encrypted the encrypted bytes
  33643. * @param {Uint32Array} key the bytes of the decryption key
  33644. * @param {Uint32Array} initVector the initialization vector (IV) to
  33645. * @param {Function} done the function to run when done
  33646. * @class Decrypter
  33647. */
  33648. var Decrypter = function () {
  33649. function Decrypter(encrypted, key, initVector, done) {
  33650. classCallCheck(this, Decrypter);
  33651. var step = Decrypter.STEP;
  33652. var encrypted32 = new Int32Array(encrypted.buffer);
  33653. var decrypted = new Uint8Array(encrypted.byteLength);
  33654. var i = 0;
  33655. this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
  33656. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  33657. for (i = step; i < encrypted32.length; i += step) {
  33658. initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
  33659. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  33660. } // invoke the done() callback when everything is finished
  33661. this.asyncStream_.push(function () {
  33662. // remove pkcs#7 padding from the decrypted bytes
  33663. done(null, unpad(decrypted));
  33664. });
  33665. }
  33666. /**
  33667. * a getter for step the maximum number of bytes to process at one time
  33668. *
  33669. * @return {Number} the value of step 32000
  33670. */
  33671. /**
  33672. * @private
  33673. */
  33674. Decrypter.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
  33675. return function () {
  33676. var bytes = decrypt(encrypted, key, initVector);
  33677. decrypted.set(bytes, encrypted.byteOffset);
  33678. };
  33679. };
  33680. createClass(Decrypter, null, [{
  33681. key: 'STEP',
  33682. get: function get$$1() {
  33683. // 4 * 8000;
  33684. return 32000;
  33685. }
  33686. }]);
  33687. return Decrypter;
  33688. }();
  33689. /**
  33690. * @videojs/http-streaming
  33691. * @version 1.10.1
  33692. * @copyright 2019 Brightcove, Inc
  33693. * @license Apache-2.0
  33694. */
  33695. /**
  33696. * @file resolve-url.js - Handling how URLs are resolved and manipulated
  33697. */
  33698. var resolveUrl$1 = function resolveUrl(baseURL, relativeURL) {
  33699. // return early if we don't need to resolve
  33700. if (/^[a-z]+:/i.test(relativeURL)) {
  33701. return relativeURL;
  33702. } // if the base URL is relative then combine with the current location
  33703. if (!/\/\//i.test(baseURL)) {
  33704. baseURL = urlToolkit.buildAbsoluteURL(window$1.location.href, baseURL);
  33705. }
  33706. return urlToolkit.buildAbsoluteURL(baseURL, relativeURL);
  33707. };
  33708. /**
  33709. * Checks whether xhr request was redirected and returns correct url depending
  33710. * on `handleManifestRedirects` option
  33711. *
  33712. * @api private
  33713. *
  33714. * @param {String} url - an url being requested
  33715. * @param {XMLHttpRequest} req - xhr request result
  33716. *
  33717. * @return {String}
  33718. */
  33719. var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) {
  33720. // To understand how the responseURL below is set and generated:
  33721. // - https://fetch.spec.whatwg.org/#concept-response-url
  33722. // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
  33723. if (handleManifestRedirect && req.responseURL && url !== req.responseURL) {
  33724. return req.responseURL;
  33725. }
  33726. return url;
  33727. };
  33728. var classCallCheck$1 = function classCallCheck(instance, Constructor) {
  33729. if (!(instance instanceof Constructor)) {
  33730. throw new TypeError("Cannot call a class as a function");
  33731. }
  33732. };
  33733. var createClass$1 = function () {
  33734. function defineProperties(target, props) {
  33735. for (var i = 0; i < props.length; i++) {
  33736. var descriptor = props[i];
  33737. descriptor.enumerable = descriptor.enumerable || false;
  33738. descriptor.configurable = true;
  33739. if ("value" in descriptor) descriptor.writable = true;
  33740. Object.defineProperty(target, descriptor.key, descriptor);
  33741. }
  33742. }
  33743. return function (Constructor, protoProps, staticProps) {
  33744. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  33745. if (staticProps) defineProperties(Constructor, staticProps);
  33746. return Constructor;
  33747. };
  33748. }();
  33749. var get$1 = function get(object, property, receiver) {
  33750. if (object === null) object = Function.prototype;
  33751. var desc = Object.getOwnPropertyDescriptor(object, property);
  33752. if (desc === undefined) {
  33753. var parent = Object.getPrototypeOf(object);
  33754. if (parent === null) {
  33755. return undefined;
  33756. } else {
  33757. return get(parent, property, receiver);
  33758. }
  33759. } else if ("value" in desc) {
  33760. return desc.value;
  33761. } else {
  33762. var getter = desc.get;
  33763. if (getter === undefined) {
  33764. return undefined;
  33765. }
  33766. return getter.call(receiver);
  33767. }
  33768. };
  33769. var inherits$1 = function inherits(subClass, superClass) {
  33770. if (typeof superClass !== "function" && superClass !== null) {
  33771. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  33772. }
  33773. subClass.prototype = Object.create(superClass && superClass.prototype, {
  33774. constructor: {
  33775. value: subClass,
  33776. enumerable: false,
  33777. writable: true,
  33778. configurable: true
  33779. }
  33780. });
  33781. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  33782. };
  33783. var possibleConstructorReturn$1 = function possibleConstructorReturn(self, call) {
  33784. if (!self) {
  33785. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  33786. }
  33787. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  33788. };
  33789. var slicedToArray = function () {
  33790. function sliceIterator(arr, i) {
  33791. var _arr = [];
  33792. var _n = true;
  33793. var _d = false;
  33794. var _e = undefined;
  33795. try {
  33796. for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
  33797. _arr.push(_s.value);
  33798. if (i && _arr.length === i) break;
  33799. }
  33800. } catch (err) {
  33801. _d = true;
  33802. _e = err;
  33803. } finally {
  33804. try {
  33805. if (!_n && _i["return"]) _i["return"]();
  33806. } finally {
  33807. if (_d) throw _e;
  33808. }
  33809. }
  33810. return _arr;
  33811. }
  33812. return function (arr, i) {
  33813. if (Array.isArray(arr)) {
  33814. return arr;
  33815. } else if (Symbol.iterator in Object(arr)) {
  33816. return sliceIterator(arr, i);
  33817. } else {
  33818. throw new TypeError("Invalid attempt to destructure non-iterable instance");
  33819. }
  33820. };
  33821. }();
  33822. /**
  33823. * @file playlist-loader.js
  33824. *
  33825. * A state machine that manages the loading, caching, and updating of
  33826. * M3U8 playlists.
  33827. *
  33828. */
  33829. var mergeOptions$1 = videojs$1.mergeOptions,
  33830. EventTarget$1 = videojs$1.EventTarget,
  33831. log$1 = videojs$1.log;
  33832. /**
  33833. * Loops through all supported media groups in master and calls the provided
  33834. * callback for each group
  33835. *
  33836. * @param {Object} master
  33837. * The parsed master manifest object
  33838. * @param {Function} callback
  33839. * Callback to call for each media group
  33840. */
  33841. var forEachMediaGroup = function forEachMediaGroup(master, callback) {
  33842. ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
  33843. for (var groupKey in master.mediaGroups[mediaType]) {
  33844. for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
  33845. var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
  33846. callback(mediaProperties, mediaType, groupKey, labelKey);
  33847. }
  33848. }
  33849. });
  33850. };
  33851. /**
  33852. * Returns a new array of segments that is the result of merging
  33853. * properties from an older list of segments onto an updated
  33854. * list. No properties on the updated playlist will be overridden.
  33855. *
  33856. * @param {Array} original the outdated list of segments
  33857. * @param {Array} update the updated list of segments
  33858. * @param {Number=} offset the index of the first update
  33859. * segment in the original segment list. For non-live playlists,
  33860. * this should always be zero and does not need to be
  33861. * specified. For live playlists, it should be the difference
  33862. * between the media sequence numbers in the original and updated
  33863. * playlists.
  33864. * @return a list of merged segment objects
  33865. */
  33866. var updateSegments = function updateSegments(original, update, offset) {
  33867. var result = update.slice();
  33868. offset = offset || 0;
  33869. var length = Math.min(original.length, update.length + offset);
  33870. for (var i = offset; i < length; i++) {
  33871. result[i - offset] = mergeOptions$1(original[i], result[i - offset]);
  33872. }
  33873. return result;
  33874. };
  33875. var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
  33876. if (!segment.resolvedUri) {
  33877. segment.resolvedUri = resolveUrl$1(baseUri, segment.uri);
  33878. }
  33879. if (segment.key && !segment.key.resolvedUri) {
  33880. segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri);
  33881. }
  33882. if (segment.map && !segment.map.resolvedUri) {
  33883. segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri);
  33884. }
  33885. };
  33886. /**
  33887. * Returns a new master playlist that is the result of merging an
  33888. * updated media playlist into the original version. If the
  33889. * updated media playlist does not match any of the playlist
  33890. * entries in the original master playlist, null is returned.
  33891. *
  33892. * @param {Object} master a parsed master M3U8 object
  33893. * @param {Object} media a parsed media M3U8 object
  33894. * @return {Object} a new object that represents the original
  33895. * master playlist with the updated media playlist merged in, or
  33896. * null if the merge produced no change.
  33897. */
  33898. var updateMaster = function updateMaster(master, media) {
  33899. var result = mergeOptions$1(master, {});
  33900. var playlist = result.playlists[media.uri];
  33901. if (!playlist) {
  33902. return null;
  33903. } // consider the playlist unchanged if the number of segments is equal, the media
  33904. // sequence number is unchanged, and this playlist hasn't become the end of the playlist
  33905. if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) {
  33906. return null;
  33907. }
  33908. var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists
  33909. if (playlist.segments) {
  33910. mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
  33911. } // resolve any segment URIs to prevent us from having to do it later
  33912. mergedPlaylist.segments.forEach(function (segment) {
  33913. resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
  33914. }); // TODO Right now in the playlists array there are two references to each playlist, one
  33915. // that is referenced by index, and one by URI. The index reference may no longer be
  33916. // necessary.
  33917. for (var i = 0; i < result.playlists.length; i++) {
  33918. if (result.playlists[i].uri === media.uri) {
  33919. result.playlists[i] = mergedPlaylist;
  33920. }
  33921. }
  33922. result.playlists[media.uri] = mergedPlaylist;
  33923. return result;
  33924. };
  33925. var setupMediaPlaylists = function setupMediaPlaylists(master) {
  33926. // setup by-URI lookups and resolve media playlist URIs
  33927. var i = master.playlists.length;
  33928. while (i--) {
  33929. var playlist = master.playlists[i];
  33930. master.playlists[playlist.uri] = playlist;
  33931. playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri);
  33932. playlist.id = i;
  33933. if (!playlist.attributes) {
  33934. // Although the spec states an #EXT-X-STREAM-INF tag MUST have a
  33935. // BANDWIDTH attribute, we can play the stream without it. This means a poorly
  33936. // formatted master playlist may not have an attribute list. An attributes
  33937. // property is added here to prevent undefined references when we encounter
  33938. // this scenario.
  33939. playlist.attributes = {};
  33940. log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
  33941. }
  33942. }
  33943. };
  33944. var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
  33945. forEachMediaGroup(master, function (properties) {
  33946. if (properties.uri) {
  33947. properties.resolvedUri = resolveUrl$1(master.uri, properties.uri);
  33948. }
  33949. });
  33950. };
  33951. /**
  33952. * Calculates the time to wait before refreshing a live playlist
  33953. *
  33954. * @param {Object} media
  33955. * The current media
  33956. * @param {Boolean} update
  33957. * True if there were any updates from the last refresh, false otherwise
  33958. * @return {Number}
  33959. * The time in ms to wait before refreshing the live playlist
  33960. */
  33961. var refreshDelay = function refreshDelay(media, update) {
  33962. var lastSegment = media.segments[media.segments.length - 1];
  33963. var delay = void 0;
  33964. if (update && lastSegment && lastSegment.duration) {
  33965. delay = lastSegment.duration * 1000;
  33966. } else {
  33967. // if the playlist is unchanged since the last reload or last segment duration
  33968. // cannot be determined, try again after half the target duration
  33969. delay = (media.targetDuration || 10) * 500;
  33970. }
  33971. return delay;
  33972. };
  33973. /**
  33974. * Load a playlist from a remote location
  33975. *
  33976. * @class PlaylistLoader
  33977. * @extends Stream
  33978. * @param {String} srcUrl the url to start with
  33979. * @param {Boolean} withCredentials the withCredentials xhr option
  33980. * @constructor
  33981. */
  33982. var PlaylistLoader = function (_EventTarget) {
  33983. inherits$1(PlaylistLoader, _EventTarget);
  33984. function PlaylistLoader(srcUrl, hls) {
  33985. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  33986. classCallCheck$1(this, PlaylistLoader);
  33987. var _this = possibleConstructorReturn$1(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this));
  33988. var _options$withCredenti = options.withCredentials,
  33989. withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
  33990. _options$handleManife = options.handleManifestRedirects,
  33991. handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
  33992. _this.srcUrl = srcUrl;
  33993. _this.hls_ = hls;
  33994. _this.withCredentials = withCredentials;
  33995. _this.handleManifestRedirects = handleManifestRedirects;
  33996. var hlsOptions = hls.options_;
  33997. _this.customTagParsers = hlsOptions && hlsOptions.customTagParsers || [];
  33998. _this.customTagMappers = hlsOptions && hlsOptions.customTagMappers || [];
  33999. if (!_this.srcUrl) {
  34000. throw new Error('A non-empty playlist URL is required');
  34001. } // initialize the loader state
  34002. _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
  34003. _this.on('mediaupdatetimeout', function () {
  34004. if (_this.state !== 'HAVE_METADATA') {
  34005. // only refresh the media playlist if no other activity is going on
  34006. return;
  34007. }
  34008. _this.state = 'HAVE_CURRENT_METADATA';
  34009. _this.request = _this.hls_.xhr({
  34010. uri: resolveUrl$1(_this.master.uri, _this.media().uri),
  34011. withCredentials: _this.withCredentials
  34012. }, function (error, req) {
  34013. // disposed
  34014. if (!_this.request) {
  34015. return;
  34016. }
  34017. if (error) {
  34018. return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA');
  34019. }
  34020. _this.haveMetadata(_this.request, _this.media().uri);
  34021. });
  34022. });
  34023. return _this;
  34024. }
  34025. createClass$1(PlaylistLoader, [{
  34026. key: 'playlistRequestError',
  34027. value: function playlistRequestError(xhr, url, startingState) {
  34028. // any in-flight request is now finished
  34029. this.request = null;
  34030. if (startingState) {
  34031. this.state = startingState;
  34032. }
  34033. this.error = {
  34034. playlist: this.master.playlists[url],
  34035. status: xhr.status,
  34036. message: 'HLS playlist request error at URL: ' + url,
  34037. responseText: xhr.responseText,
  34038. code: xhr.status >= 500 ? 4 : 2
  34039. };
  34040. this.trigger('error');
  34041. } // update the playlist loader's state in response to a new or
  34042. // updated playlist.
  34043. }, {
  34044. key: 'haveMetadata',
  34045. value: function haveMetadata(xhr, url) {
  34046. var _this2 = this; // any in-flight request is now finished
  34047. this.request = null;
  34048. this.state = 'HAVE_METADATA';
  34049. var parser = new Parser(); // adding custom tag parsers
  34050. this.customTagParsers.forEach(function (customParser) {
  34051. return parser.addParser(customParser);
  34052. }); // adding custom tag mappers
  34053. this.customTagMappers.forEach(function (mapper) {
  34054. return parser.addTagMapper(mapper);
  34055. });
  34056. parser.push(xhr.responseText);
  34057. parser.end();
  34058. parser.manifest.uri = url; // m3u8-parser does not attach an attributes property to media playlists so make
  34059. // sure that the property is attached to avoid undefined reference errors
  34060. parser.manifest.attributes = parser.manifest.attributes || {}; // merge this playlist into the master
  34061. var update = updateMaster(this.master, parser.manifest);
  34062. this.targetDuration = parser.manifest.targetDuration;
  34063. if (update) {
  34064. this.master = update;
  34065. this.media_ = this.master.playlists[parser.manifest.uri];
  34066. } else {
  34067. this.trigger('playlistunchanged');
  34068. } // refresh live playlists after a target duration passes
  34069. if (!this.media().endList) {
  34070. window$1.clearTimeout(this.mediaUpdateTimeout);
  34071. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  34072. _this2.trigger('mediaupdatetimeout');
  34073. }, refreshDelay(this.media(), !!update));
  34074. }
  34075. this.trigger('loadedplaylist');
  34076. }
  34077. /**
  34078. * Abort any outstanding work and clean up.
  34079. */
  34080. }, {
  34081. key: 'dispose',
  34082. value: function dispose() {
  34083. this.stopRequest();
  34084. window$1.clearTimeout(this.mediaUpdateTimeout);
  34085. }
  34086. }, {
  34087. key: 'stopRequest',
  34088. value: function stopRequest() {
  34089. if (this.request) {
  34090. var oldRequest = this.request;
  34091. this.request = null;
  34092. oldRequest.onreadystatechange = null;
  34093. oldRequest.abort();
  34094. }
  34095. }
  34096. /**
  34097. * When called without any arguments, returns the currently
  34098. * active media playlist. When called with a single argument,
  34099. * triggers the playlist loader to asynchronously switch to the
  34100. * specified media playlist. Calling this method while the
  34101. * loader is in the HAVE_NOTHING causes an error to be emitted
  34102. * but otherwise has no effect.
  34103. *
  34104. * @param {Object=} playlist the parsed media playlist
  34105. * object to switch to
  34106. * @return {Playlist} the current loaded media
  34107. */
  34108. }, {
  34109. key: 'media',
  34110. value: function media(playlist) {
  34111. var _this3 = this; // getter
  34112. if (!playlist) {
  34113. return this.media_;
  34114. } // setter
  34115. if (this.state === 'HAVE_NOTHING') {
  34116. throw new Error('Cannot switch media playlist from ' + this.state);
  34117. }
  34118. var startingState = this.state; // find the playlist object if the target playlist has been
  34119. // specified by URI
  34120. if (typeof playlist === 'string') {
  34121. if (!this.master.playlists[playlist]) {
  34122. throw new Error('Unknown playlist URI: ' + playlist);
  34123. }
  34124. playlist = this.master.playlists[playlist];
  34125. }
  34126. var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to fully loaded playlists immediately
  34127. if (this.master.playlists[playlist.uri].endList) {
  34128. // abort outstanding playlist requests
  34129. if (this.request) {
  34130. this.request.onreadystatechange = null;
  34131. this.request.abort();
  34132. this.request = null;
  34133. }
  34134. this.state = 'HAVE_METADATA';
  34135. this.media_ = playlist; // trigger media change if the active media has been updated
  34136. if (mediaChange) {
  34137. this.trigger('mediachanging');
  34138. this.trigger('mediachange');
  34139. }
  34140. return;
  34141. } // switching to the active playlist is a no-op
  34142. if (!mediaChange) {
  34143. return;
  34144. }
  34145. this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
  34146. if (this.request) {
  34147. if (playlist.resolvedUri === this.request.url) {
  34148. // requesting to switch to the same playlist multiple times
  34149. // has no effect after the first
  34150. return;
  34151. }
  34152. this.request.onreadystatechange = null;
  34153. this.request.abort();
  34154. this.request = null;
  34155. } // request the new playlist
  34156. if (this.media_) {
  34157. this.trigger('mediachanging');
  34158. }
  34159. this.request = this.hls_.xhr({
  34160. uri: playlist.resolvedUri,
  34161. withCredentials: this.withCredentials
  34162. }, function (error, req) {
  34163. // disposed
  34164. if (!_this3.request) {
  34165. return;
  34166. }
  34167. playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req);
  34168. if (error) {
  34169. return _this3.playlistRequestError(_this3.request, playlist.uri, startingState);
  34170. }
  34171. _this3.haveMetadata(req, playlist.uri); // fire loadedmetadata the first time a media playlist is loaded
  34172. if (startingState === 'HAVE_MASTER') {
  34173. _this3.trigger('loadedmetadata');
  34174. } else {
  34175. _this3.trigger('mediachange');
  34176. }
  34177. });
  34178. }
  34179. /**
  34180. * pause loading of the playlist
  34181. */
  34182. }, {
  34183. key: 'pause',
  34184. value: function pause() {
  34185. this.stopRequest();
  34186. window$1.clearTimeout(this.mediaUpdateTimeout);
  34187. if (this.state === 'HAVE_NOTHING') {
  34188. // If we pause the loader before any data has been retrieved, its as if we never
  34189. // started, so reset to an unstarted state.
  34190. this.started = false;
  34191. } // Need to restore state now that no activity is happening
  34192. if (this.state === 'SWITCHING_MEDIA') {
  34193. // if the loader was in the process of switching media, it should either return to
  34194. // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
  34195. // playlist yet. This is determined by the existence of loader.media_
  34196. if (this.media_) {
  34197. this.state = 'HAVE_METADATA';
  34198. } else {
  34199. this.state = 'HAVE_MASTER';
  34200. }
  34201. } else if (this.state === 'HAVE_CURRENT_METADATA') {
  34202. this.state = 'HAVE_METADATA';
  34203. }
  34204. }
  34205. /**
  34206. * start loading of the playlist
  34207. */
  34208. }, {
  34209. key: 'load',
  34210. value: function load(isFinalRendition) {
  34211. var _this4 = this;
  34212. window$1.clearTimeout(this.mediaUpdateTimeout);
  34213. var media = this.media();
  34214. if (isFinalRendition) {
  34215. var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
  34216. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  34217. return _this4.load();
  34218. }, delay);
  34219. return;
  34220. }
  34221. if (!this.started) {
  34222. this.start();
  34223. return;
  34224. }
  34225. if (media && !media.endList) {
  34226. this.trigger('mediaupdatetimeout');
  34227. } else {
  34228. this.trigger('loadedplaylist');
  34229. }
  34230. }
  34231. /**
  34232. * start loading of the playlist
  34233. */
  34234. }, {
  34235. key: 'start',
  34236. value: function start() {
  34237. var _this5 = this;
  34238. this.started = true; // request the specified URL
  34239. this.request = this.hls_.xhr({
  34240. uri: this.srcUrl,
  34241. withCredentials: this.withCredentials
  34242. }, function (error, req) {
  34243. // disposed
  34244. if (!_this5.request) {
  34245. return;
  34246. } // clear the loader's request reference
  34247. _this5.request = null;
  34248. if (error) {
  34249. _this5.error = {
  34250. status: req.status,
  34251. message: 'HLS playlist request error at URL: ' + _this5.srcUrl,
  34252. responseText: req.responseText,
  34253. // MEDIA_ERR_NETWORK
  34254. code: 2
  34255. };
  34256. if (_this5.state === 'HAVE_NOTHING') {
  34257. _this5.started = false;
  34258. }
  34259. return _this5.trigger('error');
  34260. }
  34261. var parser = new Parser(); // adding custom tag parsers
  34262. _this5.customTagParsers.forEach(function (customParser) {
  34263. return parser.addParser(customParser);
  34264. }); // adding custom tag mappers
  34265. _this5.customTagMappers.forEach(function (mapper) {
  34266. return parser.addTagMapper(mapper);
  34267. });
  34268. parser.push(req.responseText);
  34269. parser.end();
  34270. _this5.state = 'HAVE_MASTER';
  34271. _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
  34272. parser.manifest.uri = _this5.srcUrl; // loaded a master playlist
  34273. if (parser.manifest.playlists) {
  34274. _this5.master = parser.manifest;
  34275. setupMediaPlaylists(_this5.master);
  34276. resolveMediaGroupUris(_this5.master);
  34277. _this5.trigger('loadedplaylist');
  34278. if (!_this5.request) {
  34279. // no media playlist was specifically selected so start
  34280. // from the first listed one
  34281. _this5.media(parser.manifest.playlists[0]);
  34282. }
  34283. return;
  34284. } // loaded a media playlist
  34285. // infer a master playlist if none was previously requested
  34286. _this5.master = {
  34287. mediaGroups: {
  34288. 'AUDIO': {},
  34289. 'VIDEO': {},
  34290. 'CLOSED-CAPTIONS': {},
  34291. 'SUBTITLES': {}
  34292. },
  34293. uri: window$1.location.href,
  34294. playlists: [{
  34295. uri: _this5.srcUrl,
  34296. id: 0,
  34297. resolvedUri: _this5.srcUrl,
  34298. // m3u8-parser does not attach an attributes property to media playlists so make
  34299. // sure that the property is attached to avoid undefined reference errors
  34300. attributes: {}
  34301. }]
  34302. };
  34303. _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0];
  34304. _this5.haveMetadata(req, _this5.srcUrl);
  34305. return _this5.trigger('loadedmetadata');
  34306. });
  34307. }
  34308. }]);
  34309. return PlaylistLoader;
  34310. }(EventTarget$1);
  34311. /**
  34312. * @file playlist.js
  34313. *
  34314. * Playlist related utilities.
  34315. */
  34316. var createTimeRange = videojs$1.createTimeRange;
  34317. /**
  34318. * walk backward until we find a duration we can use
  34319. * or return a failure
  34320. *
  34321. * @param {Playlist} playlist the playlist to walk through
  34322. * @param {Number} endSequence the mediaSequence to stop walking on
  34323. */
  34324. var backwardDuration = function backwardDuration(playlist, endSequence) {
  34325. var result = 0;
  34326. var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
  34327. // the interval, use it
  34328. var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
  34329. // information that is earlier than endSequence
  34330. if (segment) {
  34331. if (typeof segment.start !== 'undefined') {
  34332. return {
  34333. result: segment.start,
  34334. precise: true
  34335. };
  34336. }
  34337. if (typeof segment.end !== 'undefined') {
  34338. return {
  34339. result: segment.end - segment.duration,
  34340. precise: true
  34341. };
  34342. }
  34343. }
  34344. while (i--) {
  34345. segment = playlist.segments[i];
  34346. if (typeof segment.end !== 'undefined') {
  34347. return {
  34348. result: result + segment.end,
  34349. precise: true
  34350. };
  34351. }
  34352. result += segment.duration;
  34353. if (typeof segment.start !== 'undefined') {
  34354. return {
  34355. result: result + segment.start,
  34356. precise: true
  34357. };
  34358. }
  34359. }
  34360. return {
  34361. result: result,
  34362. precise: false
  34363. };
  34364. };
  34365. /**
  34366. * walk forward until we find a duration we can use
  34367. * or return a failure
  34368. *
  34369. * @param {Playlist} playlist the playlist to walk through
  34370. * @param {Number} endSequence the mediaSequence to stop walking on
  34371. */
  34372. var forwardDuration = function forwardDuration(playlist, endSequence) {
  34373. var result = 0;
  34374. var segment = void 0;
  34375. var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
  34376. // information
  34377. for (; i < playlist.segments.length; i++) {
  34378. segment = playlist.segments[i];
  34379. if (typeof segment.start !== 'undefined') {
  34380. return {
  34381. result: segment.start - result,
  34382. precise: true
  34383. };
  34384. }
  34385. result += segment.duration;
  34386. if (typeof segment.end !== 'undefined') {
  34387. return {
  34388. result: segment.end - result,
  34389. precise: true
  34390. };
  34391. }
  34392. } // indicate we didn't find a useful duration estimate
  34393. return {
  34394. result: -1,
  34395. precise: false
  34396. };
  34397. };
  34398. /**
  34399. * Calculate the media duration from the segments associated with a
  34400. * playlist. The duration of a subinterval of the available segments
  34401. * may be calculated by specifying an end index.
  34402. *
  34403. * @param {Object} playlist a media playlist object
  34404. * @param {Number=} endSequence an exclusive upper boundary
  34405. * for the playlist. Defaults to playlist length.
  34406. * @param {Number} expired the amount of time that has dropped
  34407. * off the front of the playlist in a live scenario
  34408. * @return {Number} the duration between the first available segment
  34409. * and end index.
  34410. */
  34411. var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
  34412. var backward = void 0;
  34413. var forward = void 0;
  34414. if (typeof endSequence === 'undefined') {
  34415. endSequence = playlist.mediaSequence + playlist.segments.length;
  34416. }
  34417. if (endSequence < playlist.mediaSequence) {
  34418. return 0;
  34419. } // do a backward walk to estimate the duration
  34420. backward = backwardDuration(playlist, endSequence);
  34421. if (backward.precise) {
  34422. // if we were able to base our duration estimate on timing
  34423. // information provided directly from the Media Source, return
  34424. // it
  34425. return backward.result;
  34426. } // walk forward to see if a precise duration estimate can be made
  34427. // that way
  34428. forward = forwardDuration(playlist, endSequence);
  34429. if (forward.precise) {
  34430. // we found a segment that has been buffered and so it's
  34431. // position is known precisely
  34432. return forward.result;
  34433. } // return the less-precise, playlist-based duration estimate
  34434. return backward.result + expired;
  34435. };
  34436. /**
  34437. * Calculates the duration of a playlist. If a start and end index
  34438. * are specified, the duration will be for the subset of the media
  34439. * timeline between those two indices. The total duration for live
  34440. * playlists is always Infinity.
  34441. *
  34442. * @param {Object} playlist a media playlist object
  34443. * @param {Number=} endSequence an exclusive upper
  34444. * boundary for the playlist. Defaults to the playlist media
  34445. * sequence number plus its length.
  34446. * @param {Number=} expired the amount of time that has
  34447. * dropped off the front of the playlist in a live scenario
  34448. * @return {Number} the duration between the start index and end
  34449. * index.
  34450. */
  34451. var duration = function duration(playlist, endSequence, expired) {
  34452. if (!playlist) {
  34453. return 0;
  34454. }
  34455. if (typeof expired !== 'number') {
  34456. expired = 0;
  34457. } // if a slice of the total duration is not requested, use
  34458. // playlist-level duration indicators when they're present
  34459. if (typeof endSequence === 'undefined') {
  34460. // if present, use the duration specified in the playlist
  34461. if (playlist.totalDuration) {
  34462. return playlist.totalDuration;
  34463. } // duration should be Infinity for live playlists
  34464. if (!playlist.endList) {
  34465. return window$1.Infinity;
  34466. }
  34467. } // calculate the total duration based on the segment durations
  34468. return intervalDuration(playlist, endSequence, expired);
  34469. };
  34470. /**
  34471. * Calculate the time between two indexes in the current playlist
  34472. * neight the start- nor the end-index need to be within the current
  34473. * playlist in which case, the targetDuration of the playlist is used
  34474. * to approximate the durations of the segments
  34475. *
  34476. * @param {Object} playlist a media playlist object
  34477. * @param {Number} startIndex
  34478. * @param {Number} endIndex
  34479. * @return {Number} the number of seconds between startIndex and endIndex
  34480. */
  34481. var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
  34482. var durations = 0;
  34483. if (startIndex > endIndex) {
  34484. var _ref = [endIndex, startIndex];
  34485. startIndex = _ref[0];
  34486. endIndex = _ref[1];
  34487. }
  34488. if (startIndex < 0) {
  34489. for (var i = startIndex; i < Math.min(0, endIndex); i++) {
  34490. durations += playlist.targetDuration;
  34491. }
  34492. startIndex = 0;
  34493. }
  34494. for (var _i = startIndex; _i < endIndex; _i++) {
  34495. durations += playlist.segments[_i].duration;
  34496. }
  34497. return durations;
  34498. };
  34499. /**
  34500. * Determines the media index of the segment corresponding to the safe edge of the live
  34501. * window which is the duration of the last segment plus 2 target durations from the end
  34502. * of the playlist.
  34503. *
  34504. * @param {Object} playlist
  34505. * a media playlist object
  34506. * @return {Number}
  34507. * The media index of the segment at the safe live point. 0 if there is no "safe"
  34508. * point.
  34509. * @function safeLiveIndex
  34510. */
  34511. var safeLiveIndex = function safeLiveIndex(playlist) {
  34512. if (!playlist.segments.length) {
  34513. return 0;
  34514. }
  34515. var i = playlist.segments.length - 1;
  34516. var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration;
  34517. var safeDistance = distanceFromEnd + playlist.targetDuration * 2;
  34518. while (i--) {
  34519. distanceFromEnd += playlist.segments[i].duration;
  34520. if (distanceFromEnd >= safeDistance) {
  34521. break;
  34522. }
  34523. }
  34524. return Math.max(0, i);
  34525. };
  34526. /**
  34527. * Calculates the playlist end time
  34528. *
  34529. * @param {Object} playlist a media playlist object
  34530. * @param {Number=} expired the amount of time that has
  34531. * dropped off the front of the playlist in a live scenario
  34532. * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
  34533. * playlist end calculation should consider the safe live end
  34534. * (truncate the playlist end by three segments). This is normally
  34535. * used for calculating the end of the playlist's seekable range.
  34536. * @returns {Number} the end time of playlist
  34537. * @function playlistEnd
  34538. */
  34539. var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) {
  34540. if (!playlist || !playlist.segments) {
  34541. return null;
  34542. }
  34543. if (playlist.endList) {
  34544. return duration(playlist);
  34545. }
  34546. if (expired === null) {
  34547. return null;
  34548. }
  34549. expired = expired || 0;
  34550. var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length;
  34551. return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
  34552. };
  34553. /**
  34554. * Calculates the interval of time that is currently seekable in a
  34555. * playlist. The returned time ranges are relative to the earliest
  34556. * moment in the specified playlist that is still available. A full
  34557. * seekable implementation for live streams would need to offset
  34558. * these values by the duration of content that has expired from the
  34559. * stream.
  34560. *
  34561. * @param {Object} playlist a media playlist object
  34562. * dropped off the front of the playlist in a live scenario
  34563. * @param {Number=} expired the amount of time that has
  34564. * dropped off the front of the playlist in a live scenario
  34565. * @return {TimeRanges} the periods of time that are valid targets
  34566. * for seeking
  34567. */
  34568. var seekable = function seekable(playlist, expired) {
  34569. var useSafeLiveEnd = true;
  34570. var seekableStart = expired || 0;
  34571. var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd);
  34572. if (seekableEnd === null) {
  34573. return createTimeRange();
  34574. }
  34575. return createTimeRange(seekableStart, seekableEnd);
  34576. };
  34577. var isWholeNumber = function isWholeNumber(num) {
  34578. return num - Math.floor(num) === 0;
  34579. };
  34580. var roundSignificantDigit = function roundSignificantDigit(increment, num) {
  34581. // If we have a whole number, just add 1 to it
  34582. if (isWholeNumber(num)) {
  34583. return num + increment * 0.1;
  34584. }
  34585. var numDecimalDigits = num.toString().split('.')[1].length;
  34586. for (var i = 1; i <= numDecimalDigits; i++) {
  34587. var scale = Math.pow(10, i);
  34588. var temp = num * scale;
  34589. if (isWholeNumber(temp) || i === numDecimalDigits) {
  34590. return (temp + increment) / scale;
  34591. }
  34592. }
  34593. };
  34594. var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1);
  34595. var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1);
  34596. /**
  34597. * Determine the index and estimated starting time of the segment that
  34598. * contains a specified playback position in a media playlist.
  34599. *
  34600. * @param {Object} playlist the media playlist to query
  34601. * @param {Number} currentTime The number of seconds since the earliest
  34602. * possible position to determine the containing segment for
  34603. * @param {Number} startIndex
  34604. * @param {Number} startTime
  34605. * @return {Object}
  34606. */
  34607. var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
  34608. var i = void 0;
  34609. var segment = void 0;
  34610. var numSegments = playlist.segments.length;
  34611. var time = currentTime - startTime;
  34612. if (time < 0) {
  34613. // Walk backward from startIndex in the playlist, adding durations
  34614. // until we find a segment that contains `time` and return it
  34615. if (startIndex > 0) {
  34616. for (i = startIndex - 1; i >= 0; i--) {
  34617. segment = playlist.segments[i];
  34618. time += floorLeastSignificantDigit(segment.duration);
  34619. if (time > 0) {
  34620. return {
  34621. mediaIndex: i,
  34622. startTime: startTime - sumDurations(playlist, startIndex, i)
  34623. };
  34624. }
  34625. }
  34626. } // We were unable to find a good segment within the playlist
  34627. // so select the first segment
  34628. return {
  34629. mediaIndex: 0,
  34630. startTime: currentTime
  34631. };
  34632. } // When startIndex is negative, we first walk forward to first segment
  34633. // adding target durations. If we "run out of time" before getting to
  34634. // the first segment, return the first segment
  34635. if (startIndex < 0) {
  34636. for (i = startIndex; i < 0; i++) {
  34637. time -= playlist.targetDuration;
  34638. if (time < 0) {
  34639. return {
  34640. mediaIndex: 0,
  34641. startTime: currentTime
  34642. };
  34643. }
  34644. }
  34645. startIndex = 0;
  34646. } // Walk forward from startIndex in the playlist, subtracting durations
  34647. // until we find a segment that contains `time` and return it
  34648. for (i = startIndex; i < numSegments; i++) {
  34649. segment = playlist.segments[i];
  34650. time -= ceilLeastSignificantDigit(segment.duration);
  34651. if (time < 0) {
  34652. return {
  34653. mediaIndex: i,
  34654. startTime: startTime + sumDurations(playlist, startIndex, i)
  34655. };
  34656. }
  34657. } // We are out of possible candidates so load the last one...
  34658. return {
  34659. mediaIndex: numSegments - 1,
  34660. startTime: currentTime
  34661. };
  34662. };
  34663. /**
  34664. * Check whether the playlist is blacklisted or not.
  34665. *
  34666. * @param {Object} playlist the media playlist object
  34667. * @return {boolean} whether the playlist is blacklisted or not
  34668. * @function isBlacklisted
  34669. */
  34670. var isBlacklisted = function isBlacklisted(playlist) {
  34671. return playlist.excludeUntil && playlist.excludeUntil > Date.now();
  34672. };
  34673. /**
  34674. * Check whether the playlist is compatible with current playback configuration or has
  34675. * been blacklisted permanently for being incompatible.
  34676. *
  34677. * @param {Object} playlist the media playlist object
  34678. * @return {boolean} whether the playlist is incompatible or not
  34679. * @function isIncompatible
  34680. */
  34681. var isIncompatible = function isIncompatible(playlist) {
  34682. return playlist.excludeUntil && playlist.excludeUntil === Infinity;
  34683. };
  34684. /**
  34685. * Check whether the playlist is enabled or not.
  34686. *
  34687. * @param {Object} playlist the media playlist object
  34688. * @return {boolean} whether the playlist is enabled or not
  34689. * @function isEnabled
  34690. */
  34691. var isEnabled = function isEnabled(playlist) {
  34692. var blacklisted = isBlacklisted(playlist);
  34693. return !playlist.disabled && !blacklisted;
  34694. };
  34695. /**
  34696. * Check whether the playlist has been manually disabled through the representations api.
  34697. *
  34698. * @param {Object} playlist the media playlist object
  34699. * @return {boolean} whether the playlist is disabled manually or not
  34700. * @function isDisabled
  34701. */
  34702. var isDisabled = function isDisabled(playlist) {
  34703. return playlist.disabled;
  34704. };
  34705. /**
  34706. * Returns whether the current playlist is an AES encrypted HLS stream
  34707. *
  34708. * @return {Boolean} true if it's an AES encrypted HLS stream
  34709. */
  34710. var isAes = function isAes(media) {
  34711. for (var i = 0; i < media.segments.length; i++) {
  34712. if (media.segments[i].key) {
  34713. return true;
  34714. }
  34715. }
  34716. return false;
  34717. };
  34718. /**
  34719. * Returns whether the current playlist contains fMP4
  34720. *
  34721. * @return {Boolean} true if the playlist contains fMP4
  34722. */
  34723. var isFmp4 = function isFmp4(media) {
  34724. for (var i = 0; i < media.segments.length; i++) {
  34725. if (media.segments[i].map) {
  34726. return true;
  34727. }
  34728. }
  34729. return false;
  34730. };
  34731. /**
  34732. * Checks if the playlist has a value for the specified attribute
  34733. *
  34734. * @param {String} attr
  34735. * Attribute to check for
  34736. * @param {Object} playlist
  34737. * The media playlist object
  34738. * @return {Boolean}
  34739. * Whether the playlist contains a value for the attribute or not
  34740. * @function hasAttribute
  34741. */
  34742. var hasAttribute = function hasAttribute(attr, playlist) {
  34743. return playlist.attributes && playlist.attributes[attr];
  34744. };
  34745. /**
  34746. * Estimates the time required to complete a segment download from the specified playlist
  34747. *
  34748. * @param {Number} segmentDuration
  34749. * Duration of requested segment
  34750. * @param {Number} bandwidth
  34751. * Current measured bandwidth of the player
  34752. * @param {Object} playlist
  34753. * The media playlist object
  34754. * @param {Number=} bytesReceived
  34755. * Number of bytes already received for the request. Defaults to 0
  34756. * @return {Number|NaN}
  34757. * The estimated time to request the segment. NaN if bandwidth information for
  34758. * the given playlist is unavailable
  34759. * @function estimateSegmentRequestTime
  34760. */
  34761. var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) {
  34762. var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
  34763. if (!hasAttribute('BANDWIDTH', playlist)) {
  34764. return NaN;
  34765. }
  34766. var size = segmentDuration * playlist.attributes.BANDWIDTH;
  34767. return (size - bytesReceived * 8) / bandwidth;
  34768. };
  34769. /*
  34770. * Returns whether the current playlist is the lowest rendition
  34771. *
  34772. * @return {Boolean} true if on lowest rendition
  34773. */
  34774. var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
  34775. if (master.playlists.length === 1) {
  34776. return true;
  34777. }
  34778. var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
  34779. return master.playlists.filter(function (playlist) {
  34780. if (!isEnabled(playlist)) {
  34781. return false;
  34782. }
  34783. return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
  34784. }).length === 0;
  34785. }; // exports
  34786. var Playlist = {
  34787. duration: duration,
  34788. seekable: seekable,
  34789. safeLiveIndex: safeLiveIndex,
  34790. getMediaInfoForTime: getMediaInfoForTime,
  34791. isEnabled: isEnabled,
  34792. isDisabled: isDisabled,
  34793. isBlacklisted: isBlacklisted,
  34794. isIncompatible: isIncompatible,
  34795. playlistEnd: playlistEnd,
  34796. isAes: isAes,
  34797. isFmp4: isFmp4,
  34798. hasAttribute: hasAttribute,
  34799. estimateSegmentRequestTime: estimateSegmentRequestTime,
  34800. isLowestEnabledRendition: isLowestEnabledRendition
  34801. };
  34802. /**
  34803. * @file xhr.js
  34804. */
  34805. var videojsXHR = videojs$1.xhr,
  34806. mergeOptions$1$1 = videojs$1.mergeOptions;
  34807. var xhrFactory = function xhrFactory() {
  34808. var xhr = function XhrFunction(options, callback) {
  34809. // Add a default timeout for all hls requests
  34810. options = mergeOptions$1$1({
  34811. timeout: 45e3
  34812. }, options); // Allow an optional user-specified function to modify the option
  34813. // object before we construct the xhr request
  34814. var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest;
  34815. if (beforeRequest && typeof beforeRequest === 'function') {
  34816. var newOptions = beforeRequest(options);
  34817. if (newOptions) {
  34818. options = newOptions;
  34819. }
  34820. }
  34821. var request = videojsXHR(options, function (error, response) {
  34822. var reqResponse = request.response;
  34823. if (!error && reqResponse) {
  34824. request.responseTime = Date.now();
  34825. request.roundTripTime = request.responseTime - request.requestTime;
  34826. request.bytesReceived = reqResponse.byteLength || reqResponse.length;
  34827. if (!request.bandwidth) {
  34828. request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
  34829. }
  34830. }
  34831. if (response.headers) {
  34832. request.responseHeaders = response.headers;
  34833. } // videojs.xhr now uses a specific code on the error
  34834. // object to signal that a request has timed out instead
  34835. // of setting a boolean on the request object
  34836. if (error && error.code === 'ETIMEDOUT') {
  34837. request.timedout = true;
  34838. } // videojs.xhr no longer considers status codes outside of 200 and 0
  34839. // (for file uris) to be errors, but the old XHR did, so emulate that
  34840. // behavior. Status 206 may be used in response to byterange requests.
  34841. if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
  34842. error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
  34843. }
  34844. callback(error, request);
  34845. });
  34846. var originalAbort = request.abort;
  34847. request.abort = function () {
  34848. request.aborted = true;
  34849. return originalAbort.apply(request, arguments);
  34850. };
  34851. request.uri = options.uri;
  34852. request.requestTime = Date.now();
  34853. return request;
  34854. };
  34855. return xhr;
  34856. };
  34857. /**
  34858. * Turns segment byterange into a string suitable for use in
  34859. * HTTP Range requests
  34860. *
  34861. * @param {Object} byterange - an object with two values defining the start and end
  34862. * of a byte-range
  34863. */
  34864. var byterangeStr = function byterangeStr(byterange) {
  34865. var byterangeStart = void 0;
  34866. var byterangeEnd = void 0; // `byterangeEnd` is one less than `offset + length` because the HTTP range
  34867. // header uses inclusive ranges
  34868. byterangeEnd = byterange.offset + byterange.length - 1;
  34869. byterangeStart = byterange.offset;
  34870. return 'bytes=' + byterangeStart + '-' + byterangeEnd;
  34871. };
  34872. /**
  34873. * Defines headers for use in the xhr request for a particular segment.
  34874. *
  34875. * @param {Object} segment - a simplified copy of the segmentInfo object
  34876. * from SegmentLoader
  34877. */
  34878. var segmentXhrHeaders = function segmentXhrHeaders(segment) {
  34879. var headers = {};
  34880. if (segment.byterange) {
  34881. headers.Range = byterangeStr(segment.byterange);
  34882. }
  34883. return headers;
  34884. };
  34885. /**
  34886. * @file bin-utils.js
  34887. */
  34888. /**
  34889. * convert a TimeRange to text
  34890. *
  34891. * @param {TimeRange} range the timerange to use for conversion
  34892. * @param {Number} i the iterator on the range to convert
  34893. */
  34894. var textRange = function textRange(range, i) {
  34895. return range.start(i) + '-' + range.end(i);
  34896. };
  34897. /**
  34898. * format a number as hex string
  34899. *
  34900. * @param {Number} e The number
  34901. * @param {Number} i the iterator
  34902. */
  34903. var formatHexString = function formatHexString(e, i) {
  34904. var value = e.toString(16);
  34905. return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
  34906. };
  34907. var formatAsciiString = function formatAsciiString(e) {
  34908. if (e >= 0x20 && e < 0x7e) {
  34909. return String.fromCharCode(e);
  34910. }
  34911. return '.';
  34912. };
  34913. /**
  34914. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  34915. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  34916. *
  34917. * @param {Object} message
  34918. * Object of properties and values to send to the web worker
  34919. * @return {Object}
  34920. * Modified message with TypedArray values expanded
  34921. * @function createTransferableMessage
  34922. */
  34923. var createTransferableMessage = function createTransferableMessage(message) {
  34924. var transferable = {};
  34925. Object.keys(message).forEach(function (key) {
  34926. var value = message[key];
  34927. if (ArrayBuffer.isView(value)) {
  34928. transferable[key] = {
  34929. bytes: value.buffer,
  34930. byteOffset: value.byteOffset,
  34931. byteLength: value.byteLength
  34932. };
  34933. } else {
  34934. transferable[key] = value;
  34935. }
  34936. });
  34937. return transferable;
  34938. };
  34939. /**
  34940. * Returns a unique string identifier for a media initialization
  34941. * segment.
  34942. */
  34943. var initSegmentId = function initSegmentId(initSegment) {
  34944. var byterange = initSegment.byterange || {
  34945. length: Infinity,
  34946. offset: 0
  34947. };
  34948. return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
  34949. };
  34950. /**
  34951. * Returns a unique string identifier for a media segment key.
  34952. */
  34953. var segmentKeyId = function segmentKeyId(key) {
  34954. return key.resolvedUri;
  34955. };
  34956. /**
  34957. * utils to help dump binary data to the console
  34958. */
  34959. var hexDump = function hexDump(data) {
  34960. var bytes = Array.prototype.slice.call(data);
  34961. var step = 16;
  34962. var result = '';
  34963. var hex = void 0;
  34964. var ascii = void 0;
  34965. for (var j = 0; j < bytes.length / step; j++) {
  34966. hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
  34967. ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
  34968. result += hex + ' ' + ascii + '\n';
  34969. }
  34970. return result;
  34971. };
  34972. var tagDump = function tagDump(_ref) {
  34973. var bytes = _ref.bytes;
  34974. return hexDump(bytes);
  34975. };
  34976. var textRanges = function textRanges(ranges) {
  34977. var result = '';
  34978. var i = void 0;
  34979. for (i = 0; i < ranges.length; i++) {
  34980. result += textRange(ranges, i) + ' ';
  34981. }
  34982. return result;
  34983. };
  34984. var utils$1 =
  34985. /*#__PURE__*/
  34986. Object.freeze({
  34987. createTransferableMessage: createTransferableMessage,
  34988. initSegmentId: initSegmentId,
  34989. segmentKeyId: segmentKeyId,
  34990. hexDump: hexDump,
  34991. tagDump: tagDump,
  34992. textRanges: textRanges
  34993. }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
  34994. // Add 25% to the segment duration to account for small discrepencies in segment timing.
  34995. // 25% was arbitrarily chosen, and may need to be refined over time.
  34996. var SEGMENT_END_FUDGE_PERCENT = 0.25;
  34997. /**
  34998. * Converts a player time (any time that can be gotten/set from player.currentTime(),
  34999. * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
  35000. * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
  35001. *
  35002. * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
  35003. * point" (a point where we have a mapping from program time to player time, with player
  35004. * time being the post transmux start of the segment).
  35005. *
  35006. * For more details, see [this doc](../../docs/program-time-from-player-time.md).
  35007. *
  35008. * @param {Number} playerTime the player time
  35009. * @param {Object} segment the segment which contains the player time
  35010. * @return {Date} program time
  35011. */
  35012. var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) {
  35013. if (!segment.dateTimeObject) {
  35014. // Can't convert without an "anchor point" for the program time (i.e., a time that can
  35015. // be used to map the start of a segment with a real world time).
  35016. return null;
  35017. }
  35018. var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
  35019. var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
  35020. var startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
  35021. var offsetFromSegmentStart = playerTime - startOfSegment;
  35022. return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
  35023. };
  35024. var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) {
  35025. return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
  35026. };
  35027. /**
  35028. * Finds a segment that contains the time requested given as an ISO-8601 string. The
  35029. * returned segment might be an estimate or an accurate match.
  35030. *
  35031. * @param {String} programTime The ISO-8601 programTime to find a match for
  35032. * @param {Object} playlist A playlist object to search within
  35033. */
  35034. var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) {
  35035. // Assumptions:
  35036. // - verifyProgramDateTimeTags has already been run
  35037. // - live streams have been started
  35038. var dateTimeObject = void 0;
  35039. try {
  35040. dateTimeObject = new Date(programTime);
  35041. } catch (e) {
  35042. return null;
  35043. }
  35044. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  35045. return null;
  35046. }
  35047. var segment = playlist.segments[0];
  35048. if (dateTimeObject < segment.dateTimeObject) {
  35049. // Requested time is before stream start.
  35050. return null;
  35051. }
  35052. for (var i = 0; i < playlist.segments.length - 1; i++) {
  35053. segment = playlist.segments[i];
  35054. var nextSegmentStart = playlist.segments[i + 1].dateTimeObject;
  35055. if (dateTimeObject < nextSegmentStart) {
  35056. break;
  35057. }
  35058. }
  35059. var lastSegment = playlist.segments[playlist.segments.length - 1];
  35060. var lastSegmentStart = lastSegment.dateTimeObject;
  35061. var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
  35062. var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
  35063. if (dateTimeObject > lastSegmentEnd) {
  35064. // Beyond the end of the stream, or our best guess of the end of the stream.
  35065. return null;
  35066. }
  35067. if (dateTimeObject > lastSegmentStart) {
  35068. segment = lastSegment;
  35069. }
  35070. return {
  35071. segment: segment,
  35072. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
  35073. // Although, given that all segments have accurate date time objects, the segment
  35074. // selected should be accurate, unless the video has been transmuxed at some point
  35075. // (determined by the presence of the videoTimingInfo object), the segment's "player
  35076. // time" (the start time in the player) can't be considered accurate.
  35077. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  35078. };
  35079. };
  35080. /**
  35081. * Finds a segment that contains the given player time(in seconds).
  35082. *
  35083. * @param {Number} time The player time to find a match for
  35084. * @param {Object} playlist A playlist object to search within
  35085. */
  35086. var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) {
  35087. // Assumptions:
  35088. // - there will always be a segment.duration
  35089. // - we can start from zero
  35090. // - segments are in time order
  35091. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  35092. return null;
  35093. }
  35094. var segmentEnd = 0;
  35095. var segment = void 0;
  35096. for (var i = 0; i < playlist.segments.length; i++) {
  35097. segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
  35098. // should contain the most accurate values we have for the segment's player times.
  35099. //
  35100. // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
  35101. // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
  35102. // calculate an end value.
  35103. segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
  35104. if (time <= segmentEnd) {
  35105. break;
  35106. }
  35107. }
  35108. var lastSegment = playlist.segments[playlist.segments.length - 1];
  35109. if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
  35110. // The time requested is beyond the stream end.
  35111. return null;
  35112. }
  35113. if (time > segmentEnd) {
  35114. // The time is within or beyond the last segment.
  35115. //
  35116. // Check to see if the time is beyond a reasonable guess of the end of the stream.
  35117. if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
  35118. // Technically, because the duration value is only an estimate, the time may still
  35119. // exist in the last segment, however, there isn't enough information to make even
  35120. // a reasonable estimate.
  35121. return null;
  35122. }
  35123. segment = lastSegment;
  35124. }
  35125. return {
  35126. segment: segment,
  35127. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
  35128. // Because videoTimingInfo is only set after transmux, it is the only way to get
  35129. // accurate timing values.
  35130. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  35131. };
  35132. };
  35133. /**
  35134. * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
  35135. * If the offset returned is positive, the programTime occurs after the
  35136. * comparisonTimestamp.
  35137. * If the offset is negative, the programTime occurs before the comparisonTimestamp.
  35138. *
  35139. * @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against
  35140. * @param {String} programTime The programTime as an ISO-8601 string
  35141. * @return {Number} offset
  35142. */
  35143. var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) {
  35144. var segmentDateTime = void 0;
  35145. var programDateTime = void 0;
  35146. try {
  35147. segmentDateTime = new Date(comparisonTimeStamp);
  35148. programDateTime = new Date(programTime);
  35149. } catch (e) {// TODO handle error
  35150. }
  35151. var segmentTimeEpoch = segmentDateTime.getTime();
  35152. var programTimeEpoch = programDateTime.getTime();
  35153. return (programTimeEpoch - segmentTimeEpoch) / 1000;
  35154. };
  35155. /**
  35156. * Checks that all segments in this playlist have programDateTime tags.
  35157. *
  35158. * @param {Object} playlist A playlist object
  35159. */
  35160. var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) {
  35161. if (!playlist.segments || playlist.segments.length === 0) {
  35162. return false;
  35163. }
  35164. for (var i = 0; i < playlist.segments.length; i++) {
  35165. var segment = playlist.segments[i];
  35166. if (!segment.dateTimeObject) {
  35167. return false;
  35168. }
  35169. }
  35170. return true;
  35171. };
  35172. /**
  35173. * Returns the programTime of the media given a playlist and a playerTime.
  35174. * The playlist must have programDateTime tags for a programDateTime tag to be returned.
  35175. * If the segments containing the time requested have not been buffered yet, an estimate
  35176. * may be returned to the callback.
  35177. *
  35178. * @param {Object} args
  35179. * @param {Object} args.playlist A playlist object to search within
  35180. * @param {Number} time A playerTime in seconds
  35181. * @param {Function} callback(err, programTime)
  35182. * @returns {String} err.message A detailed error message
  35183. * @returns {Object} programTime
  35184. * @returns {Number} programTime.mediaSeconds The streamTime in seconds
  35185. * @returns {String} programTime.programDateTime The programTime as an ISO-8601 String
  35186. */
  35187. var getProgramTime = function getProgramTime(_ref) {
  35188. var playlist = _ref.playlist,
  35189. _ref$time = _ref.time,
  35190. time = _ref$time === undefined ? undefined : _ref$time,
  35191. callback = _ref.callback;
  35192. if (!callback) {
  35193. throw new Error('getProgramTime: callback must be provided');
  35194. }
  35195. if (!playlist || time === undefined) {
  35196. return callback({
  35197. message: 'getProgramTime: playlist and time must be provided'
  35198. });
  35199. }
  35200. var matchedSegment = findSegmentForPlayerTime(time, playlist);
  35201. if (!matchedSegment) {
  35202. return callback({
  35203. message: 'valid programTime was not found'
  35204. });
  35205. }
  35206. if (matchedSegment.type === 'estimate') {
  35207. return callback({
  35208. message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
  35209. seekTime: matchedSegment.estimatedStart
  35210. });
  35211. }
  35212. var programTimeObject = {
  35213. mediaSeconds: time
  35214. };
  35215. var programTime = playerTimeToProgramTime(time, matchedSegment.segment);
  35216. if (programTime) {
  35217. programTimeObject.programDateTime = programTime.toISOString();
  35218. }
  35219. return callback(null, programTimeObject);
  35220. };
  35221. /**
  35222. * Seeks in the player to a time that matches the given programTime ISO-8601 string.
  35223. *
  35224. * @param {Object} args
  35225. * @param {String} args.programTime A programTime to seek to as an ISO-8601 String
  35226. * @param {Object} args.playlist A playlist to look within
  35227. * @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2.
  35228. * @param {Function} args.seekTo A method to perform a seek
  35229. * @param {Boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
  35230. * @param {Object} args.tech The tech to seek on
  35231. * @param {Function} args.callback(err, newTime) A callback to return the new time to
  35232. * @returns {String} err.message A detailed error message
  35233. * @returns {Number} newTime The exact time that was seeked to in seconds
  35234. */
  35235. var seekToProgramTime = function seekToProgramTime(_ref2) {
  35236. var programTime = _ref2.programTime,
  35237. playlist = _ref2.playlist,
  35238. _ref2$retryCount = _ref2.retryCount,
  35239. retryCount = _ref2$retryCount === undefined ? 2 : _ref2$retryCount,
  35240. seekTo = _ref2.seekTo,
  35241. _ref2$pauseAfterSeek = _ref2.pauseAfterSeek,
  35242. pauseAfterSeek = _ref2$pauseAfterSeek === undefined ? true : _ref2$pauseAfterSeek,
  35243. tech = _ref2.tech,
  35244. callback = _ref2.callback;
  35245. if (!callback) {
  35246. throw new Error('seekToProgramTime: callback must be provided');
  35247. }
  35248. if (typeof programTime === 'undefined' || !playlist || !seekTo) {
  35249. return callback({
  35250. message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
  35251. });
  35252. }
  35253. if (!playlist.endList && !tech.hasStarted_) {
  35254. return callback({
  35255. message: 'player must be playing a live stream to start buffering'
  35256. });
  35257. }
  35258. if (!verifyProgramDateTimeTags(playlist)) {
  35259. return callback({
  35260. message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
  35261. });
  35262. }
  35263. var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
  35264. if (!matchedSegment) {
  35265. return callback({
  35266. message: programTime + ' was not found in the stream'
  35267. });
  35268. }
  35269. var segment = matchedSegment.segment;
  35270. var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
  35271. if (matchedSegment.type === 'estimate') {
  35272. // we've run out of retries
  35273. if (retryCount === 0) {
  35274. return callback({
  35275. message: programTime + ' is not buffered yet. Try again'
  35276. });
  35277. }
  35278. seekTo(matchedSegment.estimatedStart + mediaOffset);
  35279. tech.one('seeked', function () {
  35280. seekToProgramTime({
  35281. programTime: programTime,
  35282. playlist: playlist,
  35283. retryCount: retryCount - 1,
  35284. seekTo: seekTo,
  35285. pauseAfterSeek: pauseAfterSeek,
  35286. tech: tech,
  35287. callback: callback
  35288. });
  35289. });
  35290. return;
  35291. } // Since the segment.start value is determined from the buffered end or ending time
  35292. // of the prior segment, the seekToTime doesn't need to account for any transmuxer
  35293. // modifications.
  35294. var seekToTime = segment.start + mediaOffset;
  35295. var seekedCallback = function seekedCallback() {
  35296. return callback(null, tech.currentTime());
  35297. }; // listen for seeked event
  35298. tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
  35299. if (pauseAfterSeek) {
  35300. tech.pause();
  35301. }
  35302. seekTo(seekToTime);
  35303. };
  35304. /**
  35305. * ranges
  35306. *
  35307. * Utilities for working with TimeRanges.
  35308. *
  35309. */
  35310. // Fudge factor to account for TimeRanges rounding
  35311. var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
  35312. // can be misleading because of precision differences or when the current media has poorly
  35313. // aligned audio and video, which can cause values to be slightly off from what you would
  35314. // expect. This value is what we consider to be safe to use in such comparisons to account
  35315. // for these scenarios.
  35316. var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
  35317. var filterRanges = function filterRanges(timeRanges, predicate) {
  35318. var results = [];
  35319. var i = void 0;
  35320. if (timeRanges && timeRanges.length) {
  35321. // Search for ranges that match the predicate
  35322. for (i = 0; i < timeRanges.length; i++) {
  35323. if (predicate(timeRanges.start(i), timeRanges.end(i))) {
  35324. results.push([timeRanges.start(i), timeRanges.end(i)]);
  35325. }
  35326. }
  35327. }
  35328. return videojs$1.createTimeRanges(results);
  35329. };
  35330. /**
  35331. * Attempts to find the buffered TimeRange that contains the specified
  35332. * time.
  35333. * @param {TimeRanges} buffered - the TimeRanges object to query
  35334. * @param {number} time - the time to filter on.
  35335. * @returns {TimeRanges} a new TimeRanges object
  35336. */
  35337. var findRange = function findRange(buffered, time) {
  35338. return filterRanges(buffered, function (start, end) {
  35339. return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
  35340. });
  35341. };
  35342. /**
  35343. * Returns the TimeRanges that begin later than the specified time.
  35344. * @param {TimeRanges} timeRanges - the TimeRanges object to query
  35345. * @param {number} time - the time to filter on.
  35346. * @returns {TimeRanges} a new TimeRanges object.
  35347. */
  35348. var findNextRange = function findNextRange(timeRanges, time) {
  35349. return filterRanges(timeRanges, function (start) {
  35350. return start - TIME_FUDGE_FACTOR >= time;
  35351. });
  35352. };
  35353. /**
  35354. * Returns gaps within a list of TimeRanges
  35355. * @param {TimeRanges} buffered - the TimeRanges object
  35356. * @return {TimeRanges} a TimeRanges object of gaps
  35357. */
  35358. var findGaps = function findGaps(buffered) {
  35359. if (buffered.length < 2) {
  35360. return videojs$1.createTimeRanges();
  35361. }
  35362. var ranges = [];
  35363. for (var i = 1; i < buffered.length; i++) {
  35364. var start = buffered.end(i - 1);
  35365. var end = buffered.start(i);
  35366. ranges.push([start, end]);
  35367. }
  35368. return videojs$1.createTimeRanges(ranges);
  35369. };
  35370. /**
  35371. * Gets a human readable string for a TimeRange
  35372. *
  35373. * @param {TimeRange} range
  35374. * @returns {String} a human readable string
  35375. */
  35376. var printableRange = function printableRange(range) {
  35377. var strArr = [];
  35378. if (!range || !range.length) {
  35379. return '';
  35380. }
  35381. for (var i = 0; i < range.length; i++) {
  35382. strArr.push(range.start(i) + ' => ' + range.end(i));
  35383. }
  35384. return strArr.join(', ');
  35385. };
  35386. /**
  35387. * Calculates the amount of time left in seconds until the player hits the end of the
  35388. * buffer and causes a rebuffer
  35389. *
  35390. * @param {TimeRange} buffered
  35391. * The state of the buffer
  35392. * @param {Numnber} currentTime
  35393. * The current time of the player
  35394. * @param {Number} playbackRate
  35395. * The current playback rate of the player. Defaults to 1.
  35396. * @return {Number}
  35397. * Time until the player has to start rebuffering in seconds.
  35398. * @function timeUntilRebuffer
  35399. */
  35400. var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) {
  35401. var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
  35402. var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
  35403. return (bufferedEnd - currentTime) / playbackRate;
  35404. };
  35405. /**
  35406. * Converts a TimeRanges object into an array representation
  35407. * @param {TimeRanges} timeRanges
  35408. * @returns {Array}
  35409. */
  35410. var timeRangesToArray = function timeRangesToArray(timeRanges) {
  35411. var timeRangesList = [];
  35412. for (var i = 0; i < timeRanges.length; i++) {
  35413. timeRangesList.push({
  35414. start: timeRanges.start(i),
  35415. end: timeRanges.end(i)
  35416. });
  35417. }
  35418. return timeRangesList;
  35419. };
  35420. /**
  35421. * @file create-text-tracks-if-necessary.js
  35422. */
  35423. /**
  35424. * Create text tracks on video.js if they exist on a segment.
  35425. *
  35426. * @param {Object} sourceBuffer the VSB or FSB
  35427. * @param {Object} mediaSource the HTML media source
  35428. * @param {Object} segment the segment that may contain the text track
  35429. * @private
  35430. */
  35431. var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) {
  35432. var player = mediaSource.player_; // create an in-band caption track if one is present in the segment
  35433. if (segment.captions && segment.captions.length) {
  35434. if (!sourceBuffer.inbandTextTracks_) {
  35435. sourceBuffer.inbandTextTracks_ = {};
  35436. }
  35437. for (var trackId in segment.captionStreams) {
  35438. if (!sourceBuffer.inbandTextTracks_[trackId]) {
  35439. player.tech_.trigger({
  35440. type: 'usage',
  35441. name: 'hls-608'
  35442. });
  35443. var track = player.textTracks().getTrackById(trackId);
  35444. if (track) {
  35445. // Resuse an existing track with a CC# id because this was
  35446. // very likely created by videojs-contrib-hls from information
  35447. // in the m3u8 for us to use
  35448. sourceBuffer.inbandTextTracks_[trackId] = track;
  35449. } else {
  35450. // Otherwise, create a track with the default `CC#` label and
  35451. // without a language
  35452. sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({
  35453. kind: 'captions',
  35454. id: trackId,
  35455. label: trackId
  35456. }, false).track;
  35457. }
  35458. }
  35459. }
  35460. }
  35461. if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) {
  35462. sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({
  35463. kind: 'metadata',
  35464. label: 'Timed Metadata'
  35465. }, false).track;
  35466. sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType;
  35467. }
  35468. };
  35469. /**
  35470. * @file remove-cues-from-track.js
  35471. */
  35472. /**
  35473. * Remove cues from a track on video.js.
  35474. *
  35475. * @param {Double} start start of where we should remove the cue
  35476. * @param {Double} end end of where the we should remove the cue
  35477. * @param {Object} track the text track to remove the cues from
  35478. * @private
  35479. */
  35480. var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
  35481. var i = void 0;
  35482. var cue = void 0;
  35483. if (!track) {
  35484. return;
  35485. }
  35486. if (!track.cues) {
  35487. return;
  35488. }
  35489. i = track.cues.length;
  35490. while (i--) {
  35491. cue = track.cues[i]; // Remove any overlapping cue
  35492. if (cue.startTime <= end && cue.endTime >= start) {
  35493. track.removeCue(cue);
  35494. }
  35495. }
  35496. };
  35497. /**
  35498. * @file add-text-track-data.js
  35499. */
  35500. /**
  35501. * Define properties on a cue for backwards compatability,
  35502. * but warn the user that the way that they are using it
  35503. * is depricated and will be removed at a later date.
  35504. *
  35505. * @param {Cue} cue the cue to add the properties on
  35506. * @private
  35507. */
  35508. var deprecateOldCue = function deprecateOldCue(cue) {
  35509. Object.defineProperties(cue.frame, {
  35510. id: {
  35511. get: function get() {
  35512. videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
  35513. return cue.value.key;
  35514. }
  35515. },
  35516. value: {
  35517. get: function get() {
  35518. videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
  35519. return cue.value.data;
  35520. }
  35521. },
  35522. privateData: {
  35523. get: function get() {
  35524. videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
  35525. return cue.value.data;
  35526. }
  35527. }
  35528. });
  35529. };
  35530. var durationOfVideo = function durationOfVideo(duration) {
  35531. var dur = void 0;
  35532. if (isNaN(duration) || Math.abs(duration) === Infinity) {
  35533. dur = Number.MAX_VALUE;
  35534. } else {
  35535. dur = duration;
  35536. }
  35537. return dur;
  35538. };
  35539. /**
  35540. * Add text track data to a source handler given the captions and
  35541. * metadata from the buffer.
  35542. *
  35543. * @param {Object} sourceHandler the virtual source buffer
  35544. * @param {Array} captionArray an array of caption data
  35545. * @param {Array} metadataArray an array of meta data
  35546. * @private
  35547. */
  35548. var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) {
  35549. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  35550. if (captionArray) {
  35551. captionArray.forEach(function (caption) {
  35552. var track = caption.stream;
  35553. this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text));
  35554. }, sourceHandler);
  35555. }
  35556. if (metadataArray) {
  35557. var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration);
  35558. metadataArray.forEach(function (metadata) {
  35559. var time = metadata.cueTime + this.timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
  35560. // ignore this bit of metadata.
  35561. // This likely occurs when you have an non-timed ID3 tag like TIT2,
  35562. // which is the "Title/Songname/Content description" frame
  35563. if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) {
  35564. return;
  35565. }
  35566. metadata.frames.forEach(function (frame) {
  35567. var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
  35568. cue.frame = frame;
  35569. cue.value = frame;
  35570. deprecateOldCue(cue);
  35571. this.metadataTrack_.addCue(cue);
  35572. }, this);
  35573. }, sourceHandler); // Updating the metadeta cues so that
  35574. // the endTime of each cue is the startTime of the next cue
  35575. // the endTime of last cue is the duration of the video
  35576. if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) {
  35577. var cues = sourceHandler.metadataTrack_.cues;
  35578. var cuesArray = []; // Create a copy of the TextTrackCueList...
  35579. // ...disregarding cues with a falsey value
  35580. for (var i = 0; i < cues.length; i++) {
  35581. if (cues[i]) {
  35582. cuesArray.push(cues[i]);
  35583. }
  35584. } // Group cues by their startTime value
  35585. var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
  35586. var timeSlot = obj[cue.startTime] || [];
  35587. timeSlot.push(cue);
  35588. obj[cue.startTime] = timeSlot;
  35589. return obj;
  35590. }, {}); // Sort startTimes by ascending order
  35591. var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
  35592. return Number(a) - Number(b);
  35593. }); // Map each cue group's endTime to the next group's startTime
  35594. sortedStartTimes.forEach(function (startTime, idx) {
  35595. var cueGroup = cuesGroupedByStartTime[startTime];
  35596. var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime
  35597. cueGroup.forEach(function (cue) {
  35598. cue.endTime = nextTime;
  35599. });
  35600. });
  35601. }
  35602. }
  35603. };
  35604. var win = typeof window !== 'undefined' ? window : {},
  35605. TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(),
  35606. SCRIPT_TYPE = 'application/javascript',
  35607. BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder,
  35608. URL = win.URL || win.webkitURL || URL && URL.msURL,
  35609. Worker = win.Worker;
  35610. /**
  35611. * Returns a wrapper around Web Worker code that is constructible.
  35612. *
  35613. * @function shimWorker
  35614. *
  35615. * @param { String } filename The name of the file
  35616. * @param { Function } fn Function wrapping the code of the worker
  35617. */
  35618. function shimWorker(filename, fn) {
  35619. return function ShimWorker(forceFallback) {
  35620. var o = this;
  35621. if (!fn) {
  35622. return new Worker(filename);
  35623. } else if (Worker && !forceFallback) {
  35624. // Convert the function's inner code to a string to construct the worker
  35625. var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1),
  35626. objURL = createSourceObject(source);
  35627. this[TARGET] = new Worker(objURL);
  35628. wrapTerminate(this[TARGET], objURL);
  35629. return this[TARGET];
  35630. } else {
  35631. var selfShim = {
  35632. postMessage: function postMessage(m) {
  35633. if (o.onmessage) {
  35634. setTimeout(function () {
  35635. o.onmessage({
  35636. data: m,
  35637. target: selfShim
  35638. });
  35639. });
  35640. }
  35641. }
  35642. };
  35643. fn.call(selfShim);
  35644. this.postMessage = function (m) {
  35645. setTimeout(function () {
  35646. selfShim.onmessage({
  35647. data: m,
  35648. target: o
  35649. });
  35650. });
  35651. };
  35652. this.isThisThread = true;
  35653. }
  35654. };
  35655. } // Test Worker capabilities
  35656. if (Worker) {
  35657. var testWorker,
  35658. objURL = createSourceObject('self.onmessage = function () {}'),
  35659. testArray = new Uint8Array(1);
  35660. try {
  35661. testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it
  35662. testWorker.postMessage(testArray, [testArray.buffer]);
  35663. } catch (e) {
  35664. Worker = null;
  35665. } finally {
  35666. URL.revokeObjectURL(objURL);
  35667. if (testWorker) {
  35668. testWorker.terminate();
  35669. }
  35670. }
  35671. }
  35672. function createSourceObject(str) {
  35673. try {
  35674. return URL.createObjectURL(new Blob([str], {
  35675. type: SCRIPT_TYPE
  35676. }));
  35677. } catch (e) {
  35678. var blob = new BlobBuilder();
  35679. blob.append(str);
  35680. return URL.createObjectURL(blob.getBlob(type));
  35681. }
  35682. }
  35683. function wrapTerminate(worker, objURL) {
  35684. if (!worker || !objURL) return;
  35685. var term = worker.terminate;
  35686. worker.objURL = objURL;
  35687. worker.terminate = function () {
  35688. if (worker.objURL) URL.revokeObjectURL(worker.objURL);
  35689. term.call(worker);
  35690. };
  35691. }
  35692. var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) {
  35693. var self = this;
  35694. var transmuxerWorker = function () {
  35695. /**
  35696. * mux.js
  35697. *
  35698. * Copyright (c) 2015 Brightcove
  35699. * All rights reserved.
  35700. *
  35701. * Functions that generate fragmented MP4s suitable for use with Media
  35702. * Source Extensions.
  35703. */
  35704. var UINT32_MAX = Math.pow(2, 32) - 1;
  35705. var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants
  35706. (function () {
  35707. var i;
  35708. types = {
  35709. avc1: [],
  35710. // codingname
  35711. avcC: [],
  35712. btrt: [],
  35713. dinf: [],
  35714. dref: [],
  35715. esds: [],
  35716. ftyp: [],
  35717. hdlr: [],
  35718. mdat: [],
  35719. mdhd: [],
  35720. mdia: [],
  35721. mfhd: [],
  35722. minf: [],
  35723. moof: [],
  35724. moov: [],
  35725. mp4a: [],
  35726. // codingname
  35727. mvex: [],
  35728. mvhd: [],
  35729. sdtp: [],
  35730. smhd: [],
  35731. stbl: [],
  35732. stco: [],
  35733. stsc: [],
  35734. stsd: [],
  35735. stsz: [],
  35736. stts: [],
  35737. styp: [],
  35738. tfdt: [],
  35739. tfhd: [],
  35740. traf: [],
  35741. trak: [],
  35742. trun: [],
  35743. trex: [],
  35744. tkhd: [],
  35745. vmhd: []
  35746. }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
  35747. // don't throw an error
  35748. if (typeof Uint8Array === 'undefined') {
  35749. return;
  35750. }
  35751. for (i in types) {
  35752. if (types.hasOwnProperty(i)) {
  35753. types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
  35754. }
  35755. }
  35756. MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
  35757. AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
  35758. MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
  35759. VIDEO_HDLR = new Uint8Array([0x00, // version 0
  35760. 0x00, 0x00, 0x00, // flags
  35761. 0x00, 0x00, 0x00, 0x00, // pre_defined
  35762. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  35763. 0x00, 0x00, 0x00, 0x00, // reserved
  35764. 0x00, 0x00, 0x00, 0x00, // reserved
  35765. 0x00, 0x00, 0x00, 0x00, // reserved
  35766. 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  35767. ]);
  35768. AUDIO_HDLR = new Uint8Array([0x00, // version 0
  35769. 0x00, 0x00, 0x00, // flags
  35770. 0x00, 0x00, 0x00, 0x00, // pre_defined
  35771. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  35772. 0x00, 0x00, 0x00, 0x00, // reserved
  35773. 0x00, 0x00, 0x00, 0x00, // reserved
  35774. 0x00, 0x00, 0x00, 0x00, // reserved
  35775. 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  35776. ]);
  35777. HDLR_TYPES = {
  35778. video: VIDEO_HDLR,
  35779. audio: AUDIO_HDLR
  35780. };
  35781. DREF = new Uint8Array([0x00, // version 0
  35782. 0x00, 0x00, 0x00, // flags
  35783. 0x00, 0x00, 0x00, 0x01, // entry_count
  35784. 0x00, 0x00, 0x00, 0x0c, // entry_size
  35785. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  35786. 0x00, // version 0
  35787. 0x00, 0x00, 0x01 // entry_flags
  35788. ]);
  35789. SMHD = new Uint8Array([0x00, // version
  35790. 0x00, 0x00, 0x00, // flags
  35791. 0x00, 0x00, // balance, 0 means centered
  35792. 0x00, 0x00 // reserved
  35793. ]);
  35794. STCO = new Uint8Array([0x00, // version
  35795. 0x00, 0x00, 0x00, // flags
  35796. 0x00, 0x00, 0x00, 0x00 // entry_count
  35797. ]);
  35798. STSC = STCO;
  35799. STSZ = new Uint8Array([0x00, // version
  35800. 0x00, 0x00, 0x00, // flags
  35801. 0x00, 0x00, 0x00, 0x00, // sample_size
  35802. 0x00, 0x00, 0x00, 0x00 // sample_count
  35803. ]);
  35804. STTS = STCO;
  35805. VMHD = new Uint8Array([0x00, // version
  35806. 0x00, 0x00, 0x01, // flags
  35807. 0x00, 0x00, // graphicsmode
  35808. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
  35809. ]);
  35810. })();
  35811. box = function box(type) {
  35812. var payload = [],
  35813. size = 0,
  35814. i,
  35815. result,
  35816. view;
  35817. for (i = 1; i < arguments.length; i++) {
  35818. payload.push(arguments[i]);
  35819. }
  35820. i = payload.length; // calculate the total size we need to allocate
  35821. while (i--) {
  35822. size += payload[i].byteLength;
  35823. }
  35824. result = new Uint8Array(size + 8);
  35825. view = new DataView(result.buffer, result.byteOffset, result.byteLength);
  35826. view.setUint32(0, result.byteLength);
  35827. result.set(type, 4); // copy the payload into the result
  35828. for (i = 0, size = 8; i < payload.length; i++) {
  35829. result.set(payload[i], size);
  35830. size += payload[i].byteLength;
  35831. }
  35832. return result;
  35833. };
  35834. dinf = function dinf() {
  35835. return box(types.dinf, box(types.dref, DREF));
  35836. };
  35837. esds = function esds(track) {
  35838. return box(types.esds, new Uint8Array([0x00, // version
  35839. 0x00, 0x00, 0x00, // flags
  35840. // ES_Descriptor
  35841. 0x03, // tag, ES_DescrTag
  35842. 0x19, // length
  35843. 0x00, 0x00, // ES_ID
  35844. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  35845. // DecoderConfigDescriptor
  35846. 0x04, // tag, DecoderConfigDescrTag
  35847. 0x11, // length
  35848. 0x40, // object type
  35849. 0x15, // streamType
  35850. 0x00, 0x06, 0x00, // bufferSizeDB
  35851. 0x00, 0x00, 0xda, 0xc0, // maxBitrate
  35852. 0x00, 0x00, 0xda, 0xc0, // avgBitrate
  35853. // DecoderSpecificInfo
  35854. 0x05, // tag, DecoderSpecificInfoTag
  35855. 0x02, // length
  35856. // ISO/IEC 14496-3, AudioSpecificConfig
  35857. // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  35858. track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
  35859. ]));
  35860. };
  35861. ftyp = function ftyp() {
  35862. return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
  35863. };
  35864. hdlr = function hdlr(type) {
  35865. return box(types.hdlr, HDLR_TYPES[type]);
  35866. };
  35867. mdat = function mdat(data) {
  35868. return box(types.mdat, data);
  35869. };
  35870. mdhd = function mdhd(track) {
  35871. var result = new Uint8Array([0x00, // version 0
  35872. 0x00, 0x00, 0x00, // flags
  35873. 0x00, 0x00, 0x00, 0x02, // creation_time
  35874. 0x00, 0x00, 0x00, 0x03, // modification_time
  35875. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  35876. track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
  35877. 0x55, 0xc4, // 'und' language (undetermined)
  35878. 0x00, 0x00]); // Use the sample rate from the track metadata, when it is
  35879. // defined. The sample rate can be parsed out of an ADTS header, for
  35880. // instance.
  35881. if (track.samplerate) {
  35882. result[12] = track.samplerate >>> 24 & 0xFF;
  35883. result[13] = track.samplerate >>> 16 & 0xFF;
  35884. result[14] = track.samplerate >>> 8 & 0xFF;
  35885. result[15] = track.samplerate & 0xFF;
  35886. }
  35887. return box(types.mdhd, result);
  35888. };
  35889. mdia = function mdia(track) {
  35890. return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
  35891. };
  35892. mfhd = function mfhd(sequenceNumber) {
  35893. return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
  35894. (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
  35895. ]));
  35896. };
  35897. minf = function minf(track) {
  35898. return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
  35899. };
  35900. moof = function moof(sequenceNumber, tracks) {
  35901. var trackFragments = [],
  35902. i = tracks.length; // build traf boxes for each track fragment
  35903. while (i--) {
  35904. trackFragments[i] = traf(tracks[i]);
  35905. }
  35906. return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
  35907. };
  35908. /**
  35909. * Returns a movie box.
  35910. * @param tracks {array} the tracks associated with this movie
  35911. * @see ISO/IEC 14496-12:2012(E), section 8.2.1
  35912. */
  35913. moov = function moov(tracks) {
  35914. var i = tracks.length,
  35915. boxes = [];
  35916. while (i--) {
  35917. boxes[i] = trak(tracks[i]);
  35918. }
  35919. return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
  35920. };
  35921. mvex = function mvex(tracks) {
  35922. var i = tracks.length,
  35923. boxes = [];
  35924. while (i--) {
  35925. boxes[i] = trex(tracks[i]);
  35926. }
  35927. return box.apply(null, [types.mvex].concat(boxes));
  35928. };
  35929. mvhd = function mvhd(duration) {
  35930. var bytes = new Uint8Array([0x00, // version 0
  35931. 0x00, 0x00, 0x00, // flags
  35932. 0x00, 0x00, 0x00, 0x01, // creation_time
  35933. 0x00, 0x00, 0x00, 0x02, // modification_time
  35934. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  35935. (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
  35936. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  35937. 0x01, 0x00, // 1.0 volume
  35938. 0x00, 0x00, // reserved
  35939. 0x00, 0x00, 0x00, 0x00, // reserved
  35940. 0x00, 0x00, 0x00, 0x00, // reserved
  35941. 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
  35942. 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
  35943. 0xff, 0xff, 0xff, 0xff // next_track_ID
  35944. ]);
  35945. return box(types.mvhd, bytes);
  35946. };
  35947. sdtp = function sdtp(track) {
  35948. var samples = track.samples || [],
  35949. bytes = new Uint8Array(4 + samples.length),
  35950. flags,
  35951. i; // leave the full box header (4 bytes) all zero
  35952. // write the sample table
  35953. for (i = 0; i < samples.length; i++) {
  35954. flags = samples[i].flags;
  35955. bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
  35956. }
  35957. return box(types.sdtp, bytes);
  35958. };
  35959. stbl = function stbl(track) {
  35960. return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
  35961. };
  35962. (function () {
  35963. var videoSample, audioSample;
  35964. stsd = function stsd(track) {
  35965. return box(types.stsd, new Uint8Array([0x00, // version 0
  35966. 0x00, 0x00, 0x00, // flags
  35967. 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
  35968. };
  35969. videoSample = function videoSample(track) {
  35970. var sps = track.sps || [],
  35971. pps = track.pps || [],
  35972. sequenceParameterSets = [],
  35973. pictureParameterSets = [],
  35974. i; // assemble the SPSs
  35975. for (i = 0; i < sps.length; i++) {
  35976. sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
  35977. sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
  35978. sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
  35979. } // assemble the PPSs
  35980. for (i = 0; i < pps.length; i++) {
  35981. pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
  35982. pictureParameterSets.push(pps[i].byteLength & 0xFF);
  35983. pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
  35984. }
  35985. return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  35986. 0x00, 0x01, // data_reference_index
  35987. 0x00, 0x00, // pre_defined
  35988. 0x00, 0x00, // reserved
  35989. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  35990. (track.width & 0xff00) >> 8, track.width & 0xff, // width
  35991. (track.height & 0xff00) >> 8, track.height & 0xff, // height
  35992. 0x00, 0x48, 0x00, 0x00, // horizresolution
  35993. 0x00, 0x48, 0x00, 0x00, // vertresolution
  35994. 0x00, 0x00, 0x00, 0x00, // reserved
  35995. 0x00, 0x01, // frame_count
  35996. 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
  35997. 0x00, 0x18, // depth = 24
  35998. 0x11, 0x11 // pre_defined = -1
  35999. ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
  36000. track.profileIdc, // AVCProfileIndication
  36001. track.profileCompatibility, // profile_compatibility
  36002. track.levelIdc, // AVCLevelIndication
  36003. 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
  36004. ].concat([sps.length // numOfSequenceParameterSets
  36005. ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
  36006. ]).concat(pictureParameterSets))), // "PPS"
  36007. box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  36008. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  36009. 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
  36010. );
  36011. };
  36012. audioSample = function audioSample(track) {
  36013. return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
  36014. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  36015. 0x00, 0x01, // data_reference_index
  36016. // AudioSampleEntry, ISO/IEC 14496-12
  36017. 0x00, 0x00, 0x00, 0x00, // reserved
  36018. 0x00, 0x00, 0x00, 0x00, // reserved
  36019. (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
  36020. (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
  36021. 0x00, 0x00, // pre_defined
  36022. 0x00, 0x00, // reserved
  36023. (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
  36024. // MP4AudioSampleEntry, ISO/IEC 14496-14
  36025. ]), esds(track));
  36026. };
  36027. })();
  36028. tkhd = function tkhd(track) {
  36029. var result = new Uint8Array([0x00, // version 0
  36030. 0x00, 0x00, 0x07, // flags
  36031. 0x00, 0x00, 0x00, 0x00, // creation_time
  36032. 0x00, 0x00, 0x00, 0x00, // modification_time
  36033. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  36034. 0x00, 0x00, 0x00, 0x00, // reserved
  36035. (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
  36036. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  36037. 0x00, 0x00, // layer
  36038. 0x00, 0x00, // alternate_group
  36039. 0x01, 0x00, // non-audio track volume
  36040. 0x00, 0x00, // reserved
  36041. 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
  36042. (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
  36043. (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
  36044. ]);
  36045. return box(types.tkhd, result);
  36046. };
  36047. /**
  36048. * Generate a track fragment (traf) box. A traf box collects metadata
  36049. * about tracks in a movie fragment (moof) box.
  36050. */
  36051. traf = function traf(track) {
  36052. var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
  36053. trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
  36054. 0x00, 0x00, 0x3a, // flags
  36055. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  36056. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  36057. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  36058. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  36059. 0x00, 0x00, 0x00, 0x00 // default_sample_flags
  36060. ]));
  36061. upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
  36062. lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
  36063. trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
  36064. 0x00, 0x00, 0x00, // flags
  36065. // baseMediaDecodeTime
  36066. 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
  36067. // the containing moof to the first payload byte of the associated
  36068. // mdat
  36069. dataOffset = 32 + // tfhd
  36070. 20 + // tfdt
  36071. 8 + // traf header
  36072. 16 + // mfhd
  36073. 8 + // moof header
  36074. 8; // mdat header
  36075. // audio tracks require less metadata
  36076. if (track.type === 'audio') {
  36077. trackFragmentRun = trun(track, dataOffset);
  36078. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
  36079. } // video tracks should contain an independent and disposable samples
  36080. // box (sdtp)
  36081. // generate one and adjust offsets to match
  36082. sampleDependencyTable = sdtp(track);
  36083. trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
  36084. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
  36085. };
  36086. /**
  36087. * Generate a track box.
  36088. * @param track {object} a track definition
  36089. * @return {Uint8Array} the track box
  36090. */
  36091. trak = function trak(track) {
  36092. track.duration = track.duration || 0xffffffff;
  36093. return box(types.trak, tkhd(track), mdia(track));
  36094. };
  36095. trex = function trex(track) {
  36096. var result = new Uint8Array([0x00, // version 0
  36097. 0x00, 0x00, 0x00, // flags
  36098. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  36099. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  36100. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  36101. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  36102. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  36103. ]); // the last two bytes of default_sample_flags is the sample
  36104. // degradation priority, a hint about the importance of this sample
  36105. // relative to others. Lower the degradation priority for all sample
  36106. // types other than video.
  36107. if (track.type !== 'video') {
  36108. result[result.length - 1] = 0x00;
  36109. }
  36110. return box(types.trex, result);
  36111. };
  36112. (function () {
  36113. var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
  36114. // duration is present for the first sample, it will be present for
  36115. // all subsequent samples.
  36116. // see ISO/IEC 14496-12:2012, Section 8.8.8.1
  36117. trunHeader = function trunHeader(samples, offset) {
  36118. var durationPresent = 0,
  36119. sizePresent = 0,
  36120. flagsPresent = 0,
  36121. compositionTimeOffset = 0; // trun flag constants
  36122. if (samples.length) {
  36123. if (samples[0].duration !== undefined) {
  36124. durationPresent = 0x1;
  36125. }
  36126. if (samples[0].size !== undefined) {
  36127. sizePresent = 0x2;
  36128. }
  36129. if (samples[0].flags !== undefined) {
  36130. flagsPresent = 0x4;
  36131. }
  36132. if (samples[0].compositionTimeOffset !== undefined) {
  36133. compositionTimeOffset = 0x8;
  36134. }
  36135. }
  36136. return [0x00, // version 0
  36137. 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
  36138. (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
  36139. (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
  36140. ];
  36141. };
  36142. videoTrun = function videoTrun(track, offset) {
  36143. var bytes, samples, sample, i;
  36144. samples = track.samples || [];
  36145. offset += 8 + 12 + 16 * samples.length;
  36146. bytes = trunHeader(samples, offset);
  36147. for (i = 0; i < samples.length; i++) {
  36148. sample = samples[i];
  36149. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  36150. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
  36151. sample.flags.isLeading << 2 | sample.flags.dependsOn, sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample, sample.flags.degradationPriority & 0xF0 << 8, sample.flags.degradationPriority & 0x0F, // sample_flags
  36152. (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
  36153. ]);
  36154. }
  36155. return box(types.trun, new Uint8Array(bytes));
  36156. };
  36157. audioTrun = function audioTrun(track, offset) {
  36158. var bytes, samples, sample, i;
  36159. samples = track.samples || [];
  36160. offset += 8 + 12 + 8 * samples.length;
  36161. bytes = trunHeader(samples, offset);
  36162. for (i = 0; i < samples.length; i++) {
  36163. sample = samples[i];
  36164. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  36165. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
  36166. }
  36167. return box(types.trun, new Uint8Array(bytes));
  36168. };
  36169. trun = function trun(track, offset) {
  36170. if (track.type === 'audio') {
  36171. return audioTrun(track, offset);
  36172. }
  36173. return videoTrun(track, offset);
  36174. };
  36175. })();
  36176. var mp4Generator = {
  36177. ftyp: ftyp,
  36178. mdat: mdat,
  36179. moof: moof,
  36180. moov: moov,
  36181. initSegment: function initSegment(tracks) {
  36182. var fileType = ftyp(),
  36183. movie = moov(tracks),
  36184. result;
  36185. result = new Uint8Array(fileType.byteLength + movie.byteLength);
  36186. result.set(fileType);
  36187. result.set(movie, fileType.byteLength);
  36188. return result;
  36189. }
  36190. };
  36191. var toUnsigned = function toUnsigned(value) {
  36192. return value >>> 0;
  36193. };
  36194. var bin = {
  36195. toUnsigned: toUnsigned
  36196. };
  36197. var toUnsigned$1 = bin.toUnsigned;
  36198. var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path
  36199. _findBox = function findBox(data, path) {
  36200. var results = [],
  36201. i,
  36202. size,
  36203. type,
  36204. end,
  36205. subresults;
  36206. if (!path.length) {
  36207. // short-circuit the search for empty paths
  36208. return null;
  36209. }
  36210. for (i = 0; i < data.byteLength;) {
  36211. size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
  36212. type = parseType(data.subarray(i + 4, i + 8));
  36213. end = size > 1 ? i + size : data.byteLength;
  36214. if (type === path[0]) {
  36215. if (path.length === 1) {
  36216. // this is the end of the path and we've found the box we were
  36217. // looking for
  36218. results.push(data.subarray(i + 8, end));
  36219. } else {
  36220. // recursively search for the next box along the path
  36221. subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
  36222. if (subresults.length) {
  36223. results = results.concat(subresults);
  36224. }
  36225. }
  36226. }
  36227. i = end;
  36228. } // we've finished searching all of data
  36229. return results;
  36230. };
  36231. /**
  36232. * Returns the string representation of an ASCII encoded four byte buffer.
  36233. * @param buffer {Uint8Array} a four-byte buffer to translate
  36234. * @return {string} the corresponding string
  36235. */
  36236. parseType = function parseType(buffer) {
  36237. var result = '';
  36238. result += String.fromCharCode(buffer[0]);
  36239. result += String.fromCharCode(buffer[1]);
  36240. result += String.fromCharCode(buffer[2]);
  36241. result += String.fromCharCode(buffer[3]);
  36242. return result;
  36243. };
  36244. /**
  36245. * Parses an MP4 initialization segment and extracts the timescale
  36246. * values for any declared tracks. Timescale values indicate the
  36247. * number of clock ticks per second to assume for time-based values
  36248. * elsewhere in the MP4.
  36249. *
  36250. * To determine the start time of an MP4, you need two pieces of
  36251. * information: the timescale unit and the earliest base media decode
  36252. * time. Multiple timescales can be specified within an MP4 but the
  36253. * base media decode time is always expressed in the timescale from
  36254. * the media header box for the track:
  36255. * ```
  36256. * moov > trak > mdia > mdhd.timescale
  36257. * ```
  36258. * @param init {Uint8Array} the bytes of the init segment
  36259. * @return {object} a hash of track ids to timescale values or null if
  36260. * the init segment is malformed.
  36261. */
  36262. timescale = function timescale(init) {
  36263. var result = {},
  36264. traks = _findBox(init, ['moov', 'trak']); // mdhd timescale
  36265. return traks.reduce(function (result, trak) {
  36266. var tkhd, version, index, id, mdhd;
  36267. tkhd = _findBox(trak, ['tkhd'])[0];
  36268. if (!tkhd) {
  36269. return null;
  36270. }
  36271. version = tkhd[0];
  36272. index = version === 0 ? 12 : 20;
  36273. id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
  36274. mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
  36275. if (!mdhd) {
  36276. return null;
  36277. }
  36278. version = mdhd[0];
  36279. index = version === 0 ? 12 : 20;
  36280. result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
  36281. return result;
  36282. }, result);
  36283. };
  36284. /**
  36285. * Determine the base media decode start time, in seconds, for an MP4
  36286. * fragment. If multiple fragments are specified, the earliest time is
  36287. * returned.
  36288. *
  36289. * The base media decode time can be parsed from track fragment
  36290. * metadata:
  36291. * ```
  36292. * moof > traf > tfdt.baseMediaDecodeTime
  36293. * ```
  36294. * It requires the timescale value from the mdhd to interpret.
  36295. *
  36296. * @param timescale {object} a hash of track ids to timescale values.
  36297. * @return {number} the earliest base media decode start time for the
  36298. * fragment, in seconds
  36299. */
  36300. startTime = function startTime(timescale, fragment) {
  36301. var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
  36302. trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track
  36303. baseTimes = [].concat.apply([], trafs.map(function (traf) {
  36304. return _findBox(traf, ['tfhd']).map(function (tfhd) {
  36305. var id, scale, baseTime; // get the track id from the tfhd
  36306. id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
  36307. scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
  36308. baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
  36309. var version, result;
  36310. version = tfdt[0];
  36311. result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
  36312. if (version === 1) {
  36313. result *= Math.pow(2, 32);
  36314. result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
  36315. }
  36316. return result;
  36317. })[0];
  36318. baseTime = baseTime || Infinity; // convert base time to seconds
  36319. return baseTime / scale;
  36320. });
  36321. })); // return the minimum
  36322. result = Math.min.apply(null, baseTimes);
  36323. return isFinite(result) ? result : 0;
  36324. };
  36325. /**
  36326. * Find the trackIds of the video tracks in this source.
  36327. * Found by parsing the Handler Reference and Track Header Boxes:
  36328. * moov > trak > mdia > hdlr
  36329. * moov > trak > tkhd
  36330. *
  36331. * @param {Uint8Array} init - The bytes of the init segment for this source
  36332. * @return {Number[]} A list of trackIds
  36333. *
  36334. * @see ISO-BMFF-12/2015, Section 8.4.3
  36335. **/
  36336. getVideoTrackIds = function getVideoTrackIds(init) {
  36337. var traks = _findBox(init, ['moov', 'trak']);
  36338. var videoTrackIds = [];
  36339. traks.forEach(function (trak) {
  36340. var hdlrs = _findBox(trak, ['mdia', 'hdlr']);
  36341. var tkhds = _findBox(trak, ['tkhd']);
  36342. hdlrs.forEach(function (hdlr, index) {
  36343. var handlerType = parseType(hdlr.subarray(8, 12));
  36344. var tkhd = tkhds[index];
  36345. var view;
  36346. var version;
  36347. var trackId;
  36348. if (handlerType === 'vide') {
  36349. view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
  36350. version = view.getUint8(0);
  36351. trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
  36352. videoTrackIds.push(trackId);
  36353. }
  36354. });
  36355. });
  36356. return videoTrackIds;
  36357. };
  36358. var probe$$1 = {
  36359. findBox: _findBox,
  36360. parseType: parseType,
  36361. timescale: timescale,
  36362. startTime: startTime,
  36363. videoTrackIds: getVideoTrackIds
  36364. };
  36365. /**
  36366. * mux.js
  36367. *
  36368. * Copyright (c) 2014 Brightcove
  36369. * All rights reserved.
  36370. *
  36371. * A lightweight readable stream implemention that handles event dispatching.
  36372. * Objects that inherit from streams should call init in their constructors.
  36373. */
  36374. var Stream = function Stream() {
  36375. this.init = function () {
  36376. var listeners = {};
  36377. /**
  36378. * Add a listener for a specified event type.
  36379. * @param type {string} the event name
  36380. * @param listener {function} the callback to be invoked when an event of
  36381. * the specified type occurs
  36382. */
  36383. this.on = function (type, listener) {
  36384. if (!listeners[type]) {
  36385. listeners[type] = [];
  36386. }
  36387. listeners[type] = listeners[type].concat(listener);
  36388. };
  36389. /**
  36390. * Remove a listener for a specified event type.
  36391. * @param type {string} the event name
  36392. * @param listener {function} a function previously registered for this
  36393. * type of event through `on`
  36394. */
  36395. this.off = function (type, listener) {
  36396. var index;
  36397. if (!listeners[type]) {
  36398. return false;
  36399. }
  36400. index = listeners[type].indexOf(listener);
  36401. listeners[type] = listeners[type].slice();
  36402. listeners[type].splice(index, 1);
  36403. return index > -1;
  36404. };
  36405. /**
  36406. * Trigger an event of the specified type on this stream. Any additional
  36407. * arguments to this function are passed as parameters to event listeners.
  36408. * @param type {string} the event name
  36409. */
  36410. this.trigger = function (type) {
  36411. var callbacks, i, length, args;
  36412. callbacks = listeners[type];
  36413. if (!callbacks) {
  36414. return;
  36415. } // Slicing the arguments on every invocation of this method
  36416. // can add a significant amount of overhead. Avoid the
  36417. // intermediate object creation for the common case of a
  36418. // single callback argument
  36419. if (arguments.length === 2) {
  36420. length = callbacks.length;
  36421. for (i = 0; i < length; ++i) {
  36422. callbacks[i].call(this, arguments[1]);
  36423. }
  36424. } else {
  36425. args = [];
  36426. i = arguments.length;
  36427. for (i = 1; i < arguments.length; ++i) {
  36428. args.push(arguments[i]);
  36429. }
  36430. length = callbacks.length;
  36431. for (i = 0; i < length; ++i) {
  36432. callbacks[i].apply(this, args);
  36433. }
  36434. }
  36435. };
  36436. /**
  36437. * Destroys the stream and cleans up.
  36438. */
  36439. this.dispose = function () {
  36440. listeners = {};
  36441. };
  36442. };
  36443. };
  36444. /**
  36445. * Forwards all `data` events on this stream to the destination stream. The
  36446. * destination stream should provide a method `push` to receive the data
  36447. * events as they arrive.
  36448. * @param destination {stream} the stream that will receive all `data` events
  36449. * @param autoFlush {boolean} if false, we will not call `flush` on the destination
  36450. * when the current stream emits a 'done' event
  36451. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  36452. */
  36453. Stream.prototype.pipe = function (destination) {
  36454. this.on('data', function (data) {
  36455. destination.push(data);
  36456. });
  36457. this.on('done', function (flushSource) {
  36458. destination.flush(flushSource);
  36459. });
  36460. return destination;
  36461. }; // Default stream functions that are expected to be overridden to perform
  36462. // actual work. These are provided by the prototype as a sort of no-op
  36463. // implementation so that we don't have to check for their existence in the
  36464. // `pipe` function above.
  36465. Stream.prototype.push = function (data) {
  36466. this.trigger('data', data);
  36467. };
  36468. Stream.prototype.flush = function (flushSource) {
  36469. this.trigger('done', flushSource);
  36470. };
  36471. var stream = Stream; // Convert an array of nal units into an array of frames with each frame being
  36472. // composed of the nal units that make up that frame
  36473. // Also keep track of cummulative data about the frame from the nal units such
  36474. // as the frame duration, starting pts, etc.
  36475. var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
  36476. var i,
  36477. currentNal,
  36478. currentFrame = [],
  36479. frames = [];
  36480. currentFrame.byteLength = 0;
  36481. for (i = 0; i < nalUnits.length; i++) {
  36482. currentNal = nalUnits[i]; // Split on 'aud'-type nal units
  36483. if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  36484. // Since the very first nal unit is expected to be an AUD
  36485. // only push to the frames array when currentFrame is not empty
  36486. if (currentFrame.length) {
  36487. currentFrame.duration = currentNal.dts - currentFrame.dts;
  36488. frames.push(currentFrame);
  36489. }
  36490. currentFrame = [currentNal];
  36491. currentFrame.byteLength = currentNal.data.byteLength;
  36492. currentFrame.pts = currentNal.pts;
  36493. currentFrame.dts = currentNal.dts;
  36494. } else {
  36495. // Specifically flag key frames for ease of use later
  36496. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  36497. currentFrame.keyFrame = true;
  36498. }
  36499. currentFrame.duration = currentNal.dts - currentFrame.dts;
  36500. currentFrame.byteLength += currentNal.data.byteLength;
  36501. currentFrame.push(currentNal);
  36502. }
  36503. } // For the last frame, use the duration of the previous frame if we
  36504. // have nothing better to go on
  36505. if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
  36506. currentFrame.duration = frames[frames.length - 1].duration;
  36507. } // Push the final frame
  36508. frames.push(currentFrame);
  36509. return frames;
  36510. }; // Convert an array of frames into an array of Gop with each Gop being composed
  36511. // of the frames that make up that Gop
  36512. // Also keep track of cummulative data about the Gop from the frames such as the
  36513. // Gop duration, starting pts, etc.
  36514. var groupFramesIntoGops = function groupFramesIntoGops(frames) {
  36515. var i,
  36516. currentFrame,
  36517. currentGop = [],
  36518. gops = []; // We must pre-set some of the values on the Gop since we
  36519. // keep running totals of these values
  36520. currentGop.byteLength = 0;
  36521. currentGop.nalCount = 0;
  36522. currentGop.duration = 0;
  36523. currentGop.pts = frames[0].pts;
  36524. currentGop.dts = frames[0].dts; // store some metadata about all the Gops
  36525. gops.byteLength = 0;
  36526. gops.nalCount = 0;
  36527. gops.duration = 0;
  36528. gops.pts = frames[0].pts;
  36529. gops.dts = frames[0].dts;
  36530. for (i = 0; i < frames.length; i++) {
  36531. currentFrame = frames[i];
  36532. if (currentFrame.keyFrame) {
  36533. // Since the very first frame is expected to be an keyframe
  36534. // only push to the gops array when currentGop is not empty
  36535. if (currentGop.length) {
  36536. gops.push(currentGop);
  36537. gops.byteLength += currentGop.byteLength;
  36538. gops.nalCount += currentGop.nalCount;
  36539. gops.duration += currentGop.duration;
  36540. }
  36541. currentGop = [currentFrame];
  36542. currentGop.nalCount = currentFrame.length;
  36543. currentGop.byteLength = currentFrame.byteLength;
  36544. currentGop.pts = currentFrame.pts;
  36545. currentGop.dts = currentFrame.dts;
  36546. currentGop.duration = currentFrame.duration;
  36547. } else {
  36548. currentGop.duration += currentFrame.duration;
  36549. currentGop.nalCount += currentFrame.length;
  36550. currentGop.byteLength += currentFrame.byteLength;
  36551. currentGop.push(currentFrame);
  36552. }
  36553. }
  36554. if (gops.length && currentGop.duration <= 0) {
  36555. currentGop.duration = gops[gops.length - 1].duration;
  36556. }
  36557. gops.byteLength += currentGop.byteLength;
  36558. gops.nalCount += currentGop.nalCount;
  36559. gops.duration += currentGop.duration; // push the final Gop
  36560. gops.push(currentGop);
  36561. return gops;
  36562. };
  36563. /*
  36564. * Search for the first keyframe in the GOPs and throw away all frames
  36565. * until that keyframe. Then extend the duration of the pulled keyframe
  36566. * and pull the PTS and DTS of the keyframe so that it covers the time
  36567. * range of the frames that were disposed.
  36568. *
  36569. * @param {Array} gops video GOPs
  36570. * @returns {Array} modified video GOPs
  36571. */
  36572. var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
  36573. var currentGop;
  36574. if (!gops[0][0].keyFrame && gops.length > 1) {
  36575. // Remove the first GOP
  36576. currentGop = gops.shift();
  36577. gops.byteLength -= currentGop.byteLength;
  36578. gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
  36579. // first gop to cover the time period of the
  36580. // frames we just removed
  36581. gops[0][0].dts = currentGop.dts;
  36582. gops[0][0].pts = currentGop.pts;
  36583. gops[0][0].duration += currentGop.duration;
  36584. }
  36585. return gops;
  36586. };
  36587. /**
  36588. * Default sample object
  36589. * see ISO/IEC 14496-12:2012, section 8.6.4.3
  36590. */
  36591. var createDefaultSample = function createDefaultSample() {
  36592. return {
  36593. size: 0,
  36594. flags: {
  36595. isLeading: 0,
  36596. dependsOn: 1,
  36597. isDependedOn: 0,
  36598. hasRedundancy: 0,
  36599. degradationPriority: 0,
  36600. isNonSyncSample: 1
  36601. }
  36602. };
  36603. };
  36604. /*
  36605. * Collates information from a video frame into an object for eventual
  36606. * entry into an MP4 sample table.
  36607. *
  36608. * @param {Object} frame the video frame
  36609. * @param {Number} dataOffset the byte offset to position the sample
  36610. * @return {Object} object containing sample table info for a frame
  36611. */
  36612. var sampleForFrame = function sampleForFrame(frame, dataOffset) {
  36613. var sample = createDefaultSample();
  36614. sample.dataOffset = dataOffset;
  36615. sample.compositionTimeOffset = frame.pts - frame.dts;
  36616. sample.duration = frame.duration;
  36617. sample.size = 4 * frame.length; // Space for nal unit size
  36618. sample.size += frame.byteLength;
  36619. if (frame.keyFrame) {
  36620. sample.flags.dependsOn = 2;
  36621. sample.flags.isNonSyncSample = 0;
  36622. }
  36623. return sample;
  36624. }; // generate the track's sample table from an array of gops
  36625. var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
  36626. var h,
  36627. i,
  36628. sample,
  36629. currentGop,
  36630. currentFrame,
  36631. dataOffset = baseDataOffset || 0,
  36632. samples = [];
  36633. for (h = 0; h < gops.length; h++) {
  36634. currentGop = gops[h];
  36635. for (i = 0; i < currentGop.length; i++) {
  36636. currentFrame = currentGop[i];
  36637. sample = sampleForFrame(currentFrame, dataOffset);
  36638. dataOffset += sample.size;
  36639. samples.push(sample);
  36640. }
  36641. }
  36642. return samples;
  36643. }; // generate the track's raw mdat data from an array of gops
  36644. var concatenateNalData = function concatenateNalData(gops) {
  36645. var h,
  36646. i,
  36647. j,
  36648. currentGop,
  36649. currentFrame,
  36650. currentNal,
  36651. dataOffset = 0,
  36652. nalsByteLength = gops.byteLength,
  36653. numberOfNals = gops.nalCount,
  36654. totalByteLength = nalsByteLength + 4 * numberOfNals,
  36655. data = new Uint8Array(totalByteLength),
  36656. view = new DataView(data.buffer); // For each Gop..
  36657. for (h = 0; h < gops.length; h++) {
  36658. currentGop = gops[h]; // For each Frame..
  36659. for (i = 0; i < currentGop.length; i++) {
  36660. currentFrame = currentGop[i]; // For each NAL..
  36661. for (j = 0; j < currentFrame.length; j++) {
  36662. currentNal = currentFrame[j];
  36663. view.setUint32(dataOffset, currentNal.data.byteLength);
  36664. dataOffset += 4;
  36665. data.set(currentNal.data, dataOffset);
  36666. dataOffset += currentNal.data.byteLength;
  36667. }
  36668. }
  36669. }
  36670. return data;
  36671. };
  36672. var frameUtils = {
  36673. groupNalsIntoFrames: groupNalsIntoFrames,
  36674. groupFramesIntoGops: groupFramesIntoGops,
  36675. extendFirstKeyFrame: extendFirstKeyFrame,
  36676. generateSampleTable: generateSampleTable,
  36677. concatenateNalData: concatenateNalData
  36678. };
  36679. var highPrefix = [33, 16, 5, 32, 164, 27];
  36680. var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
  36681. var zeroFill = function zeroFill(count) {
  36682. var a = [];
  36683. while (count--) {
  36684. a.push(0);
  36685. }
  36686. return a;
  36687. };
  36688. var makeTable = function makeTable(metaTable) {
  36689. return Object.keys(metaTable).reduce(function (obj, key) {
  36690. obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
  36691. return arr.concat(part);
  36692. }, []));
  36693. return obj;
  36694. }, {});
  36695. }; // Frames-of-silence to use for filling in missing AAC frames
  36696. var coneOfSilence = {
  36697. 96000: [highPrefix, [227, 64], zeroFill(154), [56]],
  36698. 88200: [highPrefix, [231], zeroFill(170), [56]],
  36699. 64000: [highPrefix, [248, 192], zeroFill(240), [56]],
  36700. 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
  36701. 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
  36702. 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
  36703. 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
  36704. 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
  36705. 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]],
  36706. 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]],
  36707. 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
  36708. };
  36709. var silence = makeTable(coneOfSilence);
  36710. var ONE_SECOND_IN_TS = 90000,
  36711. // 90kHz clock
  36712. secondsToVideoTs,
  36713. secondsToAudioTs,
  36714. videoTsToSeconds,
  36715. audioTsToSeconds,
  36716. audioTsToVideoTs,
  36717. videoTsToAudioTs;
  36718. secondsToVideoTs = function secondsToVideoTs(seconds) {
  36719. return seconds * ONE_SECOND_IN_TS;
  36720. };
  36721. secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
  36722. return seconds * sampleRate;
  36723. };
  36724. videoTsToSeconds = function videoTsToSeconds(timestamp) {
  36725. return timestamp / ONE_SECOND_IN_TS;
  36726. };
  36727. audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
  36728. return timestamp / sampleRate;
  36729. };
  36730. audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
  36731. return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
  36732. };
  36733. videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
  36734. return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
  36735. };
  36736. var clock = {
  36737. secondsToVideoTs: secondsToVideoTs,
  36738. secondsToAudioTs: secondsToAudioTs,
  36739. videoTsToSeconds: videoTsToSeconds,
  36740. audioTsToSeconds: audioTsToSeconds,
  36741. audioTsToVideoTs: audioTsToVideoTs,
  36742. videoTsToAudioTs: videoTsToAudioTs
  36743. };
  36744. var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
  36745. /**
  36746. * Sum the `byteLength` properties of the data in each AAC frame
  36747. */
  36748. var sumFrameByteLengths = function sumFrameByteLengths(array) {
  36749. var i,
  36750. currentObj,
  36751. sum = 0; // sum the byteLength's all each nal unit in the frame
  36752. for (i = 0; i < array.length; i++) {
  36753. currentObj = array[i];
  36754. sum += currentObj.data.byteLength;
  36755. }
  36756. return sum;
  36757. }; // Possibly pad (prefix) the audio track with silence if appending this track
  36758. // would lead to the introduction of a gap in the audio buffer
  36759. var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
  36760. var baseMediaDecodeTimeTs,
  36761. frameDuration = 0,
  36762. audioGapDuration = 0,
  36763. audioFillFrameCount = 0,
  36764. audioFillDuration = 0,
  36765. silentFrame,
  36766. i;
  36767. if (!frames.length) {
  36768. return;
  36769. }
  36770. baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
  36771. frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
  36772. if (audioAppendStartTs && videoBaseMediaDecodeTime) {
  36773. // insert the shortest possible amount (audio gap or audio to video gap)
  36774. audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
  36775. audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
  36776. audioFillDuration = audioFillFrameCount * frameDuration;
  36777. } // don't attempt to fill gaps smaller than a single frame or larger
  36778. // than a half second
  36779. if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
  36780. return;
  36781. }
  36782. silentFrame = silence[track.samplerate];
  36783. if (!silentFrame) {
  36784. // we don't have a silent frame pregenerated for the sample rate, so use a frame
  36785. // from the content instead
  36786. silentFrame = frames[0].data;
  36787. }
  36788. for (i = 0; i < audioFillFrameCount; i++) {
  36789. frames.splice(i, 0, {
  36790. data: silentFrame
  36791. });
  36792. }
  36793. track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
  36794. }; // If the audio segment extends before the earliest allowed dts
  36795. // value, remove AAC frames until starts at or after the earliest
  36796. // allowed DTS so that we don't end up with a negative baseMedia-
  36797. // DecodeTime for the audio track
  36798. var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
  36799. if (track.minSegmentDts >= earliestAllowedDts) {
  36800. return adtsFrames;
  36801. } // We will need to recalculate the earliest segment Dts
  36802. track.minSegmentDts = Infinity;
  36803. return adtsFrames.filter(function (currentFrame) {
  36804. // If this is an allowed frame, keep it and record it's Dts
  36805. if (currentFrame.dts >= earliestAllowedDts) {
  36806. track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
  36807. track.minSegmentPts = track.minSegmentDts;
  36808. return true;
  36809. } // Otherwise, discard it
  36810. return false;
  36811. });
  36812. }; // generate the track's raw mdat data from an array of frames
  36813. var generateSampleTable$1 = function generateSampleTable(frames) {
  36814. var i,
  36815. currentFrame,
  36816. samples = [];
  36817. for (i = 0; i < frames.length; i++) {
  36818. currentFrame = frames[i];
  36819. samples.push({
  36820. size: currentFrame.data.byteLength,
  36821. duration: 1024 // For AAC audio, all samples contain 1024 samples
  36822. });
  36823. }
  36824. return samples;
  36825. }; // generate the track's sample table from an array of frames
  36826. var concatenateFrameData = function concatenateFrameData(frames) {
  36827. var i,
  36828. currentFrame,
  36829. dataOffset = 0,
  36830. data = new Uint8Array(sumFrameByteLengths(frames));
  36831. for (i = 0; i < frames.length; i++) {
  36832. currentFrame = frames[i];
  36833. data.set(currentFrame.data, dataOffset);
  36834. dataOffset += currentFrame.data.byteLength;
  36835. }
  36836. return data;
  36837. };
  36838. var audioFrameUtils = {
  36839. prefixWithSilence: prefixWithSilence,
  36840. trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
  36841. generateSampleTable: generateSampleTable$1,
  36842. concatenateFrameData: concatenateFrameData
  36843. };
  36844. var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock
  36845. /**
  36846. * Store information about the start and end of the track and the
  36847. * duration for each frame/sample we process in order to calculate
  36848. * the baseMediaDecodeTime
  36849. */
  36850. var collectDtsInfo = function collectDtsInfo(track, data) {
  36851. if (typeof data.pts === 'number') {
  36852. if (track.timelineStartInfo.pts === undefined) {
  36853. track.timelineStartInfo.pts = data.pts;
  36854. }
  36855. if (track.minSegmentPts === undefined) {
  36856. track.minSegmentPts = data.pts;
  36857. } else {
  36858. track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
  36859. }
  36860. if (track.maxSegmentPts === undefined) {
  36861. track.maxSegmentPts = data.pts;
  36862. } else {
  36863. track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
  36864. }
  36865. }
  36866. if (typeof data.dts === 'number') {
  36867. if (track.timelineStartInfo.dts === undefined) {
  36868. track.timelineStartInfo.dts = data.dts;
  36869. }
  36870. if (track.minSegmentDts === undefined) {
  36871. track.minSegmentDts = data.dts;
  36872. } else {
  36873. track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
  36874. }
  36875. if (track.maxSegmentDts === undefined) {
  36876. track.maxSegmentDts = data.dts;
  36877. } else {
  36878. track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
  36879. }
  36880. }
  36881. };
  36882. /**
  36883. * Clear values used to calculate the baseMediaDecodeTime between
  36884. * tracks
  36885. */
  36886. var clearDtsInfo = function clearDtsInfo(track) {
  36887. delete track.minSegmentDts;
  36888. delete track.maxSegmentDts;
  36889. delete track.minSegmentPts;
  36890. delete track.maxSegmentPts;
  36891. };
  36892. /**
  36893. * Calculate the track's baseMediaDecodeTime based on the earliest
  36894. * DTS the transmuxer has ever seen and the minimum DTS for the
  36895. * current track
  36896. * @param track {object} track metadata configuration
  36897. * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
  36898. * in the source; false to adjust the first segment to start at 0.
  36899. */
  36900. var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
  36901. var baseMediaDecodeTime,
  36902. scale,
  36903. minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
  36904. if (!keepOriginalTimestamps) {
  36905. minSegmentDts -= track.timelineStartInfo.dts;
  36906. } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
  36907. // we want the start of the first segment to be placed
  36908. baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
  36909. baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
  36910. baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
  36911. if (track.type === 'audio') {
  36912. // Audio has a different clock equal to the sampling_rate so we need to
  36913. // scale the PTS values into the clock rate of the track
  36914. scale = track.samplerate / ONE_SECOND_IN_TS$2;
  36915. baseMediaDecodeTime *= scale;
  36916. baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
  36917. }
  36918. return baseMediaDecodeTime;
  36919. };
  36920. var trackDecodeInfo = {
  36921. clearDtsInfo: clearDtsInfo,
  36922. calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
  36923. collectDtsInfo: collectDtsInfo
  36924. };
  36925. /**
  36926. * mux.js
  36927. *
  36928. * Copyright (c) 2015 Brightcove
  36929. * All rights reserved.
  36930. *
  36931. * Reads in-band caption information from a video elementary
  36932. * stream. Captions must follow the CEA-708 standard for injection
  36933. * into an MPEG-2 transport streams.
  36934. * @see https://en.wikipedia.org/wiki/CEA-708
  36935. * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
  36936. */
  36937. // Supplemental enhancement information (SEI) NAL units have a
  36938. // payload type field to indicate how they are to be
  36939. // interpreted. CEAS-708 caption content is always transmitted with
  36940. // payload type 0x04.
  36941. var USER_DATA_REGISTERED_ITU_T_T35 = 4,
  36942. RBSP_TRAILING_BITS = 128;
  36943. /**
  36944. * Parse a supplemental enhancement information (SEI) NAL unit.
  36945. * Stops parsing once a message of type ITU T T35 has been found.
  36946. *
  36947. * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  36948. * @return {object} the parsed SEI payload
  36949. * @see Rec. ITU-T H.264, 7.3.2.3.1
  36950. */
  36951. var parseSei = function parseSei(bytes) {
  36952. var i = 0,
  36953. result = {
  36954. payloadType: -1,
  36955. payloadSize: 0
  36956. },
  36957. payloadType = 0,
  36958. payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
  36959. while (i < bytes.byteLength) {
  36960. // stop once we have hit the end of the sei_rbsp
  36961. if (bytes[i] === RBSP_TRAILING_BITS) {
  36962. break;
  36963. } // Parse payload type
  36964. while (bytes[i] === 0xFF) {
  36965. payloadType += 255;
  36966. i++;
  36967. }
  36968. payloadType += bytes[i++]; // Parse payload size
  36969. while (bytes[i] === 0xFF) {
  36970. payloadSize += 255;
  36971. i++;
  36972. }
  36973. payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
  36974. // there can only ever be one caption message in a frame's sei
  36975. if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
  36976. result.payloadType = payloadType;
  36977. result.payloadSize = payloadSize;
  36978. result.payload = bytes.subarray(i, i + payloadSize);
  36979. break;
  36980. } // skip the payload and parse the next message
  36981. i += payloadSize;
  36982. payloadType = 0;
  36983. payloadSize = 0;
  36984. }
  36985. return result;
  36986. }; // see ANSI/SCTE 128-1 (2013), section 8.1
  36987. var parseUserData = function parseUserData(sei) {
  36988. // itu_t_t35_contry_code must be 181 (United States) for
  36989. // captions
  36990. if (sei.payload[0] !== 181) {
  36991. return null;
  36992. } // itu_t_t35_provider_code should be 49 (ATSC) for captions
  36993. if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
  36994. return null;
  36995. } // the user_identifier should be "GA94" to indicate ATSC1 data
  36996. if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
  36997. return null;
  36998. } // finally, user_data_type_code should be 0x03 for caption data
  36999. if (sei.payload[7] !== 0x03) {
  37000. return null;
  37001. } // return the user_data_type_structure and strip the trailing
  37002. // marker bits
  37003. return sei.payload.subarray(8, sei.payload.length - 1);
  37004. }; // see CEA-708-D, section 4.4
  37005. var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  37006. var results = [],
  37007. i,
  37008. count,
  37009. offset,
  37010. data; // if this is just filler, return immediately
  37011. if (!(userData[0] & 0x40)) {
  37012. return results;
  37013. } // parse out the cc_data_1 and cc_data_2 fields
  37014. count = userData[0] & 0x1f;
  37015. for (i = 0; i < count; i++) {
  37016. offset = i * 3;
  37017. data = {
  37018. type: userData[offset + 2] & 0x03,
  37019. pts: pts
  37020. }; // capture cc data when cc_valid is 1
  37021. if (userData[offset + 2] & 0x04) {
  37022. data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
  37023. results.push(data);
  37024. }
  37025. }
  37026. return results;
  37027. };
  37028. var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  37029. var length = data.byteLength,
  37030. emulationPreventionBytesPositions = [],
  37031. i = 1,
  37032. newLength,
  37033. newData; // Find all `Emulation Prevention Bytes`
  37034. while (i < length - 2) {
  37035. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  37036. emulationPreventionBytesPositions.push(i + 2);
  37037. i += 2;
  37038. } else {
  37039. i++;
  37040. }
  37041. } // If no Emulation Prevention Bytes were found just return the original
  37042. // array
  37043. if (emulationPreventionBytesPositions.length === 0) {
  37044. return data;
  37045. } // Create a new array to hold the NAL unit data
  37046. newLength = length - emulationPreventionBytesPositions.length;
  37047. newData = new Uint8Array(newLength);
  37048. var sourceIndex = 0;
  37049. for (i = 0; i < newLength; sourceIndex++, i++) {
  37050. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  37051. // Skip this byte
  37052. sourceIndex++; // Remove this position index
  37053. emulationPreventionBytesPositions.shift();
  37054. }
  37055. newData[i] = data[sourceIndex];
  37056. }
  37057. return newData;
  37058. }; // exports
  37059. var captionPacketParser = {
  37060. parseSei: parseSei,
  37061. parseUserData: parseUserData,
  37062. parseCaptionPackets: parseCaptionPackets,
  37063. discardEmulationPreventionBytes: discardEmulationPreventionBytes,
  37064. USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
  37065. }; // -----------------
  37066. // Link To Transport
  37067. // -----------------
  37068. var CaptionStream = function CaptionStream() {
  37069. CaptionStream.prototype.init.call(this);
  37070. this.captionPackets_ = [];
  37071. this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
  37072. new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
  37073. new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
  37074. new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
  37075. ];
  37076. this.reset(); // forward data and done events from CCs to this CaptionStream
  37077. this.ccStreams_.forEach(function (cc) {
  37078. cc.on('data', this.trigger.bind(this, 'data'));
  37079. cc.on('done', this.trigger.bind(this, 'done'));
  37080. }, this);
  37081. };
  37082. CaptionStream.prototype = new stream();
  37083. CaptionStream.prototype.push = function (event) {
  37084. var sei, userData, newCaptionPackets; // only examine SEI NALs
  37085. if (event.nalUnitType !== 'sei_rbsp') {
  37086. return;
  37087. } // parse the sei
  37088. sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
  37089. if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
  37090. return;
  37091. } // parse out the user data payload
  37092. userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
  37093. if (!userData) {
  37094. return;
  37095. } // Sometimes, the same segment # will be downloaded twice. To stop the
  37096. // caption data from being processed twice, we track the latest dts we've
  37097. // received and ignore everything with a dts before that. However, since
  37098. // data for a specific dts can be split across packets on either side of
  37099. // a segment boundary, we need to make sure we *don't* ignore the packets
  37100. // from the *next* segment that have dts === this.latestDts_. By constantly
  37101. // tracking the number of packets received with dts === this.latestDts_, we
  37102. // know how many should be ignored once we start receiving duplicates.
  37103. if (event.dts < this.latestDts_) {
  37104. // We've started getting older data, so set the flag.
  37105. this.ignoreNextEqualDts_ = true;
  37106. return;
  37107. } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
  37108. this.numSameDts_--;
  37109. if (!this.numSameDts_) {
  37110. // We've received the last duplicate packet, time to start processing again
  37111. this.ignoreNextEqualDts_ = false;
  37112. }
  37113. return;
  37114. } // parse out CC data packets and save them for later
  37115. newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
  37116. this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
  37117. if (this.latestDts_ !== event.dts) {
  37118. this.numSameDts_ = 0;
  37119. }
  37120. this.numSameDts_++;
  37121. this.latestDts_ = event.dts;
  37122. };
  37123. CaptionStream.prototype.flush = function () {
  37124. // make sure we actually parsed captions before proceeding
  37125. if (!this.captionPackets_.length) {
  37126. this.ccStreams_.forEach(function (cc) {
  37127. cc.flush();
  37128. }, this);
  37129. return;
  37130. } // In Chrome, the Array#sort function is not stable so add a
  37131. // presortIndex that we can use to ensure we get a stable-sort
  37132. this.captionPackets_.forEach(function (elem, idx) {
  37133. elem.presortIndex = idx;
  37134. }); // sort caption byte-pairs based on their PTS values
  37135. this.captionPackets_.sort(function (a, b) {
  37136. if (a.pts === b.pts) {
  37137. return a.presortIndex - b.presortIndex;
  37138. }
  37139. return a.pts - b.pts;
  37140. });
  37141. this.captionPackets_.forEach(function (packet) {
  37142. if (packet.type < 2) {
  37143. // Dispatch packet to the right Cea608Stream
  37144. this.dispatchCea608Packet(packet);
  37145. } // this is where an 'else' would go for a dispatching packets
  37146. // to a theoretical Cea708Stream that handles SERVICEn data
  37147. }, this);
  37148. this.captionPackets_.length = 0;
  37149. this.ccStreams_.forEach(function (cc) {
  37150. cc.flush();
  37151. }, this);
  37152. return;
  37153. };
  37154. CaptionStream.prototype.reset = function () {
  37155. this.latestDts_ = null;
  37156. this.ignoreNextEqualDts_ = false;
  37157. this.numSameDts_ = 0;
  37158. this.activeCea608Channel_ = [null, null];
  37159. this.ccStreams_.forEach(function (ccStream) {
  37160. ccStream.reset();
  37161. });
  37162. };
  37163. CaptionStream.prototype.dispatchCea608Packet = function (packet) {
  37164. // NOTE: packet.type is the CEA608 field
  37165. if (this.setsChannel1Active(packet)) {
  37166. this.activeCea608Channel_[packet.type] = 0;
  37167. } else if (this.setsChannel2Active(packet)) {
  37168. this.activeCea608Channel_[packet.type] = 1;
  37169. }
  37170. if (this.activeCea608Channel_[packet.type] === null) {
  37171. // If we haven't received anything to set the active channel, discard the
  37172. // data; we don't want jumbled captions
  37173. return;
  37174. }
  37175. this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
  37176. };
  37177. CaptionStream.prototype.setsChannel1Active = function (packet) {
  37178. return (packet.ccData & 0x7800) === 0x1000;
  37179. };
  37180. CaptionStream.prototype.setsChannel2Active = function (packet) {
  37181. return (packet.ccData & 0x7800) === 0x1800;
  37182. }; // ----------------------
  37183. // Session to Application
  37184. // ----------------------
  37185. // This hash maps non-ASCII, special, and extended character codes to their
  37186. // proper Unicode equivalent. The first keys that are only a single byte
  37187. // are the non-standard ASCII characters, which simply map the CEA608 byte
  37188. // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
  37189. // character codes, but have their MSB bitmasked with 0x03 so that a lookup
  37190. // can be performed regardless of the field and data channel on which the
  37191. // character code was received.
  37192. var CHARACTER_TRANSLATION = {
  37193. 0x2a: 0xe1,
  37194. // á
  37195. 0x5c: 0xe9,
  37196. // é
  37197. 0x5e: 0xed,
  37198. // í
  37199. 0x5f: 0xf3,
  37200. // ó
  37201. 0x60: 0xfa,
  37202. // ú
  37203. 0x7b: 0xe7,
  37204. // ç
  37205. 0x7c: 0xf7,
  37206. // ÷
  37207. 0x7d: 0xd1,
  37208. // Ñ
  37209. 0x7e: 0xf1,
  37210. // ñ
  37211. 0x7f: 0x2588,
  37212. // █
  37213. 0x0130: 0xae,
  37214. // ®
  37215. 0x0131: 0xb0,
  37216. // °
  37217. 0x0132: 0xbd,
  37218. // ½
  37219. 0x0133: 0xbf,
  37220. // ¿
  37221. 0x0134: 0x2122,
  37222. // ™
  37223. 0x0135: 0xa2,
  37224. // ¢
  37225. 0x0136: 0xa3,
  37226. // £
  37227. 0x0137: 0x266a,
  37228. // ♪
  37229. 0x0138: 0xe0,
  37230. // à
  37231. 0x0139: 0xa0,
  37232. //
  37233. 0x013a: 0xe8,
  37234. // è
  37235. 0x013b: 0xe2,
  37236. // â
  37237. 0x013c: 0xea,
  37238. // ê
  37239. 0x013d: 0xee,
  37240. // î
  37241. 0x013e: 0xf4,
  37242. // ô
  37243. 0x013f: 0xfb,
  37244. // û
  37245. 0x0220: 0xc1,
  37246. // Á
  37247. 0x0221: 0xc9,
  37248. // É
  37249. 0x0222: 0xd3,
  37250. // Ó
  37251. 0x0223: 0xda,
  37252. // Ú
  37253. 0x0224: 0xdc,
  37254. // Ü
  37255. 0x0225: 0xfc,
  37256. // ü
  37257. 0x0226: 0x2018,
  37258. // ‘
  37259. 0x0227: 0xa1,
  37260. // ¡
  37261. 0x0228: 0x2a,
  37262. // *
  37263. 0x0229: 0x27,
  37264. // '
  37265. 0x022a: 0x2014,
  37266. // —
  37267. 0x022b: 0xa9,
  37268. // ©
  37269. 0x022c: 0x2120,
  37270. // ℠
  37271. 0x022d: 0x2022,
  37272. // •
  37273. 0x022e: 0x201c,
  37274. // “
  37275. 0x022f: 0x201d,
  37276. // ”
  37277. 0x0230: 0xc0,
  37278. // À
  37279. 0x0231: 0xc2,
  37280. // Â
  37281. 0x0232: 0xc7,
  37282. // Ç
  37283. 0x0233: 0xc8,
  37284. // È
  37285. 0x0234: 0xca,
  37286. // Ê
  37287. 0x0235: 0xcb,
  37288. // Ë
  37289. 0x0236: 0xeb,
  37290. // ë
  37291. 0x0237: 0xce,
  37292. // Î
  37293. 0x0238: 0xcf,
  37294. // Ï
  37295. 0x0239: 0xef,
  37296. // ï
  37297. 0x023a: 0xd4,
  37298. // Ô
  37299. 0x023b: 0xd9,
  37300. // Ù
  37301. 0x023c: 0xf9,
  37302. // ù
  37303. 0x023d: 0xdb,
  37304. // Û
  37305. 0x023e: 0xab,
  37306. // «
  37307. 0x023f: 0xbb,
  37308. // »
  37309. 0x0320: 0xc3,
  37310. // Ã
  37311. 0x0321: 0xe3,
  37312. // ã
  37313. 0x0322: 0xcd,
  37314. // Í
  37315. 0x0323: 0xcc,
  37316. // Ì
  37317. 0x0324: 0xec,
  37318. // ì
  37319. 0x0325: 0xd2,
  37320. // Ò
  37321. 0x0326: 0xf2,
  37322. // ò
  37323. 0x0327: 0xd5,
  37324. // Õ
  37325. 0x0328: 0xf5,
  37326. // õ
  37327. 0x0329: 0x7b,
  37328. // {
  37329. 0x032a: 0x7d,
  37330. // }
  37331. 0x032b: 0x5c,
  37332. // \
  37333. 0x032c: 0x5e,
  37334. // ^
  37335. 0x032d: 0x5f,
  37336. // _
  37337. 0x032e: 0x7c,
  37338. // |
  37339. 0x032f: 0x7e,
  37340. // ~
  37341. 0x0330: 0xc4,
  37342. // Ä
  37343. 0x0331: 0xe4,
  37344. // ä
  37345. 0x0332: 0xd6,
  37346. // Ö
  37347. 0x0333: 0xf6,
  37348. // ö
  37349. 0x0334: 0xdf,
  37350. // ß
  37351. 0x0335: 0xa5,
  37352. // ¥
  37353. 0x0336: 0xa4,
  37354. // ¤
  37355. 0x0337: 0x2502,
  37356. // │
  37357. 0x0338: 0xc5,
  37358. // Å
  37359. 0x0339: 0xe5,
  37360. // å
  37361. 0x033a: 0xd8,
  37362. // Ø
  37363. 0x033b: 0xf8,
  37364. // ø
  37365. 0x033c: 0x250c,
  37366. // ┌
  37367. 0x033d: 0x2510,
  37368. // ┐
  37369. 0x033e: 0x2514,
  37370. // └
  37371. 0x033f: 0x2518 // ┘
  37372. };
  37373. var getCharFromCode = function getCharFromCode(code) {
  37374. if (code === null) {
  37375. return '';
  37376. }
  37377. code = CHARACTER_TRANSLATION[code] || code;
  37378. return String.fromCharCode(code);
  37379. }; // the index of the last row in a CEA-608 display buffer
  37380. var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
  37381. // getting it through bit logic.
  37382. 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
  37383. // cells. The "bottom" row is the last element in the outer array.
  37384. var createDisplayBuffer = function createDisplayBuffer() {
  37385. var result = [],
  37386. i = BOTTOM_ROW + 1;
  37387. while (i--) {
  37388. result.push('');
  37389. }
  37390. return result;
  37391. };
  37392. var Cea608Stream = function Cea608Stream(field, dataChannel) {
  37393. Cea608Stream.prototype.init.call(this);
  37394. this.field_ = field || 0;
  37395. this.dataChannel_ = dataChannel || 0;
  37396. this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
  37397. this.setConstants();
  37398. this.reset();
  37399. this.push = function (packet) {
  37400. var data, swap, char0, char1, text; // remove the parity bits
  37401. data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
  37402. if (data === this.lastControlCode_) {
  37403. this.lastControlCode_ = null;
  37404. return;
  37405. } // Store control codes
  37406. if ((data & 0xf000) === 0x1000) {
  37407. this.lastControlCode_ = data;
  37408. } else if (data !== this.PADDING_) {
  37409. this.lastControlCode_ = null;
  37410. }
  37411. char0 = data >>> 8;
  37412. char1 = data & 0xff;
  37413. if (data === this.PADDING_) {
  37414. return;
  37415. } else if (data === this.RESUME_CAPTION_LOADING_) {
  37416. this.mode_ = 'popOn';
  37417. } else if (data === this.END_OF_CAPTION_) {
  37418. // If an EOC is received while in paint-on mode, the displayed caption
  37419. // text should be swapped to non-displayed memory as if it was a pop-on
  37420. // caption. Because of that, we should explicitly switch back to pop-on
  37421. // mode
  37422. this.mode_ = 'popOn';
  37423. this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
  37424. this.flushDisplayed(packet.pts); // flip memory
  37425. swap = this.displayed_;
  37426. this.displayed_ = this.nonDisplayed_;
  37427. this.nonDisplayed_ = swap; // start measuring the time to display the caption
  37428. this.startPts_ = packet.pts;
  37429. } else if (data === this.ROLL_UP_2_ROWS_) {
  37430. this.rollUpRows_ = 2;
  37431. this.setRollUp(packet.pts);
  37432. } else if (data === this.ROLL_UP_3_ROWS_) {
  37433. this.rollUpRows_ = 3;
  37434. this.setRollUp(packet.pts);
  37435. } else if (data === this.ROLL_UP_4_ROWS_) {
  37436. this.rollUpRows_ = 4;
  37437. this.setRollUp(packet.pts);
  37438. } else if (data === this.CARRIAGE_RETURN_) {
  37439. this.clearFormatting(packet.pts);
  37440. this.flushDisplayed(packet.pts);
  37441. this.shiftRowsUp_();
  37442. this.startPts_ = packet.pts;
  37443. } else if (data === this.BACKSPACE_) {
  37444. if (this.mode_ === 'popOn') {
  37445. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  37446. } else {
  37447. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  37448. }
  37449. } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
  37450. this.flushDisplayed(packet.pts);
  37451. this.displayed_ = createDisplayBuffer();
  37452. } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
  37453. this.nonDisplayed_ = createDisplayBuffer();
  37454. } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
  37455. if (this.mode_ !== 'paintOn') {
  37456. // NOTE: This should be removed when proper caption positioning is
  37457. // implemented
  37458. this.flushDisplayed(packet.pts);
  37459. this.displayed_ = createDisplayBuffer();
  37460. }
  37461. this.mode_ = 'paintOn';
  37462. this.startPts_ = packet.pts; // Append special characters to caption text
  37463. } else if (this.isSpecialCharacter(char0, char1)) {
  37464. // Bitmask char0 so that we can apply character transformations
  37465. // regardless of field and data channel.
  37466. // Then byte-shift to the left and OR with char1 so we can pass the
  37467. // entire character code to `getCharFromCode`.
  37468. char0 = (char0 & 0x03) << 8;
  37469. text = getCharFromCode(char0 | char1);
  37470. this[this.mode_](packet.pts, text);
  37471. this.column_++; // Append extended characters to caption text
  37472. } else if (this.isExtCharacter(char0, char1)) {
  37473. // Extended characters always follow their "non-extended" equivalents.
  37474. // IE if a "è" is desired, you'll always receive "eè"; non-compliant
  37475. // decoders are supposed to drop the "è", while compliant decoders
  37476. // backspace the "e" and insert "è".
  37477. // Delete the previous character
  37478. if (this.mode_ === 'popOn') {
  37479. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  37480. } else {
  37481. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  37482. } // Bitmask char0 so that we can apply character transformations
  37483. // regardless of field and data channel.
  37484. // Then byte-shift to the left and OR with char1 so we can pass the
  37485. // entire character code to `getCharFromCode`.
  37486. char0 = (char0 & 0x03) << 8;
  37487. text = getCharFromCode(char0 | char1);
  37488. this[this.mode_](packet.pts, text);
  37489. this.column_++; // Process mid-row codes
  37490. } else if (this.isMidRowCode(char0, char1)) {
  37491. // Attributes are not additive, so clear all formatting
  37492. this.clearFormatting(packet.pts); // According to the standard, mid-row codes
  37493. // should be replaced with spaces, so add one now
  37494. this[this.mode_](packet.pts, ' ');
  37495. this.column_++;
  37496. if ((char1 & 0xe) === 0xe) {
  37497. this.addFormatting(packet.pts, ['i']);
  37498. }
  37499. if ((char1 & 0x1) === 0x1) {
  37500. this.addFormatting(packet.pts, ['u']);
  37501. } // Detect offset control codes and adjust cursor
  37502. } else if (this.isOffsetControlCode(char0, char1)) {
  37503. // Cursor position is set by indent PAC (see below) in 4-column
  37504. // increments, with an additional offset code of 1-3 to reach any
  37505. // of the 32 columns specified by CEA-608. So all we need to do
  37506. // here is increment the column cursor by the given offset.
  37507. this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
  37508. } else if (this.isPAC(char0, char1)) {
  37509. // There's no logic for PAC -> row mapping, so we have to just
  37510. // find the row code in an array and use its index :(
  37511. var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
  37512. if (this.mode_ === 'rollUp') {
  37513. // This implies that the base row is incorrectly set.
  37514. // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
  37515. // of roll-up rows set.
  37516. if (row - this.rollUpRows_ + 1 < 0) {
  37517. row = this.rollUpRows_ - 1;
  37518. }
  37519. this.setRollUp(packet.pts, row);
  37520. }
  37521. if (row !== this.row_) {
  37522. // formatting is only persistent for current row
  37523. this.clearFormatting(packet.pts);
  37524. this.row_ = row;
  37525. } // All PACs can apply underline, so detect and apply
  37526. // (All odd-numbered second bytes set underline)
  37527. if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
  37528. this.addFormatting(packet.pts, ['u']);
  37529. }
  37530. if ((data & 0x10) === 0x10) {
  37531. // We've got an indent level code. Each successive even number
  37532. // increments the column cursor by 4, so we can get the desired
  37533. // column position by bit-shifting to the right (to get n/2)
  37534. // and multiplying by 4.
  37535. this.column_ = ((data & 0xe) >> 1) * 4;
  37536. }
  37537. if (this.isColorPAC(char1)) {
  37538. // it's a color code, though we only support white, which
  37539. // can be either normal or italicized. white italics can be
  37540. // either 0x4e or 0x6e depending on the row, so we just
  37541. // bitwise-and with 0xe to see if italics should be turned on
  37542. if ((char1 & 0xe) === 0xe) {
  37543. this.addFormatting(packet.pts, ['i']);
  37544. }
  37545. } // We have a normal character in char0, and possibly one in char1
  37546. } else if (this.isNormalChar(char0)) {
  37547. if (char1 === 0x00) {
  37548. char1 = null;
  37549. }
  37550. text = getCharFromCode(char0);
  37551. text += getCharFromCode(char1);
  37552. this[this.mode_](packet.pts, text);
  37553. this.column_ += text.length;
  37554. } // finish data processing
  37555. };
  37556. };
  37557. Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
  37558. // display buffer
  37559. Cea608Stream.prototype.flushDisplayed = function (pts) {
  37560. var content = this.displayed_ // remove spaces from the start and end of the string
  37561. .map(function (row) {
  37562. try {
  37563. return row.trim();
  37564. } catch (e) {
  37565. // Ordinarily, this shouldn't happen. However, caption
  37566. // parsing errors should not throw exceptions and
  37567. // break playback.
  37568. // eslint-disable-next-line no-console
  37569. console.error('Skipping malformed caption.');
  37570. return '';
  37571. }
  37572. }) // combine all text rows to display in one cue
  37573. .join('\n') // and remove blank rows from the start and end, but not the middle
  37574. .replace(/^\n+|\n+$/g, '');
  37575. if (content.length) {
  37576. this.trigger('data', {
  37577. startPts: this.startPts_,
  37578. endPts: pts,
  37579. text: content,
  37580. stream: this.name_
  37581. });
  37582. }
  37583. };
  37584. /**
  37585. * Zero out the data, used for startup and on seek
  37586. */
  37587. Cea608Stream.prototype.reset = function () {
  37588. this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
  37589. // actually display captions. If a caption is shifted to a row
  37590. // with a lower index than this, it is cleared from the display
  37591. // buffer
  37592. this.topRow_ = 0;
  37593. this.startPts_ = 0;
  37594. this.displayed_ = createDisplayBuffer();
  37595. this.nonDisplayed_ = createDisplayBuffer();
  37596. this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
  37597. this.column_ = 0;
  37598. this.row_ = BOTTOM_ROW;
  37599. this.rollUpRows_ = 2; // This variable holds currently-applied formatting
  37600. this.formatting_ = [];
  37601. };
  37602. /**
  37603. * Sets up control code and related constants for this instance
  37604. */
  37605. Cea608Stream.prototype.setConstants = function () {
  37606. // The following attributes have these uses:
  37607. // ext_ : char0 for mid-row codes, and the base for extended
  37608. // chars (ext_+0, ext_+1, and ext_+2 are char0s for
  37609. // extended codes)
  37610. // control_: char0 for control codes, except byte-shifted to the
  37611. // left so that we can do this.control_ | CONTROL_CODE
  37612. // offset_: char0 for tab offset codes
  37613. //
  37614. // It's also worth noting that control codes, and _only_ control codes,
  37615. // differ between field 1 and field2. Field 2 control codes are always
  37616. // their field 1 value plus 1. That's why there's the "| field" on the
  37617. // control value.
  37618. if (this.dataChannel_ === 0) {
  37619. this.BASE_ = 0x10;
  37620. this.EXT_ = 0x11;
  37621. this.CONTROL_ = (0x14 | this.field_) << 8;
  37622. this.OFFSET_ = 0x17;
  37623. } else if (this.dataChannel_ === 1) {
  37624. this.BASE_ = 0x18;
  37625. this.EXT_ = 0x19;
  37626. this.CONTROL_ = (0x1c | this.field_) << 8;
  37627. this.OFFSET_ = 0x1f;
  37628. } // Constants for the LSByte command codes recognized by Cea608Stream. This
  37629. // list is not exhaustive. For a more comprehensive listing and semantics see
  37630. // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
  37631. // Padding
  37632. this.PADDING_ = 0x0000; // Pop-on Mode
  37633. this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
  37634. this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
  37635. this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
  37636. this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
  37637. this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
  37638. this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
  37639. this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
  37640. this.BACKSPACE_ = this.CONTROL_ | 0x21;
  37641. this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
  37642. this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
  37643. };
  37644. /**
  37645. * Detects if the 2-byte packet data is a special character
  37646. *
  37647. * Special characters have a second byte in the range 0x30 to 0x3f,
  37648. * with the first byte being 0x11 (for data channel 1) or 0x19 (for
  37649. * data channel 2).
  37650. *
  37651. * @param {Integer} char0 The first byte
  37652. * @param {Integer} char1 The second byte
  37653. * @return {Boolean} Whether the 2 bytes are an special character
  37654. */
  37655. Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
  37656. return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
  37657. };
  37658. /**
  37659. * Detects if the 2-byte packet data is an extended character
  37660. *
  37661. * Extended characters have a second byte in the range 0x20 to 0x3f,
  37662. * with the first byte being 0x12 or 0x13 (for data channel 1) or
  37663. * 0x1a or 0x1b (for data channel 2).
  37664. *
  37665. * @param {Integer} char0 The first byte
  37666. * @param {Integer} char1 The second byte
  37667. * @return {Boolean} Whether the 2 bytes are an extended character
  37668. */
  37669. Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
  37670. return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
  37671. };
  37672. /**
  37673. * Detects if the 2-byte packet is a mid-row code
  37674. *
  37675. * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
  37676. * the first byte being 0x11 (for data channel 1) or 0x19 (for data
  37677. * channel 2).
  37678. *
  37679. * @param {Integer} char0 The first byte
  37680. * @param {Integer} char1 The second byte
  37681. * @return {Boolean} Whether the 2 bytes are a mid-row code
  37682. */
  37683. Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
  37684. return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
  37685. };
  37686. /**
  37687. * Detects if the 2-byte packet is an offset control code
  37688. *
  37689. * Offset control codes have a second byte in the range 0x21 to 0x23,
  37690. * with the first byte being 0x17 (for data channel 1) or 0x1f (for
  37691. * data channel 2).
  37692. *
  37693. * @param {Integer} char0 The first byte
  37694. * @param {Integer} char1 The second byte
  37695. * @return {Boolean} Whether the 2 bytes are an offset control code
  37696. */
  37697. Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
  37698. return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
  37699. };
  37700. /**
  37701. * Detects if the 2-byte packet is a Preamble Address Code
  37702. *
  37703. * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
  37704. * or 0x18 to 0x1f (for data channel 2), with the second byte in the
  37705. * range 0x40 to 0x7f.
  37706. *
  37707. * @param {Integer} char0 The first byte
  37708. * @param {Integer} char1 The second byte
  37709. * @return {Boolean} Whether the 2 bytes are a PAC
  37710. */
  37711. Cea608Stream.prototype.isPAC = function (char0, char1) {
  37712. return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
  37713. };
  37714. /**
  37715. * Detects if a packet's second byte is in the range of a PAC color code
  37716. *
  37717. * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
  37718. * 0x60 to 0x6f.
  37719. *
  37720. * @param {Integer} char1 The second byte
  37721. * @return {Boolean} Whether the byte is a color PAC
  37722. */
  37723. Cea608Stream.prototype.isColorPAC = function (char1) {
  37724. return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
  37725. };
  37726. /**
  37727. * Detects if a single byte is in the range of a normal character
  37728. *
  37729. * Normal text bytes are in the range 0x20 to 0x7f.
  37730. *
  37731. * @param {Integer} char The byte
  37732. * @return {Boolean} Whether the byte is a normal character
  37733. */
  37734. Cea608Stream.prototype.isNormalChar = function (char) {
  37735. return char >= 0x20 && char <= 0x7f;
  37736. };
  37737. /**
  37738. * Configures roll-up
  37739. *
  37740. * @param {Integer} pts Current PTS
  37741. * @param {Integer} newBaseRow Used by PACs to slide the current window to
  37742. * a new position
  37743. */
  37744. Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
  37745. // Reset the base row to the bottom row when switching modes
  37746. if (this.mode_ !== 'rollUp') {
  37747. this.row_ = BOTTOM_ROW;
  37748. this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
  37749. this.flushDisplayed(pts);
  37750. this.nonDisplayed_ = createDisplayBuffer();
  37751. this.displayed_ = createDisplayBuffer();
  37752. }
  37753. if (newBaseRow !== undefined && newBaseRow !== this.row_) {
  37754. // move currently displayed captions (up or down) to the new base row
  37755. for (var i = 0; i < this.rollUpRows_; i++) {
  37756. this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
  37757. this.displayed_[this.row_ - i] = '';
  37758. }
  37759. }
  37760. if (newBaseRow === undefined) {
  37761. newBaseRow = this.row_;
  37762. }
  37763. this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
  37764. }; // Adds the opening HTML tag for the passed character to the caption text,
  37765. // and keeps track of it for later closing
  37766. Cea608Stream.prototype.addFormatting = function (pts, format) {
  37767. this.formatting_ = this.formatting_.concat(format);
  37768. var text = format.reduce(function (text, format) {
  37769. return text + '<' + format + '>';
  37770. }, '');
  37771. this[this.mode_](pts, text);
  37772. }; // Adds HTML closing tags for current formatting to caption text and
  37773. // clears remembered formatting
  37774. Cea608Stream.prototype.clearFormatting = function (pts) {
  37775. if (!this.formatting_.length) {
  37776. return;
  37777. }
  37778. var text = this.formatting_.reverse().reduce(function (text, format) {
  37779. return text + '</' + format + '>';
  37780. }, '');
  37781. this.formatting_ = [];
  37782. this[this.mode_](pts, text);
  37783. }; // Mode Implementations
  37784. Cea608Stream.prototype.popOn = function (pts, text) {
  37785. var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
  37786. baseRow += text;
  37787. this.nonDisplayed_[this.row_] = baseRow;
  37788. };
  37789. Cea608Stream.prototype.rollUp = function (pts, text) {
  37790. var baseRow = this.displayed_[this.row_];
  37791. baseRow += text;
  37792. this.displayed_[this.row_] = baseRow;
  37793. };
  37794. Cea608Stream.prototype.shiftRowsUp_ = function () {
  37795. var i; // clear out inactive rows
  37796. for (i = 0; i < this.topRow_; i++) {
  37797. this.displayed_[i] = '';
  37798. }
  37799. for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
  37800. this.displayed_[i] = '';
  37801. } // shift displayed rows up
  37802. for (i = this.topRow_; i < this.row_; i++) {
  37803. this.displayed_[i] = this.displayed_[i + 1];
  37804. } // clear out the bottom row
  37805. this.displayed_[this.row_] = '';
  37806. };
  37807. Cea608Stream.prototype.paintOn = function (pts, text) {
  37808. var baseRow = this.displayed_[this.row_];
  37809. baseRow += text;
  37810. this.displayed_[this.row_] = baseRow;
  37811. }; // exports
  37812. var captionStream = {
  37813. CaptionStream: CaptionStream,
  37814. Cea608Stream: Cea608Stream
  37815. };
  37816. var streamTypes = {
  37817. H264_STREAM_TYPE: 0x1B,
  37818. ADTS_STREAM_TYPE: 0x0F,
  37819. METADATA_STREAM_TYPE: 0x15
  37820. };
  37821. var MAX_TS = 8589934592;
  37822. var RO_THRESH = 4294967296;
  37823. var handleRollover = function handleRollover(value, reference) {
  37824. var direction = 1;
  37825. if (value > reference) {
  37826. // If the current timestamp value is greater than our reference timestamp and we detect a
  37827. // timestamp rollover, this means the roll over is happening in the opposite direction.
  37828. // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
  37829. // point will be set to a small number, e.g. 1. The user then seeks backwards over the
  37830. // rollover point. In loading this segment, the timestamp values will be very large,
  37831. // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
  37832. // the time stamp to be `value - 2^33`.
  37833. direction = -1;
  37834. } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
  37835. // cause an incorrect adjustment.
  37836. while (Math.abs(reference - value) > RO_THRESH) {
  37837. value += direction * MAX_TS;
  37838. }
  37839. return value;
  37840. };
  37841. var TimestampRolloverStream = function TimestampRolloverStream(type) {
  37842. var lastDTS, referenceDTS;
  37843. TimestampRolloverStream.prototype.init.call(this);
  37844. this.type_ = type;
  37845. this.push = function (data) {
  37846. if (data.type !== this.type_) {
  37847. return;
  37848. }
  37849. if (referenceDTS === undefined) {
  37850. referenceDTS = data.dts;
  37851. }
  37852. data.dts = handleRollover(data.dts, referenceDTS);
  37853. data.pts = handleRollover(data.pts, referenceDTS);
  37854. lastDTS = data.dts;
  37855. this.trigger('data', data);
  37856. };
  37857. this.flush = function () {
  37858. referenceDTS = lastDTS;
  37859. this.trigger('done');
  37860. };
  37861. this.discontinuity = function () {
  37862. referenceDTS = void 0;
  37863. lastDTS = void 0;
  37864. };
  37865. };
  37866. TimestampRolloverStream.prototype = new stream();
  37867. var timestampRolloverStream = {
  37868. TimestampRolloverStream: TimestampRolloverStream,
  37869. handleRollover: handleRollover
  37870. };
  37871. var percentEncode = function percentEncode(bytes, start, end) {
  37872. var i,
  37873. result = '';
  37874. for (i = start; i < end; i++) {
  37875. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  37876. }
  37877. return result;
  37878. },
  37879. // return the string representation of the specified byte range,
  37880. // interpreted as UTf-8.
  37881. parseUtf8 = function parseUtf8(bytes, start, end) {
  37882. return decodeURIComponent(percentEncode(bytes, start, end));
  37883. },
  37884. // return the string representation of the specified byte range,
  37885. // interpreted as ISO-8859-1.
  37886. parseIso88591 = function parseIso88591(bytes, start, end) {
  37887. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  37888. },
  37889. parseSyncSafeInteger = function parseSyncSafeInteger(data) {
  37890. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  37891. },
  37892. tagParsers = {
  37893. TXXX: function TXXX(tag) {
  37894. var i;
  37895. if (tag.data[0] !== 3) {
  37896. // ignore frames with unrecognized character encodings
  37897. return;
  37898. }
  37899. for (i = 1; i < tag.data.length; i++) {
  37900. if (tag.data[i] === 0) {
  37901. // parse the text fields
  37902. tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
  37903. tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
  37904. break;
  37905. }
  37906. }
  37907. tag.data = tag.value;
  37908. },
  37909. WXXX: function WXXX(tag) {
  37910. var i;
  37911. if (tag.data[0] !== 3) {
  37912. // ignore frames with unrecognized character encodings
  37913. return;
  37914. }
  37915. for (i = 1; i < tag.data.length; i++) {
  37916. if (tag.data[i] === 0) {
  37917. // parse the description and URL fields
  37918. tag.description = parseUtf8(tag.data, 1, i);
  37919. tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
  37920. break;
  37921. }
  37922. }
  37923. },
  37924. PRIV: function PRIV(tag) {
  37925. var i;
  37926. for (i = 0; i < tag.data.length; i++) {
  37927. if (tag.data[i] === 0) {
  37928. // parse the description and URL fields
  37929. tag.owner = parseIso88591(tag.data, 0, i);
  37930. break;
  37931. }
  37932. }
  37933. tag.privateData = tag.data.subarray(i + 1);
  37934. tag.data = tag.privateData;
  37935. }
  37936. },
  37937. _MetadataStream;
  37938. _MetadataStream = function MetadataStream(options) {
  37939. var settings = {
  37940. debug: !!(options && options.debug),
  37941. // the bytes of the program-level descriptor field in MP2T
  37942. // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
  37943. // program element descriptors"
  37944. descriptor: options && options.descriptor
  37945. },
  37946. // the total size in bytes of the ID3 tag being parsed
  37947. tagSize = 0,
  37948. // tag data that is not complete enough to be parsed
  37949. buffer = [],
  37950. // the total number of bytes currently in the buffer
  37951. bufferSize = 0,
  37952. i;
  37953. _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
  37954. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  37955. this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
  37956. if (settings.descriptor) {
  37957. for (i = 0; i < settings.descriptor.length; i++) {
  37958. this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
  37959. }
  37960. }
  37961. this.push = function (chunk) {
  37962. var tag, frameStart, frameSize, frame, i, frameHeader;
  37963. if (chunk.type !== 'timed-metadata') {
  37964. return;
  37965. } // if data_alignment_indicator is set in the PES header,
  37966. // we must have the start of a new ID3 tag. Assume anything
  37967. // remaining in the buffer was malformed and throw it out
  37968. if (chunk.dataAlignmentIndicator) {
  37969. bufferSize = 0;
  37970. buffer.length = 0;
  37971. } // ignore events that don't look like ID3 data
  37972. 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))) {
  37973. if (settings.debug) {
  37974. // eslint-disable-next-line no-console
  37975. console.log('Skipping unrecognized metadata packet');
  37976. }
  37977. return;
  37978. } // add this chunk to the data we've collected so far
  37979. buffer.push(chunk);
  37980. bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
  37981. if (buffer.length === 1) {
  37982. // the frame size is transmitted as a 28-bit integer in the
  37983. // last four bytes of the ID3 header.
  37984. // The most significant bit of each byte is dropped and the
  37985. // results concatenated to recover the actual value.
  37986. tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  37987. // convenient for our comparisons to include it
  37988. tagSize += 10;
  37989. } // if the entire frame has not arrived, wait for more data
  37990. if (bufferSize < tagSize) {
  37991. return;
  37992. } // collect the entire frame so it can be parsed
  37993. tag = {
  37994. data: new Uint8Array(tagSize),
  37995. frames: [],
  37996. pts: buffer[0].pts,
  37997. dts: buffer[0].dts
  37998. };
  37999. for (i = 0; i < tagSize;) {
  38000. tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
  38001. i += buffer[0].data.byteLength;
  38002. bufferSize -= buffer[0].data.byteLength;
  38003. buffer.shift();
  38004. } // find the start of the first frame and the end of the tag
  38005. frameStart = 10;
  38006. if (tag.data[5] & 0x40) {
  38007. // advance the frame start past the extended header
  38008. frameStart += 4; // header size field
  38009. frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
  38010. tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
  38011. } // parse one or more ID3 frames
  38012. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  38013. do {
  38014. // determine the number of bytes in this frame
  38015. frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
  38016. if (frameSize < 1) {
  38017. // eslint-disable-next-line no-console
  38018. return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
  38019. }
  38020. frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
  38021. frame = {
  38022. id: frameHeader,
  38023. data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
  38024. };
  38025. frame.key = frame.id;
  38026. if (tagParsers[frame.id]) {
  38027. tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
  38028. // time for raw AAC data
  38029. if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
  38030. var d = frame.data,
  38031. size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  38032. size *= 4;
  38033. size += d[7] & 0x03;
  38034. frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
  38035. // on the value of this frame
  38036. // we couldn't have known the appropriate pts and dts before
  38037. // parsing this ID3 tag so set those values now
  38038. if (tag.pts === undefined && tag.dts === undefined) {
  38039. tag.pts = frame.timeStamp;
  38040. tag.dts = frame.timeStamp;
  38041. }
  38042. this.trigger('timestamp', frame);
  38043. }
  38044. }
  38045. tag.frames.push(frame);
  38046. frameStart += 10; // advance past the frame header
  38047. frameStart += frameSize; // advance past the frame body
  38048. } while (frameStart < tagSize);
  38049. this.trigger('data', tag);
  38050. };
  38051. };
  38052. _MetadataStream.prototype = new stream();
  38053. var metadataStream = _MetadataStream;
  38054. var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
  38055. var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
  38056. var MP2T_PACKET_LENGTH = 188,
  38057. // bytes
  38058. SYNC_BYTE = 0x47;
  38059. /**
  38060. * Splits an incoming stream of binary data into MPEG-2 Transport
  38061. * Stream packets.
  38062. */
  38063. _TransportPacketStream = function TransportPacketStream() {
  38064. var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
  38065. bytesInBuffer = 0;
  38066. _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
  38067. /**
  38068. * Split a stream of data into M2TS packets
  38069. **/
  38070. this.push = function (bytes) {
  38071. var startIndex = 0,
  38072. endIndex = MP2T_PACKET_LENGTH,
  38073. everything; // If there are bytes remaining from the last segment, prepend them to the
  38074. // bytes that were pushed in
  38075. if (bytesInBuffer) {
  38076. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  38077. everything.set(buffer.subarray(0, bytesInBuffer));
  38078. everything.set(bytes, bytesInBuffer);
  38079. bytesInBuffer = 0;
  38080. } else {
  38081. everything = bytes;
  38082. } // While we have enough data for a packet
  38083. while (endIndex < everything.byteLength) {
  38084. // Look for a pair of start and end sync bytes in the data..
  38085. if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
  38086. // We found a packet so emit it and jump one whole packet forward in
  38087. // the stream
  38088. this.trigger('data', everything.subarray(startIndex, endIndex));
  38089. startIndex += MP2T_PACKET_LENGTH;
  38090. endIndex += MP2T_PACKET_LENGTH;
  38091. continue;
  38092. } // If we get here, we have somehow become de-synchronized and we need to step
  38093. // forward one byte at a time until we find a pair of sync bytes that denote
  38094. // a packet
  38095. startIndex++;
  38096. endIndex++;
  38097. } // If there was some data left over at the end of the segment that couldn't
  38098. // possibly be a whole packet, keep it because it might be the start of a packet
  38099. // that continues in the next segment
  38100. if (startIndex < everything.byteLength) {
  38101. buffer.set(everything.subarray(startIndex), 0);
  38102. bytesInBuffer = everything.byteLength - startIndex;
  38103. }
  38104. };
  38105. /**
  38106. * Passes identified M2TS packets to the TransportParseStream to be parsed
  38107. **/
  38108. this.flush = function () {
  38109. // If the buffer contains a whole packet when we are being flushed, emit it
  38110. // and empty the buffer. Otherwise hold onto the data because it may be
  38111. // important for decoding the next segment
  38112. if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
  38113. this.trigger('data', buffer);
  38114. bytesInBuffer = 0;
  38115. }
  38116. this.trigger('done');
  38117. };
  38118. };
  38119. _TransportPacketStream.prototype = new stream();
  38120. /**
  38121. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  38122. * forms of the individual transport stream packets.
  38123. */
  38124. _TransportParseStream = function TransportParseStream() {
  38125. var parsePsi, parsePat, parsePmt, self;
  38126. _TransportParseStream.prototype.init.call(this);
  38127. self = this;
  38128. this.packetsWaitingForPmt = [];
  38129. this.programMapTable = undefined;
  38130. parsePsi = function parsePsi(payload, psi) {
  38131. var offset = 0; // PSI packets may be split into multiple sections and those
  38132. // sections may be split into multiple packets. If a PSI
  38133. // section starts in this packet, the payload_unit_start_indicator
  38134. // will be true and the first byte of the payload will indicate
  38135. // the offset from the current position to the start of the
  38136. // section.
  38137. if (psi.payloadUnitStartIndicator) {
  38138. offset += payload[offset] + 1;
  38139. }
  38140. if (psi.type === 'pat') {
  38141. parsePat(payload.subarray(offset), psi);
  38142. } else {
  38143. parsePmt(payload.subarray(offset), psi);
  38144. }
  38145. };
  38146. parsePat = function parsePat(payload, pat) {
  38147. pat.section_number = payload[7]; // eslint-disable-line camelcase
  38148. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  38149. // skip the PSI header and parse the first PMT entry
  38150. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  38151. pat.pmtPid = self.pmtPid;
  38152. };
  38153. /**
  38154. * Parse out the relevant fields of a Program Map Table (PMT).
  38155. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  38156. * packet. The first byte in this array should be the table_id
  38157. * field.
  38158. * @param pmt {object} the object that should be decorated with
  38159. * fields parsed from the PMT.
  38160. */
  38161. parsePmt = function parsePmt(payload, pmt) {
  38162. var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
  38163. // take effect. We don't believe this should ever be the case
  38164. // for HLS but we'll ignore "forward" PMT declarations if we see
  38165. // them. Future PMT declarations have the current_next_indicator
  38166. // set to zero.
  38167. if (!(payload[5] & 0x01)) {
  38168. return;
  38169. } // overwrite any existing program map table
  38170. self.programMapTable = {
  38171. video: null,
  38172. audio: null,
  38173. 'timed-metadata': {}
  38174. }; // the mapping table ends at the end of the current section
  38175. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  38176. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  38177. // long the program info descriptors are
  38178. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
  38179. offset = 12 + programInfoLength;
  38180. while (offset < tableEnd) {
  38181. var streamType = payload[offset];
  38182. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
  38183. // TODO: should this be done for metadata too? for now maintain behavior of
  38184. // multiple metadata streams
  38185. if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
  38186. self.programMapTable.video = pid;
  38187. } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
  38188. self.programMapTable.audio = pid;
  38189. } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
  38190. // map pid to stream type for metadata streams
  38191. self.programMapTable['timed-metadata'][pid] = streamType;
  38192. } // move to the next table entry
  38193. // skip past the elementary stream descriptors, if present
  38194. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  38195. } // record the map on the packet as well
  38196. pmt.programMapTable = self.programMapTable;
  38197. };
  38198. /**
  38199. * Deliver a new MP2T packet to the next stream in the pipeline.
  38200. */
  38201. this.push = function (packet) {
  38202. var result = {},
  38203. offset = 4;
  38204. result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
  38205. result.pid = packet[1] & 0x1f;
  38206. result.pid <<= 8;
  38207. result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
  38208. // fifth byte of the TS packet header. The adaptation field is
  38209. // used to add stuffing to PES packets that don't fill a complete
  38210. // TS packet, and to specify some forms of timing and control data
  38211. // that we do not currently use.
  38212. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  38213. offset += packet[offset] + 1;
  38214. } // parse the rest of the packet based on the type
  38215. if (result.pid === 0) {
  38216. result.type = 'pat';
  38217. parsePsi(packet.subarray(offset), result);
  38218. this.trigger('data', result);
  38219. } else if (result.pid === this.pmtPid) {
  38220. result.type = 'pmt';
  38221. parsePsi(packet.subarray(offset), result);
  38222. this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
  38223. while (this.packetsWaitingForPmt.length) {
  38224. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  38225. }
  38226. } else if (this.programMapTable === undefined) {
  38227. // When we have not seen a PMT yet, defer further processing of
  38228. // PES packets until one has been parsed
  38229. this.packetsWaitingForPmt.push([packet, offset, result]);
  38230. } else {
  38231. this.processPes_(packet, offset, result);
  38232. }
  38233. };
  38234. this.processPes_ = function (packet, offset, result) {
  38235. // set the appropriate stream type
  38236. if (result.pid === this.programMapTable.video) {
  38237. result.streamType = streamTypes.H264_STREAM_TYPE;
  38238. } else if (result.pid === this.programMapTable.audio) {
  38239. result.streamType = streamTypes.ADTS_STREAM_TYPE;
  38240. } else {
  38241. // if not video or audio, it is timed-metadata or unknown
  38242. // if unknown, streamType will be undefined
  38243. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  38244. }
  38245. result.type = 'pes';
  38246. result.data = packet.subarray(offset);
  38247. this.trigger('data', result);
  38248. };
  38249. };
  38250. _TransportParseStream.prototype = new stream();
  38251. _TransportParseStream.STREAM_TYPES = {
  38252. h264: 0x1b,
  38253. adts: 0x0f
  38254. };
  38255. /**
  38256. * Reconsistutes program elementary stream (PES) packets from parsed
  38257. * transport stream packets. That is, if you pipe an
  38258. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  38259. * events will be events which capture the bytes for individual PES
  38260. * packets plus relevant metadata that has been extracted from the
  38261. * container.
  38262. */
  38263. _ElementaryStream = function ElementaryStream() {
  38264. var self = this,
  38265. // PES packet fragments
  38266. video = {
  38267. data: [],
  38268. size: 0
  38269. },
  38270. audio = {
  38271. data: [],
  38272. size: 0
  38273. },
  38274. timedMetadata = {
  38275. data: [],
  38276. size: 0
  38277. },
  38278. parsePes = function parsePes(payload, pes) {
  38279. var ptsDtsFlags; // get the packet length, this will be 0 for video
  38280. pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
  38281. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
  38282. // and a DTS value. Determine what combination of values is
  38283. // available to work with.
  38284. ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  38285. // performs all bitwise operations on 32-bit integers but javascript
  38286. // supports a much greater range (52-bits) of integer using standard
  38287. // mathematical operations.
  38288. // We construct a 31-bit value using bitwise operators over the 31
  38289. // most significant bits and then multiply by 4 (equal to a left-shift
  38290. // of 2) before we add the final 2 least significant bits of the
  38291. // timestamp (equal to an OR.)
  38292. if (ptsDtsFlags & 0xC0) {
  38293. // the PTS and DTS are not written out directly. For information
  38294. // on how they are encoded, see
  38295. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  38296. pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
  38297. pes.pts *= 4; // Left shift by 2
  38298. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  38299. pes.dts = pes.pts;
  38300. if (ptsDtsFlags & 0x40) {
  38301. pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
  38302. pes.dts *= 4; // Left shift by 2
  38303. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  38304. }
  38305. } // the data section starts immediately after the PES header.
  38306. // pes_header_data_length specifies the number of header bytes
  38307. // that follow the last byte of the field.
  38308. pes.data = payload.subarray(9 + payload[8]);
  38309. },
  38310. /**
  38311. * Pass completely parsed PES packets to the next stream in the pipeline
  38312. **/
  38313. flushStream = function flushStream(stream$$1, type, forceFlush) {
  38314. var packetData = new Uint8Array(stream$$1.size),
  38315. event = {
  38316. type: type
  38317. },
  38318. i = 0,
  38319. offset = 0,
  38320. packetFlushable = false,
  38321. fragment; // do nothing if there is not enough buffered data for a complete
  38322. // PES header
  38323. if (!stream$$1.data.length || stream$$1.size < 9) {
  38324. return;
  38325. }
  38326. event.trackId = stream$$1.data[0].pid; // reassemble the packet
  38327. for (i = 0; i < stream$$1.data.length; i++) {
  38328. fragment = stream$$1.data[i];
  38329. packetData.set(fragment.data, offset);
  38330. offset += fragment.data.byteLength;
  38331. } // parse assembled packet's PES header
  38332. parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
  38333. // check that there is enough stream data to fill the packet
  38334. packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right
  38335. if (forceFlush || packetFlushable) {
  38336. stream$$1.size = 0;
  38337. stream$$1.data.length = 0;
  38338. } // only emit packets that are complete. this is to avoid assembling
  38339. // incomplete PES packets due to poor segmentation
  38340. if (packetFlushable) {
  38341. self.trigger('data', event);
  38342. }
  38343. };
  38344. _ElementaryStream.prototype.init.call(this);
  38345. /**
  38346. * Identifies M2TS packet types and parses PES packets using metadata
  38347. * parsed from the PMT
  38348. **/
  38349. this.push = function (data) {
  38350. ({
  38351. pat: function pat() {// we have to wait for the PMT to arrive as well before we
  38352. // have any meaningful metadata
  38353. },
  38354. pes: function pes() {
  38355. var stream$$1, streamType;
  38356. switch (data.streamType) {
  38357. case streamTypes.H264_STREAM_TYPE:
  38358. case streamTypes.H264_STREAM_TYPE:
  38359. stream$$1 = video;
  38360. streamType = 'video';
  38361. break;
  38362. case streamTypes.ADTS_STREAM_TYPE:
  38363. stream$$1 = audio;
  38364. streamType = 'audio';
  38365. break;
  38366. case streamTypes.METADATA_STREAM_TYPE:
  38367. stream$$1 = timedMetadata;
  38368. streamType = 'timed-metadata';
  38369. break;
  38370. default:
  38371. // ignore unknown stream types
  38372. return;
  38373. } // if a new packet is starting, we can flush the completed
  38374. // packet
  38375. if (data.payloadUnitStartIndicator) {
  38376. flushStream(stream$$1, streamType, true);
  38377. } // buffer this fragment until we are sure we've received the
  38378. // complete payload
  38379. stream$$1.data.push(data);
  38380. stream$$1.size += data.data.byteLength;
  38381. },
  38382. pmt: function pmt() {
  38383. var event = {
  38384. type: 'metadata',
  38385. tracks: []
  38386. },
  38387. programMapTable = data.programMapTable; // translate audio and video streams to tracks
  38388. if (programMapTable.video !== null) {
  38389. event.tracks.push({
  38390. timelineStartInfo: {
  38391. baseMediaDecodeTime: 0
  38392. },
  38393. id: +programMapTable.video,
  38394. codec: 'avc',
  38395. type: 'video'
  38396. });
  38397. }
  38398. if (programMapTable.audio !== null) {
  38399. event.tracks.push({
  38400. timelineStartInfo: {
  38401. baseMediaDecodeTime: 0
  38402. },
  38403. id: +programMapTable.audio,
  38404. codec: 'adts',
  38405. type: 'audio'
  38406. });
  38407. }
  38408. self.trigger('data', event);
  38409. }
  38410. })[data.type]();
  38411. };
  38412. /**
  38413. * Flush any remaining input. Video PES packets may be of variable
  38414. * length. Normally, the start of a new video packet can trigger the
  38415. * finalization of the previous packet. That is not possible if no
  38416. * more video is forthcoming, however. In that case, some other
  38417. * mechanism (like the end of the file) has to be employed. When it is
  38418. * clear that no additional data is forthcoming, calling this method
  38419. * will flush the buffered packets.
  38420. */
  38421. this.flush = function () {
  38422. // !!THIS ORDER IS IMPORTANT!!
  38423. // video first then audio
  38424. flushStream(video, 'video');
  38425. flushStream(audio, 'audio');
  38426. flushStream(timedMetadata, 'timed-metadata');
  38427. this.trigger('done');
  38428. };
  38429. };
  38430. _ElementaryStream.prototype = new stream();
  38431. var m2ts = {
  38432. PAT_PID: 0x0000,
  38433. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
  38434. TransportPacketStream: _TransportPacketStream,
  38435. TransportParseStream: _TransportParseStream,
  38436. ElementaryStream: _ElementaryStream,
  38437. TimestampRolloverStream: TimestampRolloverStream$1,
  38438. CaptionStream: captionStream.CaptionStream,
  38439. Cea608Stream: captionStream.Cea608Stream,
  38440. MetadataStream: metadataStream
  38441. };
  38442. for (var type in streamTypes) {
  38443. if (streamTypes.hasOwnProperty(type)) {
  38444. m2ts[type] = streamTypes[type];
  38445. }
  38446. }
  38447. var m2ts_1 = m2ts;
  38448. var _AdtsStream;
  38449. var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  38450. /*
  38451. * Accepts a ElementaryStream and emits data events with parsed
  38452. * AAC Audio Frames of the individual packets. Input audio in ADTS
  38453. * format is unpacked and re-emitted as AAC frames.
  38454. *
  38455. * @see http://wiki.multimedia.cx/index.php?title=ADTS
  38456. * @see http://wiki.multimedia.cx/?title=Understanding_AAC
  38457. */
  38458. _AdtsStream = function AdtsStream() {
  38459. var buffer;
  38460. _AdtsStream.prototype.init.call(this);
  38461. this.push = function (packet) {
  38462. var i = 0,
  38463. frameNum = 0,
  38464. frameLength,
  38465. protectionSkipBytes,
  38466. frameEnd,
  38467. oldBuffer,
  38468. sampleCount,
  38469. adtsFrameDuration;
  38470. if (packet.type !== 'audio') {
  38471. // ignore non-audio data
  38472. return;
  38473. } // Prepend any data in the buffer to the input data so that we can parse
  38474. // aac frames the cross a PES packet boundary
  38475. if (buffer) {
  38476. oldBuffer = buffer;
  38477. buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
  38478. buffer.set(oldBuffer);
  38479. buffer.set(packet.data, oldBuffer.byteLength);
  38480. } else {
  38481. buffer = packet.data;
  38482. } // unpack any ADTS frames which have been fully received
  38483. // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
  38484. while (i + 5 < buffer.length) {
  38485. // Loook for the start of an ADTS header..
  38486. if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
  38487. // If a valid header was not found, jump one forward and attempt to
  38488. // find a valid ADTS header starting at the next byte
  38489. i++;
  38490. continue;
  38491. } // The protection skip bit tells us if we have 2 bytes of CRC data at the
  38492. // end of the ADTS header
  38493. protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
  38494. // end of the sync sequence
  38495. frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
  38496. sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
  38497. adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
  38498. frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
  38499. // and wait for more data
  38500. if (buffer.byteLength < frameEnd) {
  38501. return;
  38502. } // Otherwise, deliver the complete AAC frame
  38503. this.trigger('data', {
  38504. pts: packet.pts + frameNum * adtsFrameDuration,
  38505. dts: packet.dts + frameNum * adtsFrameDuration,
  38506. sampleCount: sampleCount,
  38507. audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
  38508. channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
  38509. samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
  38510. samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
  38511. // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
  38512. samplesize: 16,
  38513. data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
  38514. }); // If the buffer is empty, clear it and return
  38515. if (buffer.byteLength === frameEnd) {
  38516. buffer = undefined;
  38517. return;
  38518. }
  38519. frameNum++; // Remove the finished frame from the buffer and start the process again
  38520. buffer = buffer.subarray(frameEnd);
  38521. }
  38522. };
  38523. this.flush = function () {
  38524. this.trigger('done');
  38525. };
  38526. };
  38527. _AdtsStream.prototype = new stream();
  38528. var adts = _AdtsStream;
  38529. var ExpGolomb;
  38530. /**
  38531. * Parser for exponential Golomb codes, a variable-bitwidth number encoding
  38532. * scheme used by h264.
  38533. */
  38534. ExpGolomb = function ExpGolomb(workingData) {
  38535. var // the number of bytes left to examine in workingData
  38536. workingBytesAvailable = workingData.byteLength,
  38537. // the current word being examined
  38538. workingWord = 0,
  38539. // :uint
  38540. // the number of bits left to examine in the current word
  38541. workingBitsAvailable = 0; // :uint;
  38542. // ():uint
  38543. this.length = function () {
  38544. return 8 * workingBytesAvailable;
  38545. }; // ():uint
  38546. this.bitsAvailable = function () {
  38547. return 8 * workingBytesAvailable + workingBitsAvailable;
  38548. }; // ():void
  38549. this.loadWord = function () {
  38550. var position = workingData.byteLength - workingBytesAvailable,
  38551. workingBytes = new Uint8Array(4),
  38552. availableBytes = Math.min(4, workingBytesAvailable);
  38553. if (availableBytes === 0) {
  38554. throw new Error('no bytes available');
  38555. }
  38556. workingBytes.set(workingData.subarray(position, position + availableBytes));
  38557. workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
  38558. workingBitsAvailable = availableBytes * 8;
  38559. workingBytesAvailable -= availableBytes;
  38560. }; // (count:int):void
  38561. this.skipBits = function (count) {
  38562. var skipBytes; // :int
  38563. if (workingBitsAvailable > count) {
  38564. workingWord <<= count;
  38565. workingBitsAvailable -= count;
  38566. } else {
  38567. count -= workingBitsAvailable;
  38568. skipBytes = Math.floor(count / 8);
  38569. count -= skipBytes * 8;
  38570. workingBytesAvailable -= skipBytes;
  38571. this.loadWord();
  38572. workingWord <<= count;
  38573. workingBitsAvailable -= count;
  38574. }
  38575. }; // (size:int):uint
  38576. this.readBits = function (size) {
  38577. var bits = Math.min(workingBitsAvailable, size),
  38578. // :uint
  38579. valu = workingWord >>> 32 - bits; // :uint
  38580. // if size > 31, handle error
  38581. workingBitsAvailable -= bits;
  38582. if (workingBitsAvailable > 0) {
  38583. workingWord <<= bits;
  38584. } else if (workingBytesAvailable > 0) {
  38585. this.loadWord();
  38586. }
  38587. bits = size - bits;
  38588. if (bits > 0) {
  38589. return valu << bits | this.readBits(bits);
  38590. }
  38591. return valu;
  38592. }; // ():uint
  38593. this.skipLeadingZeros = function () {
  38594. var leadingZeroCount; // :uint
  38595. for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
  38596. if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
  38597. // the first bit of working word is 1
  38598. workingWord <<= leadingZeroCount;
  38599. workingBitsAvailable -= leadingZeroCount;
  38600. return leadingZeroCount;
  38601. }
  38602. } // we exhausted workingWord and still have not found a 1
  38603. this.loadWord();
  38604. return leadingZeroCount + this.skipLeadingZeros();
  38605. }; // ():void
  38606. this.skipUnsignedExpGolomb = function () {
  38607. this.skipBits(1 + this.skipLeadingZeros());
  38608. }; // ():void
  38609. this.skipExpGolomb = function () {
  38610. this.skipBits(1 + this.skipLeadingZeros());
  38611. }; // ():uint
  38612. this.readUnsignedExpGolomb = function () {
  38613. var clz = this.skipLeadingZeros(); // :uint
  38614. return this.readBits(clz + 1) - 1;
  38615. }; // ():int
  38616. this.readExpGolomb = function () {
  38617. var valu = this.readUnsignedExpGolomb(); // :int
  38618. if (0x01 & valu) {
  38619. // the number is odd if the low order bit is set
  38620. return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
  38621. }
  38622. return -1 * (valu >>> 1); // divide by two then make it negative
  38623. }; // Some convenience functions
  38624. // :Boolean
  38625. this.readBoolean = function () {
  38626. return this.readBits(1) === 1;
  38627. }; // ():int
  38628. this.readUnsignedByte = function () {
  38629. return this.readBits(8);
  38630. };
  38631. this.loadWord();
  38632. };
  38633. var expGolomb = ExpGolomb;
  38634. var _H264Stream, _NalByteStream;
  38635. var PROFILES_WITH_OPTIONAL_SPS_DATA;
  38636. /**
  38637. * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
  38638. */
  38639. _NalByteStream = function NalByteStream() {
  38640. var syncPoint = 0,
  38641. i,
  38642. buffer;
  38643. _NalByteStream.prototype.init.call(this);
  38644. /*
  38645. * Scans a byte stream and triggers a data event with the NAL units found.
  38646. * @param {Object} data Event received from H264Stream
  38647. * @param {Uint8Array} data.data The h264 byte stream to be scanned
  38648. *
  38649. * @see H264Stream.push
  38650. */
  38651. this.push = function (data) {
  38652. var swapBuffer;
  38653. if (!buffer) {
  38654. buffer = data.data;
  38655. } else {
  38656. swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
  38657. swapBuffer.set(buffer);
  38658. swapBuffer.set(data.data, buffer.byteLength);
  38659. buffer = swapBuffer;
  38660. } // Rec. ITU-T H.264, Annex B
  38661. // scan for NAL unit boundaries
  38662. // a match looks like this:
  38663. // 0 0 1 .. NAL .. 0 0 1
  38664. // ^ sync point ^ i
  38665. // or this:
  38666. // 0 0 1 .. NAL .. 0 0 0
  38667. // ^ sync point ^ i
  38668. // advance the sync point to a NAL start, if necessary
  38669. for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
  38670. if (buffer[syncPoint + 2] === 1) {
  38671. // the sync point is properly aligned
  38672. i = syncPoint + 5;
  38673. break;
  38674. }
  38675. }
  38676. while (i < buffer.byteLength) {
  38677. // look at the current byte to determine if we've hit the end of
  38678. // a NAL unit boundary
  38679. switch (buffer[i]) {
  38680. case 0:
  38681. // skip past non-sync sequences
  38682. if (buffer[i - 1] !== 0) {
  38683. i += 2;
  38684. break;
  38685. } else if (buffer[i - 2] !== 0) {
  38686. i++;
  38687. break;
  38688. } // deliver the NAL unit if it isn't empty
  38689. if (syncPoint + 3 !== i - 2) {
  38690. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  38691. } // drop trailing zeroes
  38692. do {
  38693. i++;
  38694. } while (buffer[i] !== 1 && i < buffer.length);
  38695. syncPoint = i - 2;
  38696. i += 3;
  38697. break;
  38698. case 1:
  38699. // skip past non-sync sequences
  38700. if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
  38701. i += 3;
  38702. break;
  38703. } // deliver the NAL unit
  38704. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  38705. syncPoint = i - 2;
  38706. i += 3;
  38707. break;
  38708. default:
  38709. // the current byte isn't a one or zero, so it cannot be part
  38710. // of a sync sequence
  38711. i += 3;
  38712. break;
  38713. }
  38714. } // filter out the NAL units that were delivered
  38715. buffer = buffer.subarray(syncPoint);
  38716. i -= syncPoint;
  38717. syncPoint = 0;
  38718. };
  38719. this.flush = function () {
  38720. // deliver the last buffered NAL unit
  38721. if (buffer && buffer.byteLength > 3) {
  38722. this.trigger('data', buffer.subarray(syncPoint + 3));
  38723. } // reset the stream state
  38724. buffer = null;
  38725. syncPoint = 0;
  38726. this.trigger('done');
  38727. };
  38728. };
  38729. _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
  38730. // see Recommendation ITU-T H.264 (4/2013),
  38731. // 7.3.2.1.1 Sequence parameter set data syntax
  38732. PROFILES_WITH_OPTIONAL_SPS_DATA = {
  38733. 100: true,
  38734. 110: true,
  38735. 122: true,
  38736. 244: true,
  38737. 44: true,
  38738. 83: true,
  38739. 86: true,
  38740. 118: true,
  38741. 128: true,
  38742. 138: true,
  38743. 139: true,
  38744. 134: true
  38745. };
  38746. /**
  38747. * Accepts input from a ElementaryStream and produces H.264 NAL unit data
  38748. * events.
  38749. */
  38750. _H264Stream = function H264Stream() {
  38751. var nalByteStream = new _NalByteStream(),
  38752. self,
  38753. trackId,
  38754. currentPts,
  38755. currentDts,
  38756. discardEmulationPreventionBytes,
  38757. readSequenceParameterSet,
  38758. skipScalingList;
  38759. _H264Stream.prototype.init.call(this);
  38760. self = this;
  38761. /*
  38762. * Pushes a packet from a stream onto the NalByteStream
  38763. *
  38764. * @param {Object} packet - A packet received from a stream
  38765. * @param {Uint8Array} packet.data - The raw bytes of the packet
  38766. * @param {Number} packet.dts - Decode timestamp of the packet
  38767. * @param {Number} packet.pts - Presentation timestamp of the packet
  38768. * @param {Number} packet.trackId - The id of the h264 track this packet came from
  38769. * @param {('video'|'audio')} packet.type - The type of packet
  38770. *
  38771. */
  38772. this.push = function (packet) {
  38773. if (packet.type !== 'video') {
  38774. return;
  38775. }
  38776. trackId = packet.trackId;
  38777. currentPts = packet.pts;
  38778. currentDts = packet.dts;
  38779. nalByteStream.push(packet);
  38780. };
  38781. /*
  38782. * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
  38783. * for the NALUs to the next stream component.
  38784. * Also, preprocess caption and sequence parameter NALUs.
  38785. *
  38786. * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
  38787. * @see NalByteStream.push
  38788. */
  38789. nalByteStream.on('data', function (data) {
  38790. var event = {
  38791. trackId: trackId,
  38792. pts: currentPts,
  38793. dts: currentDts,
  38794. data: data
  38795. };
  38796. switch (data[0] & 0x1f) {
  38797. case 0x05:
  38798. event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
  38799. break;
  38800. case 0x06:
  38801. event.nalUnitType = 'sei_rbsp';
  38802. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  38803. break;
  38804. case 0x07:
  38805. event.nalUnitType = 'seq_parameter_set_rbsp';
  38806. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  38807. event.config = readSequenceParameterSet(event.escapedRBSP);
  38808. break;
  38809. case 0x08:
  38810. event.nalUnitType = 'pic_parameter_set_rbsp';
  38811. break;
  38812. case 0x09:
  38813. event.nalUnitType = 'access_unit_delimiter_rbsp';
  38814. break;
  38815. default:
  38816. break;
  38817. } // This triggers data on the H264Stream
  38818. self.trigger('data', event);
  38819. });
  38820. nalByteStream.on('done', function () {
  38821. self.trigger('done');
  38822. });
  38823. this.flush = function () {
  38824. nalByteStream.flush();
  38825. };
  38826. /**
  38827. * Advance the ExpGolomb decoder past a scaling list. The scaling
  38828. * list is optionally transmitted as part of a sequence parameter
  38829. * set and is not relevant to transmuxing.
  38830. * @param count {number} the number of entries in this scaling list
  38831. * @param expGolombDecoder {object} an ExpGolomb pointed to the
  38832. * start of a scaling list
  38833. * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  38834. */
  38835. skipScalingList = function skipScalingList(count, expGolombDecoder) {
  38836. var lastScale = 8,
  38837. nextScale = 8,
  38838. j,
  38839. deltaScale;
  38840. for (j = 0; j < count; j++) {
  38841. if (nextScale !== 0) {
  38842. deltaScale = expGolombDecoder.readExpGolomb();
  38843. nextScale = (lastScale + deltaScale + 256) % 256;
  38844. }
  38845. lastScale = nextScale === 0 ? lastScale : nextScale;
  38846. }
  38847. };
  38848. /**
  38849. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  38850. * Sequence Payload"
  38851. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  38852. * unit
  38853. * @return {Uint8Array} the RBSP without any Emulation
  38854. * Prevention Bytes
  38855. */
  38856. discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  38857. var length = data.byteLength,
  38858. emulationPreventionBytesPositions = [],
  38859. i = 1,
  38860. newLength,
  38861. newData; // Find all `Emulation Prevention Bytes`
  38862. while (i < length - 2) {
  38863. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  38864. emulationPreventionBytesPositions.push(i + 2);
  38865. i += 2;
  38866. } else {
  38867. i++;
  38868. }
  38869. } // If no Emulation Prevention Bytes were found just return the original
  38870. // array
  38871. if (emulationPreventionBytesPositions.length === 0) {
  38872. return data;
  38873. } // Create a new array to hold the NAL unit data
  38874. newLength = length - emulationPreventionBytesPositions.length;
  38875. newData = new Uint8Array(newLength);
  38876. var sourceIndex = 0;
  38877. for (i = 0; i < newLength; sourceIndex++, i++) {
  38878. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  38879. // Skip this byte
  38880. sourceIndex++; // Remove this position index
  38881. emulationPreventionBytesPositions.shift();
  38882. }
  38883. newData[i] = data[sourceIndex];
  38884. }
  38885. return newData;
  38886. };
  38887. /**
  38888. * Read a sequence parameter set and return some interesting video
  38889. * properties. A sequence parameter set is the H264 metadata that
  38890. * describes the properties of upcoming video frames.
  38891. * @param data {Uint8Array} the bytes of a sequence parameter set
  38892. * @return {object} an object with configuration parsed from the
  38893. * sequence parameter set, including the dimensions of the
  38894. * associated video frames.
  38895. */
  38896. readSequenceParameterSet = function readSequenceParameterSet(data) {
  38897. var frameCropLeftOffset = 0,
  38898. frameCropRightOffset = 0,
  38899. frameCropTopOffset = 0,
  38900. frameCropBottomOffset = 0,
  38901. sarScale = 1,
  38902. expGolombDecoder,
  38903. profileIdc,
  38904. levelIdc,
  38905. profileCompatibility,
  38906. chromaFormatIdc,
  38907. picOrderCntType,
  38908. numRefFramesInPicOrderCntCycle,
  38909. picWidthInMbsMinus1,
  38910. picHeightInMapUnitsMinus1,
  38911. frameMbsOnlyFlag,
  38912. scalingListCount,
  38913. sarRatio,
  38914. aspectRatioIdc,
  38915. i;
  38916. expGolombDecoder = new expGolomb(data);
  38917. profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
  38918. profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
  38919. levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
  38920. expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
  38921. // some profiles have more optional data we don't need
  38922. if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
  38923. chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  38924. if (chromaFormatIdc === 3) {
  38925. expGolombDecoder.skipBits(1); // separate_colour_plane_flag
  38926. }
  38927. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
  38928. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
  38929. expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  38930. if (expGolombDecoder.readBoolean()) {
  38931. // seq_scaling_matrix_present_flag
  38932. scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
  38933. for (i = 0; i < scalingListCount; i++) {
  38934. if (expGolombDecoder.readBoolean()) {
  38935. // seq_scaling_list_present_flag[ i ]
  38936. if (i < 6) {
  38937. skipScalingList(16, expGolombDecoder);
  38938. } else {
  38939. skipScalingList(64, expGolombDecoder);
  38940. }
  38941. }
  38942. }
  38943. }
  38944. }
  38945. expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
  38946. picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  38947. if (picOrderCntType === 0) {
  38948. expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
  38949. } else if (picOrderCntType === 1) {
  38950. expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
  38951. expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
  38952. expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
  38953. numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
  38954. for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  38955. expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
  38956. }
  38957. }
  38958. expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
  38959. expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  38960. picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  38961. picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  38962. frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  38963. if (frameMbsOnlyFlag === 0) {
  38964. expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
  38965. }
  38966. expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
  38967. if (expGolombDecoder.readBoolean()) {
  38968. // frame_cropping_flag
  38969. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  38970. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  38971. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  38972. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  38973. }
  38974. if (expGolombDecoder.readBoolean()) {
  38975. // vui_parameters_present_flag
  38976. if (expGolombDecoder.readBoolean()) {
  38977. // aspect_ratio_info_present_flag
  38978. aspectRatioIdc = expGolombDecoder.readUnsignedByte();
  38979. switch (aspectRatioIdc) {
  38980. case 1:
  38981. sarRatio = [1, 1];
  38982. break;
  38983. case 2:
  38984. sarRatio = [12, 11];
  38985. break;
  38986. case 3:
  38987. sarRatio = [10, 11];
  38988. break;
  38989. case 4:
  38990. sarRatio = [16, 11];
  38991. break;
  38992. case 5:
  38993. sarRatio = [40, 33];
  38994. break;
  38995. case 6:
  38996. sarRatio = [24, 11];
  38997. break;
  38998. case 7:
  38999. sarRatio = [20, 11];
  39000. break;
  39001. case 8:
  39002. sarRatio = [32, 11];
  39003. break;
  39004. case 9:
  39005. sarRatio = [80, 33];
  39006. break;
  39007. case 10:
  39008. sarRatio = [18, 11];
  39009. break;
  39010. case 11:
  39011. sarRatio = [15, 11];
  39012. break;
  39013. case 12:
  39014. sarRatio = [64, 33];
  39015. break;
  39016. case 13:
  39017. sarRatio = [160, 99];
  39018. break;
  39019. case 14:
  39020. sarRatio = [4, 3];
  39021. break;
  39022. case 15:
  39023. sarRatio = [3, 2];
  39024. break;
  39025. case 16:
  39026. sarRatio = [2, 1];
  39027. break;
  39028. case 255:
  39029. {
  39030. sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
  39031. break;
  39032. }
  39033. }
  39034. if (sarRatio) {
  39035. sarScale = sarRatio[0] / sarRatio[1];
  39036. }
  39037. }
  39038. }
  39039. return {
  39040. profileIdc: profileIdc,
  39041. levelIdc: levelIdc,
  39042. profileCompatibility: profileCompatibility,
  39043. width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
  39044. height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
  39045. };
  39046. };
  39047. };
  39048. _H264Stream.prototype = new stream();
  39049. var h264 = {
  39050. H264Stream: _H264Stream,
  39051. NalByteStream: _NalByteStream
  39052. };
  39053. /**
  39054. * mux.js
  39055. *
  39056. * Copyright (c) 2016 Brightcove
  39057. * All rights reserved.
  39058. *
  39059. * Utilities to detect basic properties and metadata about Aac data.
  39060. */
  39061. var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  39062. var isLikelyAacData = function isLikelyAacData(data) {
  39063. if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
  39064. return true;
  39065. }
  39066. return false;
  39067. };
  39068. var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
  39069. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  39070. }; // return a percent-encoded representation of the specified byte range
  39071. // @see http://en.wikipedia.org/wiki/Percent-encoding
  39072. var percentEncode$1 = function percentEncode(bytes, start, end) {
  39073. var i,
  39074. result = '';
  39075. for (i = start; i < end; i++) {
  39076. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  39077. }
  39078. return result;
  39079. }; // return the string representation of the specified byte range,
  39080. // interpreted as ISO-8859-1.
  39081. var parseIso88591$1 = function parseIso88591(bytes, start, end) {
  39082. return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
  39083. };
  39084. var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
  39085. var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
  39086. flags = header[byteIndex + 5],
  39087. footerPresent = (flags & 16) >> 4;
  39088. if (footerPresent) {
  39089. return returnSize + 20;
  39090. }
  39091. return returnSize + 10;
  39092. };
  39093. var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
  39094. var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
  39095. middle = header[byteIndex + 4] << 3,
  39096. highTwo = header[byteIndex + 3] & 0x3 << 11;
  39097. return highTwo | middle | lowThree;
  39098. };
  39099. var parseType$1 = function parseType(header, byteIndex) {
  39100. if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
  39101. return 'timed-metadata';
  39102. } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
  39103. return 'audio';
  39104. }
  39105. return null;
  39106. };
  39107. var parseSampleRate = function parseSampleRate(packet) {
  39108. var i = 0;
  39109. while (i + 5 < packet.length) {
  39110. if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
  39111. // If a valid header was not found, jump one forward and attempt to
  39112. // find a valid ADTS header starting at the next byte
  39113. i++;
  39114. continue;
  39115. }
  39116. return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
  39117. }
  39118. return null;
  39119. };
  39120. var parseAacTimestamp = function parseAacTimestamp(packet) {
  39121. var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
  39122. frameStart = 10;
  39123. if (packet[5] & 0x40) {
  39124. // advance the frame start past the extended header
  39125. frameStart += 4; // header size field
  39126. frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
  39127. } // parse one or more ID3 frames
  39128. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  39129. do {
  39130. // determine the number of bytes in this frame
  39131. frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
  39132. if (frameSize < 1) {
  39133. return null;
  39134. }
  39135. frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
  39136. if (frameHeader === 'PRIV') {
  39137. frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
  39138. for (var i = 0; i < frame.byteLength; i++) {
  39139. if (frame[i] === 0) {
  39140. var owner = parseIso88591$1(frame, 0, i);
  39141. if (owner === 'com.apple.streaming.transportStreamTimestamp') {
  39142. var d = frame.subarray(i + 1);
  39143. var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  39144. size *= 4;
  39145. size += d[7] & 0x03;
  39146. return size;
  39147. }
  39148. break;
  39149. }
  39150. }
  39151. }
  39152. frameStart += 10; // advance past the frame header
  39153. frameStart += frameSize; // advance past the frame body
  39154. } while (frameStart < packet.byteLength);
  39155. return null;
  39156. };
  39157. var utils = {
  39158. isLikelyAacData: isLikelyAacData,
  39159. parseId3TagSize: parseId3TagSize,
  39160. parseAdtsSize: parseAdtsSize,
  39161. parseType: parseType$1,
  39162. parseSampleRate: parseSampleRate,
  39163. parseAacTimestamp: parseAacTimestamp
  39164. }; // Constants
  39165. var _AacStream;
  39166. /**
  39167. * Splits an incoming stream of binary data into ADTS and ID3 Frames.
  39168. */
  39169. _AacStream = function AacStream() {
  39170. var everything = new Uint8Array(),
  39171. timeStamp = 0;
  39172. _AacStream.prototype.init.call(this);
  39173. this.setTimestamp = function (timestamp) {
  39174. timeStamp = timestamp;
  39175. };
  39176. this.push = function (bytes) {
  39177. var frameSize = 0,
  39178. byteIndex = 0,
  39179. bytesLeft,
  39180. chunk,
  39181. packet,
  39182. tempLength; // If there are bytes remaining from the last segment, prepend them to the
  39183. // bytes that were pushed in
  39184. if (everything.length) {
  39185. tempLength = everything.length;
  39186. everything = new Uint8Array(bytes.byteLength + tempLength);
  39187. everything.set(everything.subarray(0, tempLength));
  39188. everything.set(bytes, tempLength);
  39189. } else {
  39190. everything = bytes;
  39191. }
  39192. while (everything.length - byteIndex >= 3) {
  39193. if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
  39194. // Exit early because we don't have enough to parse
  39195. // the ID3 tag header
  39196. if (everything.length - byteIndex < 10) {
  39197. break;
  39198. } // check framesize
  39199. frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  39200. // to emit a full packet
  39201. // Add to byteIndex to support multiple ID3 tags in sequence
  39202. if (byteIndex + frameSize > everything.length) {
  39203. break;
  39204. }
  39205. chunk = {
  39206. type: 'timed-metadata',
  39207. data: everything.subarray(byteIndex, byteIndex + frameSize)
  39208. };
  39209. this.trigger('data', chunk);
  39210. byteIndex += frameSize;
  39211. continue;
  39212. } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
  39213. // Exit early because we don't have enough to parse
  39214. // the ADTS frame header
  39215. if (everything.length - byteIndex < 7) {
  39216. break;
  39217. }
  39218. frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  39219. // to emit a full packet
  39220. if (byteIndex + frameSize > everything.length) {
  39221. break;
  39222. }
  39223. packet = {
  39224. type: 'audio',
  39225. data: everything.subarray(byteIndex, byteIndex + frameSize),
  39226. pts: timeStamp,
  39227. dts: timeStamp
  39228. };
  39229. this.trigger('data', packet);
  39230. byteIndex += frameSize;
  39231. continue;
  39232. }
  39233. byteIndex++;
  39234. }
  39235. bytesLeft = everything.length - byteIndex;
  39236. if (bytesLeft > 0) {
  39237. everything = everything.subarray(byteIndex);
  39238. } else {
  39239. everything = new Uint8Array();
  39240. }
  39241. };
  39242. };
  39243. _AacStream.prototype = new stream();
  39244. var aac = _AacStream;
  39245. var H264Stream = h264.H264Stream;
  39246. var isLikelyAacData$1 = utils.isLikelyAacData; // constants
  39247. var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
  39248. var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types
  39249. var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
  39250. /**
  39251. * Compare two arrays (even typed) for same-ness
  39252. */
  39253. var arrayEquals = function arrayEquals(a, b) {
  39254. var i;
  39255. if (a.length !== b.length) {
  39256. return false;
  39257. } // compare the value of each element in the array
  39258. for (i = 0; i < a.length; i++) {
  39259. if (a[i] !== b[i]) {
  39260. return false;
  39261. }
  39262. }
  39263. return true;
  39264. };
  39265. var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
  39266. var ptsOffsetFromDts = startPts - startDts,
  39267. decodeDuration = endDts - startDts,
  39268. presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
  39269. // however, the player time values will reflect a start from the baseMediaDecodeTime.
  39270. // In order to provide relevant values for the player times, base timing info on the
  39271. // baseMediaDecodeTime and the DTS and PTS durations of the segment.
  39272. return {
  39273. start: {
  39274. dts: baseMediaDecodeTime,
  39275. pts: baseMediaDecodeTime + ptsOffsetFromDts
  39276. },
  39277. end: {
  39278. dts: baseMediaDecodeTime + decodeDuration,
  39279. pts: baseMediaDecodeTime + presentationDuration
  39280. },
  39281. prependedContentDuration: prependedContentDuration,
  39282. baseMediaDecodeTime: baseMediaDecodeTime
  39283. };
  39284. };
  39285. /**
  39286. * Constructs a single-track, ISO BMFF media segment from AAC data
  39287. * events. The output of this stream can be fed to a SourceBuffer
  39288. * configured with a suitable initialization segment.
  39289. * @param track {object} track metadata configuration
  39290. * @param options {object} transmuxer options object
  39291. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  39292. * in the source; false to adjust the first segment to start at 0.
  39293. */
  39294. _AudioSegmentStream = function AudioSegmentStream(track, options) {
  39295. var adtsFrames = [],
  39296. sequenceNumber = 0,
  39297. earliestAllowedDts = 0,
  39298. audioAppendStartTs = 0,
  39299. videoBaseMediaDecodeTime = Infinity;
  39300. options = options || {};
  39301. _AudioSegmentStream.prototype.init.call(this);
  39302. this.push = function (data) {
  39303. trackDecodeInfo.collectDtsInfo(track, data);
  39304. if (track) {
  39305. AUDIO_PROPERTIES.forEach(function (prop) {
  39306. track[prop] = data[prop];
  39307. });
  39308. } // buffer audio data until end() is called
  39309. adtsFrames.push(data);
  39310. };
  39311. this.setEarliestDts = function (earliestDts) {
  39312. earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
  39313. };
  39314. this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  39315. videoBaseMediaDecodeTime = baseMediaDecodeTime;
  39316. };
  39317. this.setAudioAppendStart = function (timestamp) {
  39318. audioAppendStartTs = timestamp;
  39319. };
  39320. this.flush = function () {
  39321. var frames, moof, mdat, boxes; // return early if no audio data has been observed
  39322. if (adtsFrames.length === 0) {
  39323. this.trigger('done', 'AudioSegmentStream');
  39324. return;
  39325. }
  39326. frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
  39327. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  39328. audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
  39329. // samples (that is, adts frames) in the audio data
  39330. track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
  39331. mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
  39332. adtsFrames = [];
  39333. moof = mp4Generator.moof(sequenceNumber, [track]);
  39334. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
  39335. sequenceNumber++;
  39336. boxes.set(moof);
  39337. boxes.set(mdat, moof.byteLength);
  39338. trackDecodeInfo.clearDtsInfo(track);
  39339. this.trigger('data', {
  39340. track: track,
  39341. boxes: boxes
  39342. });
  39343. this.trigger('done', 'AudioSegmentStream');
  39344. };
  39345. };
  39346. _AudioSegmentStream.prototype = new stream();
  39347. /**
  39348. * Constructs a single-track, ISO BMFF media segment from H264 data
  39349. * events. The output of this stream can be fed to a SourceBuffer
  39350. * configured with a suitable initialization segment.
  39351. * @param track {object} track metadata configuration
  39352. * @param options {object} transmuxer options object
  39353. * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
  39354. * gopsToAlignWith list when attempting to align gop pts
  39355. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  39356. * in the source; false to adjust the first segment to start at 0.
  39357. */
  39358. _VideoSegmentStream = function VideoSegmentStream(track, options) {
  39359. var sequenceNumber = 0,
  39360. nalUnits = [],
  39361. gopsToAlignWith = [],
  39362. config,
  39363. pps;
  39364. options = options || {};
  39365. _VideoSegmentStream.prototype.init.call(this);
  39366. delete track.minPTS;
  39367. this.gopCache_ = [];
  39368. /**
  39369. * Constructs a ISO BMFF segment given H264 nalUnits
  39370. * @param {Object} nalUnit A data event representing a nalUnit
  39371. * @param {String} nalUnit.nalUnitType
  39372. * @param {Object} nalUnit.config Properties for a mp4 track
  39373. * @param {Uint8Array} nalUnit.data The nalUnit bytes
  39374. * @see lib/codecs/h264.js
  39375. **/
  39376. this.push = function (nalUnit) {
  39377. trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
  39378. if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
  39379. config = nalUnit.config;
  39380. track.sps = [nalUnit.data];
  39381. VIDEO_PROPERTIES.forEach(function (prop) {
  39382. track[prop] = config[prop];
  39383. }, this);
  39384. }
  39385. if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
  39386. pps = nalUnit.data;
  39387. track.pps = [nalUnit.data];
  39388. } // buffer video until flush() is called
  39389. nalUnits.push(nalUnit);
  39390. };
  39391. /**
  39392. * Pass constructed ISO BMFF track and boxes on to the
  39393. * next stream in the pipeline
  39394. **/
  39395. this.flush = function () {
  39396. var frames,
  39397. gopForFusion,
  39398. gops,
  39399. moof,
  39400. mdat,
  39401. boxes,
  39402. prependedContentDuration = 0,
  39403. firstGop,
  39404. lastGop; // Throw away nalUnits at the start of the byte stream until
  39405. // we find the first AUD
  39406. while (nalUnits.length) {
  39407. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  39408. break;
  39409. }
  39410. nalUnits.shift();
  39411. } // Return early if no video data has been observed
  39412. if (nalUnits.length === 0) {
  39413. this.resetStream_();
  39414. this.trigger('done', 'VideoSegmentStream');
  39415. return;
  39416. } // Organize the raw nal-units into arrays that represent
  39417. // higher-level constructs such as frames and gops
  39418. // (group-of-pictures)
  39419. frames = frameUtils.groupNalsIntoFrames(nalUnits);
  39420. gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
  39421. // a problem since MSE (on Chrome) requires a leading keyframe.
  39422. //
  39423. // We have two approaches to repairing this situation:
  39424. // 1) GOP-FUSION:
  39425. // This is where we keep track of the GOPS (group-of-pictures)
  39426. // from previous fragments and attempt to find one that we can
  39427. // prepend to the current fragment in order to create a valid
  39428. // fragment.
  39429. // 2) KEYFRAME-PULLING:
  39430. // Here we search for the first keyframe in the fragment and
  39431. // throw away all the frames between the start of the fragment
  39432. // and that keyframe. We then extend the duration and pull the
  39433. // PTS of the keyframe forward so that it covers the time range
  39434. // of the frames that were disposed of.
  39435. //
  39436. // #1 is far prefereable over #2 which can cause "stuttering" but
  39437. // requires more things to be just right.
  39438. if (!gops[0][0].keyFrame) {
  39439. // Search for a gop for fusion from our gopCache
  39440. gopForFusion = this.getGopForFusion_(nalUnits[0], track);
  39441. if (gopForFusion) {
  39442. // in order to provide more accurate timing information about the segment, save
  39443. // the number of seconds prepended to the original segment due to GOP fusion
  39444. prependedContentDuration = gopForFusion.duration;
  39445. gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
  39446. // new gop at the beginning
  39447. gops.byteLength += gopForFusion.byteLength;
  39448. gops.nalCount += gopForFusion.nalCount;
  39449. gops.pts = gopForFusion.pts;
  39450. gops.dts = gopForFusion.dts;
  39451. gops.duration += gopForFusion.duration;
  39452. } else {
  39453. // If we didn't find a candidate gop fall back to keyframe-pulling
  39454. gops = frameUtils.extendFirstKeyFrame(gops);
  39455. }
  39456. } // Trim gops to align with gopsToAlignWith
  39457. if (gopsToAlignWith.length) {
  39458. var alignedGops;
  39459. if (options.alignGopsAtEnd) {
  39460. alignedGops = this.alignGopsAtEnd_(gops);
  39461. } else {
  39462. alignedGops = this.alignGopsAtStart_(gops);
  39463. }
  39464. if (!alignedGops) {
  39465. // save all the nals in the last GOP into the gop cache
  39466. this.gopCache_.unshift({
  39467. gop: gops.pop(),
  39468. pps: track.pps,
  39469. sps: track.sps
  39470. }); // Keep a maximum of 6 GOPs in the cache
  39471. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  39472. nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
  39473. this.resetStream_();
  39474. this.trigger('done', 'VideoSegmentStream');
  39475. return;
  39476. } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
  39477. // when recalculated before sending off to CoalesceStream
  39478. trackDecodeInfo.clearDtsInfo(track);
  39479. gops = alignedGops;
  39480. }
  39481. trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
  39482. // samples (that is, frames) in the video data
  39483. track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
  39484. mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
  39485. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  39486. this.trigger('processedGopsInfo', gops.map(function (gop) {
  39487. return {
  39488. pts: gop.pts,
  39489. dts: gop.dts,
  39490. byteLength: gop.byteLength
  39491. };
  39492. }));
  39493. firstGop = gops[0];
  39494. lastGop = gops[gops.length - 1];
  39495. this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); // save all the nals in the last GOP into the gop cache
  39496. this.gopCache_.unshift({
  39497. gop: gops.pop(),
  39498. pps: track.pps,
  39499. sps: track.sps
  39500. }); // Keep a maximum of 6 GOPs in the cache
  39501. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  39502. nalUnits = [];
  39503. this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
  39504. this.trigger('timelineStartInfo', track.timelineStartInfo);
  39505. moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
  39506. // throwing away hundreds of media segment fragments
  39507. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
  39508. sequenceNumber++;
  39509. boxes.set(moof);
  39510. boxes.set(mdat, moof.byteLength);
  39511. this.trigger('data', {
  39512. track: track,
  39513. boxes: boxes
  39514. });
  39515. this.resetStream_(); // Continue with the flush process now
  39516. this.trigger('done', 'VideoSegmentStream');
  39517. };
  39518. this.resetStream_ = function () {
  39519. trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
  39520. // for instance, when we are rendition switching
  39521. config = undefined;
  39522. pps = undefined;
  39523. }; // Search for a candidate Gop for gop-fusion from the gop cache and
  39524. // return it or return null if no good candidate was found
  39525. this.getGopForFusion_ = function (nalUnit) {
  39526. var halfSecond = 45000,
  39527. // Half-a-second in a 90khz clock
  39528. allowableOverlap = 10000,
  39529. // About 3 frames @ 30fps
  39530. nearestDistance = Infinity,
  39531. dtsDistance,
  39532. nearestGopObj,
  39533. currentGop,
  39534. currentGopObj,
  39535. i; // Search for the GOP nearest to the beginning of this nal unit
  39536. for (i = 0; i < this.gopCache_.length; i++) {
  39537. currentGopObj = this.gopCache_[i];
  39538. currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
  39539. if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
  39540. continue;
  39541. } // Reject Gops that would require a negative baseMediaDecodeTime
  39542. if (currentGop.dts < track.timelineStartInfo.dts) {
  39543. continue;
  39544. } // The distance between the end of the gop and the start of the nalUnit
  39545. dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
  39546. // a half-second of the nal unit
  39547. if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
  39548. // Always use the closest GOP we found if there is more than
  39549. // one candidate
  39550. if (!nearestGopObj || nearestDistance > dtsDistance) {
  39551. nearestGopObj = currentGopObj;
  39552. nearestDistance = dtsDistance;
  39553. }
  39554. }
  39555. }
  39556. if (nearestGopObj) {
  39557. return nearestGopObj.gop;
  39558. }
  39559. return null;
  39560. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  39561. // of gopsToAlignWith starting from the START of the list
  39562. this.alignGopsAtStart_ = function (gops) {
  39563. var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
  39564. byteLength = gops.byteLength;
  39565. nalCount = gops.nalCount;
  39566. duration = gops.duration;
  39567. alignIndex = gopIndex = 0;
  39568. while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
  39569. align = gopsToAlignWith[alignIndex];
  39570. gop = gops[gopIndex];
  39571. if (align.pts === gop.pts) {
  39572. break;
  39573. }
  39574. if (gop.pts > align.pts) {
  39575. // this current gop starts after the current gop we want to align on, so increment
  39576. // align index
  39577. alignIndex++;
  39578. continue;
  39579. } // current gop starts before the current gop we want to align on. so increment gop
  39580. // index
  39581. gopIndex++;
  39582. byteLength -= gop.byteLength;
  39583. nalCount -= gop.nalCount;
  39584. duration -= gop.duration;
  39585. }
  39586. if (gopIndex === 0) {
  39587. // no gops to trim
  39588. return gops;
  39589. }
  39590. if (gopIndex === gops.length) {
  39591. // all gops trimmed, skip appending all gops
  39592. return null;
  39593. }
  39594. alignedGops = gops.slice(gopIndex);
  39595. alignedGops.byteLength = byteLength;
  39596. alignedGops.duration = duration;
  39597. alignedGops.nalCount = nalCount;
  39598. alignedGops.pts = alignedGops[0].pts;
  39599. alignedGops.dts = alignedGops[0].dts;
  39600. return alignedGops;
  39601. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  39602. // of gopsToAlignWith starting from the END of the list
  39603. this.alignGopsAtEnd_ = function (gops) {
  39604. var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
  39605. alignIndex = gopsToAlignWith.length - 1;
  39606. gopIndex = gops.length - 1;
  39607. alignEndIndex = null;
  39608. matchFound = false;
  39609. while (alignIndex >= 0 && gopIndex >= 0) {
  39610. align = gopsToAlignWith[alignIndex];
  39611. gop = gops[gopIndex];
  39612. if (align.pts === gop.pts) {
  39613. matchFound = true;
  39614. break;
  39615. }
  39616. if (align.pts > gop.pts) {
  39617. alignIndex--;
  39618. continue;
  39619. }
  39620. if (alignIndex === gopsToAlignWith.length - 1) {
  39621. // gop.pts is greater than the last alignment candidate. If no match is found
  39622. // by the end of this loop, we still want to append gops that come after this
  39623. // point
  39624. alignEndIndex = gopIndex;
  39625. }
  39626. gopIndex--;
  39627. }
  39628. if (!matchFound && alignEndIndex === null) {
  39629. return null;
  39630. }
  39631. var trimIndex;
  39632. if (matchFound) {
  39633. trimIndex = gopIndex;
  39634. } else {
  39635. trimIndex = alignEndIndex;
  39636. }
  39637. if (trimIndex === 0) {
  39638. return gops;
  39639. }
  39640. var alignedGops = gops.slice(trimIndex);
  39641. var metadata = alignedGops.reduce(function (total, gop) {
  39642. total.byteLength += gop.byteLength;
  39643. total.duration += gop.duration;
  39644. total.nalCount += gop.nalCount;
  39645. return total;
  39646. }, {
  39647. byteLength: 0,
  39648. duration: 0,
  39649. nalCount: 0
  39650. });
  39651. alignedGops.byteLength = metadata.byteLength;
  39652. alignedGops.duration = metadata.duration;
  39653. alignedGops.nalCount = metadata.nalCount;
  39654. alignedGops.pts = alignedGops[0].pts;
  39655. alignedGops.dts = alignedGops[0].dts;
  39656. return alignedGops;
  39657. };
  39658. this.alignGopsWith = function (newGopsToAlignWith) {
  39659. gopsToAlignWith = newGopsToAlignWith;
  39660. };
  39661. };
  39662. _VideoSegmentStream.prototype = new stream();
  39663. /**
  39664. * A Stream that can combine multiple streams (ie. audio & video)
  39665. * into a single output segment for MSE. Also supports audio-only
  39666. * and video-only streams.
  39667. * @param options {object} transmuxer options object
  39668. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  39669. * in the source; false to adjust the first segment to start at media timeline start.
  39670. */
  39671. _CoalesceStream = function CoalesceStream(options, metadataStream) {
  39672. // Number of Tracks per output segment
  39673. // If greater than 1, we combine multiple
  39674. // tracks into a single segment
  39675. this.numberOfTracks = 0;
  39676. this.metadataStream = metadataStream;
  39677. options = options || {};
  39678. if (typeof options.remux !== 'undefined') {
  39679. this.remuxTracks = !!options.remux;
  39680. } else {
  39681. this.remuxTracks = true;
  39682. }
  39683. if (typeof options.keepOriginalTimestamps === 'boolean') {
  39684. this.keepOriginalTimestamps = options.keepOriginalTimestamps;
  39685. }
  39686. this.pendingTracks = [];
  39687. this.videoTrack = null;
  39688. this.pendingBoxes = [];
  39689. this.pendingCaptions = [];
  39690. this.pendingMetadata = [];
  39691. this.pendingBytes = 0;
  39692. this.emittedTracks = 0;
  39693. _CoalesceStream.prototype.init.call(this); // Take output from multiple
  39694. this.push = function (output) {
  39695. // buffer incoming captions until the associated video segment
  39696. // finishes
  39697. if (output.text) {
  39698. return this.pendingCaptions.push(output);
  39699. } // buffer incoming id3 tags until the final flush
  39700. if (output.frames) {
  39701. return this.pendingMetadata.push(output);
  39702. } // Add this track to the list of pending tracks and store
  39703. // important information required for the construction of
  39704. // the final segment
  39705. this.pendingTracks.push(output.track);
  39706. this.pendingBoxes.push(output.boxes);
  39707. this.pendingBytes += output.boxes.byteLength;
  39708. if (output.track.type === 'video') {
  39709. this.videoTrack = output.track;
  39710. }
  39711. if (output.track.type === 'audio') {
  39712. this.audioTrack = output.track;
  39713. }
  39714. };
  39715. };
  39716. _CoalesceStream.prototype = new stream();
  39717. _CoalesceStream.prototype.flush = function (flushSource) {
  39718. var offset = 0,
  39719. event = {
  39720. captions: [],
  39721. captionStreams: {},
  39722. metadata: [],
  39723. info: {}
  39724. },
  39725. caption,
  39726. id3,
  39727. initSegment,
  39728. timelineStartPts = 0,
  39729. i;
  39730. if (this.pendingTracks.length < this.numberOfTracks) {
  39731. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  39732. // Return because we haven't received a flush from a data-generating
  39733. // portion of the segment (meaning that we have only recieved meta-data
  39734. // or captions.)
  39735. return;
  39736. } else if (this.remuxTracks) {
  39737. // Return until we have enough tracks from the pipeline to remux (if we
  39738. // are remuxing audio and video into a single MP4)
  39739. return;
  39740. } else if (this.pendingTracks.length === 0) {
  39741. // In the case where we receive a flush without any data having been
  39742. // received we consider it an emitted track for the purposes of coalescing
  39743. // `done` events.
  39744. // We do this for the case where there is an audio and video track in the
  39745. // segment but no audio data. (seen in several playlists with alternate
  39746. // audio tracks and no audio present in the main TS segments.)
  39747. this.emittedTracks++;
  39748. if (this.emittedTracks >= this.numberOfTracks) {
  39749. this.trigger('done');
  39750. this.emittedTracks = 0;
  39751. }
  39752. return;
  39753. }
  39754. }
  39755. if (this.videoTrack) {
  39756. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  39757. VIDEO_PROPERTIES.forEach(function (prop) {
  39758. event.info[prop] = this.videoTrack[prop];
  39759. }, this);
  39760. } else if (this.audioTrack) {
  39761. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  39762. AUDIO_PROPERTIES.forEach(function (prop) {
  39763. event.info[prop] = this.audioTrack[prop];
  39764. }, this);
  39765. }
  39766. if (this.pendingTracks.length === 1) {
  39767. event.type = this.pendingTracks[0].type;
  39768. } else {
  39769. event.type = 'combined';
  39770. }
  39771. this.emittedTracks += this.pendingTracks.length;
  39772. initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
  39773. event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
  39774. // and track definitions
  39775. event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
  39776. event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
  39777. for (i = 0; i < this.pendingBoxes.length; i++) {
  39778. event.data.set(this.pendingBoxes[i], offset);
  39779. offset += this.pendingBoxes[i].byteLength;
  39780. } // Translate caption PTS times into second offsets to match the
  39781. // video timeline for the segment, and add track info
  39782. for (i = 0; i < this.pendingCaptions.length; i++) {
  39783. caption = this.pendingCaptions[i];
  39784. caption.startTime = caption.startPts;
  39785. if (!this.keepOriginalTimestamps) {
  39786. caption.startTime -= timelineStartPts;
  39787. }
  39788. caption.startTime /= 90e3;
  39789. caption.endTime = caption.endPts;
  39790. if (!this.keepOriginalTimestamps) {
  39791. caption.endTime -= timelineStartPts;
  39792. }
  39793. caption.endTime /= 90e3;
  39794. event.captionStreams[caption.stream] = true;
  39795. event.captions.push(caption);
  39796. } // Translate ID3 frame PTS times into second offsets to match the
  39797. // video timeline for the segment
  39798. for (i = 0; i < this.pendingMetadata.length; i++) {
  39799. id3 = this.pendingMetadata[i];
  39800. id3.cueTime = id3.pts;
  39801. if (!this.keepOriginalTimestamps) {
  39802. id3.cueTime -= timelineStartPts;
  39803. }
  39804. id3.cueTime /= 90e3;
  39805. event.metadata.push(id3);
  39806. } // We add this to every single emitted segment even though we only need
  39807. // it for the first
  39808. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  39809. this.pendingTracks.length = 0;
  39810. this.videoTrack = null;
  39811. this.pendingBoxes.length = 0;
  39812. this.pendingCaptions.length = 0;
  39813. this.pendingBytes = 0;
  39814. this.pendingMetadata.length = 0; // Emit the built segment
  39815. this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted
  39816. if (this.emittedTracks >= this.numberOfTracks) {
  39817. this.trigger('done');
  39818. this.emittedTracks = 0;
  39819. }
  39820. };
  39821. /**
  39822. * A Stream that expects MP2T binary data as input and produces
  39823. * corresponding media segments, suitable for use with Media Source
  39824. * Extension (MSE) implementations that support the ISO BMFF byte
  39825. * stream format, like Chrome.
  39826. */
  39827. _Transmuxer = function Transmuxer(options) {
  39828. var self = this,
  39829. hasFlushed = true,
  39830. videoTrack,
  39831. audioTrack;
  39832. _Transmuxer.prototype.init.call(this);
  39833. options = options || {};
  39834. this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
  39835. this.transmuxPipeline_ = {};
  39836. this.setupAacPipeline = function () {
  39837. var pipeline = {};
  39838. this.transmuxPipeline_ = pipeline;
  39839. pipeline.type = 'aac';
  39840. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  39841. pipeline.aacStream = new aac();
  39842. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  39843. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  39844. pipeline.adtsStream = new adts();
  39845. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  39846. pipeline.headOfPipeline = pipeline.aacStream;
  39847. pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  39848. pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
  39849. pipeline.metadataStream.on('timestamp', function (frame) {
  39850. pipeline.aacStream.setTimestamp(frame.timeStamp);
  39851. });
  39852. pipeline.aacStream.on('data', function (data) {
  39853. if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
  39854. audioTrack = audioTrack || {
  39855. timelineStartInfo: {
  39856. baseMediaDecodeTime: self.baseMediaDecodeTime
  39857. },
  39858. codec: 'adts',
  39859. type: 'audio'
  39860. }; // hook up the audio segment stream to the first track with aac data
  39861. pipeline.coalesceStream.numberOfTracks++;
  39862. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  39863. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  39864. }
  39865. }); // Re-emit any data coming from the coalesce stream to the outside world
  39866. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  39867. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  39868. };
  39869. this.setupTsPipeline = function () {
  39870. var pipeline = {};
  39871. this.transmuxPipeline_ = pipeline;
  39872. pipeline.type = 'ts';
  39873. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  39874. pipeline.packetStream = new m2ts_1.TransportPacketStream();
  39875. pipeline.parseStream = new m2ts_1.TransportParseStream();
  39876. pipeline.elementaryStream = new m2ts_1.ElementaryStream();
  39877. pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
  39878. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  39879. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  39880. pipeline.adtsStream = new adts();
  39881. pipeline.h264Stream = new H264Stream();
  39882. pipeline.captionStream = new m2ts_1.CaptionStream();
  39883. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  39884. pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
  39885. pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!!
  39886. // demux the streams
  39887. pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
  39888. pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  39889. pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
  39890. pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
  39891. pipeline.elementaryStream.on('data', function (data) {
  39892. var i;
  39893. if (data.type === 'metadata') {
  39894. i = data.tracks.length; // scan the tracks listed in the metadata
  39895. while (i--) {
  39896. if (!videoTrack && data.tracks[i].type === 'video') {
  39897. videoTrack = data.tracks[i];
  39898. videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  39899. } else if (!audioTrack && data.tracks[i].type === 'audio') {
  39900. audioTrack = data.tracks[i];
  39901. audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  39902. }
  39903. } // hook up the video segment stream to the first track with h264 data
  39904. if (videoTrack && !pipeline.videoSegmentStream) {
  39905. pipeline.coalesceStream.numberOfTracks++;
  39906. pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
  39907. pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
  39908. // When video emits timelineStartInfo data after a flush, we forward that
  39909. // info to the AudioSegmentStream, if it exists, because video timeline
  39910. // data takes precedence.
  39911. if (audioTrack) {
  39912. audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
  39913. // very earliest DTS we have seen in video because Chrome will
  39914. // interpret any video track with a baseMediaDecodeTime that is
  39915. // non-zero as a gap.
  39916. pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
  39917. }
  39918. });
  39919. pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
  39920. pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
  39921. pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
  39922. if (audioTrack) {
  39923. pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
  39924. }
  39925. }); // Set up the final part of the video pipeline
  39926. pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
  39927. }
  39928. if (audioTrack && !pipeline.audioSegmentStream) {
  39929. // hook up the audio segment stream to the first track with aac data
  39930. pipeline.coalesceStream.numberOfTracks++;
  39931. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  39932. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  39933. }
  39934. }
  39935. }); // Re-emit any data coming from the coalesce stream to the outside world
  39936. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  39937. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  39938. }; // hook up the segment streams once track metadata is delivered
  39939. this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  39940. var pipeline = this.transmuxPipeline_;
  39941. if (!options.keepOriginalTimestamps) {
  39942. this.baseMediaDecodeTime = baseMediaDecodeTime;
  39943. }
  39944. if (audioTrack) {
  39945. audioTrack.timelineStartInfo.dts = undefined;
  39946. audioTrack.timelineStartInfo.pts = undefined;
  39947. trackDecodeInfo.clearDtsInfo(audioTrack);
  39948. if (!options.keepOriginalTimestamps) {
  39949. audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  39950. }
  39951. if (pipeline.audioTimestampRolloverStream) {
  39952. pipeline.audioTimestampRolloverStream.discontinuity();
  39953. }
  39954. }
  39955. if (videoTrack) {
  39956. if (pipeline.videoSegmentStream) {
  39957. pipeline.videoSegmentStream.gopCache_ = [];
  39958. pipeline.videoTimestampRolloverStream.discontinuity();
  39959. }
  39960. videoTrack.timelineStartInfo.dts = undefined;
  39961. videoTrack.timelineStartInfo.pts = undefined;
  39962. trackDecodeInfo.clearDtsInfo(videoTrack);
  39963. pipeline.captionStream.reset();
  39964. if (!options.keepOriginalTimestamps) {
  39965. videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  39966. }
  39967. }
  39968. if (pipeline.timedMetadataTimestampRolloverStream) {
  39969. pipeline.timedMetadataTimestampRolloverStream.discontinuity();
  39970. }
  39971. };
  39972. this.setAudioAppendStart = function (timestamp) {
  39973. if (audioTrack) {
  39974. this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
  39975. }
  39976. };
  39977. this.alignGopsWith = function (gopsToAlignWith) {
  39978. if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
  39979. this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
  39980. }
  39981. }; // feed incoming data to the front of the parsing pipeline
  39982. this.push = function (data) {
  39983. if (hasFlushed) {
  39984. var isAac = isLikelyAacData$1(data);
  39985. if (isAac && this.transmuxPipeline_.type !== 'aac') {
  39986. this.setupAacPipeline();
  39987. } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
  39988. this.setupTsPipeline();
  39989. }
  39990. hasFlushed = false;
  39991. }
  39992. this.transmuxPipeline_.headOfPipeline.push(data);
  39993. }; // flush any buffered data
  39994. this.flush = function () {
  39995. hasFlushed = true; // Start at the top of the pipeline and flush all pending work
  39996. this.transmuxPipeline_.headOfPipeline.flush();
  39997. }; // Caption data has to be reset when seeking outside buffered range
  39998. this.resetCaptions = function () {
  39999. if (this.transmuxPipeline_.captionStream) {
  40000. this.transmuxPipeline_.captionStream.reset();
  40001. }
  40002. };
  40003. };
  40004. _Transmuxer.prototype = new stream();
  40005. var transmuxer = {
  40006. Transmuxer: _Transmuxer,
  40007. VideoSegmentStream: _VideoSegmentStream,
  40008. AudioSegmentStream: _AudioSegmentStream,
  40009. AUDIO_PROPERTIES: AUDIO_PROPERTIES,
  40010. VIDEO_PROPERTIES: VIDEO_PROPERTIES,
  40011. // exported for testing
  40012. generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
  40013. };
  40014. var inspectMp4,
  40015. _textifyMp,
  40016. parseType$2 = probe$$1.parseType,
  40017. parseMp4Date = function parseMp4Date(seconds) {
  40018. return new Date(seconds * 1000 - 2082844800000);
  40019. },
  40020. parseSampleFlags = function parseSampleFlags(flags) {
  40021. return {
  40022. isLeading: (flags[0] & 0x0c) >>> 2,
  40023. dependsOn: flags[0] & 0x03,
  40024. isDependedOn: (flags[1] & 0xc0) >>> 6,
  40025. hasRedundancy: (flags[1] & 0x30) >>> 4,
  40026. paddingValue: (flags[1] & 0x0e) >>> 1,
  40027. isNonSyncSample: flags[1] & 0x01,
  40028. degradationPriority: flags[2] << 8 | flags[3]
  40029. };
  40030. },
  40031. nalParse = function nalParse(avcStream) {
  40032. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  40033. result = [],
  40034. i,
  40035. length;
  40036. for (i = 0; i + 4 < avcStream.length; i += length) {
  40037. length = avcView.getUint32(i);
  40038. i += 4; // bail if this doesn't appear to be an H264 stream
  40039. if (length <= 0) {
  40040. result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
  40041. continue;
  40042. }
  40043. switch (avcStream[i] & 0x1F) {
  40044. case 0x01:
  40045. result.push('slice_layer_without_partitioning_rbsp');
  40046. break;
  40047. case 0x05:
  40048. result.push('slice_layer_without_partitioning_rbsp_idr');
  40049. break;
  40050. case 0x06:
  40051. result.push('sei_rbsp');
  40052. break;
  40053. case 0x07:
  40054. result.push('seq_parameter_set_rbsp');
  40055. break;
  40056. case 0x08:
  40057. result.push('pic_parameter_set_rbsp');
  40058. break;
  40059. case 0x09:
  40060. result.push('access_unit_delimiter_rbsp');
  40061. break;
  40062. default:
  40063. result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
  40064. break;
  40065. }
  40066. }
  40067. return result;
  40068. },
  40069. // registry of handlers for individual mp4 box types
  40070. parse$$1 = {
  40071. // codingname, not a first-class box type. stsd entries share the
  40072. // same format as real boxes so the parsing infrastructure can be
  40073. // shared
  40074. avc1: function avc1(data) {
  40075. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  40076. return {
  40077. dataReferenceIndex: view.getUint16(6),
  40078. width: view.getUint16(24),
  40079. height: view.getUint16(26),
  40080. horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
  40081. vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
  40082. frameCount: view.getUint16(40),
  40083. depth: view.getUint16(74),
  40084. config: inspectMp4(data.subarray(78, data.byteLength))
  40085. };
  40086. },
  40087. avcC: function avcC(data) {
  40088. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40089. result = {
  40090. configurationVersion: data[0],
  40091. avcProfileIndication: data[1],
  40092. profileCompatibility: data[2],
  40093. avcLevelIndication: data[3],
  40094. lengthSizeMinusOne: data[4] & 0x03,
  40095. sps: [],
  40096. pps: []
  40097. },
  40098. numOfSequenceParameterSets = data[5] & 0x1f,
  40099. numOfPictureParameterSets,
  40100. nalSize,
  40101. offset,
  40102. i; // iterate past any SPSs
  40103. offset = 6;
  40104. for (i = 0; i < numOfSequenceParameterSets; i++) {
  40105. nalSize = view.getUint16(offset);
  40106. offset += 2;
  40107. result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  40108. offset += nalSize;
  40109. } // iterate past any PPSs
  40110. numOfPictureParameterSets = data[offset];
  40111. offset++;
  40112. for (i = 0; i < numOfPictureParameterSets; i++) {
  40113. nalSize = view.getUint16(offset);
  40114. offset += 2;
  40115. result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  40116. offset += nalSize;
  40117. }
  40118. return result;
  40119. },
  40120. btrt: function btrt(data) {
  40121. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  40122. return {
  40123. bufferSizeDB: view.getUint32(0),
  40124. maxBitrate: view.getUint32(4),
  40125. avgBitrate: view.getUint32(8)
  40126. };
  40127. },
  40128. esds: function esds(data) {
  40129. return {
  40130. version: data[0],
  40131. flags: new Uint8Array(data.subarray(1, 4)),
  40132. esId: data[6] << 8 | data[7],
  40133. streamPriority: data[8] & 0x1f,
  40134. decoderConfig: {
  40135. objectProfileIndication: data[11],
  40136. streamType: data[12] >>> 2 & 0x3f,
  40137. bufferSize: data[13] << 16 | data[14] << 8 | data[15],
  40138. maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
  40139. avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
  40140. decoderConfigDescriptor: {
  40141. tag: data[24],
  40142. length: data[25],
  40143. audioObjectType: data[26] >>> 3 & 0x1f,
  40144. samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
  40145. channelConfiguration: data[27] >>> 3 & 0x0f
  40146. }
  40147. }
  40148. };
  40149. },
  40150. ftyp: function ftyp(data) {
  40151. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40152. result = {
  40153. majorBrand: parseType$2(data.subarray(0, 4)),
  40154. minorVersion: view.getUint32(4),
  40155. compatibleBrands: []
  40156. },
  40157. i = 8;
  40158. while (i < data.byteLength) {
  40159. result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4)));
  40160. i += 4;
  40161. }
  40162. return result;
  40163. },
  40164. dinf: function dinf(data) {
  40165. return {
  40166. boxes: inspectMp4(data)
  40167. };
  40168. },
  40169. dref: function dref(data) {
  40170. return {
  40171. version: data[0],
  40172. flags: new Uint8Array(data.subarray(1, 4)),
  40173. dataReferences: inspectMp4(data.subarray(8))
  40174. };
  40175. },
  40176. hdlr: function hdlr(data) {
  40177. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40178. result = {
  40179. version: view.getUint8(0),
  40180. flags: new Uint8Array(data.subarray(1, 4)),
  40181. handlerType: parseType$2(data.subarray(8, 12)),
  40182. name: ''
  40183. },
  40184. i = 8; // parse out the name field
  40185. for (i = 24; i < data.byteLength; i++) {
  40186. if (data[i] === 0x00) {
  40187. // the name field is null-terminated
  40188. i++;
  40189. break;
  40190. }
  40191. result.name += String.fromCharCode(data[i]);
  40192. } // decode UTF-8 to javascript's internal representation
  40193. // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
  40194. result.name = decodeURIComponent(escape(result.name));
  40195. return result;
  40196. },
  40197. mdat: function mdat(data) {
  40198. return {
  40199. byteLength: data.byteLength,
  40200. nals: nalParse(data)
  40201. };
  40202. },
  40203. mdhd: function mdhd(data) {
  40204. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40205. i = 4,
  40206. language,
  40207. result = {
  40208. version: view.getUint8(0),
  40209. flags: new Uint8Array(data.subarray(1, 4)),
  40210. language: ''
  40211. };
  40212. if (result.version === 1) {
  40213. i += 4;
  40214. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40215. i += 8;
  40216. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40217. i += 4;
  40218. result.timescale = view.getUint32(i);
  40219. i += 8;
  40220. result.duration = view.getUint32(i); // truncating top 4 bytes
  40221. } else {
  40222. result.creationTime = parseMp4Date(view.getUint32(i));
  40223. i += 4;
  40224. result.modificationTime = parseMp4Date(view.getUint32(i));
  40225. i += 4;
  40226. result.timescale = view.getUint32(i);
  40227. i += 4;
  40228. result.duration = view.getUint32(i);
  40229. }
  40230. i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
  40231. // each field is the packed difference between its ASCII value and 0x60
  40232. language = view.getUint16(i);
  40233. result.language += String.fromCharCode((language >> 10) + 0x60);
  40234. result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
  40235. result.language += String.fromCharCode((language & 0x1f) + 0x60);
  40236. return result;
  40237. },
  40238. mdia: function mdia(data) {
  40239. return {
  40240. boxes: inspectMp4(data)
  40241. };
  40242. },
  40243. mfhd: function mfhd(data) {
  40244. return {
  40245. version: data[0],
  40246. flags: new Uint8Array(data.subarray(1, 4)),
  40247. sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  40248. };
  40249. },
  40250. minf: function minf(data) {
  40251. return {
  40252. boxes: inspectMp4(data)
  40253. };
  40254. },
  40255. // codingname, not a first-class box type. stsd entries share the
  40256. // same format as real boxes so the parsing infrastructure can be
  40257. // shared
  40258. mp4a: function mp4a(data) {
  40259. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40260. result = {
  40261. // 6 bytes reserved
  40262. dataReferenceIndex: view.getUint16(6),
  40263. // 4 + 4 bytes reserved
  40264. channelcount: view.getUint16(16),
  40265. samplesize: view.getUint16(18),
  40266. // 2 bytes pre_defined
  40267. // 2 bytes reserved
  40268. samplerate: view.getUint16(24) + view.getUint16(26) / 65536
  40269. }; // if there are more bytes to process, assume this is an ISO/IEC
  40270. // 14496-14 MP4AudioSampleEntry and parse the ESDBox
  40271. if (data.byteLength > 28) {
  40272. result.streamDescriptor = inspectMp4(data.subarray(28))[0];
  40273. }
  40274. return result;
  40275. },
  40276. moof: function moof(data) {
  40277. return {
  40278. boxes: inspectMp4(data)
  40279. };
  40280. },
  40281. moov: function moov(data) {
  40282. return {
  40283. boxes: inspectMp4(data)
  40284. };
  40285. },
  40286. mvex: function mvex(data) {
  40287. return {
  40288. boxes: inspectMp4(data)
  40289. };
  40290. },
  40291. mvhd: function mvhd(data) {
  40292. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40293. i = 4,
  40294. result = {
  40295. version: view.getUint8(0),
  40296. flags: new Uint8Array(data.subarray(1, 4))
  40297. };
  40298. if (result.version === 1) {
  40299. i += 4;
  40300. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40301. i += 8;
  40302. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40303. i += 4;
  40304. result.timescale = view.getUint32(i);
  40305. i += 8;
  40306. result.duration = view.getUint32(i); // truncating top 4 bytes
  40307. } else {
  40308. result.creationTime = parseMp4Date(view.getUint32(i));
  40309. i += 4;
  40310. result.modificationTime = parseMp4Date(view.getUint32(i));
  40311. i += 4;
  40312. result.timescale = view.getUint32(i);
  40313. i += 4;
  40314. result.duration = view.getUint32(i);
  40315. }
  40316. i += 4; // convert fixed-point, base 16 back to a number
  40317. result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
  40318. i += 4;
  40319. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  40320. i += 2;
  40321. i += 2;
  40322. i += 2 * 4;
  40323. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  40324. i += 9 * 4;
  40325. i += 6 * 4;
  40326. result.nextTrackId = view.getUint32(i);
  40327. return result;
  40328. },
  40329. pdin: function pdin(data) {
  40330. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  40331. return {
  40332. version: view.getUint8(0),
  40333. flags: new Uint8Array(data.subarray(1, 4)),
  40334. rate: view.getUint32(4),
  40335. initialDelay: view.getUint32(8)
  40336. };
  40337. },
  40338. sdtp: function sdtp(data) {
  40339. var result = {
  40340. version: data[0],
  40341. flags: new Uint8Array(data.subarray(1, 4)),
  40342. samples: []
  40343. },
  40344. i;
  40345. for (i = 4; i < data.byteLength; i++) {
  40346. result.samples.push({
  40347. dependsOn: (data[i] & 0x30) >> 4,
  40348. isDependedOn: (data[i] & 0x0c) >> 2,
  40349. hasRedundancy: data[i] & 0x03
  40350. });
  40351. }
  40352. return result;
  40353. },
  40354. sidx: function sidx(data) {
  40355. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40356. result = {
  40357. version: data[0],
  40358. flags: new Uint8Array(data.subarray(1, 4)),
  40359. references: [],
  40360. referenceId: view.getUint32(4),
  40361. timescale: view.getUint32(8),
  40362. earliestPresentationTime: view.getUint32(12),
  40363. firstOffset: view.getUint32(16)
  40364. },
  40365. referenceCount = view.getUint16(22),
  40366. i;
  40367. for (i = 24; referenceCount; i += 12, referenceCount--) {
  40368. result.references.push({
  40369. referenceType: (data[i] & 0x80) >>> 7,
  40370. referencedSize: view.getUint32(i) & 0x7FFFFFFF,
  40371. subsegmentDuration: view.getUint32(i + 4),
  40372. startsWithSap: !!(data[i + 8] & 0x80),
  40373. sapType: (data[i + 8] & 0x70) >>> 4,
  40374. sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
  40375. });
  40376. }
  40377. return result;
  40378. },
  40379. smhd: function smhd(data) {
  40380. return {
  40381. version: data[0],
  40382. flags: new Uint8Array(data.subarray(1, 4)),
  40383. balance: data[4] + data[5] / 256
  40384. };
  40385. },
  40386. stbl: function stbl(data) {
  40387. return {
  40388. boxes: inspectMp4(data)
  40389. };
  40390. },
  40391. stco: function stco(data) {
  40392. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40393. result = {
  40394. version: data[0],
  40395. flags: new Uint8Array(data.subarray(1, 4)),
  40396. chunkOffsets: []
  40397. },
  40398. entryCount = view.getUint32(4),
  40399. i;
  40400. for (i = 8; entryCount; i += 4, entryCount--) {
  40401. result.chunkOffsets.push(view.getUint32(i));
  40402. }
  40403. return result;
  40404. },
  40405. stsc: function stsc(data) {
  40406. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40407. entryCount = view.getUint32(4),
  40408. result = {
  40409. version: data[0],
  40410. flags: new Uint8Array(data.subarray(1, 4)),
  40411. sampleToChunks: []
  40412. },
  40413. i;
  40414. for (i = 8; entryCount; i += 12, entryCount--) {
  40415. result.sampleToChunks.push({
  40416. firstChunk: view.getUint32(i),
  40417. samplesPerChunk: view.getUint32(i + 4),
  40418. sampleDescriptionIndex: view.getUint32(i + 8)
  40419. });
  40420. }
  40421. return result;
  40422. },
  40423. stsd: function stsd(data) {
  40424. return {
  40425. version: data[0],
  40426. flags: new Uint8Array(data.subarray(1, 4)),
  40427. sampleDescriptions: inspectMp4(data.subarray(8))
  40428. };
  40429. },
  40430. stsz: function stsz(data) {
  40431. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40432. result = {
  40433. version: data[0],
  40434. flags: new Uint8Array(data.subarray(1, 4)),
  40435. sampleSize: view.getUint32(4),
  40436. entries: []
  40437. },
  40438. i;
  40439. for (i = 12; i < data.byteLength; i += 4) {
  40440. result.entries.push(view.getUint32(i));
  40441. }
  40442. return result;
  40443. },
  40444. stts: function stts(data) {
  40445. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40446. result = {
  40447. version: data[0],
  40448. flags: new Uint8Array(data.subarray(1, 4)),
  40449. timeToSamples: []
  40450. },
  40451. entryCount = view.getUint32(4),
  40452. i;
  40453. for (i = 8; entryCount; i += 8, entryCount--) {
  40454. result.timeToSamples.push({
  40455. sampleCount: view.getUint32(i),
  40456. sampleDelta: view.getUint32(i + 4)
  40457. });
  40458. }
  40459. return result;
  40460. },
  40461. styp: function styp(data) {
  40462. return parse$$1.ftyp(data);
  40463. },
  40464. tfdt: function tfdt(data) {
  40465. var result = {
  40466. version: data[0],
  40467. flags: new Uint8Array(data.subarray(1, 4)),
  40468. baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  40469. };
  40470. if (result.version === 1) {
  40471. result.baseMediaDecodeTime *= Math.pow(2, 32);
  40472. result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11];
  40473. }
  40474. return result;
  40475. },
  40476. tfhd: function tfhd(data) {
  40477. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40478. result = {
  40479. version: data[0],
  40480. flags: new Uint8Array(data.subarray(1, 4)),
  40481. trackId: view.getUint32(4)
  40482. },
  40483. baseDataOffsetPresent = result.flags[2] & 0x01,
  40484. sampleDescriptionIndexPresent = result.flags[2] & 0x02,
  40485. defaultSampleDurationPresent = result.flags[2] & 0x08,
  40486. defaultSampleSizePresent = result.flags[2] & 0x10,
  40487. defaultSampleFlagsPresent = result.flags[2] & 0x20,
  40488. durationIsEmpty = result.flags[0] & 0x010000,
  40489. defaultBaseIsMoof = result.flags[0] & 0x020000,
  40490. i;
  40491. i = 8;
  40492. if (baseDataOffsetPresent) {
  40493. i += 4; // truncate top 4 bytes
  40494. // FIXME: should we read the full 64 bits?
  40495. result.baseDataOffset = view.getUint32(12);
  40496. i += 4;
  40497. }
  40498. if (sampleDescriptionIndexPresent) {
  40499. result.sampleDescriptionIndex = view.getUint32(i);
  40500. i += 4;
  40501. }
  40502. if (defaultSampleDurationPresent) {
  40503. result.defaultSampleDuration = view.getUint32(i);
  40504. i += 4;
  40505. }
  40506. if (defaultSampleSizePresent) {
  40507. result.defaultSampleSize = view.getUint32(i);
  40508. i += 4;
  40509. }
  40510. if (defaultSampleFlagsPresent) {
  40511. result.defaultSampleFlags = view.getUint32(i);
  40512. }
  40513. if (durationIsEmpty) {
  40514. result.durationIsEmpty = true;
  40515. }
  40516. if (!baseDataOffsetPresent && defaultBaseIsMoof) {
  40517. result.baseDataOffsetIsMoof = true;
  40518. }
  40519. return result;
  40520. },
  40521. tkhd: function tkhd(data) {
  40522. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40523. i = 4,
  40524. result = {
  40525. version: view.getUint8(0),
  40526. flags: new Uint8Array(data.subarray(1, 4))
  40527. };
  40528. if (result.version === 1) {
  40529. i += 4;
  40530. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40531. i += 8;
  40532. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40533. i += 4;
  40534. result.trackId = view.getUint32(i);
  40535. i += 4;
  40536. i += 8;
  40537. result.duration = view.getUint32(i); // truncating top 4 bytes
  40538. } else {
  40539. result.creationTime = parseMp4Date(view.getUint32(i));
  40540. i += 4;
  40541. result.modificationTime = parseMp4Date(view.getUint32(i));
  40542. i += 4;
  40543. result.trackId = view.getUint32(i);
  40544. i += 4;
  40545. i += 4;
  40546. result.duration = view.getUint32(i);
  40547. }
  40548. i += 4;
  40549. i += 2 * 4;
  40550. result.layer = view.getUint16(i);
  40551. i += 2;
  40552. result.alternateGroup = view.getUint16(i);
  40553. i += 2; // convert fixed-point, base 16 back to a number
  40554. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  40555. i += 2;
  40556. i += 2;
  40557. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  40558. i += 9 * 4;
  40559. result.width = view.getUint16(i) + view.getUint16(i + 2) / 16;
  40560. i += 4;
  40561. result.height = view.getUint16(i) + view.getUint16(i + 2) / 16;
  40562. return result;
  40563. },
  40564. traf: function traf(data) {
  40565. return {
  40566. boxes: inspectMp4(data)
  40567. };
  40568. },
  40569. trak: function trak(data) {
  40570. return {
  40571. boxes: inspectMp4(data)
  40572. };
  40573. },
  40574. trex: function trex(data) {
  40575. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  40576. return {
  40577. version: data[0],
  40578. flags: new Uint8Array(data.subarray(1, 4)),
  40579. trackId: view.getUint32(4),
  40580. defaultSampleDescriptionIndex: view.getUint32(8),
  40581. defaultSampleDuration: view.getUint32(12),
  40582. defaultSampleSize: view.getUint32(16),
  40583. sampleDependsOn: data[20] & 0x03,
  40584. sampleIsDependedOn: (data[21] & 0xc0) >> 6,
  40585. sampleHasRedundancy: (data[21] & 0x30) >> 4,
  40586. samplePaddingValue: (data[21] & 0x0e) >> 1,
  40587. sampleIsDifferenceSample: !!(data[21] & 0x01),
  40588. sampleDegradationPriority: view.getUint16(22)
  40589. };
  40590. },
  40591. trun: function trun(data) {
  40592. var result = {
  40593. version: data[0],
  40594. flags: new Uint8Array(data.subarray(1, 4)),
  40595. samples: []
  40596. },
  40597. view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40598. // Flag interpretation
  40599. dataOffsetPresent = result.flags[2] & 0x01,
  40600. // compare with 2nd byte of 0x1
  40601. firstSampleFlagsPresent = result.flags[2] & 0x04,
  40602. // compare with 2nd byte of 0x4
  40603. sampleDurationPresent = result.flags[1] & 0x01,
  40604. // compare with 2nd byte of 0x100
  40605. sampleSizePresent = result.flags[1] & 0x02,
  40606. // compare with 2nd byte of 0x200
  40607. sampleFlagsPresent = result.flags[1] & 0x04,
  40608. // compare with 2nd byte of 0x400
  40609. sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
  40610. // compare with 2nd byte of 0x800
  40611. sampleCount = view.getUint32(4),
  40612. offset = 8,
  40613. sample;
  40614. if (dataOffsetPresent) {
  40615. // 32 bit signed integer
  40616. result.dataOffset = view.getInt32(offset);
  40617. offset += 4;
  40618. } // Overrides the flags for the first sample only. The order of
  40619. // optional values will be: duration, size, compositionTimeOffset
  40620. if (firstSampleFlagsPresent && sampleCount) {
  40621. sample = {
  40622. flags: parseSampleFlags(data.subarray(offset, offset + 4))
  40623. };
  40624. offset += 4;
  40625. if (sampleDurationPresent) {
  40626. sample.duration = view.getUint32(offset);
  40627. offset += 4;
  40628. }
  40629. if (sampleSizePresent) {
  40630. sample.size = view.getUint32(offset);
  40631. offset += 4;
  40632. }
  40633. if (sampleCompositionTimeOffsetPresent) {
  40634. // Note: this should be a signed int if version is 1
  40635. sample.compositionTimeOffset = view.getUint32(offset);
  40636. offset += 4;
  40637. }
  40638. result.samples.push(sample);
  40639. sampleCount--;
  40640. }
  40641. while (sampleCount--) {
  40642. sample = {};
  40643. if (sampleDurationPresent) {
  40644. sample.duration = view.getUint32(offset);
  40645. offset += 4;
  40646. }
  40647. if (sampleSizePresent) {
  40648. sample.size = view.getUint32(offset);
  40649. offset += 4;
  40650. }
  40651. if (sampleFlagsPresent) {
  40652. sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
  40653. offset += 4;
  40654. }
  40655. if (sampleCompositionTimeOffsetPresent) {
  40656. // Note: this should be a signed int if version is 1
  40657. sample.compositionTimeOffset = view.getUint32(offset);
  40658. offset += 4;
  40659. }
  40660. result.samples.push(sample);
  40661. }
  40662. return result;
  40663. },
  40664. 'url ': function url(data) {
  40665. return {
  40666. version: data[0],
  40667. flags: new Uint8Array(data.subarray(1, 4))
  40668. };
  40669. },
  40670. vmhd: function vmhd(data) {
  40671. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  40672. return {
  40673. version: data[0],
  40674. flags: new Uint8Array(data.subarray(1, 4)),
  40675. graphicsmode: view.getUint16(4),
  40676. opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
  40677. };
  40678. }
  40679. };
  40680. /**
  40681. * Return a javascript array of box objects parsed from an ISO base
  40682. * media file.
  40683. * @param data {Uint8Array} the binary data of the media to be inspected
  40684. * @return {array} a javascript array of potentially nested box objects
  40685. */
  40686. inspectMp4 = function inspectMp4(data) {
  40687. var i = 0,
  40688. result = [],
  40689. view,
  40690. size,
  40691. type,
  40692. end,
  40693. box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
  40694. var ab = new ArrayBuffer(data.length);
  40695. var v = new Uint8Array(ab);
  40696. for (var z = 0; z < data.length; ++z) {
  40697. v[z] = data[z];
  40698. }
  40699. view = new DataView(ab);
  40700. while (i < data.byteLength) {
  40701. // parse box data
  40702. size = view.getUint32(i);
  40703. type = parseType$2(data.subarray(i + 4, i + 8));
  40704. end = size > 1 ? i + size : data.byteLength; // parse type-specific data
  40705. box = (parse$$1[type] || function (data) {
  40706. return {
  40707. data: data
  40708. };
  40709. })(data.subarray(i + 8, end));
  40710. box.size = size;
  40711. box.type = type; // store this box and move to the next
  40712. result.push(box);
  40713. i = end;
  40714. }
  40715. return result;
  40716. };
  40717. /**
  40718. * Returns a textual representation of the javascript represtentation
  40719. * of an MP4 file. You can use it as an alternative to
  40720. * JSON.stringify() to compare inspected MP4s.
  40721. * @param inspectedMp4 {array} the parsed array of boxes in an MP4
  40722. * file
  40723. * @param depth {number} (optional) the number of ancestor boxes of
  40724. * the elements of inspectedMp4. Assumed to be zero if unspecified.
  40725. * @return {string} a text representation of the parsed MP4
  40726. */
  40727. _textifyMp = function textifyMp4(inspectedMp4, depth) {
  40728. var indent;
  40729. depth = depth || 0;
  40730. indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
  40731. return inspectedMp4.map(function (box, index) {
  40732. // list the box type first at the current indentation level
  40733. return indent + box.type + '\n' + // the type is already included and handle child boxes separately
  40734. Object.keys(box).filter(function (key) {
  40735. return key !== 'type' && key !== 'boxes'; // output all the box properties
  40736. }).map(function (key) {
  40737. var prefix = indent + ' ' + key + ': ',
  40738. value = box[key]; // print out raw bytes as hexademical
  40739. if (value instanceof Uint8Array || value instanceof Uint32Array) {
  40740. var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) {
  40741. return ' ' + ('00' + byte.toString(16)).slice(-2);
  40742. }).join('').match(/.{1,24}/g);
  40743. if (!bytes) {
  40744. return prefix + '<>';
  40745. }
  40746. if (bytes.length === 1) {
  40747. return prefix + '<' + bytes.join('').slice(1) + '>';
  40748. }
  40749. return prefix + '<\n' + bytes.map(function (line) {
  40750. return indent + ' ' + line;
  40751. }).join('\n') + '\n' + indent + ' >';
  40752. } // stringify generic objects
  40753. return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
  40754. if (index === 0) {
  40755. return line;
  40756. }
  40757. return indent + ' ' + line;
  40758. }).join('\n');
  40759. }).join('\n') + ( // recursively textify the child boxes
  40760. box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
  40761. }).join('\n');
  40762. };
  40763. var mp4Inspector$$1 = {
  40764. inspect: inspectMp4,
  40765. textify: _textifyMp,
  40766. parseTfdt: parse$$1.tfdt,
  40767. parseHdlr: parse$$1.hdlr,
  40768. parseTfhd: parse$$1.tfhd,
  40769. parseTrun: parse$$1.trun,
  40770. parseSidx: parse$$1.sidx
  40771. };
  40772. var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
  40773. var CaptionStream$1 = captionStream.CaptionStream;
  40774. /**
  40775. * Maps an offset in the mdat to a sample based on the the size of the samples.
  40776. * Assumes that `parseSamples` has been called first.
  40777. *
  40778. * @param {Number} offset - The offset into the mdat
  40779. * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
  40780. * @return {?Object} The matching sample, or null if no match was found.
  40781. *
  40782. * @see ISO-BMFF-12/2015, Section 8.8.8
  40783. **/
  40784. var mapToSample = function mapToSample(offset, samples) {
  40785. var approximateOffset = offset;
  40786. for (var i = 0; i < samples.length; i++) {
  40787. var sample = samples[i];
  40788. if (approximateOffset < sample.size) {
  40789. return sample;
  40790. }
  40791. approximateOffset -= sample.size;
  40792. }
  40793. return null;
  40794. };
  40795. /**
  40796. * Finds SEI nal units contained in a Media Data Box.
  40797. * Assumes that `parseSamples` has been called first.
  40798. *
  40799. * @param {Uint8Array} avcStream - The bytes of the mdat
  40800. * @param {Object[]} samples - The samples parsed out by `parseSamples`
  40801. * @param {Number} trackId - The trackId of this video track
  40802. * @return {Object[]} seiNals - the parsed SEI NALUs found.
  40803. * The contents of the seiNal should match what is expected by
  40804. * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
  40805. *
  40806. * @see ISO-BMFF-12/2015, Section 8.1.1
  40807. * @see Rec. ITU-T H.264, 7.3.2.3.1
  40808. **/
  40809. var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
  40810. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  40811. result = [],
  40812. seiNal,
  40813. i,
  40814. length,
  40815. lastMatchedSample;
  40816. for (i = 0; i + 4 < avcStream.length; i += length) {
  40817. length = avcView.getUint32(i);
  40818. i += 4; // Bail if this doesn't appear to be an H264 stream
  40819. if (length <= 0) {
  40820. continue;
  40821. }
  40822. switch (avcStream[i] & 0x1F) {
  40823. case 0x06:
  40824. var data = avcStream.subarray(i + 1, i + 1 + length);
  40825. var matchingSample = mapToSample(i, samples);
  40826. seiNal = {
  40827. nalUnitType: 'sei_rbsp',
  40828. size: length,
  40829. data: data,
  40830. escapedRBSP: discardEmulationPreventionBytes$1(data),
  40831. trackId: trackId
  40832. };
  40833. if (matchingSample) {
  40834. seiNal.pts = matchingSample.pts;
  40835. seiNal.dts = matchingSample.dts;
  40836. lastMatchedSample = matchingSample;
  40837. } else {
  40838. // If a matching sample cannot be found, use the last
  40839. // sample's values as they should be as close as possible
  40840. seiNal.pts = lastMatchedSample.pts;
  40841. seiNal.dts = lastMatchedSample.dts;
  40842. }
  40843. result.push(seiNal);
  40844. break;
  40845. default:
  40846. break;
  40847. }
  40848. }
  40849. return result;
  40850. };
  40851. /**
  40852. * Parses sample information out of Track Run Boxes and calculates
  40853. * the absolute presentation and decode timestamps of each sample.
  40854. *
  40855. * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
  40856. * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
  40857. @see ISO-BMFF-12/2015, Section 8.8.12
  40858. * @param {Object} tfhd - The parsed Track Fragment Header
  40859. * @see inspect.parseTfhd
  40860. * @return {Object[]} the parsed samples
  40861. *
  40862. * @see ISO-BMFF-12/2015, Section 8.8.8
  40863. **/
  40864. var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
  40865. var currentDts = baseMediaDecodeTime;
  40866. var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
  40867. var defaultSampleSize = tfhd.defaultSampleSize || 0;
  40868. var trackId = tfhd.trackId;
  40869. var allSamples = [];
  40870. truns.forEach(function (trun) {
  40871. // Note: We currently do not parse the sample table as well
  40872. // as the trun. It's possible some sources will require this.
  40873. // moov > trak > mdia > minf > stbl
  40874. var trackRun = mp4Inspector$$1.parseTrun(trun);
  40875. var samples = trackRun.samples;
  40876. samples.forEach(function (sample) {
  40877. if (sample.duration === undefined) {
  40878. sample.duration = defaultSampleDuration;
  40879. }
  40880. if (sample.size === undefined) {
  40881. sample.size = defaultSampleSize;
  40882. }
  40883. sample.trackId = trackId;
  40884. sample.dts = currentDts;
  40885. if (sample.compositionTimeOffset === undefined) {
  40886. sample.compositionTimeOffset = 0;
  40887. }
  40888. sample.pts = currentDts + sample.compositionTimeOffset;
  40889. currentDts += sample.duration;
  40890. });
  40891. allSamples = allSamples.concat(samples);
  40892. });
  40893. return allSamples;
  40894. };
  40895. /**
  40896. * Parses out caption nals from an FMP4 segment's video tracks.
  40897. *
  40898. * @param {Uint8Array} segment - The bytes of a single segment
  40899. * @param {Number} videoTrackId - The trackId of a video track in the segment
  40900. * @return {Object.<Number, Object[]>} A mapping of video trackId to
  40901. * a list of seiNals found in that track
  40902. **/
  40903. var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
  40904. // To get the samples
  40905. var trafs = probe$$1.findBox(segment, ['moof', 'traf']); // To get SEI NAL units
  40906. var mdats = probe$$1.findBox(segment, ['mdat']);
  40907. var captionNals = {};
  40908. var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
  40909. mdats.forEach(function (mdat, index) {
  40910. var matchingTraf = trafs[index];
  40911. mdatTrafPairs.push({
  40912. mdat: mdat,
  40913. traf: matchingTraf
  40914. });
  40915. });
  40916. mdatTrafPairs.forEach(function (pair) {
  40917. var mdat = pair.mdat;
  40918. var traf = pair.traf;
  40919. var tfhd = probe$$1.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
  40920. var headerInfo = mp4Inspector$$1.parseTfhd(tfhd[0]);
  40921. var trackId = headerInfo.trackId;
  40922. var tfdt = probe$$1.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
  40923. var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector$$1.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
  40924. var truns = probe$$1.findBox(traf, ['trun']);
  40925. var samples;
  40926. var seiNals; // Only parse video data for the chosen video track
  40927. if (videoTrackId === trackId && truns.length > 0) {
  40928. samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
  40929. seiNals = findSeiNals(mdat, samples, trackId);
  40930. if (!captionNals[trackId]) {
  40931. captionNals[trackId] = [];
  40932. }
  40933. captionNals[trackId] = captionNals[trackId].concat(seiNals);
  40934. }
  40935. });
  40936. return captionNals;
  40937. };
  40938. /**
  40939. * Parses out inband captions from an MP4 container and returns
  40940. * caption objects that can be used by WebVTT and the TextTrack API.
  40941. * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
  40942. * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
  40943. * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
  40944. *
  40945. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  40946. * @param {Number} trackId - The id of the video track to parse
  40947. * @param {Number} timescale - The timescale for the video track from the init segment
  40948. *
  40949. * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
  40950. * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
  40951. * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
  40952. * @return {String} parsedCaptions[].text - The visible content of the caption
  40953. **/
  40954. var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
  40955. var seiNals;
  40956. if (!trackId) {
  40957. return null;
  40958. }
  40959. seiNals = parseCaptionNals(segment, trackId);
  40960. return {
  40961. seiNals: seiNals[trackId],
  40962. timescale: timescale
  40963. };
  40964. };
  40965. /**
  40966. * Converts SEI NALUs into captions that can be used by video.js
  40967. **/
  40968. var CaptionParser$$1 = function CaptionParser$$1() {
  40969. var isInitialized = false;
  40970. var captionStream$$1; // Stores segments seen before trackId and timescale are set
  40971. var segmentCache; // Stores video track ID of the track being parsed
  40972. var trackId; // Stores the timescale of the track being parsed
  40973. var timescale; // Stores captions parsed so far
  40974. var parsedCaptions;
  40975. /**
  40976. * A method to indicate whether a CaptionParser has been initalized
  40977. * @returns {Boolean}
  40978. **/
  40979. this.isInitialized = function () {
  40980. return isInitialized;
  40981. };
  40982. /**
  40983. * Initializes the underlying CaptionStream, SEI NAL parsing
  40984. * and management, and caption collection
  40985. **/
  40986. this.init = function () {
  40987. captionStream$$1 = new CaptionStream$1();
  40988. isInitialized = true; // Collect dispatched captions
  40989. captionStream$$1.on('data', function (event) {
  40990. // Convert to seconds in the source's timescale
  40991. event.startTime = event.startPts / timescale;
  40992. event.endTime = event.endPts / timescale;
  40993. parsedCaptions.captions.push(event);
  40994. parsedCaptions.captionStreams[event.stream] = true;
  40995. });
  40996. };
  40997. /**
  40998. * Determines if a new video track will be selected
  40999. * or if the timescale changed
  41000. * @return {Boolean}
  41001. **/
  41002. this.isNewInit = function (videoTrackIds, timescales) {
  41003. if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
  41004. return false;
  41005. }
  41006. return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
  41007. };
  41008. /**
  41009. * Parses out SEI captions and interacts with underlying
  41010. * CaptionStream to return dispatched captions
  41011. *
  41012. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  41013. * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
  41014. * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
  41015. * @see parseEmbeddedCaptions
  41016. * @see m2ts/caption-stream.js
  41017. **/
  41018. this.parse = function (segment, videoTrackIds, timescales) {
  41019. var parsedData;
  41020. if (!this.isInitialized()) {
  41021. return null; // This is not likely to be a video segment
  41022. } else if (!videoTrackIds || !timescales) {
  41023. return null;
  41024. } else if (this.isNewInit(videoTrackIds, timescales)) {
  41025. // Use the first video track only as there is no
  41026. // mechanism to switch to other video tracks
  41027. trackId = videoTrackIds[0];
  41028. timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
  41029. // data until we have one
  41030. } else if (!trackId || !timescale) {
  41031. segmentCache.push(segment);
  41032. return null;
  41033. } // Now that a timescale and trackId is set, parse cached segments
  41034. while (segmentCache.length > 0) {
  41035. var cachedSegment = segmentCache.shift();
  41036. this.parse(cachedSegment, videoTrackIds, timescales);
  41037. }
  41038. parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
  41039. if (parsedData === null || !parsedData.seiNals) {
  41040. return null;
  41041. }
  41042. this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
  41043. this.flushStream();
  41044. return parsedCaptions;
  41045. };
  41046. /**
  41047. * Pushes SEI NALUs onto CaptionStream
  41048. * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
  41049. * Assumes that `parseCaptionNals` has been called first
  41050. * @see m2ts/caption-stream.js
  41051. **/
  41052. this.pushNals = function (nals) {
  41053. if (!this.isInitialized() || !nals || nals.length === 0) {
  41054. return null;
  41055. }
  41056. nals.forEach(function (nal) {
  41057. captionStream$$1.push(nal);
  41058. });
  41059. };
  41060. /**
  41061. * Flushes underlying CaptionStream to dispatch processed, displayable captions
  41062. * @see m2ts/caption-stream.js
  41063. **/
  41064. this.flushStream = function () {
  41065. if (!this.isInitialized()) {
  41066. return null;
  41067. }
  41068. captionStream$$1.flush();
  41069. };
  41070. /**
  41071. * Reset caption buckets for new data
  41072. **/
  41073. this.clearParsedCaptions = function () {
  41074. parsedCaptions.captions = [];
  41075. parsedCaptions.captionStreams = {};
  41076. };
  41077. /**
  41078. * Resets underlying CaptionStream
  41079. * @see m2ts/caption-stream.js
  41080. **/
  41081. this.resetCaptionStream = function () {
  41082. if (!this.isInitialized()) {
  41083. return null;
  41084. }
  41085. captionStream$$1.reset();
  41086. };
  41087. /**
  41088. * Convenience method to clear all captions flushed from the
  41089. * CaptionStream and still being parsed
  41090. * @see m2ts/caption-stream.js
  41091. **/
  41092. this.clearAllCaptions = function () {
  41093. this.clearParsedCaptions();
  41094. this.resetCaptionStream();
  41095. };
  41096. /**
  41097. * Reset caption parser
  41098. **/
  41099. this.reset = function () {
  41100. segmentCache = [];
  41101. trackId = null;
  41102. timescale = null;
  41103. if (!parsedCaptions) {
  41104. parsedCaptions = {
  41105. captions: [],
  41106. // CC1, CC2, CC3, CC4
  41107. captionStreams: {}
  41108. };
  41109. } else {
  41110. this.clearParsedCaptions();
  41111. }
  41112. this.resetCaptionStream();
  41113. };
  41114. this.reset();
  41115. };
  41116. var captionParser = CaptionParser$$1;
  41117. var mp4$$1 = {
  41118. generator: mp4Generator,
  41119. probe: probe$$1,
  41120. Transmuxer: transmuxer.Transmuxer,
  41121. AudioSegmentStream: transmuxer.AudioSegmentStream,
  41122. VideoSegmentStream: transmuxer.VideoSegmentStream,
  41123. CaptionParser: captionParser
  41124. };
  41125. var classCallCheck = function classCallCheck(instance, Constructor) {
  41126. if (!(instance instanceof Constructor)) {
  41127. throw new TypeError("Cannot call a class as a function");
  41128. }
  41129. };
  41130. var createClass = function () {
  41131. function defineProperties(target, props) {
  41132. for (var i = 0; i < props.length; i++) {
  41133. var descriptor = props[i];
  41134. descriptor.enumerable = descriptor.enumerable || false;
  41135. descriptor.configurable = true;
  41136. if ("value" in descriptor) descriptor.writable = true;
  41137. Object.defineProperty(target, descriptor.key, descriptor);
  41138. }
  41139. }
  41140. return function (Constructor, protoProps, staticProps) {
  41141. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  41142. if (staticProps) defineProperties(Constructor, staticProps);
  41143. return Constructor;
  41144. };
  41145. }();
  41146. /**
  41147. * @file transmuxer-worker.js
  41148. */
  41149. /**
  41150. * Re-emits transmuxer events by converting them into messages to the
  41151. * world outside the worker.
  41152. *
  41153. * @param {Object} transmuxer the transmuxer to wire events on
  41154. * @private
  41155. */
  41156. var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer) {
  41157. transmuxer.on('data', function (segment) {
  41158. // transfer ownership of the underlying ArrayBuffer
  41159. // instead of doing a copy to save memory
  41160. // ArrayBuffers are transferable but generic TypedArrays are not
  41161. // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
  41162. var initArray = segment.initSegment;
  41163. segment.initSegment = {
  41164. data: initArray.buffer,
  41165. byteOffset: initArray.byteOffset,
  41166. byteLength: initArray.byteLength
  41167. };
  41168. var typedArray = segment.data;
  41169. segment.data = typedArray.buffer;
  41170. self.postMessage({
  41171. action: 'data',
  41172. segment: segment,
  41173. byteOffset: typedArray.byteOffset,
  41174. byteLength: typedArray.byteLength
  41175. }, [segment.data]);
  41176. });
  41177. if (transmuxer.captionStream) {
  41178. transmuxer.captionStream.on('data', function (caption) {
  41179. self.postMessage({
  41180. action: 'caption',
  41181. data: caption
  41182. });
  41183. });
  41184. }
  41185. transmuxer.on('done', function (data) {
  41186. self.postMessage({
  41187. action: 'done'
  41188. });
  41189. });
  41190. transmuxer.on('gopInfo', function (gopInfo) {
  41191. self.postMessage({
  41192. action: 'gopInfo',
  41193. gopInfo: gopInfo
  41194. });
  41195. });
  41196. transmuxer.on('videoSegmentTimingInfo', function (videoSegmentTimingInfo) {
  41197. self.postMessage({
  41198. action: 'videoSegmentTimingInfo',
  41199. videoSegmentTimingInfo: videoSegmentTimingInfo
  41200. });
  41201. });
  41202. };
  41203. /**
  41204. * All incoming messages route through this hash. If no function exists
  41205. * to handle an incoming message, then we ignore the message.
  41206. *
  41207. * @class MessageHandlers
  41208. * @param {Object} options the options to initialize with
  41209. */
  41210. var MessageHandlers = function () {
  41211. function MessageHandlers(self, options) {
  41212. classCallCheck(this, MessageHandlers);
  41213. this.options = options || {};
  41214. this.self = self;
  41215. this.init();
  41216. }
  41217. /**
  41218. * initialize our web worker and wire all the events.
  41219. */
  41220. createClass(MessageHandlers, [{
  41221. key: 'init',
  41222. value: function init() {
  41223. if (this.transmuxer) {
  41224. this.transmuxer.dispose();
  41225. }
  41226. this.transmuxer = new mp4$$1.Transmuxer(this.options);
  41227. wireTransmuxerEvents(this.self, this.transmuxer);
  41228. }
  41229. /**
  41230. * Adds data (a ts segment) to the start of the transmuxer pipeline for
  41231. * processing.
  41232. *
  41233. * @param {ArrayBuffer} data data to push into the muxer
  41234. */
  41235. }, {
  41236. key: 'push',
  41237. value: function push(data) {
  41238. // Cast array buffer to correct type for transmuxer
  41239. var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
  41240. this.transmuxer.push(segment);
  41241. }
  41242. /**
  41243. * Recreate the transmuxer so that the next segment added via `push`
  41244. * start with a fresh transmuxer.
  41245. */
  41246. }, {
  41247. key: 'reset',
  41248. value: function reset() {
  41249. this.init();
  41250. }
  41251. /**
  41252. * Set the value that will be used as the `baseMediaDecodeTime` time for the
  41253. * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
  41254. * set relative to the first based on the PTS values.
  41255. *
  41256. * @param {Object} data used to set the timestamp offset in the muxer
  41257. */
  41258. }, {
  41259. key: 'setTimestampOffset',
  41260. value: function setTimestampOffset(data) {
  41261. var timestampOffset = data.timestampOffset || 0;
  41262. this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000));
  41263. }
  41264. }, {
  41265. key: 'setAudioAppendStart',
  41266. value: function setAudioAppendStart(data) {
  41267. this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000));
  41268. }
  41269. /**
  41270. * Forces the pipeline to finish processing the last segment and emit it's
  41271. * results.
  41272. *
  41273. * @param {Object} data event data, not really used
  41274. */
  41275. }, {
  41276. key: 'flush',
  41277. value: function flush(data) {
  41278. this.transmuxer.flush();
  41279. }
  41280. }, {
  41281. key: 'resetCaptions',
  41282. value: function resetCaptions() {
  41283. this.transmuxer.resetCaptions();
  41284. }
  41285. }, {
  41286. key: 'alignGopsWith',
  41287. value: function alignGopsWith(data) {
  41288. this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
  41289. }
  41290. }]);
  41291. return MessageHandlers;
  41292. }();
  41293. /**
  41294. * Our web wroker interface so that things can talk to mux.js
  41295. * that will be running in a web worker. the scope is passed to this by
  41296. * webworkify.
  41297. *
  41298. * @param {Object} self the scope for the web worker
  41299. */
  41300. var TransmuxerWorker = function TransmuxerWorker(self) {
  41301. self.onmessage = function (event) {
  41302. if (event.data.action === 'init' && event.data.options) {
  41303. this.messageHandlers = new MessageHandlers(self, event.data.options);
  41304. return;
  41305. }
  41306. if (!this.messageHandlers) {
  41307. this.messageHandlers = new MessageHandlers(self);
  41308. }
  41309. if (event.data && event.data.action && event.data.action !== 'init') {
  41310. if (this.messageHandlers[event.data.action]) {
  41311. this.messageHandlers[event.data.action](event.data);
  41312. }
  41313. }
  41314. };
  41315. };
  41316. var transmuxerWorker = new TransmuxerWorker(self);
  41317. return transmuxerWorker;
  41318. }();
  41319. });
  41320. /**
  41321. * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
  41322. * codec strings, or translating codec strings into objects that can be examined.
  41323. */
  41324. // Default codec parameters if none were provided for video and/or audio
  41325. var defaultCodecs = {
  41326. videoCodec: 'avc1',
  41327. videoObjectTypeIndicator: '.4d400d',
  41328. // AAC-LC
  41329. audioProfile: '2'
  41330. };
  41331. /**
  41332. * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
  41333. * `avc1.<hhhhhh>`
  41334. *
  41335. * @param {Array} codecs an array of codec strings to fix
  41336. * @return {Array} the translated codec array
  41337. * @private
  41338. */
  41339. var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
  41340. return codecs.map(function (codec) {
  41341. return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
  41342. var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
  41343. var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
  41344. return 'avc1.' + profileHex + '00' + avcLevelHex;
  41345. });
  41346. });
  41347. };
  41348. /**
  41349. * Parses a codec string to retrieve the number of codecs specified,
  41350. * the video codec and object type indicator, and the audio profile.
  41351. */
  41352. var parseCodecs = function parseCodecs() {
  41353. var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
  41354. var result = {
  41355. codecCount: 0
  41356. };
  41357. var parsed = void 0;
  41358. result.codecCount = codecs.split(',').length;
  41359. result.codecCount = result.codecCount || 2; // parse the video codec
  41360. parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs);
  41361. if (parsed) {
  41362. result.videoCodec = parsed[2];
  41363. result.videoObjectTypeIndicator = parsed[3];
  41364. } // parse the last field of the audio codec
  41365. result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs);
  41366. result.audioProfile = result.audioProfile && result.audioProfile[2];
  41367. return result;
  41368. };
  41369. /**
  41370. * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
  41371. * standard `avc1.<hhhhhh>`.
  41372. *
  41373. * @param codecString {String} the codec string
  41374. * @return {String} the codec string with old apple-style codecs replaced
  41375. *
  41376. * @private
  41377. */
  41378. var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
  41379. return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
  41380. return translateLegacyCodecs([match])[0];
  41381. });
  41382. };
  41383. /**
  41384. * Build a media mime-type string from a set of parameters
  41385. * @param {String} type either 'audio' or 'video'
  41386. * @param {String} container either 'mp2t' or 'mp4'
  41387. * @param {Array} codecs an array of codec strings to add
  41388. * @return {String} a valid media mime-type
  41389. */
  41390. var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) {
  41391. // The codecs array is filtered so that falsey values are
  41392. // dropped and don't cause Array#join to create spurious
  41393. // commas
  41394. return type + '/' + container + '; codecs="' + codecs.filter(function (c) {
  41395. return !!c;
  41396. }).join(', ') + '"';
  41397. };
  41398. /**
  41399. * Returns the type container based on information in the playlist
  41400. * @param {Playlist} media the current media playlist
  41401. * @return {String} a valid media container type
  41402. */
  41403. var getContainerType = function getContainerType(media) {
  41404. // An initialization segment means the media playlist is an iframe
  41405. // playlist or is using the mp4 container. We don't currently
  41406. // support iframe playlists, so assume this is signalling mp4
  41407. // fragments.
  41408. if (media.segments && media.segments.length && media.segments[0].map) {
  41409. return 'mp4';
  41410. }
  41411. return 'mp2t';
  41412. };
  41413. /**
  41414. * Returns a set of codec strings parsed from the playlist or the default
  41415. * codec strings if no codecs were specified in the playlist
  41416. * @param {Playlist} media the current media playlist
  41417. * @return {Object} an object with the video and audio codecs
  41418. */
  41419. var getCodecs = function getCodecs(media) {
  41420. // if the codecs were explicitly specified, use them instead of the
  41421. // defaults
  41422. var mediaAttributes = media.attributes || {};
  41423. if (mediaAttributes.CODECS) {
  41424. return parseCodecs(mediaAttributes.CODECS);
  41425. }
  41426. return defaultCodecs;
  41427. };
  41428. var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) {
  41429. if (!master.mediaGroups.AUDIO || !audioGroupId) {
  41430. return null;
  41431. }
  41432. var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
  41433. if (!audioGroup) {
  41434. return null;
  41435. }
  41436. for (var name in audioGroup) {
  41437. var audioType = audioGroup[name];
  41438. if (audioType.default && audioType.playlists) {
  41439. // codec should be the same for all playlists within the audio type
  41440. return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
  41441. }
  41442. }
  41443. return null;
  41444. };
  41445. /**
  41446. * Calculates the MIME type strings for a working configuration of
  41447. * SourceBuffers to play variant streams in a master playlist. If
  41448. * there is no possible working configuration, an empty array will be
  41449. * returned.
  41450. *
  41451. * @param master {Object} the m3u8 object for the master playlist
  41452. * @param media {Object} the m3u8 object for the variant playlist
  41453. * @return {Array} the MIME type strings. If the array has more than
  41454. * one entry, the first element should be applied to the video
  41455. * SourceBuffer and the second to the audio SourceBuffer.
  41456. *
  41457. * @private
  41458. */
  41459. var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) {
  41460. var containerType = getContainerType(media);
  41461. var codecInfo = getCodecs(media);
  41462. var mediaAttributes = media.attributes || {}; // Default condition for a traditional HLS (no demuxed audio/video)
  41463. var isMuxed = true;
  41464. var isMaat = false;
  41465. if (!media) {
  41466. // Not enough information
  41467. return [];
  41468. }
  41469. if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) {
  41470. var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; // Handle the case where we are in a multiple-audio track scenario
  41471. if (audioGroup) {
  41472. isMaat = true; // Start with the everything demuxed then...
  41473. isMuxed = false; // ...check to see if any audio group tracks are muxed (ie. lacking a uri)
  41474. for (var groupId in audioGroup) {
  41475. // either a uri is present (if the case of HLS and an external playlist), or
  41476. // playlists is present (in the case of DASH where we don't have external audio
  41477. // playlists)
  41478. if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
  41479. isMuxed = true;
  41480. break;
  41481. }
  41482. }
  41483. }
  41484. } // HLS with multiple-audio tracks must always get an audio codec.
  41485. // Put another way, there is no way to have a video-only multiple-audio HLS!
  41486. if (isMaat && !codecInfo.audioProfile) {
  41487. if (!isMuxed) {
  41488. // It is possible for codecs to be specified on the audio media group playlist but
  41489. // not on the rendition playlist. This is mostly the case for DASH, where audio and
  41490. // video are always separate (and separately specified).
  41491. codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO);
  41492. }
  41493. if (!codecInfo.audioProfile) {
  41494. videojs$1.log.warn('Multiple audio tracks present but no audio codec string is specified. ' + 'Attempting to use the default audio codec (mp4a.40.2)');
  41495. codecInfo.audioProfile = defaultCodecs.audioProfile;
  41496. }
  41497. } // Generate the final codec strings from the codec object generated above
  41498. var codecStrings = {};
  41499. if (codecInfo.videoCodec) {
  41500. codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator;
  41501. }
  41502. if (codecInfo.audioProfile) {
  41503. codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile;
  41504. } // Finally, make and return an array with proper mime-types depending on
  41505. // the configuration
  41506. var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]);
  41507. var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]);
  41508. var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]);
  41509. if (isMaat) {
  41510. if (!isMuxed && codecStrings.video) {
  41511. return [justVideo, justAudio];
  41512. }
  41513. if (!isMuxed && !codecStrings.video) {
  41514. // There is no muxed content and no video codec string, so this is an audio only
  41515. // stream with alternate audio.
  41516. return [justAudio, justAudio];
  41517. } // There exists the possiblity that this will return a `video/container`
  41518. // mime-type for the first entry in the array even when there is only audio.
  41519. // This doesn't appear to be a problem and simplifies the code.
  41520. return [bothVideoAudio, justAudio];
  41521. } // If there is no video codec at all, always just return a single
  41522. // audio/<container> mime-type
  41523. if (!codecStrings.video) {
  41524. return [justAudio];
  41525. } // When not using separate audio media groups, audio and video is
  41526. // *always* muxed
  41527. return [bothVideoAudio];
  41528. };
  41529. /**
  41530. * Parse a content type header into a type and parameters
  41531. * object
  41532. *
  41533. * @param {String} type the content type header
  41534. * @return {Object} the parsed content-type
  41535. * @private
  41536. */
  41537. var parseContentType = function parseContentType(type) {
  41538. var object = {
  41539. type: '',
  41540. parameters: {}
  41541. };
  41542. var parameters = type.trim().split(';'); // first parameter should always be content-type
  41543. object.type = parameters.shift().trim();
  41544. parameters.forEach(function (parameter) {
  41545. var pair = parameter.trim().split('=');
  41546. if (pair.length > 1) {
  41547. var name = pair[0].replace(/"/g, '').trim();
  41548. var value = pair[1].replace(/"/g, '').trim();
  41549. object.parameters[name] = value;
  41550. }
  41551. });
  41552. return object;
  41553. };
  41554. /**
  41555. * Check if a codec string refers to an audio codec.
  41556. *
  41557. * @param {String} codec codec string to check
  41558. * @return {Boolean} if this is an audio codec
  41559. * @private
  41560. */
  41561. var isAudioCodec = function isAudioCodec(codec) {
  41562. return /mp4a\.\d+.\d+/i.test(codec);
  41563. };
  41564. /**
  41565. * Check if a codec string refers to a video codec.
  41566. *
  41567. * @param {String} codec codec string to check
  41568. * @return {Boolean} if this is a video codec
  41569. * @private
  41570. */
  41571. var isVideoCodec = function isVideoCodec(codec) {
  41572. return /avc1\.[\da-f]+/i.test(codec);
  41573. };
  41574. /**
  41575. * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
  41576. * front of current time.
  41577. *
  41578. * @param {Array} buffer
  41579. * The current buffer of gop information
  41580. * @param {Number} currentTime
  41581. * The current time
  41582. * @param {Double} mapping
  41583. * Offset to map display time to stream presentation time
  41584. * @return {Array}
  41585. * List of gops considered safe to append over
  41586. */
  41587. var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) {
  41588. if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
  41589. return [];
  41590. } // pts value for current time + 3 seconds to give a bit more wiggle room
  41591. var currentTimePts = Math.ceil((currentTime - mapping + 3) * 90000);
  41592. var i = void 0;
  41593. for (i = 0; i < buffer.length; i++) {
  41594. if (buffer[i].pts > currentTimePts) {
  41595. break;
  41596. }
  41597. }
  41598. return buffer.slice(i);
  41599. };
  41600. /**
  41601. * Appends gop information (timing and byteLength) received by the transmuxer for the
  41602. * gops appended in the last call to appendBuffer
  41603. *
  41604. * @param {Array} buffer
  41605. * The current buffer of gop information
  41606. * @param {Array} gops
  41607. * List of new gop information
  41608. * @param {boolean} replace
  41609. * If true, replace the buffer with the new gop information. If false, append the
  41610. * new gop information to the buffer in the right location of time.
  41611. * @return {Array}
  41612. * Updated list of gop information
  41613. */
  41614. var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
  41615. if (!gops.length) {
  41616. return buffer;
  41617. }
  41618. if (replace) {
  41619. // If we are in safe append mode, then completely overwrite the gop buffer
  41620. // with the most recent appeneded data. This will make sure that when appending
  41621. // future segments, we only try to align with gops that are both ahead of current
  41622. // time and in the last segment appended.
  41623. return gops.slice();
  41624. }
  41625. var start = gops[0].pts;
  41626. var i = 0;
  41627. for (i; i < buffer.length; i++) {
  41628. if (buffer[i].pts >= start) {
  41629. break;
  41630. }
  41631. }
  41632. return buffer.slice(0, i).concat(gops);
  41633. };
  41634. /**
  41635. * Removes gop information in buffer that overlaps with provided start and end
  41636. *
  41637. * @param {Array} buffer
  41638. * The current buffer of gop information
  41639. * @param {Double} start
  41640. * position to start the remove at
  41641. * @param {Double} end
  41642. * position to end the remove at
  41643. * @param {Double} mapping
  41644. * Offset to map display time to stream presentation time
  41645. */
  41646. var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
  41647. var startPts = Math.ceil((start - mapping) * 90000);
  41648. var endPts = Math.ceil((end - mapping) * 90000);
  41649. var updatedBuffer = buffer.slice();
  41650. var i = buffer.length;
  41651. while (i--) {
  41652. if (buffer[i].pts <= endPts) {
  41653. break;
  41654. }
  41655. }
  41656. if (i === -1) {
  41657. // no removal because end of remove range is before start of buffer
  41658. return updatedBuffer;
  41659. }
  41660. var j = i + 1;
  41661. while (j--) {
  41662. if (buffer[j].pts <= startPts) {
  41663. break;
  41664. }
  41665. } // clamp remove range start to 0 index
  41666. j = Math.max(j, 0);
  41667. updatedBuffer.splice(j, i - j + 1);
  41668. return updatedBuffer;
  41669. };
  41670. var buffered = function buffered(videoBuffer, audioBuffer, audioDisabled) {
  41671. var start = null;
  41672. var end = null;
  41673. var arity = 0;
  41674. var extents = [];
  41675. var ranges = []; // neither buffer has been created yet
  41676. if (!videoBuffer && !audioBuffer) {
  41677. return videojs$1.createTimeRange();
  41678. } // only one buffer is configured
  41679. if (!videoBuffer) {
  41680. return audioBuffer.buffered;
  41681. }
  41682. if (!audioBuffer) {
  41683. return videoBuffer.buffered;
  41684. } // both buffers are configured
  41685. if (audioDisabled) {
  41686. return videoBuffer.buffered;
  41687. } // both buffers are empty
  41688. if (videoBuffer.buffered.length === 0 && audioBuffer.buffered.length === 0) {
  41689. return videojs$1.createTimeRange();
  41690. } // Handle the case where we have both buffers and create an
  41691. // intersection of the two
  41692. var videoBuffered = videoBuffer.buffered;
  41693. var audioBuffered = audioBuffer.buffered;
  41694. var count = videoBuffered.length; // A) Gather up all start and end times
  41695. while (count--) {
  41696. extents.push({
  41697. time: videoBuffered.start(count),
  41698. type: 'start'
  41699. });
  41700. extents.push({
  41701. time: videoBuffered.end(count),
  41702. type: 'end'
  41703. });
  41704. }
  41705. count = audioBuffered.length;
  41706. while (count--) {
  41707. extents.push({
  41708. time: audioBuffered.start(count),
  41709. type: 'start'
  41710. });
  41711. extents.push({
  41712. time: audioBuffered.end(count),
  41713. type: 'end'
  41714. });
  41715. } // B) Sort them by time
  41716. extents.sort(function (a, b) {
  41717. return a.time - b.time;
  41718. }); // C) Go along one by one incrementing arity for start and decrementing
  41719. // arity for ends
  41720. for (count = 0; count < extents.length; count++) {
  41721. if (extents[count].type === 'start') {
  41722. arity++; // D) If arity is ever incremented to 2 we are entering an
  41723. // overlapping range
  41724. if (arity === 2) {
  41725. start = extents[count].time;
  41726. }
  41727. } else if (extents[count].type === 'end') {
  41728. arity--; // E) If arity is ever decremented to 1 we leaving an
  41729. // overlapping range
  41730. if (arity === 1) {
  41731. end = extents[count].time;
  41732. }
  41733. } // F) Record overlapping ranges
  41734. if (start !== null && end !== null) {
  41735. ranges.push([start, end]);
  41736. start = null;
  41737. end = null;
  41738. }
  41739. }
  41740. return videojs$1.createTimeRanges(ranges);
  41741. };
  41742. /**
  41743. * @file virtual-source-buffer.js
  41744. */
  41745. var ONE_SECOND_IN_TS$3 = 90000; // We create a wrapper around the SourceBuffer so that we can manage the
  41746. // state of the `updating` property manually. We have to do this because
  41747. // Firefox changes `updating` to false long before triggering `updateend`
  41748. // events and that was causing strange problems in videojs-contrib-hls
  41749. var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) {
  41750. var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
  41751. var wrapper = Object.create(null);
  41752. wrapper.updating = false;
  41753. wrapper.realBuffer_ = sourceBuffer;
  41754. var _loop = function _loop(key) {
  41755. if (typeof sourceBuffer[key] === 'function') {
  41756. wrapper[key] = function () {
  41757. return sourceBuffer[key].apply(sourceBuffer, arguments);
  41758. };
  41759. } else if (typeof wrapper[key] === 'undefined') {
  41760. Object.defineProperty(wrapper, key, {
  41761. get: function get$$1() {
  41762. return sourceBuffer[key];
  41763. },
  41764. set: function set$$1(v) {
  41765. return sourceBuffer[key] = v;
  41766. }
  41767. });
  41768. }
  41769. };
  41770. for (var key in sourceBuffer) {
  41771. _loop(key);
  41772. }
  41773. return wrapper;
  41774. };
  41775. /**
  41776. * VirtualSourceBuffers exist so that we can transmux non native formats
  41777. * into a native format, but keep the same api as a native source buffer.
  41778. * It creates a transmuxer, that works in its own thread (a web worker) and
  41779. * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
  41780. * then send all of that data to the naive sourcebuffer so that it is
  41781. * indestinguishable from a natively supported format.
  41782. *
  41783. * @param {HtmlMediaSource} mediaSource the parent mediaSource
  41784. * @param {Array} codecs array of codecs that we will be dealing with
  41785. * @class VirtualSourceBuffer
  41786. * @extends video.js.EventTarget
  41787. */
  41788. var VirtualSourceBuffer = function (_videojs$EventTarget) {
  41789. inherits$1(VirtualSourceBuffer, _videojs$EventTarget);
  41790. function VirtualSourceBuffer(mediaSource, codecs) {
  41791. classCallCheck$1(this, VirtualSourceBuffer);
  41792. var _this = possibleConstructorReturn$1(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget));
  41793. _this.timestampOffset_ = 0;
  41794. _this.pendingBuffers_ = [];
  41795. _this.bufferUpdating_ = false;
  41796. _this.mediaSource_ = mediaSource;
  41797. _this.codecs_ = codecs;
  41798. _this.audioCodec_ = null;
  41799. _this.videoCodec_ = null;
  41800. _this.audioDisabled_ = false;
  41801. _this.appendAudioInitSegment_ = true;
  41802. _this.gopBuffer_ = [];
  41803. _this.timeMapping_ = 0;
  41804. _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11;
  41805. var options = {
  41806. remux: false,
  41807. alignGopsAtEnd: _this.safeAppend_
  41808. };
  41809. _this.codecs_.forEach(function (codec) {
  41810. if (isAudioCodec(codec)) {
  41811. _this.audioCodec_ = codec;
  41812. } else if (isVideoCodec(codec)) {
  41813. _this.videoCodec_ = codec;
  41814. }
  41815. }); // append muxed segments to their respective native buffers as
  41816. // soon as they are available
  41817. _this.transmuxer_ = new TransmuxWorker();
  41818. _this.transmuxer_.postMessage({
  41819. action: 'init',
  41820. options: options
  41821. });
  41822. _this.transmuxer_.onmessage = function (event) {
  41823. if (event.data.action === 'data') {
  41824. return _this.data_(event);
  41825. }
  41826. if (event.data.action === 'done') {
  41827. return _this.done_(event);
  41828. }
  41829. if (event.data.action === 'gopInfo') {
  41830. return _this.appendGopInfo_(event);
  41831. }
  41832. if (event.data.action === 'videoSegmentTimingInfo') {
  41833. return _this.videoSegmentTimingInfo_(event.data.videoSegmentTimingInfo);
  41834. }
  41835. }; // this timestampOffset is a property with the side-effect of resetting
  41836. // baseMediaDecodeTime in the transmuxer on the setter
  41837. Object.defineProperty(_this, 'timestampOffset', {
  41838. get: function get$$1() {
  41839. return this.timestampOffset_;
  41840. },
  41841. set: function set$$1(val) {
  41842. if (typeof val === 'number' && val >= 0) {
  41843. this.timestampOffset_ = val;
  41844. this.appendAudioInitSegment_ = true; // reset gop buffer on timestampoffset as this signals a change in timeline
  41845. this.gopBuffer_.length = 0;
  41846. this.timeMapping_ = 0; // We have to tell the transmuxer to set the baseMediaDecodeTime to
  41847. // the desired timestampOffset for the next segment
  41848. this.transmuxer_.postMessage({
  41849. action: 'setTimestampOffset',
  41850. timestampOffset: val
  41851. });
  41852. }
  41853. }
  41854. }); // setting the append window affects both source buffers
  41855. Object.defineProperty(_this, 'appendWindowStart', {
  41856. get: function get$$1() {
  41857. return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
  41858. },
  41859. set: function set$$1(start) {
  41860. if (this.videoBuffer_) {
  41861. this.videoBuffer_.appendWindowStart = start;
  41862. }
  41863. if (this.audioBuffer_) {
  41864. this.audioBuffer_.appendWindowStart = start;
  41865. }
  41866. }
  41867. }); // this buffer is "updating" if either of its native buffers are
  41868. Object.defineProperty(_this, 'updating', {
  41869. get: function get$$1() {
  41870. return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating);
  41871. }
  41872. }); // the buffered property is the intersection of the buffered
  41873. // ranges of the native source buffers
  41874. Object.defineProperty(_this, 'buffered', {
  41875. get: function get$$1() {
  41876. return buffered(this.videoBuffer_, this.audioBuffer_, this.audioDisabled_);
  41877. }
  41878. });
  41879. return _this;
  41880. }
  41881. /**
  41882. * When we get a data event from the transmuxer
  41883. * we call this function and handle the data that
  41884. * was sent to us
  41885. *
  41886. * @private
  41887. * @param {Event} event the data event from the transmuxer
  41888. */
  41889. createClass$1(VirtualSourceBuffer, [{
  41890. key: 'data_',
  41891. value: function data_(event) {
  41892. var segment = event.data.segment; // Cast ArrayBuffer to TypedArray
  41893. segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength);
  41894. segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength);
  41895. createTextTracksIfNecessary(this, this.mediaSource_, segment); // Add the segments to the pendingBuffers array
  41896. this.pendingBuffers_.push(segment);
  41897. return;
  41898. }
  41899. /**
  41900. * When we get a done event from the transmuxer
  41901. * we call this function and we process all
  41902. * of the pending data that we have been saving in the
  41903. * data_ function
  41904. *
  41905. * @private
  41906. * @param {Event} event the done event from the transmuxer
  41907. */
  41908. }, {
  41909. key: 'done_',
  41910. value: function done_(event) {
  41911. // Don't process and append data if the mediaSource is closed
  41912. if (this.mediaSource_.readyState === 'closed') {
  41913. this.pendingBuffers_.length = 0;
  41914. return;
  41915. } // All buffers should have been flushed from the muxer
  41916. // start processing anything we have received
  41917. this.processPendingSegments_();
  41918. return;
  41919. }
  41920. }, {
  41921. key: 'videoSegmentTimingInfo_',
  41922. value: function videoSegmentTimingInfo_(timingInfo) {
  41923. var timingInfoInSeconds = {
  41924. start: {
  41925. decode: timingInfo.start.dts / ONE_SECOND_IN_TS$3,
  41926. presentation: timingInfo.start.pts / ONE_SECOND_IN_TS$3
  41927. },
  41928. end: {
  41929. decode: timingInfo.end.dts / ONE_SECOND_IN_TS$3,
  41930. presentation: timingInfo.end.pts / ONE_SECOND_IN_TS$3
  41931. },
  41932. baseMediaDecodeTime: timingInfo.baseMediaDecodeTime / ONE_SECOND_IN_TS$3
  41933. };
  41934. if (timingInfo.prependedContentDuration) {
  41935. timingInfoInSeconds.prependedContentDuration = timingInfo.prependedContentDuration / ONE_SECOND_IN_TS$3;
  41936. }
  41937. this.trigger({
  41938. type: 'videoSegmentTimingInfo',
  41939. videoSegmentTimingInfo: timingInfoInSeconds
  41940. });
  41941. }
  41942. /**
  41943. * Create our internal native audio/video source buffers and add
  41944. * event handlers to them with the following conditions:
  41945. * 1. they do not already exist on the mediaSource
  41946. * 2. this VSB has a codec for them
  41947. *
  41948. * @private
  41949. */
  41950. }, {
  41951. key: 'createRealSourceBuffers_',
  41952. value: function createRealSourceBuffers_() {
  41953. var _this2 = this;
  41954. var types = ['audio', 'video'];
  41955. types.forEach(function (type) {
  41956. // Don't create a SourceBuffer of this type if we don't have a
  41957. // codec for it
  41958. if (!_this2[type + 'Codec_']) {
  41959. return;
  41960. } // Do nothing if a SourceBuffer of this type already exists
  41961. if (_this2[type + 'Buffer_']) {
  41962. return;
  41963. }
  41964. var buffer = null; // If the mediasource already has a SourceBuffer for the codec
  41965. // use that
  41966. if (_this2.mediaSource_[type + 'Buffer_']) {
  41967. buffer = _this2.mediaSource_[type + 'Buffer_']; // In multiple audio track cases, the audio source buffer is disabled
  41968. // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier
  41969. // than createRealSourceBuffers_ is called to create the second
  41970. // VirtualSourceBuffer because that happens as a side-effect of
  41971. // videojs-contrib-hls starting the audioSegmentLoader. As a result,
  41972. // the audioBuffer is essentially "ownerless" and no one will toggle
  41973. // the `updating` state back to false once the `updateend` event is received
  41974. //
  41975. // Setting `updating` to false manually will work around this
  41976. // situation and allow work to continue
  41977. buffer.updating = false;
  41978. } else {
  41979. var codecProperty = type + 'Codec_';
  41980. var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"';
  41981. buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType);
  41982. _this2.mediaSource_[type + 'Buffer_'] = buffer;
  41983. }
  41984. _this2[type + 'Buffer_'] = buffer; // Wire up the events to the SourceBuffer
  41985. ['update', 'updatestart', 'updateend'].forEach(function (event) {
  41986. buffer.addEventListener(event, function () {
  41987. // if audio is disabled
  41988. if (type === 'audio' && _this2.audioDisabled_) {
  41989. return;
  41990. }
  41991. if (event === 'updateend') {
  41992. _this2[type + 'Buffer_'].updating = false;
  41993. }
  41994. var shouldTrigger = types.every(function (t) {
  41995. // skip checking audio's updating status if audio
  41996. // is not enabled
  41997. if (t === 'audio' && _this2.audioDisabled_) {
  41998. return true;
  41999. } // if the other type is updating we don't trigger
  42000. if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) {
  42001. return false;
  42002. }
  42003. return true;
  42004. });
  42005. if (shouldTrigger) {
  42006. return _this2.trigger(event);
  42007. }
  42008. });
  42009. });
  42010. });
  42011. }
  42012. /**
  42013. * Emulate the native mediasource function, but our function will
  42014. * send all of the proposed segments to the transmuxer so that we
  42015. * can transmux them before we append them to our internal
  42016. * native source buffers in the correct format.
  42017. *
  42018. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
  42019. * @param {Uint8Array} segment the segment to append to the buffer
  42020. */
  42021. }, {
  42022. key: 'appendBuffer',
  42023. value: function appendBuffer(segment) {
  42024. // Start the internal "updating" state
  42025. this.bufferUpdating_ = true;
  42026. if (this.audioBuffer_ && this.audioBuffer_.buffered.length) {
  42027. var audioBuffered = this.audioBuffer_.buffered;
  42028. this.transmuxer_.postMessage({
  42029. action: 'setAudioAppendStart',
  42030. appendStart: audioBuffered.end(audioBuffered.length - 1)
  42031. });
  42032. }
  42033. if (this.videoBuffer_) {
  42034. this.transmuxer_.postMessage({
  42035. action: 'alignGopsWith',
  42036. gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_ ? this.mediaSource_.player_.currentTime() : null, this.timeMapping_)
  42037. });
  42038. }
  42039. this.transmuxer_.postMessage({
  42040. action: 'push',
  42041. // Send the typed-array of data as an ArrayBuffer so that
  42042. // it can be sent as a "Transferable" and avoid the costly
  42043. // memory copy
  42044. data: segment.buffer,
  42045. // To recreate the original typed-array, we need information
  42046. // about what portion of the ArrayBuffer it was a view into
  42047. byteOffset: segment.byteOffset,
  42048. byteLength: segment.byteLength
  42049. }, [segment.buffer]);
  42050. this.transmuxer_.postMessage({
  42051. action: 'flush'
  42052. });
  42053. }
  42054. /**
  42055. * Appends gop information (timing and byteLength) received by the transmuxer for the
  42056. * gops appended in the last call to appendBuffer
  42057. *
  42058. * @param {Event} event
  42059. * The gopInfo event from the transmuxer
  42060. * @param {Array} event.data.gopInfo
  42061. * List of gop info to append
  42062. */
  42063. }, {
  42064. key: 'appendGopInfo_',
  42065. value: function appendGopInfo_(event) {
  42066. this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_);
  42067. }
  42068. /**
  42069. * Emulate the native mediasource function and remove parts
  42070. * of the buffer from any of our internal buffers that exist
  42071. *
  42072. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
  42073. * @param {Double} start position to start the remove at
  42074. * @param {Double} end position to end the remove at
  42075. */
  42076. }, {
  42077. key: 'remove',
  42078. value: function remove(start, end) {
  42079. if (this.videoBuffer_) {
  42080. this.videoBuffer_.updating = true;
  42081. this.videoBuffer_.remove(start, end);
  42082. this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
  42083. }
  42084. if (!this.audioDisabled_ && this.audioBuffer_) {
  42085. this.audioBuffer_.updating = true;
  42086. this.audioBuffer_.remove(start, end);
  42087. } // Remove Metadata Cues (id3)
  42088. removeCuesFromTrack(start, end, this.metadataTrack_); // Remove Any Captions
  42089. if (this.inbandTextTracks_) {
  42090. for (var track in this.inbandTextTracks_) {
  42091. removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
  42092. }
  42093. }
  42094. }
  42095. /**
  42096. * Process any segments that the muxer has output
  42097. * Concatenate segments together based on type and append them into
  42098. * their respective sourceBuffers
  42099. *
  42100. * @private
  42101. */
  42102. }, {
  42103. key: 'processPendingSegments_',
  42104. value: function processPendingSegments_() {
  42105. var sortedSegments = {
  42106. video: {
  42107. segments: [],
  42108. bytes: 0
  42109. },
  42110. audio: {
  42111. segments: [],
  42112. bytes: 0
  42113. },
  42114. captions: [],
  42115. metadata: []
  42116. }; // Sort segments into separate video/audio arrays and
  42117. // keep track of their total byte lengths
  42118. sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) {
  42119. var type = segment.type;
  42120. var data = segment.data;
  42121. var initSegment = segment.initSegment;
  42122. segmentObj[type].segments.push(data);
  42123. segmentObj[type].bytes += data.byteLength;
  42124. segmentObj[type].initSegment = initSegment; // Gather any captions into a single array
  42125. if (segment.captions) {
  42126. segmentObj.captions = segmentObj.captions.concat(segment.captions);
  42127. }
  42128. if (segment.info) {
  42129. segmentObj[type].info = segment.info;
  42130. } // Gather any metadata into a single array
  42131. if (segment.metadata) {
  42132. segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
  42133. }
  42134. return segmentObj;
  42135. }, sortedSegments); // Create the real source buffers if they don't exist by now since we
  42136. // finally are sure what tracks are contained in the source
  42137. if (!this.videoBuffer_ && !this.audioBuffer_) {
  42138. // Remove any codecs that may have been specified by default but
  42139. // are no longer applicable now
  42140. if (sortedSegments.video.bytes === 0) {
  42141. this.videoCodec_ = null;
  42142. }
  42143. if (sortedSegments.audio.bytes === 0) {
  42144. this.audioCodec_ = null;
  42145. }
  42146. this.createRealSourceBuffers_();
  42147. }
  42148. if (sortedSegments.audio.info) {
  42149. this.mediaSource_.trigger({
  42150. type: 'audioinfo',
  42151. info: sortedSegments.audio.info
  42152. });
  42153. }
  42154. if (sortedSegments.video.info) {
  42155. this.mediaSource_.trigger({
  42156. type: 'videoinfo',
  42157. info: sortedSegments.video.info
  42158. });
  42159. }
  42160. if (this.appendAudioInitSegment_) {
  42161. if (!this.audioDisabled_ && this.audioBuffer_) {
  42162. sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment);
  42163. sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength;
  42164. }
  42165. this.appendAudioInitSegment_ = false;
  42166. }
  42167. var triggerUpdateend = false; // Merge multiple video and audio segments into one and append
  42168. if (this.videoBuffer_ && sortedSegments.video.bytes) {
  42169. sortedSegments.video.segments.unshift(sortedSegments.video.initSegment);
  42170. sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength;
  42171. this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
  42172. } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) {
  42173. // The transmuxer did not return any bytes of video, meaning it was all trimmed
  42174. // for gop alignment. Since we have a video buffer and audio is disabled, updateend
  42175. // will never be triggered by this source buffer, which will cause contrib-hls
  42176. // to be stuck forever waiting for updateend. If audio is not disabled, updateend
  42177. // will be triggered by the audio buffer, which will be sent upwards since the video
  42178. // buffer will not be in an updating state.
  42179. triggerUpdateend = true;
  42180. } // Add text-track data for all
  42181. addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata);
  42182. if (!this.audioDisabled_ && this.audioBuffer_) {
  42183. this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
  42184. }
  42185. this.pendingBuffers_.length = 0;
  42186. if (triggerUpdateend) {
  42187. this.trigger('updateend');
  42188. } // We are no longer in the internal "updating" state
  42189. this.bufferUpdating_ = false;
  42190. }
  42191. /**
  42192. * Combine all segments into a single Uint8Array and then append them
  42193. * to the destination buffer
  42194. *
  42195. * @param {Object} segmentObj
  42196. * @param {SourceBuffer} destinationBuffer native source buffer to append data to
  42197. * @private
  42198. */
  42199. }, {
  42200. key: 'concatAndAppendSegments_',
  42201. value: function concatAndAppendSegments_(segmentObj, destinationBuffer) {
  42202. var offset = 0;
  42203. var tempBuffer = void 0;
  42204. if (segmentObj.bytes) {
  42205. tempBuffer = new Uint8Array(segmentObj.bytes); // Combine the individual segments into one large typed-array
  42206. segmentObj.segments.forEach(function (segment) {
  42207. tempBuffer.set(segment, offset);
  42208. offset += segment.byteLength;
  42209. });
  42210. try {
  42211. destinationBuffer.updating = true;
  42212. destinationBuffer.appendBuffer(tempBuffer);
  42213. } catch (error) {
  42214. if (this.mediaSource_.player_) {
  42215. this.mediaSource_.player_.error({
  42216. code: -3,
  42217. type: 'APPEND_BUFFER_ERR',
  42218. message: error.message,
  42219. originalError: error
  42220. });
  42221. }
  42222. }
  42223. }
  42224. }
  42225. /**
  42226. * Emulate the native mediasource function. abort any soureBuffer
  42227. * actions and throw out any un-appended data.
  42228. *
  42229. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
  42230. */
  42231. }, {
  42232. key: 'abort',
  42233. value: function abort() {
  42234. if (this.videoBuffer_) {
  42235. this.videoBuffer_.abort();
  42236. }
  42237. if (!this.audioDisabled_ && this.audioBuffer_) {
  42238. this.audioBuffer_.abort();
  42239. }
  42240. if (this.transmuxer_) {
  42241. this.transmuxer_.postMessage({
  42242. action: 'reset'
  42243. });
  42244. }
  42245. this.pendingBuffers_.length = 0;
  42246. this.bufferUpdating_ = false;
  42247. }
  42248. }]);
  42249. return VirtualSourceBuffer;
  42250. }(videojs$1.EventTarget);
  42251. /**
  42252. * @file html-media-source.js
  42253. */
  42254. /**
  42255. * Our MediaSource implementation in HTML, mimics native
  42256. * MediaSource where/if possible.
  42257. *
  42258. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
  42259. * @class HtmlMediaSource
  42260. * @extends videojs.EventTarget
  42261. */
  42262. var HtmlMediaSource = function (_videojs$EventTarget) {
  42263. inherits$1(HtmlMediaSource, _videojs$EventTarget);
  42264. function HtmlMediaSource() {
  42265. classCallCheck$1(this, HtmlMediaSource);
  42266. var _this = possibleConstructorReturn$1(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this));
  42267. var property = void 0;
  42268. _this.nativeMediaSource_ = new window$1.MediaSource(); // delegate to the native MediaSource's methods by default
  42269. for (property in _this.nativeMediaSource_) {
  42270. if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') {
  42271. _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_);
  42272. }
  42273. } // emulate `duration` and `seekable` until seeking can be
  42274. // handled uniformly for live streams
  42275. // see https://github.com/w3c/media-source/issues/5
  42276. _this.duration_ = NaN;
  42277. Object.defineProperty(_this, 'duration', {
  42278. get: function get$$1() {
  42279. if (this.duration_ === Infinity) {
  42280. return this.duration_;
  42281. }
  42282. return this.nativeMediaSource_.duration;
  42283. },
  42284. set: function set$$1(duration) {
  42285. this.duration_ = duration;
  42286. if (duration !== Infinity) {
  42287. this.nativeMediaSource_.duration = duration;
  42288. return;
  42289. }
  42290. }
  42291. });
  42292. Object.defineProperty(_this, 'seekable', {
  42293. get: function get$$1() {
  42294. if (this.duration_ === Infinity) {
  42295. return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]);
  42296. }
  42297. return this.nativeMediaSource_.seekable;
  42298. }
  42299. });
  42300. Object.defineProperty(_this, 'readyState', {
  42301. get: function get$$1() {
  42302. return this.nativeMediaSource_.readyState;
  42303. }
  42304. });
  42305. Object.defineProperty(_this, 'activeSourceBuffers', {
  42306. get: function get$$1() {
  42307. return this.activeSourceBuffers_;
  42308. }
  42309. }); // the list of virtual and native SourceBuffers created by this
  42310. // MediaSource
  42311. _this.sourceBuffers = [];
  42312. _this.activeSourceBuffers_ = [];
  42313. /**
  42314. * update the list of active source buffers based upon various
  42315. * imformation from HLS and video.js
  42316. *
  42317. * @private
  42318. */
  42319. _this.updateActiveSourceBuffers_ = function () {
  42320. // Retain the reference but empty the array
  42321. _this.activeSourceBuffers_.length = 0; // If there is only one source buffer, then it will always be active and audio will
  42322. // be disabled based on the codec of the source buffer
  42323. if (_this.sourceBuffers.length === 1) {
  42324. var sourceBuffer = _this.sourceBuffers[0];
  42325. sourceBuffer.appendAudioInitSegment_ = true;
  42326. sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_;
  42327. _this.activeSourceBuffers_.push(sourceBuffer);
  42328. return;
  42329. } // There are 2 source buffers, a combined (possibly video only) source buffer and
  42330. // and an audio only source buffer.
  42331. // By default, the audio in the combined virtual source buffer is enabled
  42332. // and the audio-only source buffer (if it exists) is disabled.
  42333. var disableCombined = false;
  42334. var disableAudioOnly = true; // TODO: maybe we can store the sourcebuffers on the track objects?
  42335. // safari may do something like this
  42336. for (var i = 0; i < _this.player_.audioTracks().length; i++) {
  42337. var track = _this.player_.audioTracks()[i];
  42338. if (track.enabled && track.kind !== 'main') {
  42339. // The enabled track is an alternate audio track so disable the audio in
  42340. // the combined source buffer and enable the audio-only source buffer.
  42341. disableCombined = true;
  42342. disableAudioOnly = false;
  42343. break;
  42344. }
  42345. }
  42346. _this.sourceBuffers.forEach(function (sourceBuffer, index) {
  42347. /* eslinst-disable */
  42348. // TODO once codecs are required, we can switch to using the codecs to determine
  42349. // what stream is the video stream, rather than relying on videoTracks
  42350. /* eslinst-enable */
  42351. sourceBuffer.appendAudioInitSegment_ = true;
  42352. if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
  42353. // combined
  42354. sourceBuffer.audioDisabled_ = disableCombined;
  42355. } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) {
  42356. // If the "combined" source buffer is video only, then we do not want
  42357. // disable the audio-only source buffer (this is mostly for demuxed
  42358. // audio and video hls)
  42359. sourceBuffer.audioDisabled_ = true;
  42360. disableAudioOnly = false;
  42361. } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
  42362. // audio only
  42363. // In the case of audio only with alternate audio and disableAudioOnly is true
  42364. // this means we want to disable the audio on the alternate audio sourcebuffer
  42365. // but not the main "combined" source buffer. The "combined" source buffer is
  42366. // always at index 0, so this ensures audio won't be disabled in both source
  42367. // buffers.
  42368. sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly;
  42369. if (sourceBuffer.audioDisabled_) {
  42370. return;
  42371. }
  42372. }
  42373. _this.activeSourceBuffers_.push(sourceBuffer);
  42374. });
  42375. };
  42376. _this.onPlayerMediachange_ = function () {
  42377. _this.sourceBuffers.forEach(function (sourceBuffer) {
  42378. sourceBuffer.appendAudioInitSegment_ = true;
  42379. });
  42380. };
  42381. _this.onHlsReset_ = function () {
  42382. _this.sourceBuffers.forEach(function (sourceBuffer) {
  42383. if (sourceBuffer.transmuxer_) {
  42384. sourceBuffer.transmuxer_.postMessage({
  42385. action: 'resetCaptions'
  42386. });
  42387. }
  42388. });
  42389. };
  42390. _this.onHlsSegmentTimeMapping_ = function (event) {
  42391. _this.sourceBuffers.forEach(function (buffer) {
  42392. return buffer.timeMapping_ = event.mapping;
  42393. });
  42394. }; // Re-emit MediaSource events on the polyfill
  42395. ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) {
  42396. this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
  42397. }, _this); // capture the associated player when the MediaSource is
  42398. // successfully attached
  42399. _this.on('sourceopen', function (event) {
  42400. // Get the player this MediaSource is attached to
  42401. var video = document.querySelector('[src="' + _this.url_ + '"]');
  42402. if (!video) {
  42403. return;
  42404. }
  42405. _this.player_ = videojs$1(video.parentNode);
  42406. if (!_this.player_) {
  42407. return;
  42408. } // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader
  42409. // resets its state and flushes the buffer
  42410. _this.player_.tech_.on('hls-reset', _this.onHlsReset_); // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main
  42411. // SegmentLoader inspects an MTS segment and has an accurate stream to display
  42412. // time mapping
  42413. _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_);
  42414. if (_this.player_.audioTracks && _this.player_.audioTracks()) {
  42415. _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_);
  42416. _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_);
  42417. _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_);
  42418. }
  42419. _this.player_.on('mediachange', _this.onPlayerMediachange_);
  42420. });
  42421. _this.on('sourceended', function (event) {
  42422. var duration = durationOfVideo(_this.duration);
  42423. for (var i = 0; i < _this.sourceBuffers.length; i++) {
  42424. var sourcebuffer = _this.sourceBuffers[i];
  42425. var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues;
  42426. if (cues && cues.length) {
  42427. cues[cues.length - 1].endTime = duration;
  42428. }
  42429. }
  42430. }); // explicitly terminate any WebWorkers that were created
  42431. // by SourceHandlers
  42432. _this.on('sourceclose', function (event) {
  42433. this.sourceBuffers.forEach(function (sourceBuffer) {
  42434. if (sourceBuffer.transmuxer_) {
  42435. sourceBuffer.transmuxer_.terminate();
  42436. }
  42437. });
  42438. this.sourceBuffers.length = 0;
  42439. if (!this.player_) {
  42440. return;
  42441. }
  42442. if (this.player_.audioTracks && this.player_.audioTracks()) {
  42443. this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
  42444. this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
  42445. this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
  42446. } // We can only change this if the player hasn't been disposed of yet
  42447. // because `off` eventually tries to use the el_ property. If it has
  42448. // been disposed of, then don't worry about it because there are no
  42449. // event handlers left to unbind anyway
  42450. if (this.player_.el_) {
  42451. this.player_.off('mediachange', this.onPlayerMediachange_);
  42452. }
  42453. if (this.player_.tech_ && this.player_.tech_.el_) {
  42454. this.player_.tech_.off('hls-reset', this.onHlsReset_);
  42455. this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_);
  42456. }
  42457. });
  42458. return _this;
  42459. }
  42460. /**
  42461. * Add a range that that can now be seeked to.
  42462. *
  42463. * @param {Double} start where to start the addition
  42464. * @param {Double} end where to end the addition
  42465. * @private
  42466. */
  42467. createClass$1(HtmlMediaSource, [{
  42468. key: 'addSeekableRange_',
  42469. value: function addSeekableRange_(start, end) {
  42470. var error = void 0;
  42471. if (this.duration !== Infinity) {
  42472. error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
  42473. error.name = 'InvalidStateError';
  42474. error.code = 11;
  42475. throw error;
  42476. }
  42477. if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) {
  42478. this.nativeMediaSource_.duration = end;
  42479. }
  42480. }
  42481. /**
  42482. * Add a source buffer to the media source.
  42483. *
  42484. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
  42485. * @param {String} type the content-type of the content
  42486. * @return {Object} the created source buffer
  42487. */
  42488. }, {
  42489. key: 'addSourceBuffer',
  42490. value: function addSourceBuffer(type) {
  42491. var buffer = void 0;
  42492. var parsedType = parseContentType(type); // Create a VirtualSourceBuffer to transmux MPEG-2 transport
  42493. // stream segments into fragmented MP4s
  42494. if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) {
  42495. var codecs = [];
  42496. if (parsedType.parameters && parsedType.parameters.codecs) {
  42497. codecs = parsedType.parameters.codecs.split(',');
  42498. codecs = translateLegacyCodecs(codecs);
  42499. codecs = codecs.filter(function (codec) {
  42500. return isAudioCodec(codec) || isVideoCodec(codec);
  42501. });
  42502. }
  42503. if (codecs.length === 0) {
  42504. codecs = ['avc1.4d400d', 'mp4a.40.2'];
  42505. }
  42506. buffer = new VirtualSourceBuffer(this, codecs);
  42507. if (this.sourceBuffers.length !== 0) {
  42508. // If another VirtualSourceBuffer already exists, then we are creating a
  42509. // SourceBuffer for an alternate audio track and therefore we know that
  42510. // the source has both an audio and video track.
  42511. // That means we should trigger the manual creation of the real
  42512. // SourceBuffers instead of waiting for the transmuxer to return data
  42513. this.sourceBuffers[0].createRealSourceBuffers_();
  42514. buffer.createRealSourceBuffers_(); // Automatically disable the audio on the first source buffer if
  42515. // a second source buffer is ever created
  42516. this.sourceBuffers[0].audioDisabled_ = true;
  42517. }
  42518. } else {
  42519. // delegate to the native implementation
  42520. buffer = this.nativeMediaSource_.addSourceBuffer(type);
  42521. }
  42522. this.sourceBuffers.push(buffer);
  42523. return buffer;
  42524. }
  42525. }]);
  42526. return HtmlMediaSource;
  42527. }(videojs$1.EventTarget);
  42528. /**
  42529. * @file videojs-contrib-media-sources.js
  42530. */
  42531. var urlCount = 0; // ------------
  42532. // Media Source
  42533. // ------------
  42534. // store references to the media sources so they can be connected
  42535. // to a video element (a swf object)
  42536. // TODO: can we store this somewhere local to this module?
  42537. videojs$1.mediaSources = {};
  42538. /**
  42539. * Provide a method for a swf object to notify JS that a
  42540. * media source is now open.
  42541. *
  42542. * @param {String} msObjectURL string referencing the MSE Object URL
  42543. * @param {String} swfId the swf id
  42544. */
  42545. var open = function open(msObjectURL, swfId) {
  42546. var mediaSource = videojs$1.mediaSources[msObjectURL];
  42547. if (mediaSource) {
  42548. mediaSource.trigger({
  42549. type: 'sourceopen',
  42550. swfId: swfId
  42551. });
  42552. } else {
  42553. throw new Error('Media Source not found (Video.js)');
  42554. }
  42555. };
  42556. /**
  42557. * Check to see if the native MediaSource object exists and supports
  42558. * an MP4 container with both H.264 video and AAC-LC audio.
  42559. *
  42560. * @return {Boolean} if native media sources are supported
  42561. */
  42562. var supportsNativeMediaSources = function supportsNativeMediaSources() {
  42563. return !!window$1.MediaSource && !!window$1.MediaSource.isTypeSupported && window$1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"');
  42564. };
  42565. /**
  42566. * An emulation of the MediaSource API so that we can support
  42567. * native and non-native functionality. returns an instance of
  42568. * HtmlMediaSource.
  42569. *
  42570. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource
  42571. */
  42572. var MediaSource = function MediaSource() {
  42573. this.MediaSource = {
  42574. open: open,
  42575. supportsNativeMediaSources: supportsNativeMediaSources
  42576. };
  42577. if (supportsNativeMediaSources()) {
  42578. return new HtmlMediaSource();
  42579. }
  42580. throw new Error('Cannot use create a virtual MediaSource for this video');
  42581. };
  42582. MediaSource.open = open;
  42583. MediaSource.supportsNativeMediaSources = supportsNativeMediaSources;
  42584. /**
  42585. * A wrapper around the native URL for our MSE object
  42586. * implementation, this object is exposed under videojs.URL
  42587. *
  42588. * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
  42589. */
  42590. var URL$1 = {
  42591. /**
  42592. * A wrapper around the native createObjectURL for our objects.
  42593. * This function maps a native or emulated mediaSource to a blob
  42594. * url so that it can be loaded into video.js
  42595. *
  42596. * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
  42597. * @param {MediaSource} object the object to create a blob url to
  42598. */
  42599. createObjectURL: function createObjectURL(object) {
  42600. var objectUrlPrefix = 'blob:vjs-media-source/';
  42601. var url = void 0; // use the native MediaSource to generate an object URL
  42602. if (object instanceof HtmlMediaSource) {
  42603. url = window$1.URL.createObjectURL(object.nativeMediaSource_);
  42604. object.url_ = url;
  42605. return url;
  42606. } // if the object isn't an emulated MediaSource, delegate to the
  42607. // native implementation
  42608. if (!(object instanceof HtmlMediaSource)) {
  42609. url = window$1.URL.createObjectURL(object);
  42610. object.url_ = url;
  42611. return url;
  42612. } // build a URL that can be used to map back to the emulated
  42613. // MediaSource
  42614. url = objectUrlPrefix + urlCount;
  42615. urlCount++; // setup the mapping back to object
  42616. videojs$1.mediaSources[url] = object;
  42617. return url;
  42618. }
  42619. };
  42620. videojs$1.MediaSource = MediaSource;
  42621. videojs$1.URL = URL$1;
  42622. var EventTarget$1$1 = videojs$1.EventTarget,
  42623. mergeOptions$2 = videojs$1.mergeOptions;
  42624. /**
  42625. * Returns a new master manifest that is the result of merging an updated master manifest
  42626. * into the original version.
  42627. *
  42628. * @param {Object} oldMaster
  42629. * The old parsed mpd object
  42630. * @param {Object} newMaster
  42631. * The updated parsed mpd object
  42632. * @return {Object}
  42633. * A new object representing the original master manifest with the updated media
  42634. * playlists merged in
  42635. */
  42636. var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) {
  42637. var noChanges = void 0;
  42638. var update = mergeOptions$2(oldMaster, {
  42639. // These are top level properties that can be updated
  42640. duration: newMaster.duration,
  42641. minimumUpdatePeriod: newMaster.minimumUpdatePeriod
  42642. }); // First update the playlists in playlist list
  42643. for (var i = 0; i < newMaster.playlists.length; i++) {
  42644. var playlistUpdate = updateMaster(update, newMaster.playlists[i]);
  42645. if (playlistUpdate) {
  42646. update = playlistUpdate;
  42647. } else {
  42648. noChanges = true;
  42649. }
  42650. } // Then update media group playlists
  42651. forEachMediaGroup(newMaster, function (properties, type, group, label) {
  42652. if (properties.playlists && properties.playlists.length) {
  42653. var uri = properties.playlists[0].uri;
  42654. var _playlistUpdate = updateMaster(update, properties.playlists[0]);
  42655. if (_playlistUpdate) {
  42656. update = _playlistUpdate; // update the playlist reference within media groups
  42657. update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri];
  42658. noChanges = false;
  42659. }
  42660. }
  42661. });
  42662. if (noChanges) {
  42663. return null;
  42664. }
  42665. return update;
  42666. };
  42667. var generateSidxKey = function generateSidxKey(sidxInfo) {
  42668. // should be non-inclusive
  42669. var sidxByteRangeEnd = sidxInfo.byterange.offset + sidxInfo.byterange.length - 1;
  42670. return sidxInfo.uri + '-' + sidxInfo.byterange.offset + '-' + sidxByteRangeEnd;
  42671. }; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
  42672. // If the SIDXs have maps, the two maps should match,
  42673. // both `a` and `b` missing SIDXs is considered matching.
  42674. // If `a` or `b` but not both have a map, they aren't matching.
  42675. var equivalentSidx = function equivalentSidx(a, b) {
  42676. var neitherMap = Boolean(!a.map && !b.map);
  42677. var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
  42678. return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
  42679. }; // exported for testing
  42680. var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) {
  42681. var newSidxMapping = {};
  42682. for (var uri in playlists) {
  42683. var playlist = playlists[uri];
  42684. var currentSidxInfo = playlist.sidx;
  42685. if (currentSidxInfo) {
  42686. var key = generateSidxKey(currentSidxInfo);
  42687. if (!oldSidxMapping[key]) {
  42688. break;
  42689. }
  42690. var savedSidxInfo = oldSidxMapping[key].sidxInfo;
  42691. if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
  42692. newSidxMapping[key] = oldSidxMapping[key];
  42693. }
  42694. }
  42695. }
  42696. return newSidxMapping;
  42697. };
  42698. /**
  42699. * A function that filters out changed items as they need to be requested separately.
  42700. *
  42701. * The method is exported for testing
  42702. *
  42703. * @param {Object} masterXml the mpd XML
  42704. * @param {string} srcUrl the mpd url
  42705. * @param {Date} clientOffset a time difference between server and client (passed through and not used)
  42706. * @param {Object} oldSidxMapping the SIDX to compare against
  42707. */
  42708. var filterChangedSidxMappings = function filterChangedSidxMappings(masterXml, srcUrl, clientOffset, oldSidxMapping) {
  42709. // Don't pass current sidx mapping
  42710. var master = parse(masterXml, {
  42711. manifestUri: srcUrl,
  42712. clientOffset: clientOffset
  42713. });
  42714. var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping);
  42715. var mediaGroupSidx = videoSidx;
  42716. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  42717. if (properties.playlists && properties.playlists.length) {
  42718. var playlists = properties.playlists;
  42719. mediaGroupSidx = mergeOptions$2(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
  42720. }
  42721. });
  42722. return mediaGroupSidx;
  42723. }; // exported for testing
  42724. var requestSidx_ = function requestSidx_(sidxRange, playlist, xhr, options, finishProcessingFn) {
  42725. var sidxInfo = {
  42726. // resolve the segment URL relative to the playlist
  42727. uri: resolveManifestRedirect(options.handleManifestRedirects, sidxRange.resolvedUri),
  42728. // resolvedUri: sidxRange.resolvedUri,
  42729. byterange: sidxRange.byterange,
  42730. // the segment's playlist
  42731. playlist: playlist
  42732. };
  42733. var sidxRequestOptions = videojs$1.mergeOptions(sidxInfo, {
  42734. responseType: 'arraybuffer',
  42735. headers: segmentXhrHeaders(sidxInfo)
  42736. });
  42737. return xhr(sidxRequestOptions, finishProcessingFn);
  42738. };
  42739. var DashPlaylistLoader = function (_EventTarget) {
  42740. inherits$1(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent
  42741. // playlist loader setups from media groups will expect to be able to pass a playlist
  42742. // (since there aren't external URLs to media playlists with DASH)
  42743. function DashPlaylistLoader(srcUrlOrPlaylist, hls) {
  42744. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  42745. var masterPlaylistLoader = arguments[3];
  42746. classCallCheck$1(this, DashPlaylistLoader);
  42747. var _this = possibleConstructorReturn$1(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this));
  42748. var _options$withCredenti = options.withCredentials,
  42749. withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
  42750. _options$handleManife = options.handleManifestRedirects,
  42751. handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
  42752. _this.hls_ = hls;
  42753. _this.withCredentials = withCredentials;
  42754. _this.handleManifestRedirects = handleManifestRedirects;
  42755. if (!srcUrlOrPlaylist) {
  42756. throw new Error('A non-empty playlist URL or playlist is required');
  42757. } // event naming?
  42758. _this.on('minimumUpdatePeriod', function () {
  42759. _this.refreshXml_();
  42760. }); // live playlist staleness timeout
  42761. _this.on('mediaupdatetimeout', function () {
  42762. _this.refreshMedia_(_this.media().uri);
  42763. });
  42764. _this.state = 'HAVE_NOTHING';
  42765. _this.loadedPlaylists_ = {}; // initialize the loader state
  42766. // The masterPlaylistLoader will be created with a string
  42767. if (typeof srcUrlOrPlaylist === 'string') {
  42768. _this.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
  42769. // once multi-period is refactored
  42770. _this.sidxMapping_ = {};
  42771. return possibleConstructorReturn$1(_this);
  42772. }
  42773. _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist);
  42774. return _this;
  42775. }
  42776. createClass$1(DashPlaylistLoader, [{
  42777. key: 'setupChildLoader',
  42778. value: function setupChildLoader(masterPlaylistLoader, playlist) {
  42779. this.masterPlaylistLoader_ = masterPlaylistLoader;
  42780. this.childPlaylist_ = playlist;
  42781. }
  42782. }, {
  42783. key: 'dispose',
  42784. value: function dispose() {
  42785. this.stopRequest();
  42786. this.loadedPlaylists_ = {};
  42787. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  42788. window$1.clearTimeout(this.mediaRequest_);
  42789. window$1.clearTimeout(this.mediaUpdateTimeout);
  42790. }
  42791. }, {
  42792. key: 'hasPendingRequest',
  42793. value: function hasPendingRequest() {
  42794. return this.request || this.mediaRequest_;
  42795. }
  42796. }, {
  42797. key: 'stopRequest',
  42798. value: function stopRequest() {
  42799. if (this.request) {
  42800. var oldRequest = this.request;
  42801. this.request = null;
  42802. oldRequest.onreadystatechange = null;
  42803. oldRequest.abort();
  42804. }
  42805. }
  42806. }, {
  42807. key: 'sidxRequestFinished_',
  42808. value: function sidxRequestFinished_(playlist, master, startingState, doneFn) {
  42809. var _this2 = this;
  42810. return function (err, request) {
  42811. // disposed
  42812. if (!_this2.request) {
  42813. return;
  42814. } // pending request is cleared
  42815. _this2.request = null;
  42816. if (err) {
  42817. _this2.error = {
  42818. status: request.status,
  42819. message: 'DASH playlist request error at URL: ' + playlist.uri,
  42820. response: request.response,
  42821. // MEDIA_ERR_NETWORK
  42822. code: 2
  42823. };
  42824. if (startingState) {
  42825. _this2.state = startingState;
  42826. }
  42827. _this2.trigger('error');
  42828. return doneFn(master, null);
  42829. }
  42830. var bytes = new Uint8Array(request.response);
  42831. var sidx = mp4Inspector.parseSidx(bytes.subarray(8));
  42832. return doneFn(master, sidx);
  42833. };
  42834. }
  42835. }, {
  42836. key: 'media',
  42837. value: function media(playlist) {
  42838. var _this3 = this; // getter
  42839. if (!playlist) {
  42840. return this.media_;
  42841. } // setter
  42842. if (this.state === 'HAVE_NOTHING') {
  42843. throw new Error('Cannot switch media playlist from ' + this.state);
  42844. }
  42845. var startingState = this.state; // find the playlist object if the target playlist has been specified by URI
  42846. if (typeof playlist === 'string') {
  42847. if (!this.master.playlists[playlist]) {
  42848. throw new Error('Unknown playlist URI: ' + playlist);
  42849. }
  42850. playlist = this.master.playlists[playlist];
  42851. }
  42852. var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to previously loaded playlists immediately
  42853. if (mediaChange && this.loadedPlaylists_[playlist.uri] && this.loadedPlaylists_[playlist.uri].endList) {
  42854. this.state = 'HAVE_METADATA';
  42855. this.media_ = playlist; // trigger media change if the active media has been updated
  42856. if (mediaChange) {
  42857. this.trigger('mediachanging');
  42858. this.trigger('mediachange');
  42859. }
  42860. return;
  42861. } // switching to the active playlist is a no-op
  42862. if (!mediaChange) {
  42863. return;
  42864. } // switching from an already loaded playlist
  42865. if (this.media_) {
  42866. this.trigger('mediachanging');
  42867. }
  42868. if (!playlist.sidx) {
  42869. // Continue asynchronously if there is no sidx
  42870. // wait one tick to allow haveMaster to run first on a child loader
  42871. this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, {
  42872. startingState: startingState,
  42873. playlist: playlist
  42874. }), 0); // exit early and don't do sidx work
  42875. return;
  42876. } // we have sidx mappings
  42877. var oldMaster = void 0;
  42878. var sidxMapping = void 0; // sidxMapping is used when parsing the masterXml, so store
  42879. // it on the masterPlaylistLoader
  42880. if (this.masterPlaylistLoader_) {
  42881. oldMaster = this.masterPlaylistLoader_.master;
  42882. sidxMapping = this.masterPlaylistLoader_.sidxMapping_;
  42883. } else {
  42884. oldMaster = this.master;
  42885. sidxMapping = this.sidxMapping_;
  42886. }
  42887. var sidxKey = generateSidxKey(playlist.sidx);
  42888. sidxMapping[sidxKey] = {
  42889. sidxInfo: playlist.sidx
  42890. };
  42891. this.request = requestSidx_(playlist.sidx, playlist, this.hls_.xhr, {
  42892. handleManifestRedirects: this.handleManifestRedirects
  42893. }, this.sidxRequestFinished_(playlist, oldMaster, startingState, function (newMaster, sidx) {
  42894. if (!newMaster || !sidx) {
  42895. throw new Error('failed to request sidx');
  42896. } // update loader's sidxMapping with parsed sidx box
  42897. sidxMapping[sidxKey].sidx = sidx; // everything is ready just continue to haveMetadata
  42898. _this3.haveMetadata({
  42899. startingState: startingState,
  42900. playlist: newMaster.playlists[playlist.uri]
  42901. });
  42902. }));
  42903. }
  42904. }, {
  42905. key: 'haveMetadata',
  42906. value: function haveMetadata(_ref) {
  42907. var startingState = _ref.startingState,
  42908. playlist = _ref.playlist;
  42909. this.state = 'HAVE_METADATA';
  42910. this.loadedPlaylists_[playlist.uri] = playlist;
  42911. this.mediaRequest_ = null; // This will trigger loadedplaylist
  42912. this.refreshMedia_(playlist.uri); // fire loadedmetadata the first time a media playlist is loaded
  42913. // to resolve setup of media groups
  42914. if (startingState === 'HAVE_MASTER') {
  42915. this.trigger('loadedmetadata');
  42916. } else {
  42917. // trigger media change if the active media has been updated
  42918. this.trigger('mediachange');
  42919. }
  42920. }
  42921. }, {
  42922. key: 'pause',
  42923. value: function pause() {
  42924. this.stopRequest();
  42925. window$1.clearTimeout(this.mediaUpdateTimeout);
  42926. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  42927. if (this.state === 'HAVE_NOTHING') {
  42928. // If we pause the loader before any data has been retrieved, its as if we never
  42929. // started, so reset to an unstarted state.
  42930. this.started = false;
  42931. }
  42932. }
  42933. }, {
  42934. key: 'load',
  42935. value: function load(isFinalRendition) {
  42936. var _this4 = this;
  42937. window$1.clearTimeout(this.mediaUpdateTimeout);
  42938. window$1.clearTimeout(this.minimumUpdatePeriodTimeout_);
  42939. var media = this.media();
  42940. if (isFinalRendition) {
  42941. var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
  42942. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  42943. return _this4.load();
  42944. }, delay);
  42945. return;
  42946. } // because the playlists are internal to the manifest, load should either load the
  42947. // main manifest, or do nothing but trigger an event
  42948. if (!this.started) {
  42949. this.start();
  42950. return;
  42951. }
  42952. this.trigger('loadedplaylist');
  42953. }
  42954. /**
  42955. * Parses the master xml string and updates playlist uri references
  42956. *
  42957. * @return {Object}
  42958. * The parsed mpd manifest object
  42959. */
  42960. }, {
  42961. key: 'parseMasterXml',
  42962. value: function parseMasterXml() {
  42963. var master = parse(this.masterXml_, {
  42964. manifestUri: this.srcUrl,
  42965. clientOffset: this.clientOffset_,
  42966. sidxMapping: this.sidxMapping_
  42967. });
  42968. master.uri = this.srcUrl; // Set up phony URIs for the playlists since we won't have external URIs for DASH
  42969. // but reference playlists by their URI throughout the project
  42970. // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes).
  42971. for (var i = 0; i < master.playlists.length; i++) {
  42972. var phonyUri = 'placeholder-uri-' + i;
  42973. master.playlists[i].uri = phonyUri; // set up by URI references
  42974. master.playlists[phonyUri] = master.playlists[i];
  42975. } // set up phony URIs for the media group playlists since we won't have external
  42976. // URIs for DASH but reference playlists by their URI throughout the project
  42977. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  42978. if (properties.playlists && properties.playlists.length) {
  42979. var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey;
  42980. properties.playlists[0].uri = _phonyUri; // setup URI references
  42981. master.playlists[_phonyUri] = properties.playlists[0];
  42982. }
  42983. });
  42984. setupMediaPlaylists(master);
  42985. resolveMediaGroupUris(master);
  42986. return master;
  42987. }
  42988. }, {
  42989. key: 'start',
  42990. value: function start() {
  42991. var _this5 = this;
  42992. this.started = true; // We don't need to request the master manifest again
  42993. // Call this asynchronously to match the xhr request behavior below
  42994. if (this.masterPlaylistLoader_) {
  42995. this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0);
  42996. return;
  42997. } // request the specified URL
  42998. this.request = this.hls_.xhr({
  42999. uri: this.srcUrl,
  43000. withCredentials: this.withCredentials
  43001. }, function (error, req) {
  43002. // disposed
  43003. if (!_this5.request) {
  43004. return;
  43005. } // clear the loader's request reference
  43006. _this5.request = null;
  43007. if (error) {
  43008. _this5.error = {
  43009. status: req.status,
  43010. message: 'DASH playlist request error at URL: ' + _this5.srcUrl,
  43011. responseText: req.responseText,
  43012. // MEDIA_ERR_NETWORK
  43013. code: 2
  43014. };
  43015. if (_this5.state === 'HAVE_NOTHING') {
  43016. _this5.started = false;
  43017. }
  43018. return _this5.trigger('error');
  43019. }
  43020. _this5.masterXml_ = req.responseText;
  43021. if (req.responseHeaders && req.responseHeaders.date) {
  43022. _this5.masterLoaded_ = Date.parse(req.responseHeaders.date);
  43023. } else {
  43024. _this5.masterLoaded_ = Date.now();
  43025. }
  43026. _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
  43027. _this5.syncClientServerClock_(_this5.onClientServerClockSync_.bind(_this5));
  43028. });
  43029. }
  43030. /**
  43031. * Parses the master xml for UTCTiming node to sync the client clock to the server
  43032. * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
  43033. *
  43034. * @param {Function} done
  43035. * Function to call when clock sync has completed
  43036. */
  43037. }, {
  43038. key: 'syncClientServerClock_',
  43039. value: function syncClientServerClock_(done) {
  43040. var _this6 = this;
  43041. var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
  43042. // server clock
  43043. if (utcTiming === null) {
  43044. this.clientOffset_ = this.masterLoaded_ - Date.now();
  43045. return done();
  43046. }
  43047. if (utcTiming.method === 'DIRECT') {
  43048. this.clientOffset_ = utcTiming.value - Date.now();
  43049. return done();
  43050. }
  43051. this.request = this.hls_.xhr({
  43052. uri: resolveUrl$1(this.srcUrl, utcTiming.value),
  43053. method: utcTiming.method,
  43054. withCredentials: this.withCredentials
  43055. }, function (error, req) {
  43056. // disposed
  43057. if (!_this6.request) {
  43058. return;
  43059. }
  43060. if (error) {
  43061. // sync request failed, fall back to using date header from mpd
  43062. // TODO: log warning
  43063. _this6.clientOffset_ = _this6.masterLoaded_ - Date.now();
  43064. return done();
  43065. }
  43066. var serverTime = void 0;
  43067. if (utcTiming.method === 'HEAD') {
  43068. if (!req.responseHeaders || !req.responseHeaders.date) {
  43069. // expected date header not preset, fall back to using date header from mpd
  43070. // TODO: log warning
  43071. serverTime = _this6.masterLoaded_;
  43072. } else {
  43073. serverTime = Date.parse(req.responseHeaders.date);
  43074. }
  43075. } else {
  43076. serverTime = Date.parse(req.responseText);
  43077. }
  43078. _this6.clientOffset_ = serverTime - Date.now();
  43079. done();
  43080. });
  43081. }
  43082. }, {
  43083. key: 'haveMaster_',
  43084. value: function haveMaster_() {
  43085. this.state = 'HAVE_MASTER'; // clear media request
  43086. this.mediaRequest_ = null;
  43087. if (!this.masterPlaylistLoader_) {
  43088. this.master = this.parseMasterXml(); // We have the master playlist at this point, so
  43089. // trigger this to allow MasterPlaylistController
  43090. // to make an initial playlist selection
  43091. this.trigger('loadedplaylist');
  43092. } else if (!this.media_) {
  43093. // no media playlist was specifically selected so select
  43094. // the one the child playlist loader was created with
  43095. this.media(this.childPlaylist_);
  43096. }
  43097. }
  43098. /**
  43099. * Handler for after client/server clock synchronization has happened. Sets up
  43100. * xml refresh timer if specificed by the manifest.
  43101. */
  43102. }, {
  43103. key: 'onClientServerClockSync_',
  43104. value: function onClientServerClockSync_() {
  43105. var _this7 = this;
  43106. this.haveMaster_();
  43107. if (!this.hasPendingRequest() && !this.media_) {
  43108. this.media(this.master.playlists[0]);
  43109. } // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not
  43110. // be refreshed when this is the case. The inter-op guide says that when the
  43111. // minimumUpdatePeriod is 0, the manifest should outline all currently available
  43112. // segments, but future segments may require an update. I think a good solution
  43113. // would be to update the manifest at the same rate that the media playlists
  43114. // are "refreshed", i.e. every targetDuration.
  43115. if (this.master && this.master.minimumUpdatePeriod) {
  43116. this.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  43117. _this7.trigger('minimumUpdatePeriod');
  43118. }, this.master.minimumUpdatePeriod);
  43119. }
  43120. }
  43121. /**
  43122. * Sends request to refresh the master xml and updates the parsed master manifest
  43123. * TODO: Does the client offset need to be recalculated when the xml is refreshed?
  43124. */
  43125. }, {
  43126. key: 'refreshXml_',
  43127. value: function refreshXml_() {
  43128. var _this8 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when
  43129. // sidx is implemented
  43130. this.request = this.hls_.xhr({
  43131. uri: this.srcUrl,
  43132. withCredentials: this.withCredentials
  43133. }, function (error, req) {
  43134. // disposed
  43135. if (!_this8.request) {
  43136. return;
  43137. } // clear the loader's request reference
  43138. _this8.request = null;
  43139. if (error) {
  43140. _this8.error = {
  43141. status: req.status,
  43142. message: 'DASH playlist request error at URL: ' + _this8.srcUrl,
  43143. responseText: req.responseText,
  43144. // MEDIA_ERR_NETWORK
  43145. code: 2
  43146. };
  43147. if (_this8.state === 'HAVE_NOTHING') {
  43148. _this8.started = false;
  43149. }
  43150. return _this8.trigger('error');
  43151. }
  43152. _this8.masterXml_ = req.responseText; // This will filter out updated sidx info from the mapping
  43153. _this8.sidxMapping_ = filterChangedSidxMappings(_this8.masterXml_, _this8.srcUrl, _this8.clientOffset_, _this8.sidxMapping_);
  43154. var master = _this8.parseMasterXml();
  43155. var updatedMaster = updateMaster$1(_this8.master, master);
  43156. if (updatedMaster) {
  43157. var sidxKey = generateSidxKey(_this8.media().sidx); // the sidx was updated, so the previous mapping was removed
  43158. if (!_this8.sidxMapping_[sidxKey]) {
  43159. var playlist = _this8.media();
  43160. _this8.request = requestSidx_(playlist.sidx, playlist, _this8.hls_.xhr, {
  43161. handleManifestRedirects: _this8.handleManifestRedirects
  43162. }, _this8.sidxRequestFinished_(playlist, master, _this8.state, function (newMaster, sidx) {
  43163. if (!newMaster || !sidx) {
  43164. throw new Error('failed to request sidx on minimumUpdatePeriod');
  43165. } // update loader's sidxMapping with parsed sidx box
  43166. _this8.sidxMapping_[sidxKey].sidx = sidx;
  43167. _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  43168. _this8.trigger('minimumUpdatePeriod');
  43169. }, _this8.master.minimumUpdatePeriod); // TODO: do we need to reload the current playlist?
  43170. _this8.refreshMedia_(_this8.media().uri);
  43171. return;
  43172. }));
  43173. } else {
  43174. _this8.master = updatedMaster;
  43175. }
  43176. }
  43177. _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () {
  43178. _this8.trigger('minimumUpdatePeriod');
  43179. }, _this8.master.minimumUpdatePeriod);
  43180. });
  43181. }
  43182. /**
  43183. * Refreshes the media playlist by re-parsing the master xml and updating playlist
  43184. * references. If this is an alternate loader, the updated parsed manifest is retrieved
  43185. * from the master loader.
  43186. */
  43187. }, {
  43188. key: 'refreshMedia_',
  43189. value: function refreshMedia_(mediaUri) {
  43190. var _this9 = this;
  43191. if (!mediaUri) {
  43192. throw new Error('refreshMedia_ must take a media uri');
  43193. }
  43194. var oldMaster = void 0;
  43195. var newMaster = void 0;
  43196. if (this.masterPlaylistLoader_) {
  43197. oldMaster = this.masterPlaylistLoader_.master;
  43198. newMaster = this.masterPlaylistLoader_.parseMasterXml();
  43199. } else {
  43200. oldMaster = this.master;
  43201. newMaster = this.parseMasterXml();
  43202. }
  43203. var updatedMaster = updateMaster$1(oldMaster, newMaster);
  43204. if (updatedMaster) {
  43205. if (this.masterPlaylistLoader_) {
  43206. this.masterPlaylistLoader_.master = updatedMaster;
  43207. } else {
  43208. this.master = updatedMaster;
  43209. }
  43210. this.media_ = updatedMaster.playlists[mediaUri];
  43211. } else {
  43212. this.media_ = newMaster.playlists[mediaUri];
  43213. this.trigger('playlistunchanged');
  43214. }
  43215. if (!this.media().endList) {
  43216. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  43217. _this9.trigger('mediaupdatetimeout');
  43218. }, refreshDelay(this.media(), !!updatedMaster));
  43219. }
  43220. this.trigger('loadedplaylist');
  43221. }
  43222. }]);
  43223. return DashPlaylistLoader;
  43224. }(EventTarget$1$1);
  43225. var logger = function logger(source) {
  43226. if (videojs$1.log.debug) {
  43227. return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >');
  43228. }
  43229. return function () {};
  43230. };
  43231. function noop$1() {}
  43232. /**
  43233. * @file source-updater.js
  43234. */
  43235. /**
  43236. * A queue of callbacks to be serialized and applied when a
  43237. * MediaSource and its associated SourceBuffers are not in the
  43238. * updating state. It is used by the segment loader to update the
  43239. * underlying SourceBuffers when new data is loaded, for instance.
  43240. *
  43241. * @class SourceUpdater
  43242. * @param {MediaSource} mediaSource the MediaSource to create the
  43243. * SourceBuffer from
  43244. * @param {String} mimeType the desired MIME type of the underlying
  43245. * SourceBuffer
  43246. * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is
  43247. * added to the media source
  43248. */
  43249. var SourceUpdater = function () {
  43250. function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) {
  43251. classCallCheck$1(this, SourceUpdater);
  43252. this.callbacks_ = [];
  43253. this.pendingCallback_ = null;
  43254. this.timestampOffset_ = 0;
  43255. this.mediaSource = mediaSource;
  43256. this.processedAppend_ = false;
  43257. this.type_ = type;
  43258. this.mimeType_ = mimeType;
  43259. this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']');
  43260. if (mediaSource.readyState === 'closed') {
  43261. mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter));
  43262. } else {
  43263. this.createSourceBuffer_(mimeType, sourceBufferEmitter);
  43264. }
  43265. }
  43266. createClass$1(SourceUpdater, [{
  43267. key: 'createSourceBuffer_',
  43268. value: function createSourceBuffer_(mimeType, sourceBufferEmitter) {
  43269. var _this = this;
  43270. this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType);
  43271. this.logger_('created SourceBuffer');
  43272. if (sourceBufferEmitter) {
  43273. sourceBufferEmitter.trigger('sourcebufferadded');
  43274. if (this.mediaSource.sourceBuffers.length < 2) {
  43275. // There's another source buffer we must wait for before we can start updating
  43276. // our own (or else we can get into a bad state, i.e., appending video/audio data
  43277. // before the other video/audio source buffer is available and leading to a video
  43278. // or audio only buffer).
  43279. sourceBufferEmitter.on('sourcebufferadded', function () {
  43280. _this.start_();
  43281. });
  43282. return;
  43283. }
  43284. }
  43285. this.start_();
  43286. }
  43287. }, {
  43288. key: 'start_',
  43289. value: function start_() {
  43290. var _this2 = this;
  43291. this.started_ = true; // run completion handlers and process callbacks as updateend
  43292. // events fire
  43293. this.onUpdateendCallback_ = function () {
  43294. var pendingCallback = _this2.pendingCallback_;
  43295. _this2.pendingCallback_ = null;
  43296. _this2.sourceBuffer_.removing = false;
  43297. _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']');
  43298. if (pendingCallback) {
  43299. pendingCallback();
  43300. }
  43301. _this2.runCallback_();
  43302. };
  43303. this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_);
  43304. this.runCallback_();
  43305. }
  43306. /**
  43307. * Aborts the current segment and resets the segment parser.
  43308. *
  43309. * @param {Function} done function to call when done
  43310. * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
  43311. */
  43312. }, {
  43313. key: 'abort',
  43314. value: function abort(done) {
  43315. var _this3 = this;
  43316. if (this.processedAppend_) {
  43317. this.queueCallback_(function () {
  43318. _this3.sourceBuffer_.abort();
  43319. }, done);
  43320. }
  43321. }
  43322. /**
  43323. * Queue an update to append an ArrayBuffer.
  43324. *
  43325. * @param {ArrayBuffer} bytes
  43326. * @param {Function} done the function to call when done
  43327. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
  43328. */
  43329. }, {
  43330. key: 'appendBuffer',
  43331. value: function appendBuffer(config, done) {
  43332. var _this4 = this;
  43333. this.processedAppend_ = true;
  43334. this.queueCallback_(function () {
  43335. if (config.videoSegmentTimingInfoCallback) {
  43336. _this4.sourceBuffer_.addEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
  43337. }
  43338. _this4.sourceBuffer_.appendBuffer(config.bytes);
  43339. }, function () {
  43340. if (config.videoSegmentTimingInfoCallback) {
  43341. _this4.sourceBuffer_.removeEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
  43342. }
  43343. done();
  43344. });
  43345. }
  43346. /**
  43347. * Indicates what TimeRanges are buffered in the managed SourceBuffer.
  43348. *
  43349. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
  43350. */
  43351. }, {
  43352. key: 'buffered',
  43353. value: function buffered() {
  43354. if (!this.sourceBuffer_) {
  43355. return videojs$1.createTimeRanges();
  43356. }
  43357. return this.sourceBuffer_.buffered;
  43358. }
  43359. /**
  43360. * Queue an update to remove a time range from the buffer.
  43361. *
  43362. * @param {Number} start where to start the removal
  43363. * @param {Number} end where to end the removal
  43364. * @param {Function} [done=noop] optional callback to be executed when the remove
  43365. * operation is complete
  43366. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
  43367. */
  43368. }, {
  43369. key: 'remove',
  43370. value: function remove(start, end) {
  43371. var _this5 = this;
  43372. var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop$1;
  43373. if (this.processedAppend_) {
  43374. this.queueCallback_(function () {
  43375. _this5.logger_('remove [' + start + ' => ' + end + ']');
  43376. _this5.sourceBuffer_.removing = true;
  43377. _this5.sourceBuffer_.remove(start, end);
  43378. }, done);
  43379. }
  43380. }
  43381. /**
  43382. * Whether the underlying sourceBuffer is updating or not
  43383. *
  43384. * @return {Boolean} the updating status of the SourceBuffer
  43385. */
  43386. }, {
  43387. key: 'updating',
  43388. value: function updating() {
  43389. // we are updating if the sourcebuffer is updating or
  43390. return !this.sourceBuffer_ || this.sourceBuffer_.updating || // if we have a pending callback that is not our internal noop
  43391. !!this.pendingCallback_ && this.pendingCallback_ !== noop$1;
  43392. }
  43393. /**
  43394. * Set/get the timestampoffset on the SourceBuffer
  43395. *
  43396. * @return {Number} the timestamp offset
  43397. */
  43398. }, {
  43399. key: 'timestampOffset',
  43400. value: function timestampOffset(offset) {
  43401. var _this6 = this;
  43402. if (typeof offset !== 'undefined') {
  43403. this.queueCallback_(function () {
  43404. _this6.sourceBuffer_.timestampOffset = offset;
  43405. });
  43406. this.timestampOffset_ = offset;
  43407. }
  43408. return this.timestampOffset_;
  43409. }
  43410. /**
  43411. * Queue a callback to run
  43412. */
  43413. }, {
  43414. key: 'queueCallback_',
  43415. value: function queueCallback_(callback, done) {
  43416. this.callbacks_.push([callback.bind(this), done]);
  43417. this.runCallback_();
  43418. }
  43419. /**
  43420. * Run a queued callback
  43421. */
  43422. }, {
  43423. key: 'runCallback_',
  43424. value: function runCallback_() {
  43425. var callbacks = void 0;
  43426. if (!this.updating() && this.callbacks_.length && this.started_) {
  43427. callbacks = this.callbacks_.shift();
  43428. this.pendingCallback_ = callbacks[1];
  43429. callbacks[0]();
  43430. }
  43431. }
  43432. /**
  43433. * dispose of the source updater and the underlying sourceBuffer
  43434. */
  43435. }, {
  43436. key: 'dispose',
  43437. value: function dispose() {
  43438. var _this7 = this;
  43439. var disposeFn = function disposeFn() {
  43440. if (_this7.sourceBuffer_ && _this7.mediaSource.readyState === 'open') {
  43441. _this7.sourceBuffer_.abort();
  43442. }
  43443. _this7.sourceBuffer_.removeEventListener('updateend', disposeFn);
  43444. };
  43445. this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
  43446. if (this.sourceBuffer_.removing) {
  43447. this.sourceBuffer_.addEventListener('updateend', disposeFn);
  43448. } else {
  43449. disposeFn();
  43450. }
  43451. }
  43452. }]);
  43453. return SourceUpdater;
  43454. }();
  43455. var Config = {
  43456. GOAL_BUFFER_LENGTH: 30,
  43457. MAX_GOAL_BUFFER_LENGTH: 60,
  43458. GOAL_BUFFER_LENGTH_RATE: 1,
  43459. // 0.5 MB/s
  43460. INITIAL_BANDWIDTH: 4194304,
  43461. // A fudge factor to apply to advertised playlist bitrates to account for
  43462. // temporary flucations in client bandwidth
  43463. BANDWIDTH_VARIANCE: 1.2,
  43464. // How much of the buffer must be filled before we consider upswitching
  43465. BUFFER_LOW_WATER_LINE: 0,
  43466. MAX_BUFFER_LOW_WATER_LINE: 30,
  43467. BUFFER_LOW_WATER_LINE_RATE: 1
  43468. };
  43469. var REQUEST_ERRORS = {
  43470. FAILURE: 2,
  43471. TIMEOUT: -101,
  43472. ABORTED: -102
  43473. };
  43474. /**
  43475. * Abort all requests
  43476. *
  43477. * @param {Object} activeXhrs - an object that tracks all XHR requests
  43478. */
  43479. var abortAll = function abortAll(activeXhrs) {
  43480. activeXhrs.forEach(function (xhr) {
  43481. xhr.abort();
  43482. });
  43483. };
  43484. /**
  43485. * Gather important bandwidth stats once a request has completed
  43486. *
  43487. * @param {Object} request - the XHR request from which to gather stats
  43488. */
  43489. var getRequestStats = function getRequestStats(request) {
  43490. return {
  43491. bandwidth: request.bandwidth,
  43492. bytesReceived: request.bytesReceived || 0,
  43493. roundTripTime: request.roundTripTime || 0
  43494. };
  43495. };
  43496. /**
  43497. * If possible gather bandwidth stats as a request is in
  43498. * progress
  43499. *
  43500. * @param {Event} progressEvent - an event object from an XHR's progress event
  43501. */
  43502. var getProgressStats = function getProgressStats(progressEvent) {
  43503. var request = progressEvent.target;
  43504. var roundTripTime = Date.now() - request.requestTime;
  43505. var stats = {
  43506. bandwidth: Infinity,
  43507. bytesReceived: 0,
  43508. roundTripTime: roundTripTime || 0
  43509. };
  43510. stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
  43511. // because we should only use bandwidth stats on progress to determine when
  43512. // abort a request early due to insufficient bandwidth
  43513. stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
  43514. return stats;
  43515. };
  43516. /**
  43517. * Handle all error conditions in one place and return an object
  43518. * with all the information
  43519. *
  43520. * @param {Error|null} error - if non-null signals an error occured with the XHR
  43521. * @param {Object} request - the XHR request that possibly generated the error
  43522. */
  43523. var handleErrors = function handleErrors(error, request) {
  43524. if (request.timedout) {
  43525. return {
  43526. status: request.status,
  43527. message: 'HLS request timed-out at URL: ' + request.uri,
  43528. code: REQUEST_ERRORS.TIMEOUT,
  43529. xhr: request
  43530. };
  43531. }
  43532. if (request.aborted) {
  43533. return {
  43534. status: request.status,
  43535. message: 'HLS request aborted at URL: ' + request.uri,
  43536. code: REQUEST_ERRORS.ABORTED,
  43537. xhr: request
  43538. };
  43539. }
  43540. if (error) {
  43541. return {
  43542. status: request.status,
  43543. message: 'HLS request errored at URL: ' + request.uri,
  43544. code: REQUEST_ERRORS.FAILURE,
  43545. xhr: request
  43546. };
  43547. }
  43548. return null;
  43549. };
  43550. /**
  43551. * Handle responses for key data and convert the key data to the correct format
  43552. * for the decryption step later
  43553. *
  43554. * @param {Object} segment - a simplified copy of the segmentInfo object
  43555. * from SegmentLoader
  43556. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  43557. * this request
  43558. */
  43559. var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) {
  43560. return function (error, request) {
  43561. var response = request.response;
  43562. var errorObj = handleErrors(error, request);
  43563. if (errorObj) {
  43564. return finishProcessingFn(errorObj, segment);
  43565. }
  43566. if (response.byteLength !== 16) {
  43567. return finishProcessingFn({
  43568. status: request.status,
  43569. message: 'Invalid HLS key at URL: ' + request.uri,
  43570. code: REQUEST_ERRORS.FAILURE,
  43571. xhr: request
  43572. }, segment);
  43573. }
  43574. var view = new DataView(response);
  43575. segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
  43576. return finishProcessingFn(null, segment);
  43577. };
  43578. };
  43579. /**
  43580. * Handle init-segment responses
  43581. *
  43582. * @param {Object} segment - a simplified copy of the segmentInfo object
  43583. * from SegmentLoader
  43584. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  43585. * this request
  43586. */
  43587. var handleInitSegmentResponse = function handleInitSegmentResponse(segment, captionParser, finishProcessingFn) {
  43588. return function (error, request) {
  43589. var response = request.response;
  43590. var errorObj = handleErrors(error, request);
  43591. if (errorObj) {
  43592. return finishProcessingFn(errorObj, segment);
  43593. } // stop processing if received empty content
  43594. if (response.byteLength === 0) {
  43595. return finishProcessingFn({
  43596. status: request.status,
  43597. message: 'Empty HLS segment content at URL: ' + request.uri,
  43598. code: REQUEST_ERRORS.FAILURE,
  43599. xhr: request
  43600. }, segment);
  43601. }
  43602. segment.map.bytes = new Uint8Array(request.response); // Initialize CaptionParser if it hasn't been yet
  43603. if (!captionParser.isInitialized()) {
  43604. captionParser.init();
  43605. }
  43606. segment.map.timescales = probe.timescale(segment.map.bytes);
  43607. segment.map.videoTrackIds = probe.videoTrackIds(segment.map.bytes);
  43608. return finishProcessingFn(null, segment);
  43609. };
  43610. };
  43611. /**
  43612. * Response handler for segment-requests being sure to set the correct
  43613. * property depending on whether the segment is encryped or not
  43614. * Also records and keeps track of stats that are used for ABR purposes
  43615. *
  43616. * @param {Object} segment - a simplified copy of the segmentInfo object
  43617. * from SegmentLoader
  43618. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  43619. * this request
  43620. */
  43621. var handleSegmentResponse = function handleSegmentResponse(segment, captionParser, finishProcessingFn) {
  43622. return function (error, request) {
  43623. var response = request.response;
  43624. var errorObj = handleErrors(error, request);
  43625. var parsed = void 0;
  43626. if (errorObj) {
  43627. return finishProcessingFn(errorObj, segment);
  43628. } // stop processing if received empty content
  43629. if (response.byteLength === 0) {
  43630. return finishProcessingFn({
  43631. status: request.status,
  43632. message: 'Empty HLS segment content at URL: ' + request.uri,
  43633. code: REQUEST_ERRORS.FAILURE,
  43634. xhr: request
  43635. }, segment);
  43636. }
  43637. segment.stats = getRequestStats(request);
  43638. if (segment.key) {
  43639. segment.encryptedBytes = new Uint8Array(request.response);
  43640. } else {
  43641. segment.bytes = new Uint8Array(request.response);
  43642. } // This is likely an FMP4 and has the init segment.
  43643. // Run through the CaptionParser in case there are captions.
  43644. if (segment.map && segment.map.bytes) {
  43645. // Initialize CaptionParser if it hasn't been yet
  43646. if (!captionParser.isInitialized()) {
  43647. captionParser.init();
  43648. }
  43649. parsed = captionParser.parse(segment.bytes, segment.map.videoTrackIds, segment.map.timescales);
  43650. if (parsed && parsed.captions) {
  43651. segment.captionStreams = parsed.captionStreams;
  43652. segment.fmp4Captions = parsed.captions;
  43653. }
  43654. }
  43655. return finishProcessingFn(null, segment);
  43656. };
  43657. };
  43658. /**
  43659. * Decrypt the segment via the decryption web worker
  43660. *
  43661. * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
  43662. * @param {Object} segment - a simplified copy of the segmentInfo object
  43663. * from SegmentLoader
  43664. * @param {Function} doneFn - a callback that is executed after decryption has completed
  43665. */
  43666. var decryptSegment = function decryptSegment(decrypter, segment, doneFn) {
  43667. var decryptionHandler = function decryptionHandler(event) {
  43668. if (event.data.source === segment.requestId) {
  43669. decrypter.removeEventListener('message', decryptionHandler);
  43670. var decrypted = event.data.decrypted;
  43671. segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength);
  43672. return doneFn(null, segment);
  43673. }
  43674. };
  43675. decrypter.addEventListener('message', decryptionHandler);
  43676. var keyBytes = segment.key.bytes.slice(); // this is an encrypted segment
  43677. // incrementally decrypt the segment
  43678. decrypter.postMessage(createTransferableMessage({
  43679. source: segment.requestId,
  43680. encrypted: segment.encryptedBytes,
  43681. key: keyBytes,
  43682. iv: segment.key.iv
  43683. }), [segment.encryptedBytes.buffer, keyBytes.buffer]);
  43684. };
  43685. /**
  43686. * This function waits for all XHRs to finish (with either success or failure)
  43687. * before continueing processing via it's callback. The function gathers errors
  43688. * from each request into a single errors array so that the error status for
  43689. * each request can be examined later.
  43690. *
  43691. * @param {Object} activeXhrs - an object that tracks all XHR requests
  43692. * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
  43693. * @param {Function} doneFn - a callback that is executed after all resources have been
  43694. * downloaded and any decryption completed
  43695. */
  43696. var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) {
  43697. var count = 0;
  43698. var didError = false;
  43699. return function (error, segment) {
  43700. if (didError) {
  43701. return;
  43702. }
  43703. if (error) {
  43704. didError = true; // If there are errors, we have to abort any outstanding requests
  43705. abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
  43706. // handle the aborted events from those requests, there are some cases where we may
  43707. // never get an aborted event. For instance, if the network connection is lost and
  43708. // there were two requests, the first may have triggered an error immediately, while
  43709. // the second request remains unsent. In that case, the aborted algorithm will not
  43710. // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
  43711. //
  43712. // We also can't rely on the ready state of the XHR, since the request that
  43713. // triggered the connection error may also show as a ready state of 0 (unsent).
  43714. // Therefore, we have to finish this group of requests immediately after the first
  43715. // seen error.
  43716. return doneFn(error, segment);
  43717. }
  43718. count += 1;
  43719. if (count === activeXhrs.length) {
  43720. // Keep track of when *all* of the requests have completed
  43721. segment.endOfAllRequests = Date.now();
  43722. if (segment.encryptedBytes) {
  43723. return decryptSegment(decrypter, segment, doneFn);
  43724. } // Otherwise, everything is ready just continue
  43725. return doneFn(null, segment);
  43726. }
  43727. };
  43728. };
  43729. /**
  43730. * Simple progress event callback handler that gathers some stats before
  43731. * executing a provided callback with the `segment` object
  43732. *
  43733. * @param {Object} segment - a simplified copy of the segmentInfo object
  43734. * from SegmentLoader
  43735. * @param {Function} progressFn - a callback that is executed each time a progress event
  43736. * is received
  43737. * @param {Event} event - the progress event object from XMLHttpRequest
  43738. */
  43739. var handleProgress = function handleProgress(segment, progressFn) {
  43740. return function (event) {
  43741. segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
  43742. if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
  43743. segment.stats.firstBytesReceivedAt = Date.now();
  43744. }
  43745. return progressFn(event, segment);
  43746. };
  43747. };
  43748. /**
  43749. * Load all resources and does any processing necessary for a media-segment
  43750. *
  43751. * Features:
  43752. * decrypts the media-segment if it has a key uri and an iv
  43753. * aborts *all* requests if *any* one request fails
  43754. *
  43755. * The segment object, at minimum, has the following format:
  43756. * {
  43757. * resolvedUri: String,
  43758. * [byterange]: {
  43759. * offset: Number,
  43760. * length: Number
  43761. * },
  43762. * [key]: {
  43763. * resolvedUri: String
  43764. * [byterange]: {
  43765. * offset: Number,
  43766. * length: Number
  43767. * },
  43768. * iv: {
  43769. * bytes: Uint32Array
  43770. * }
  43771. * },
  43772. * [map]: {
  43773. * resolvedUri: String,
  43774. * [byterange]: {
  43775. * offset: Number,
  43776. * length: Number
  43777. * },
  43778. * [bytes]: Uint8Array
  43779. * }
  43780. * }
  43781. * ...where [name] denotes optional properties
  43782. *
  43783. * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
  43784. * @param {Object} xhrOptions - the base options to provide to all xhr requests
  43785. * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
  43786. * decryption routines
  43787. * @param {Object} segment - a simplified copy of the segmentInfo object
  43788. * from SegmentLoader
  43789. * @param {Function} progressFn - a callback that receives progress events from the main
  43790. * segment's xhr request
  43791. * @param {Function} doneFn - a callback that is executed only once all requests have
  43792. * succeeded or failed
  43793. * @returns {Function} a function that, when invoked, immediately aborts all
  43794. * outstanding requests
  43795. */
  43796. var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, captionParser, segment, progressFn, doneFn) {
  43797. var activeXhrs = [];
  43798. var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); // optionally, request the decryption key
  43799. if (segment.key && !segment.key.bytes) {
  43800. var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, {
  43801. uri: segment.key.resolvedUri,
  43802. responseType: 'arraybuffer'
  43803. });
  43804. var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn);
  43805. var keyXhr = xhr(keyRequestOptions, keyRequestCallback);
  43806. activeXhrs.push(keyXhr);
  43807. } // optionally, request the associated media init segment
  43808. if (segment.map && !segment.map.bytes) {
  43809. var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, {
  43810. uri: segment.map.resolvedUri,
  43811. responseType: 'arraybuffer',
  43812. headers: segmentXhrHeaders(segment.map)
  43813. });
  43814. var initSegmentRequestCallback = handleInitSegmentResponse(segment, captionParser, finishProcessingFn);
  43815. var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
  43816. activeXhrs.push(initSegmentXhr);
  43817. }
  43818. var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, {
  43819. uri: segment.resolvedUri,
  43820. responseType: 'arraybuffer',
  43821. headers: segmentXhrHeaders(segment)
  43822. });
  43823. var segmentRequestCallback = handleSegmentResponse(segment, captionParser, finishProcessingFn);
  43824. var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
  43825. segmentXhr.addEventListener('progress', handleProgress(segment, progressFn));
  43826. activeXhrs.push(segmentXhr);
  43827. return function () {
  43828. return abortAll(activeXhrs);
  43829. };
  43830. }; // Utilities
  43831. /**
  43832. * Returns the CSS value for the specified property on an element
  43833. * using `getComputedStyle`. Firefox has a long-standing issue where
  43834. * getComputedStyle() may return null when running in an iframe with
  43835. * `display: none`.
  43836. *
  43837. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
  43838. * @param {HTMLElement} el the htmlelement to work on
  43839. * @param {string} the proprety to get the style for
  43840. */
  43841. var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
  43842. var result = void 0;
  43843. if (!el) {
  43844. return '';
  43845. }
  43846. result = window$1.getComputedStyle(el);
  43847. if (!result) {
  43848. return '';
  43849. }
  43850. return result[property];
  43851. };
  43852. /**
  43853. * Resuable stable sort function
  43854. *
  43855. * @param {Playlists} array
  43856. * @param {Function} sortFn Different comparators
  43857. * @function stableSort
  43858. */
  43859. var stableSort = function stableSort(array, sortFn) {
  43860. var newArray = array.slice();
  43861. array.sort(function (left, right) {
  43862. var cmp = sortFn(left, right);
  43863. if (cmp === 0) {
  43864. return newArray.indexOf(left) - newArray.indexOf(right);
  43865. }
  43866. return cmp;
  43867. });
  43868. };
  43869. /**
  43870. * A comparator function to sort two playlist object by bandwidth.
  43871. *
  43872. * @param {Object} left a media playlist object
  43873. * @param {Object} right a media playlist object
  43874. * @return {Number} Greater than zero if the bandwidth attribute of
  43875. * left is greater than the corresponding attribute of right. Less
  43876. * than zero if the bandwidth of right is greater than left and
  43877. * exactly zero if the two are equal.
  43878. */
  43879. var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
  43880. var leftBandwidth = void 0;
  43881. var rightBandwidth = void 0;
  43882. if (left.attributes.BANDWIDTH) {
  43883. leftBandwidth = left.attributes.BANDWIDTH;
  43884. }
  43885. leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE;
  43886. if (right.attributes.BANDWIDTH) {
  43887. rightBandwidth = right.attributes.BANDWIDTH;
  43888. }
  43889. rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE;
  43890. return leftBandwidth - rightBandwidth;
  43891. };
  43892. /**
  43893. * A comparator function to sort two playlist object by resolution (width).
  43894. * @param {Object} left a media playlist object
  43895. * @param {Object} right a media playlist object
  43896. * @return {Number} Greater than zero if the resolution.width attribute of
  43897. * left is greater than the corresponding attribute of right. Less
  43898. * than zero if the resolution.width of right is greater than left and
  43899. * exactly zero if the two are equal.
  43900. */
  43901. var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
  43902. var leftWidth = void 0;
  43903. var rightWidth = void 0;
  43904. if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
  43905. leftWidth = left.attributes.RESOLUTION.width;
  43906. }
  43907. leftWidth = leftWidth || window$1.Number.MAX_VALUE;
  43908. if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
  43909. rightWidth = right.attributes.RESOLUTION.width;
  43910. }
  43911. rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
  43912. // have the same media dimensions/ resolution
  43913. if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
  43914. return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
  43915. }
  43916. return leftWidth - rightWidth;
  43917. };
  43918. /**
  43919. * Chooses the appropriate media playlist based on bandwidth and player size
  43920. *
  43921. * @param {Object} master
  43922. * Object representation of the master manifest
  43923. * @param {Number} playerBandwidth
  43924. * Current calculated bandwidth of the player
  43925. * @param {Number} playerWidth
  43926. * Current width of the player element
  43927. * @param {Number} playerHeight
  43928. * Current height of the player element
  43929. * @param {Boolean} limitRenditionByPlayerDimensions
  43930. * True if the player width and height should be used during the selection, false otherwise
  43931. * @return {Playlist} the highest bitrate playlist less than the
  43932. * currently detected bandwidth, accounting for some amount of
  43933. * bandwidth variance
  43934. */
  43935. var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) {
  43936. // convert the playlists to an intermediary representation to make comparisons easier
  43937. var sortedPlaylistReps = master.playlists.map(function (playlist) {
  43938. var width = void 0;
  43939. var height = void 0;
  43940. var bandwidth = void 0;
  43941. width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
  43942. height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
  43943. bandwidth = playlist.attributes.BANDWIDTH;
  43944. bandwidth = bandwidth || window$1.Number.MAX_VALUE;
  43945. return {
  43946. bandwidth: bandwidth,
  43947. width: width,
  43948. height: height,
  43949. playlist: playlist
  43950. };
  43951. });
  43952. stableSort(sortedPlaylistReps, function (left, right) {
  43953. return left.bandwidth - right.bandwidth;
  43954. }); // filter out any playlists that have been excluded due to
  43955. // incompatible configurations
  43956. sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  43957. return !Playlist.isIncompatible(rep.playlist);
  43958. }); // filter out any playlists that have been disabled manually through the representations
  43959. // api or blacklisted temporarily due to playback errors.
  43960. var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  43961. return Playlist.isEnabled(rep.playlist);
  43962. });
  43963. if (!enabledPlaylistReps.length) {
  43964. // if there are no enabled playlists, then they have all been blacklisted or disabled
  43965. // by the user through the representations api. In this case, ignore blacklisting and
  43966. // fallback to what the user wants by using playlists the user has not disabled.
  43967. enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  43968. return !Playlist.isDisabled(rep.playlist);
  43969. });
  43970. } // filter out any variant that has greater effective bitrate
  43971. // than the current estimated bandwidth
  43972. var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
  43973. return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
  43974. });
  43975. var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
  43976. // and then taking the very first element
  43977. var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
  43978. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  43979. })[0]; // if we're not going to limit renditions by player size, make an early decision.
  43980. if (limitRenditionByPlayerDimensions === false) {
  43981. var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  43982. return _chosenRep ? _chosenRep.playlist : null;
  43983. } // filter out playlists without resolution information
  43984. var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
  43985. return rep.width && rep.height;
  43986. }); // sort variants by resolution
  43987. stableSort(haveResolution, function (left, right) {
  43988. return left.width - right.width;
  43989. }); // if we have the exact resolution as the player use it
  43990. var resolutionBestRepList = haveResolution.filter(function (rep) {
  43991. return rep.width === playerWidth && rep.height === playerHeight;
  43992. });
  43993. highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
  43994. var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
  43995. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  43996. })[0];
  43997. var resolutionPlusOneList = void 0;
  43998. var resolutionPlusOneSmallest = void 0;
  43999. var resolutionPlusOneRep = void 0; // find the smallest variant that is larger than the player
  44000. // if there is no match of exact resolution
  44001. if (!resolutionBestRep) {
  44002. resolutionPlusOneList = haveResolution.filter(function (rep) {
  44003. return rep.width > playerWidth || rep.height > playerHeight;
  44004. }); // find all the variants have the same smallest resolution
  44005. resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
  44006. return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
  44007. }); // ensure that we also pick the highest bandwidth variant that
  44008. // is just-larger-than the video player
  44009. highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
  44010. resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
  44011. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  44012. })[0];
  44013. } // fallback chain of variants
  44014. var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  44015. return chosenRep ? chosenRep.playlist : null;
  44016. }; // Playlist Selectors
  44017. /**
  44018. * Chooses the appropriate media playlist based on the most recent
  44019. * bandwidth estimate and the player size.
  44020. *
  44021. * Expects to be called within the context of an instance of HlsHandler
  44022. *
  44023. * @return {Playlist} the highest bitrate playlist less than the
  44024. * currently detected bandwidth, accounting for some amount of
  44025. * bandwidth variance
  44026. */
  44027. var lastBandwidthSelector = function lastBandwidthSelector() {
  44028. return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), this.limitRenditionByPlayerDimensions);
  44029. };
  44030. /**
  44031. * Chooses the appropriate media playlist based on the potential to rebuffer
  44032. *
  44033. * @param {Object} settings
  44034. * Object of information required to use this selector
  44035. * @param {Object} settings.master
  44036. * Object representation of the master manifest
  44037. * @param {Number} settings.currentTime
  44038. * The current time of the player
  44039. * @param {Number} settings.bandwidth
  44040. * Current measured bandwidth
  44041. * @param {Number} settings.duration
  44042. * Duration of the media
  44043. * @param {Number} settings.segmentDuration
  44044. * Segment duration to be used in round trip time calculations
  44045. * @param {Number} settings.timeUntilRebuffer
  44046. * Time left in seconds until the player has to rebuffer
  44047. * @param {Number} settings.currentTimeline
  44048. * The current timeline segments are being loaded from
  44049. * @param {SyncController} settings.syncController
  44050. * SyncController for determining if we have a sync point for a given playlist
  44051. * @return {Object|null}
  44052. * {Object} return.playlist
  44053. * The highest bandwidth playlist with the least amount of rebuffering
  44054. * {Number} return.rebufferingImpact
  44055. * The amount of time in seconds switching to this playlist will rebuffer. A
  44056. * negative value means that switching will cause zero rebuffering.
  44057. */
  44058. var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
  44059. var master = settings.master,
  44060. currentTime = settings.currentTime,
  44061. bandwidth = settings.bandwidth,
  44062. duration$$1 = settings.duration,
  44063. segmentDuration = settings.segmentDuration,
  44064. timeUntilRebuffer = settings.timeUntilRebuffer,
  44065. currentTimeline = settings.currentTimeline,
  44066. syncController = settings.syncController; // filter out any playlists that have been excluded due to
  44067. // incompatible configurations
  44068. var compatiblePlaylists = master.playlists.filter(function (playlist) {
  44069. return !Playlist.isIncompatible(playlist);
  44070. }); // filter out any playlists that have been disabled manually through the representations
  44071. // api or blacklisted temporarily due to playback errors.
  44072. var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
  44073. if (!enabledPlaylists.length) {
  44074. // if there are no enabled playlists, then they have all been blacklisted or disabled
  44075. // by the user through the representations api. In this case, ignore blacklisting and
  44076. // fallback to what the user wants by using playlists the user has not disabled.
  44077. enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
  44078. return !Playlist.isDisabled(playlist);
  44079. });
  44080. }
  44081. var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
  44082. var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
  44083. var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
  44084. // sync request first. This will double the request time
  44085. var numRequests = syncPoint ? 1 : 2;
  44086. var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
  44087. var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
  44088. return {
  44089. playlist: playlist,
  44090. rebufferingImpact: rebufferingImpact
  44091. };
  44092. });
  44093. var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
  44094. return estimate.rebufferingImpact <= 0;
  44095. }); // Sort by bandwidth DESC
  44096. stableSort(noRebufferingPlaylists, function (a, b) {
  44097. return comparePlaylistBandwidth(b.playlist, a.playlist);
  44098. });
  44099. if (noRebufferingPlaylists.length) {
  44100. return noRebufferingPlaylists[0];
  44101. }
  44102. stableSort(rebufferingEstimates, function (a, b) {
  44103. return a.rebufferingImpact - b.rebufferingImpact;
  44104. });
  44105. return rebufferingEstimates[0] || null;
  44106. };
  44107. /**
  44108. * Chooses the appropriate media playlist, which in this case is the lowest bitrate
  44109. * one with video. If no renditions with video exist, return the lowest audio rendition.
  44110. *
  44111. * Expects to be called within the context of an instance of HlsHandler
  44112. *
  44113. * @return {Object|null}
  44114. * {Object} return.playlist
  44115. * The lowest bitrate playlist that contains a video codec. If no such rendition
  44116. * exists pick the lowest audio rendition.
  44117. */
  44118. var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
  44119. // filter out any playlists that have been excluded due to
  44120. // incompatible configurations or playback errors
  44121. var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
  44122. stableSort(playlists, function (a, b) {
  44123. return comparePlaylistBandwidth(a, b);
  44124. }); // Parse and assume that playlists with no video codec have no video
  44125. // (this is not necessarily true, although it is generally true).
  44126. //
  44127. // If an entire manifest has no valid videos everything will get filtered
  44128. // out.
  44129. var playlistsWithVideo = playlists.filter(function (playlist) {
  44130. return parseCodecs(playlist.attributes.CODECS).videoCodec;
  44131. });
  44132. return playlistsWithVideo[0] || null;
  44133. };
  44134. /**
  44135. * Create captions text tracks on video.js if they do not exist
  44136. *
  44137. * @param {Object} inbandTextTracks a reference to current inbandTextTracks
  44138. * @param {Object} tech the video.js tech
  44139. * @param {Object} captionStreams the caption streams to create
  44140. * @private
  44141. */
  44142. var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStreams) {
  44143. for (var trackId in captionStreams) {
  44144. if (!inbandTextTracks[trackId]) {
  44145. tech.trigger({
  44146. type: 'usage',
  44147. name: 'hls-608'
  44148. });
  44149. var track = tech.textTracks().getTrackById(trackId);
  44150. if (track) {
  44151. // Resuse an existing track with a CC# id because this was
  44152. // very likely created by videojs-contrib-hls from information
  44153. // in the m3u8 for us to use
  44154. inbandTextTracks[trackId] = track;
  44155. } else {
  44156. // Otherwise, create a track with the default `CC#` label and
  44157. // without a language
  44158. inbandTextTracks[trackId] = tech.addRemoteTextTrack({
  44159. kind: 'captions',
  44160. id: trackId,
  44161. label: trackId
  44162. }, false).track;
  44163. }
  44164. }
  44165. }
  44166. };
  44167. var addCaptionData = function addCaptionData(_ref) {
  44168. var inbandTextTracks = _ref.inbandTextTracks,
  44169. captionArray = _ref.captionArray,
  44170. timestampOffset = _ref.timestampOffset;
  44171. if (!captionArray) {
  44172. return;
  44173. }
  44174. var Cue = window.WebKitDataCue || window.VTTCue;
  44175. captionArray.forEach(function (caption) {
  44176. var track = caption.stream;
  44177. var startTime = caption.startTime;
  44178. var endTime = caption.endTime;
  44179. if (!inbandTextTracks[track]) {
  44180. return;
  44181. }
  44182. startTime += timestampOffset;
  44183. endTime += timestampOffset;
  44184. inbandTextTracks[track].addCue(new Cue(startTime, endTime, caption.text));
  44185. });
  44186. };
  44187. /**
  44188. * @file segment-loader.js
  44189. */
  44190. // in ms
  44191. var CHECK_BUFFER_DELAY = 500;
  44192. /**
  44193. * Determines if we should call endOfStream on the media source based
  44194. * on the state of the buffer or if appened segment was the final
  44195. * segment in the playlist.
  44196. *
  44197. * @param {Object} playlist a media playlist object
  44198. * @param {Object} mediaSource the MediaSource object
  44199. * @param {Number} segmentIndex the index of segment we last appended
  44200. * @returns {Boolean} do we need to call endOfStream on the MediaSource
  44201. */
  44202. var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) {
  44203. if (!playlist || !mediaSource) {
  44204. return false;
  44205. }
  44206. var segments = playlist.segments; // determine a few boolean values to help make the branch below easier
  44207. // to read
  44208. var appendedLastSegment = segmentIndex === segments.length; // if we've buffered to the end of the video, we need to call endOfStream
  44209. // so that MediaSources can trigger the `ended` event when it runs out of
  44210. // buffered data instead of waiting for me
  44211. return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment;
  44212. };
  44213. var finite = function finite(num) {
  44214. return typeof num === 'number' && isFinite(num);
  44215. };
  44216. var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) {
  44217. // Although these checks should most likely cover non 'main' types, for now it narrows
  44218. // the scope of our checks.
  44219. if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) {
  44220. return null;
  44221. }
  44222. if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) {
  44223. return 'Neither audio nor video found in segment.';
  44224. }
  44225. if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) {
  44226. 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.';
  44227. }
  44228. if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) {
  44229. 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.';
  44230. }
  44231. return null;
  44232. };
  44233. /**
  44234. * Calculates a time value that is safe to remove from the back buffer without interupting
  44235. * playback.
  44236. *
  44237. * @param {TimeRange} seekable
  44238. * The current seekable range
  44239. * @param {Number} currentTime
  44240. * The current time of the player
  44241. * @param {Number} targetDuration
  44242. * The target duration of the current playlist
  44243. * @return {Number}
  44244. * Time that is safe to remove from the back buffer without interupting playback
  44245. */
  44246. var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) {
  44247. var removeToTime = void 0;
  44248. if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) {
  44249. // If we have a seekable range use that as the limit for what can be removed safely
  44250. removeToTime = seekable$$1.start(0);
  44251. } else {
  44252. // otherwise remove anything older than 30 seconds before the current play head
  44253. removeToTime = currentTime - 30;
  44254. } // Don't allow removing from the buffer within target duration of current time
  44255. // to avoid the possibility of removing the GOP currently being played which could
  44256. // cause playback stalls.
  44257. return Math.min(removeToTime, currentTime - targetDuration);
  44258. };
  44259. var segmentInfoString = function segmentInfoString(segmentInfo) {
  44260. var _segmentInfo$segment = segmentInfo.segment,
  44261. start = _segmentInfo$segment.start,
  44262. end = _segmentInfo$segment.end,
  44263. _segmentInfo$playlist = segmentInfo.playlist,
  44264. seq = _segmentInfo$playlist.mediaSequence,
  44265. id = _segmentInfo$playlist.id,
  44266. _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
  44267. segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2,
  44268. index = segmentInfo.mediaIndex,
  44269. timeline = segmentInfo.timeline;
  44270. return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' ');
  44271. };
  44272. /**
  44273. * An object that manages segment loading and appending.
  44274. *
  44275. * @class SegmentLoader
  44276. * @param {Object} options required and optional options
  44277. * @extends videojs.EventTarget
  44278. */
  44279. var SegmentLoader = function (_videojs$EventTarget) {
  44280. inherits$1(SegmentLoader, _videojs$EventTarget);
  44281. function SegmentLoader(settings) {
  44282. classCallCheck$1(this, SegmentLoader); // check pre-conditions
  44283. var _this = possibleConstructorReturn$1(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this));
  44284. if (!settings) {
  44285. throw new TypeError('Initialization settings are required');
  44286. }
  44287. if (typeof settings.currentTime !== 'function') {
  44288. throw new TypeError('No currentTime getter specified');
  44289. }
  44290. if (!settings.mediaSource) {
  44291. throw new TypeError('No MediaSource specified');
  44292. } // public properties
  44293. _this.bandwidth = settings.bandwidth;
  44294. _this.throughput = {
  44295. rate: 0,
  44296. count: 0
  44297. };
  44298. _this.roundTrip = NaN;
  44299. _this.resetStats_();
  44300. _this.mediaIndex = null; // private settings
  44301. _this.hasPlayed_ = settings.hasPlayed;
  44302. _this.currentTime_ = settings.currentTime;
  44303. _this.seekable_ = settings.seekable;
  44304. _this.seeking_ = settings.seeking;
  44305. _this.duration_ = settings.duration;
  44306. _this.mediaSource_ = settings.mediaSource;
  44307. _this.hls_ = settings.hls;
  44308. _this.loaderType_ = settings.loaderType;
  44309. _this.startingMedia_ = void 0;
  44310. _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
  44311. _this.goalBufferLength_ = settings.goalBufferLength;
  44312. _this.sourceType_ = settings.sourceType;
  44313. _this.inbandTextTracks_ = settings.inbandTextTracks;
  44314. _this.state_ = 'INIT'; // private instance variables
  44315. _this.checkBufferTimeout_ = null;
  44316. _this.error_ = void 0;
  44317. _this.currentTimeline_ = -1;
  44318. _this.pendingSegment_ = null;
  44319. _this.mimeType_ = null;
  44320. _this.sourceUpdater_ = null;
  44321. _this.xhrOptions_ = null; // Fragmented mp4 playback
  44322. _this.activeInitSegmentId_ = null;
  44323. _this.initSegments_ = {}; // HLSe playback
  44324. _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
  44325. _this.keyCache_ = {}; // Fmp4 CaptionParser
  44326. _this.captionParser_ = new mp4_6();
  44327. _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
  44328. // between a time in the display time and a segment index within
  44329. // a playlist
  44330. _this.syncController_ = settings.syncController;
  44331. _this.syncPoint_ = {
  44332. segmentIndex: 0,
  44333. time: 0
  44334. };
  44335. _this.syncController_.on('syncinfoupdate', function () {
  44336. return _this.trigger('syncinfoupdate');
  44337. });
  44338. _this.mediaSource_.addEventListener('sourceopen', function () {
  44339. return _this.ended_ = false;
  44340. }); // ...for determining the fetch location
  44341. _this.fetchAtBuffer_ = false;
  44342. _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']');
  44343. Object.defineProperty(_this, 'state', {
  44344. get: function get$$1() {
  44345. return this.state_;
  44346. },
  44347. set: function set$$1(newState) {
  44348. if (newState !== this.state_) {
  44349. this.logger_(this.state_ + ' -> ' + newState);
  44350. this.state_ = newState;
  44351. }
  44352. }
  44353. });
  44354. return _this;
  44355. }
  44356. /**
  44357. * reset all of our media stats
  44358. *
  44359. * @private
  44360. */
  44361. createClass$1(SegmentLoader, [{
  44362. key: 'resetStats_',
  44363. value: function resetStats_() {
  44364. this.mediaBytesTransferred = 0;
  44365. this.mediaRequests = 0;
  44366. this.mediaRequestsAborted = 0;
  44367. this.mediaRequestsTimedout = 0;
  44368. this.mediaRequestsErrored = 0;
  44369. this.mediaTransferDuration = 0;
  44370. this.mediaSecondsLoaded = 0;
  44371. }
  44372. /**
  44373. * dispose of the SegmentLoader and reset to the default state
  44374. */
  44375. }, {
  44376. key: 'dispose',
  44377. value: function dispose() {
  44378. this.state = 'DISPOSED';
  44379. this.pause();
  44380. this.abort_();
  44381. if (this.sourceUpdater_) {
  44382. this.sourceUpdater_.dispose();
  44383. }
  44384. this.resetStats_();
  44385. this.captionParser_.reset();
  44386. }
  44387. /**
  44388. * abort anything that is currently doing on with the SegmentLoader
  44389. * and reset to a default state
  44390. */
  44391. }, {
  44392. key: 'abort',
  44393. value: function abort() {
  44394. if (this.state !== 'WAITING') {
  44395. if (this.pendingSegment_) {
  44396. this.pendingSegment_ = null;
  44397. }
  44398. return;
  44399. }
  44400. this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
  44401. // since we are no longer "waiting" on any requests. XHR callback is not always run
  44402. // when the request is aborted. This will prevent the loader from being stuck in the
  44403. // WAITING state indefinitely.
  44404. this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
  44405. // next segment
  44406. if (!this.paused()) {
  44407. this.monitorBuffer_();
  44408. }
  44409. }
  44410. /**
  44411. * abort all pending xhr requests and null any pending segements
  44412. *
  44413. * @private
  44414. */
  44415. }, {
  44416. key: 'abort_',
  44417. value: function abort_() {
  44418. if (this.pendingSegment_) {
  44419. this.pendingSegment_.abortRequests();
  44420. } // clear out the segment being processed
  44421. this.pendingSegment_ = null;
  44422. }
  44423. /**
  44424. * set an error on the segment loader and null out any pending segements
  44425. *
  44426. * @param {Error} error the error to set on the SegmentLoader
  44427. * @return {Error} the error that was set or that is currently set
  44428. */
  44429. }, {
  44430. key: 'error',
  44431. value: function error(_error) {
  44432. if (typeof _error !== 'undefined') {
  44433. this.error_ = _error;
  44434. }
  44435. this.pendingSegment_ = null;
  44436. return this.error_;
  44437. }
  44438. }, {
  44439. key: 'endOfStream',
  44440. value: function endOfStream() {
  44441. this.ended_ = true;
  44442. this.pause();
  44443. this.trigger('ended');
  44444. }
  44445. /**
  44446. * Indicates which time ranges are buffered
  44447. *
  44448. * @return {TimeRange}
  44449. * TimeRange object representing the current buffered ranges
  44450. */
  44451. }, {
  44452. key: 'buffered_',
  44453. value: function buffered_() {
  44454. if (!this.sourceUpdater_) {
  44455. return videojs$1.createTimeRanges();
  44456. }
  44457. return this.sourceUpdater_.buffered();
  44458. }
  44459. /**
  44460. * Gets and sets init segment for the provided map
  44461. *
  44462. * @param {Object} map
  44463. * The map object representing the init segment to get or set
  44464. * @param {Boolean=} set
  44465. * If true, the init segment for the provided map should be saved
  44466. * @return {Object}
  44467. * map object for desired init segment
  44468. */
  44469. }, {
  44470. key: 'initSegment',
  44471. value: function initSegment(map) {
  44472. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  44473. if (!map) {
  44474. return null;
  44475. }
  44476. var id = initSegmentId(map);
  44477. var storedMap = this.initSegments_[id];
  44478. if (set$$1 && !storedMap && map.bytes) {
  44479. this.initSegments_[id] = storedMap = {
  44480. resolvedUri: map.resolvedUri,
  44481. byterange: map.byterange,
  44482. bytes: map.bytes,
  44483. timescales: map.timescales,
  44484. videoTrackIds: map.videoTrackIds
  44485. };
  44486. }
  44487. return storedMap || map;
  44488. }
  44489. /**
  44490. * Gets and sets key for the provided key
  44491. *
  44492. * @param {Object} key
  44493. * The key object representing the key to get or set
  44494. * @param {Boolean=} set
  44495. * If true, the key for the provided key should be saved
  44496. * @return {Object}
  44497. * Key object for desired key
  44498. */
  44499. }, {
  44500. key: 'segmentKey',
  44501. value: function segmentKey(key) {
  44502. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  44503. if (!key) {
  44504. return null;
  44505. }
  44506. var id = segmentKeyId(key);
  44507. var storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
  44508. // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
  44509. if (this.cacheEncryptionKeys_ && set$$1 && !storedKey && key.bytes) {
  44510. this.keyCache_[id] = storedKey = {
  44511. resolvedUri: key.resolvedUri,
  44512. bytes: key.bytes
  44513. };
  44514. }
  44515. var result = {
  44516. resolvedUri: (storedKey || key).resolvedUri
  44517. };
  44518. if (storedKey) {
  44519. result.bytes = storedKey.bytes;
  44520. }
  44521. return result;
  44522. }
  44523. /**
  44524. * Returns true if all configuration required for loading is present, otherwise false.
  44525. *
  44526. * @return {Boolean} True if the all configuration is ready for loading
  44527. * @private
  44528. */
  44529. }, {
  44530. key: 'couldBeginLoading_',
  44531. value: function couldBeginLoading_() {
  44532. return this.playlist_ && ( // the source updater is created when init_ is called, so either having a
  44533. // source updater or being in the INIT state with a mimeType is enough
  44534. // to say we have all the needed configuration to start loading.
  44535. this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused();
  44536. }
  44537. /**
  44538. * load a playlist and start to fill the buffer
  44539. */
  44540. }, {
  44541. key: 'load',
  44542. value: function load() {
  44543. // un-pause
  44544. this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
  44545. // specified
  44546. if (!this.playlist_) {
  44547. return;
  44548. } // not sure if this is the best place for this
  44549. this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading
  44550. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  44551. return this.init_();
  44552. } // if we're in the middle of processing a segment already, don't
  44553. // kick off an additional segment request
  44554. if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
  44555. return;
  44556. }
  44557. this.state = 'READY';
  44558. }
  44559. /**
  44560. * Once all the starting parameters have been specified, begin
  44561. * operation. This method should only be invoked from the INIT
  44562. * state.
  44563. *
  44564. * @private
  44565. */
  44566. }, {
  44567. key: 'init_',
  44568. value: function init_() {
  44569. this.state = 'READY';
  44570. this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_);
  44571. this.resetEverything();
  44572. return this.monitorBuffer_();
  44573. }
  44574. /**
  44575. * set a playlist on the segment loader
  44576. *
  44577. * @param {PlaylistLoader} media the playlist to set on the segment loader
  44578. */
  44579. }, {
  44580. key: 'playlist',
  44581. value: function playlist(newPlaylist) {
  44582. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  44583. if (!newPlaylist) {
  44584. return;
  44585. }
  44586. var oldPlaylist = this.playlist_;
  44587. var segmentInfo = this.pendingSegment_;
  44588. this.playlist_ = newPlaylist;
  44589. this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
  44590. // is always our zero-time so force a sync update each time the playlist
  44591. // is refreshed from the server
  44592. if (!this.hasPlayed_()) {
  44593. newPlaylist.syncInfo = {
  44594. mediaSequence: newPlaylist.mediaSequence,
  44595. time: 0
  44596. };
  44597. }
  44598. var oldId = null;
  44599. if (oldPlaylist) {
  44600. if (oldPlaylist.id) {
  44601. oldId = oldPlaylist.id;
  44602. } else if (oldPlaylist.uri) {
  44603. oldId = oldPlaylist.uri;
  44604. }
  44605. }
  44606. this.logger_('playlist update [' + oldId + ' => ' + (newPlaylist.id || newPlaylist.uri) + ']'); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
  44607. // in LIVE, we always want to update with new playlists (including refreshes)
  44608. this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
  44609. // buffering now
  44610. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  44611. return this.init_();
  44612. }
  44613. if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
  44614. if (this.mediaIndex !== null) {
  44615. // we must "resync" the segment loader when we switch renditions and
  44616. // the segment loader is already synced to the previous rendition
  44617. this.resyncLoader();
  44618. } // the rest of this function depends on `oldPlaylist` being defined
  44619. return;
  44620. } // we reloaded the same playlist so we are in a live scenario
  44621. // and we will likely need to adjust the mediaIndex
  44622. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
  44623. this.logger_('live window shift [' + mediaSequenceDiff + ']'); // update the mediaIndex on the SegmentLoader
  44624. // this is important because we can abort a request and this value must be
  44625. // equal to the last appended mediaIndex
  44626. if (this.mediaIndex !== null) {
  44627. this.mediaIndex -= mediaSequenceDiff;
  44628. } // update the mediaIndex on the SegmentInfo object
  44629. // this is important because we will update this.mediaIndex with this value
  44630. // in `handleUpdateEnd_` after the segment has been successfully appended
  44631. if (segmentInfo) {
  44632. segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is
  44633. // saved for the new playlist's segment, however, if the segment fell off the
  44634. // playlist, we can leave the old reference and just lose the timing info
  44635. if (segmentInfo.mediaIndex >= 0) {
  44636. segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
  44637. }
  44638. }
  44639. this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
  44640. }
  44641. /**
  44642. * Prevent the loader from fetching additional segments. If there
  44643. * is a segment request outstanding, it will finish processing
  44644. * before the loader halts. A segment loader can be unpaused by
  44645. * calling load().
  44646. */
  44647. }, {
  44648. key: 'pause',
  44649. value: function pause() {
  44650. if (this.checkBufferTimeout_) {
  44651. window$1.clearTimeout(this.checkBufferTimeout_);
  44652. this.checkBufferTimeout_ = null;
  44653. }
  44654. }
  44655. /**
  44656. * Returns whether the segment loader is fetching additional
  44657. * segments when given the opportunity. This property can be
  44658. * modified through calls to pause() and load().
  44659. */
  44660. }, {
  44661. key: 'paused',
  44662. value: function paused() {
  44663. return this.checkBufferTimeout_ === null;
  44664. }
  44665. /**
  44666. * create/set the following mimetype on the SourceBuffer through a
  44667. * SourceUpdater
  44668. *
  44669. * @param {String} mimeType the mime type string to use
  44670. * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer
  44671. * is added to the media source
  44672. */
  44673. }, {
  44674. key: 'mimeType',
  44675. value: function mimeType(_mimeType, sourceBufferEmitter) {
  44676. if (this.mimeType_) {
  44677. return;
  44678. }
  44679. this.mimeType_ = _mimeType;
  44680. this.sourceBufferEmitter_ = sourceBufferEmitter; // if we were unpaused but waiting for a sourceUpdater, start
  44681. // buffering now
  44682. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  44683. this.init_();
  44684. }
  44685. }
  44686. /**
  44687. * Delete all the buffered data and reset the SegmentLoader
  44688. * @param {Function} [done] an optional callback to be executed when the remove
  44689. * operation is complete
  44690. */
  44691. }, {
  44692. key: 'resetEverything',
  44693. value: function resetEverything(done) {
  44694. this.ended_ = false;
  44695. this.resetLoader();
  44696. this.remove(0, this.duration_(), done); // clears fmp4 captions
  44697. this.captionParser_.clearAllCaptions();
  44698. this.trigger('reseteverything');
  44699. }
  44700. /**
  44701. * Force the SegmentLoader to resync and start loading around the currentTime instead
  44702. * of starting at the end of the buffer
  44703. *
  44704. * Useful for fast quality changes
  44705. */
  44706. }, {
  44707. key: 'resetLoader',
  44708. value: function resetLoader() {
  44709. this.fetchAtBuffer_ = false;
  44710. this.resyncLoader();
  44711. }
  44712. /**
  44713. * Force the SegmentLoader to restart synchronization and make a conservative guess
  44714. * before returning to the simple walk-forward method
  44715. */
  44716. }, {
  44717. key: 'resyncLoader',
  44718. value: function resyncLoader() {
  44719. this.mediaIndex = null;
  44720. this.syncPoint_ = null;
  44721. this.abort();
  44722. }
  44723. /**
  44724. * Remove any data in the source buffer between start and end times
  44725. * @param {Number} start - the start time of the region to remove from the buffer
  44726. * @param {Number} end - the end time of the region to remove from the buffer
  44727. * @param {Function} [done] - an optional callback to be executed when the remove
  44728. * operation is complete
  44729. */
  44730. }, {
  44731. key: 'remove',
  44732. value: function remove(start, end, done) {
  44733. if (this.sourceUpdater_) {
  44734. this.sourceUpdater_.remove(start, end, done);
  44735. }
  44736. removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
  44737. if (this.inbandTextTracks_) {
  44738. for (var id in this.inbandTextTracks_) {
  44739. removeCuesFromTrack(start, end, this.inbandTextTracks_[id]);
  44740. }
  44741. }
  44742. }
  44743. /**
  44744. * (re-)schedule monitorBufferTick_ to run as soon as possible
  44745. *
  44746. * @private
  44747. */
  44748. }, {
  44749. key: 'monitorBuffer_',
  44750. value: function monitorBuffer_() {
  44751. if (this.checkBufferTimeout_) {
  44752. window$1.clearTimeout(this.checkBufferTimeout_);
  44753. }
  44754. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1);
  44755. }
  44756. /**
  44757. * As long as the SegmentLoader is in the READY state, periodically
  44758. * invoke fillBuffer_().
  44759. *
  44760. * @private
  44761. */
  44762. }, {
  44763. key: 'monitorBufferTick_',
  44764. value: function monitorBufferTick_() {
  44765. if (this.state === 'READY') {
  44766. this.fillBuffer_();
  44767. }
  44768. if (this.checkBufferTimeout_) {
  44769. window$1.clearTimeout(this.checkBufferTimeout_);
  44770. }
  44771. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
  44772. }
  44773. /**
  44774. * fill the buffer with segements unless the sourceBuffers are
  44775. * currently updating
  44776. *
  44777. * Note: this function should only ever be called by monitorBuffer_
  44778. * and never directly
  44779. *
  44780. * @private
  44781. */
  44782. }, {
  44783. key: 'fillBuffer_',
  44784. value: function fillBuffer_() {
  44785. if (this.sourceUpdater_.updating()) {
  44786. return;
  44787. }
  44788. if (!this.syncPoint_) {
  44789. this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  44790. } // see if we need to begin loading immediately
  44791. var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
  44792. if (!segmentInfo) {
  44793. return;
  44794. }
  44795. if (this.isEndOfStream_(segmentInfo.mediaIndex)) {
  44796. this.endOfStream();
  44797. return;
  44798. }
  44799. if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
  44800. return;
  44801. } // We will need to change timestampOffset of the sourceBuffer if either of
  44802. // the following conditions are true:
  44803. // - The segment.timeline !== this.currentTimeline
  44804. // (we are crossing a discontinuity somehow)
  44805. // - The "timestampOffset" for the start of this segment is less than
  44806. // the currently set timestampOffset
  44807. // Also, clear captions if we are crossing a discontinuity boundary
  44808. if (segmentInfo.timeline !== this.currentTimeline_ || segmentInfo.startOfSegment !== null && segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset()) {
  44809. this.syncController_.reset();
  44810. segmentInfo.timestampOffset = segmentInfo.startOfSegment;
  44811. this.captionParser_.clearAllCaptions();
  44812. }
  44813. this.loadSegment_(segmentInfo);
  44814. }
  44815. /**
  44816. * Determines if this segment loader is at the end of it's stream.
  44817. *
  44818. * @param {Number} mediaIndex the index of segment we last appended
  44819. * @param {Object} [playlist=this.playlist_] a media playlist object
  44820. * @returns {Boolean} true if at end of stream, false otherwise.
  44821. */
  44822. }, {
  44823. key: 'isEndOfStream_',
  44824. value: function isEndOfStream_(mediaIndex) {
  44825. var playlist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.playlist_;
  44826. return detectEndOfStream(playlist, this.mediaSource_, mediaIndex) && !this.sourceUpdater_.updating();
  44827. }
  44828. /**
  44829. * Determines what segment request should be made, given current playback
  44830. * state.
  44831. *
  44832. * @param {TimeRanges} buffered - the state of the buffer
  44833. * @param {Object} playlist - the playlist object to fetch segments from
  44834. * @param {Number} mediaIndex - the previous mediaIndex fetched or null
  44835. * @param {Boolean} hasPlayed - a flag indicating whether we have played or not
  44836. * @param {Number} currentTime - the playback position in seconds
  44837. * @param {Object} syncPoint - a segment info object that describes the
  44838. * @returns {Object} a segment request object that describes the segment to load
  44839. */
  44840. }, {
  44841. key: 'checkBuffer_',
  44842. value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) {
  44843. var lastBufferedEnd = 0;
  44844. var startOfSegment = void 0;
  44845. if (buffered.length) {
  44846. lastBufferedEnd = buffered.end(buffered.length - 1);
  44847. }
  44848. var bufferedTime = Math.max(0, lastBufferedEnd - currentTime);
  44849. if (!playlist.segments.length) {
  44850. return null;
  44851. } // if there is plenty of content buffered, and the video has
  44852. // been played before relax for awhile
  44853. if (bufferedTime >= this.goalBufferLength_()) {
  44854. return null;
  44855. } // if the video has not yet played once, and we already have
  44856. // one segment downloaded do nothing
  44857. if (!hasPlayed && bufferedTime >= 1) {
  44858. return null;
  44859. } // When the syncPoint is null, there is no way of determining a good
  44860. // conservative segment index to fetch from
  44861. // The best thing to do here is to get the kind of sync-point data by
  44862. // making a request
  44863. if (syncPoint === null) {
  44864. mediaIndex = this.getSyncSegmentCandidate_(playlist);
  44865. return this.generateSegmentInfo_(playlist, mediaIndex, null, true);
  44866. } // Under normal playback conditions fetching is a simple walk forward
  44867. if (mediaIndex !== null) {
  44868. var segment = playlist.segments[mediaIndex];
  44869. if (segment && segment.end) {
  44870. startOfSegment = segment.end;
  44871. } else {
  44872. startOfSegment = lastBufferedEnd;
  44873. }
  44874. return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false);
  44875. } // There is a sync-point but the lack of a mediaIndex indicates that
  44876. // we need to make a good conservative guess about which segment to
  44877. // fetch
  44878. if (this.fetchAtBuffer_) {
  44879. // Find the segment containing the end of the buffer
  44880. var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time);
  44881. mediaIndex = mediaSourceInfo.mediaIndex;
  44882. startOfSegment = mediaSourceInfo.startTime;
  44883. } else {
  44884. // Find the segment containing currentTime
  44885. var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time);
  44886. mediaIndex = _mediaSourceInfo.mediaIndex;
  44887. startOfSegment = _mediaSourceInfo.startTime;
  44888. }
  44889. return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false);
  44890. }
  44891. /**
  44892. * The segment loader has no recourse except to fetch a segment in the
  44893. * current playlist and use the internal timestamps in that segment to
  44894. * generate a syncPoint. This function returns a good candidate index
  44895. * for that process.
  44896. *
  44897. * @param {Object} playlist - the playlist object to look for a
  44898. * @returns {Number} An index of a segment from the playlist to load
  44899. */
  44900. }, {
  44901. key: 'getSyncSegmentCandidate_',
  44902. value: function getSyncSegmentCandidate_(playlist) {
  44903. var _this2 = this;
  44904. if (this.currentTimeline_ === -1) {
  44905. return 0;
  44906. }
  44907. var segmentIndexArray = playlist.segments.map(function (s, i) {
  44908. return {
  44909. timeline: s.timeline,
  44910. segmentIndex: i
  44911. };
  44912. }).filter(function (s) {
  44913. return s.timeline === _this2.currentTimeline_;
  44914. });
  44915. if (segmentIndexArray.length) {
  44916. return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex;
  44917. }
  44918. return Math.max(playlist.segments.length - 1, 0);
  44919. }
  44920. }, {
  44921. key: 'generateSegmentInfo_',
  44922. value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) {
  44923. if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) {
  44924. return null;
  44925. }
  44926. var segment = playlist.segments[mediaIndex];
  44927. return {
  44928. requestId: 'segment-loader-' + Math.random(),
  44929. // resolve the segment URL relative to the playlist
  44930. uri: segment.resolvedUri,
  44931. // the segment's mediaIndex at the time it was requested
  44932. mediaIndex: mediaIndex,
  44933. // whether or not to update the SegmentLoader's state with this
  44934. // segment's mediaIndex
  44935. isSyncRequest: isSyncRequest,
  44936. startOfSegment: startOfSegment,
  44937. // the segment's playlist
  44938. playlist: playlist,
  44939. // unencrypted bytes of the segment
  44940. bytes: null,
  44941. // when a key is defined for this segment, the encrypted bytes
  44942. encryptedBytes: null,
  44943. // The target timestampOffset for this segment when we append it
  44944. // to the source buffer
  44945. timestampOffset: null,
  44946. // The timeline that the segment is in
  44947. timeline: segment.timeline,
  44948. // The expected duration of the segment in seconds
  44949. duration: segment.duration,
  44950. // retain the segment in case the playlist updates while doing an async process
  44951. segment: segment
  44952. };
  44953. }
  44954. /**
  44955. * Determines if the network has enough bandwidth to complete the current segment
  44956. * request in a timely manner. If not, the request will be aborted early and bandwidth
  44957. * updated to trigger a playlist switch.
  44958. *
  44959. * @param {Object} stats
  44960. * Object containing stats about the request timing and size
  44961. * @return {Boolean} True if the request was aborted, false otherwise
  44962. * @private
  44963. */
  44964. }, {
  44965. key: 'abortRequestEarly_',
  44966. value: function abortRequestEarly_(stats) {
  44967. if (this.hls_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition
  44968. // TODO: Replace using timeout with a boolean indicating whether this playlist is
  44969. // the lowestEnabledRendition.
  44970. !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes
  44971. !this.playlist_.attributes.BANDWIDTH) {
  44972. return false;
  44973. } // Wait at least 1 second since the first byte of data has been received before
  44974. // using the calculated bandwidth from the progress event to allow the bitrate
  44975. // to stabilize
  44976. if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
  44977. return false;
  44978. }
  44979. var currentTime = this.currentTime_();
  44980. var measuredBandwidth = stats.bandwidth;
  44981. var segmentDuration = this.pendingSegment_.duration;
  44982. var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
  44983. // if we are only left with less than 1 second when the request completes.
  44984. // A negative timeUntilRebuffering indicates we are already rebuffering
  44985. var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
  44986. // is larger than the estimated time until the player runs out of forward buffer
  44987. if (requestTimeRemaining <= timeUntilRebuffer$$1) {
  44988. return false;
  44989. }
  44990. var switchCandidate = minRebufferMaxBandwidthSelector({
  44991. master: this.hls_.playlists.master,
  44992. currentTime: currentTime,
  44993. bandwidth: measuredBandwidth,
  44994. duration: this.duration_(),
  44995. segmentDuration: segmentDuration,
  44996. timeUntilRebuffer: timeUntilRebuffer$$1,
  44997. currentTimeline: this.currentTimeline_,
  44998. syncController: this.syncController_
  44999. });
  45000. if (!switchCandidate) {
  45001. return;
  45002. }
  45003. var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1;
  45004. var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
  45005. var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
  45006. // potential round trip time of the new request so that we are not too aggressive
  45007. // with switching to a playlist that might save us a fraction of a second.
  45008. if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) {
  45009. minimumTimeSaving = 1;
  45010. }
  45011. if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
  45012. return false;
  45013. } // set the bandwidth to that of the desired playlist being sure to scale by
  45014. // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
  45015. // don't trigger a bandwidthupdate as the bandwidth is artifial
  45016. this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
  45017. this.abort();
  45018. this.trigger('earlyabort');
  45019. return true;
  45020. }
  45021. /**
  45022. * XHR `progress` event handler
  45023. *
  45024. * @param {Event}
  45025. * The XHR `progress` event
  45026. * @param {Object} simpleSegment
  45027. * A simplified segment object copy
  45028. * @private
  45029. */
  45030. }, {
  45031. key: 'handleProgress_',
  45032. value: function handleProgress_(event, simpleSegment) {
  45033. if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) {
  45034. return;
  45035. }
  45036. this.trigger('progress');
  45037. }
  45038. /**
  45039. * load a specific segment from a request into the buffer
  45040. *
  45041. * @private
  45042. */
  45043. }, {
  45044. key: 'loadSegment_',
  45045. value: function loadSegment_(segmentInfo) {
  45046. this.state = 'WAITING';
  45047. this.pendingSegment_ = segmentInfo;
  45048. this.trimBackBuffer_(segmentInfo);
  45049. segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.captionParser_, this.createSimplifiedSegmentObj_(segmentInfo), // progress callback
  45050. this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this));
  45051. }
  45052. /**
  45053. * trim the back buffer so that we don't have too much data
  45054. * in the source buffer
  45055. *
  45056. * @private
  45057. *
  45058. * @param {Object} segmentInfo - the current segment
  45059. */
  45060. }, {
  45061. key: 'trimBackBuffer_',
  45062. value: function trimBackBuffer_(segmentInfo) {
  45063. var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
  45064. // buffer and a very conservative "garbage collector"
  45065. // We manually clear out the old buffer to ensure
  45066. // we don't trigger the QuotaExceeded error
  45067. // on the source buffer during subsequent appends
  45068. if (removeToTime > 0) {
  45069. this.remove(0, removeToTime);
  45070. }
  45071. }
  45072. /**
  45073. * created a simplified copy of the segment object with just the
  45074. * information necessary to perform the XHR and decryption
  45075. *
  45076. * @private
  45077. *
  45078. * @param {Object} segmentInfo - the current segment
  45079. * @returns {Object} a simplified segment object copy
  45080. */
  45081. }, {
  45082. key: 'createSimplifiedSegmentObj_',
  45083. value: function createSimplifiedSegmentObj_(segmentInfo) {
  45084. var segment = segmentInfo.segment;
  45085. var simpleSegment = {
  45086. resolvedUri: segment.resolvedUri,
  45087. byterange: segment.byterange,
  45088. requestId: segmentInfo.requestId
  45089. };
  45090. if (segment.key) {
  45091. // if the media sequence is greater than 2^32, the IV will be incorrect
  45092. // assuming 10s segments, that would be about 1300 years
  45093. var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
  45094. simpleSegment.key = this.segmentKey(segment.key);
  45095. simpleSegment.key.iv = iv;
  45096. }
  45097. if (segment.map) {
  45098. simpleSegment.map = this.initSegment(segment.map);
  45099. }
  45100. return simpleSegment;
  45101. }
  45102. /**
  45103. * Handle the callback from the segmentRequest function and set the
  45104. * associated SegmentLoader state and errors if necessary
  45105. *
  45106. * @private
  45107. */
  45108. }, {
  45109. key: 'segmentRequestFinished_',
  45110. value: function segmentRequestFinished_(error, simpleSegment) {
  45111. // every request counts as a media request even if it has been aborted
  45112. // or canceled due to a timeout
  45113. this.mediaRequests += 1;
  45114. if (simpleSegment.stats) {
  45115. this.mediaBytesTransferred += simpleSegment.stats.bytesReceived;
  45116. this.mediaTransferDuration += simpleSegment.stats.roundTripTime;
  45117. } // The request was aborted and the SegmentLoader has already been reset
  45118. if (!this.pendingSegment_) {
  45119. this.mediaRequestsAborted += 1;
  45120. return;
  45121. } // the request was aborted and the SegmentLoader has already started
  45122. // another request. this can happen when the timeout for an aborted
  45123. // request triggers due to a limitation in the XHR library
  45124. // do not count this as any sort of request or we risk double-counting
  45125. if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
  45126. return;
  45127. } // an error occurred from the active pendingSegment_ so reset everything
  45128. if (error) {
  45129. this.pendingSegment_ = null;
  45130. this.state = 'READY'; // the requests were aborted just record the aborted stat and exit
  45131. // this is not a true error condition and nothing corrective needs
  45132. // to be done
  45133. if (error.code === REQUEST_ERRORS.ABORTED) {
  45134. this.mediaRequestsAborted += 1;
  45135. return;
  45136. }
  45137. this.pause(); // the error is really just that at least one of the requests timed-out
  45138. // set the bandwidth to a very low value and trigger an ABR switch to
  45139. // take emergency action
  45140. if (error.code === REQUEST_ERRORS.TIMEOUT) {
  45141. this.mediaRequestsTimedout += 1;
  45142. this.bandwidth = 1;
  45143. this.roundTrip = NaN;
  45144. this.trigger('bandwidthupdate');
  45145. return;
  45146. } // if control-flow has arrived here, then the error is real
  45147. // emit an error event to blacklist the current playlist
  45148. this.mediaRequestsErrored += 1;
  45149. this.error(error);
  45150. this.trigger('error');
  45151. return;
  45152. } // the response was a success so set any bandwidth stats the request
  45153. // generated for ABR purposes
  45154. this.bandwidth = simpleSegment.stats.bandwidth;
  45155. this.roundTrip = simpleSegment.stats.roundTripTime; // if this request included an initialization segment, save that data
  45156. // to the initSegment cache
  45157. if (simpleSegment.map) {
  45158. simpleSegment.map = this.initSegment(simpleSegment.map, true);
  45159. } // if this request included a segment key, save that data in the cache
  45160. if (simpleSegment.key) {
  45161. this.segmentKey(simpleSegment.key, true);
  45162. }
  45163. this.processSegmentResponse_(simpleSegment);
  45164. }
  45165. /**
  45166. * Move any important data from the simplified segment object
  45167. * back to the real segment object for future phases
  45168. *
  45169. * @private
  45170. */
  45171. }, {
  45172. key: 'processSegmentResponse_',
  45173. value: function processSegmentResponse_(simpleSegment) {
  45174. var segmentInfo = this.pendingSegment_;
  45175. segmentInfo.bytes = simpleSegment.bytes;
  45176. if (simpleSegment.map) {
  45177. segmentInfo.segment.map.bytes = simpleSegment.map.bytes;
  45178. }
  45179. segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; // This has fmp4 captions, add them to text tracks
  45180. if (simpleSegment.fmp4Captions) {
  45181. createCaptionsTrackIfNotExists(this.inbandTextTracks_, this.hls_.tech_, simpleSegment.captionStreams);
  45182. addCaptionData({
  45183. inbandTextTracks: this.inbandTextTracks_,
  45184. captionArray: simpleSegment.fmp4Captions,
  45185. // fmp4s will not have a timestamp offset
  45186. timestampOffset: 0
  45187. }); // Reset stored captions since we added parsed
  45188. // captions to a text track at this point
  45189. this.captionParser_.clearParsedCaptions();
  45190. }
  45191. this.handleSegment_();
  45192. }
  45193. /**
  45194. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  45195. *
  45196. * @private
  45197. */
  45198. }, {
  45199. key: 'handleSegment_',
  45200. value: function handleSegment_() {
  45201. var _this3 = this;
  45202. if (!this.pendingSegment_) {
  45203. this.state = 'READY';
  45204. return;
  45205. }
  45206. var segmentInfo = this.pendingSegment_;
  45207. var segment = segmentInfo.segment;
  45208. var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); // When we have our first timing info, determine what media types this loader is
  45209. // dealing with. Although we're maintaining extra state, it helps to preserve the
  45210. // separation of segment loader from the actual source buffers.
  45211. if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( // Guard against cases where we're not getting timing info at all until we are
  45212. // certain that all streams will provide it.
  45213. timingInfo.containsAudio || timingInfo.containsVideo)) {
  45214. this.startingMedia_ = {
  45215. containsAudio: timingInfo.containsAudio,
  45216. containsVideo: timingInfo.containsVideo
  45217. };
  45218. }
  45219. var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo);
  45220. if (illegalMediaSwitchError) {
  45221. this.error({
  45222. message: illegalMediaSwitchError,
  45223. blacklistDuration: Infinity
  45224. });
  45225. this.trigger('error');
  45226. return;
  45227. }
  45228. if (segmentInfo.isSyncRequest) {
  45229. this.trigger('syncinfoupdate');
  45230. this.pendingSegment_ = null;
  45231. this.state = 'READY';
  45232. return;
  45233. }
  45234. if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
  45235. this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); // fired when a timestamp offset is set in HLS (can also identify discontinuities)
  45236. this.trigger('timestampoffset');
  45237. }
  45238. var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline);
  45239. if (timelineMapping !== null) {
  45240. this.trigger({
  45241. type: 'segmenttimemapping',
  45242. mapping: timelineMapping
  45243. });
  45244. }
  45245. this.state = 'APPENDING'; // if the media initialization segment is changing, append it
  45246. // before the content segment
  45247. if (segment.map) {
  45248. var initId = initSegmentId(segment.map);
  45249. if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) {
  45250. var initSegment = this.initSegment(segment.map);
  45251. this.sourceUpdater_.appendBuffer({
  45252. bytes: initSegment.bytes
  45253. }, function () {
  45254. _this3.activeInitSegmentId_ = initId;
  45255. });
  45256. }
  45257. }
  45258. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  45259. if (typeof segment.start === 'number' && typeof segment.end === 'number') {
  45260. this.mediaSecondsLoaded += segment.end - segment.start;
  45261. } else {
  45262. this.mediaSecondsLoaded += segment.duration;
  45263. }
  45264. this.logger_(segmentInfoString(segmentInfo));
  45265. this.sourceUpdater_.appendBuffer({
  45266. bytes: segmentInfo.bytes,
  45267. videoSegmentTimingInfoCallback: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId)
  45268. }, this.handleUpdateEnd_.bind(this));
  45269. }
  45270. }, {
  45271. key: 'handleVideoSegmentTimingInfo_',
  45272. value: function handleVideoSegmentTimingInfo_(requestId, event) {
  45273. if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
  45274. return;
  45275. }
  45276. var segment = this.pendingSegment_.segment;
  45277. if (!segment.videoTimingInfo) {
  45278. segment.videoTimingInfo = {};
  45279. }
  45280. segment.videoTimingInfo.transmuxerPrependedSeconds = event.videoSegmentTimingInfo.prependedContentDuration || 0;
  45281. segment.videoTimingInfo.transmuxedPresentationStart = event.videoSegmentTimingInfo.start.presentation;
  45282. segment.videoTimingInfo.transmuxedPresentationEnd = event.videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging
  45283. segment.videoTimingInfo.baseMediaDecodeTime = event.videoSegmentTimingInfo.baseMediaDecodeTime;
  45284. }
  45285. /**
  45286. * callback to run when appendBuffer is finished. detects if we are
  45287. * in a good state to do things with the data we got, or if we need
  45288. * to wait for more
  45289. *
  45290. * @private
  45291. */
  45292. }, {
  45293. key: 'handleUpdateEnd_',
  45294. value: function handleUpdateEnd_() {
  45295. if (!this.pendingSegment_) {
  45296. this.state = 'READY';
  45297. if (!this.paused()) {
  45298. this.monitorBuffer_();
  45299. }
  45300. return;
  45301. }
  45302. var segmentInfo = this.pendingSegment_;
  45303. var segment = segmentInfo.segment;
  45304. var isWalkingForward = this.mediaIndex !== null;
  45305. this.pendingSegment_ = null;
  45306. this.recordThroughput_(segmentInfo);
  45307. this.addSegmentMetadataCue_(segmentInfo);
  45308. this.state = 'READY';
  45309. this.mediaIndex = segmentInfo.mediaIndex;
  45310. this.fetchAtBuffer_ = true;
  45311. this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
  45312. // the following conditional otherwise it may consider this a bad "guess"
  45313. // and attempt to resync when the post-update seekable window and live
  45314. // point would mean that this was the perfect segment to fetch
  45315. this.trigger('syncinfoupdate'); // If we previously appended a segment that ends more than 3 targetDurations before
  45316. // the currentTime_ that means that our conservative guess was too conservative.
  45317. // In that case, reset the loader state so that we try to use any information gained
  45318. // from the previous request to create a new, more accurate, sync-point.
  45319. if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
  45320. this.resetEverything();
  45321. return;
  45322. } // Don't do a rendition switch unless we have enough time to get a sync segment
  45323. // and conservatively guess
  45324. if (isWalkingForward) {
  45325. this.trigger('bandwidthupdate');
  45326. }
  45327. this.trigger('progress'); // any time an update finishes and the last segment is in the
  45328. // buffer, end the stream. this ensures the "ended" event will
  45329. // fire if playback reaches that point.
  45330. if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) {
  45331. this.endOfStream();
  45332. }
  45333. if (!this.paused()) {
  45334. this.monitorBuffer_();
  45335. }
  45336. }
  45337. /**
  45338. * Records the current throughput of the decrypt, transmux, and append
  45339. * portion of the semgment pipeline. `throughput.rate` is a the cumulative
  45340. * moving average of the throughput. `throughput.count` is the number of
  45341. * data points in the average.
  45342. *
  45343. * @private
  45344. * @param {Object} segmentInfo the object returned by loadSegment
  45345. */
  45346. }, {
  45347. key: 'recordThroughput_',
  45348. value: function recordThroughput_(segmentInfo) {
  45349. var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
  45350. // by zero in the case where the throughput is ridiculously high
  45351. var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
  45352. var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
  45353. // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
  45354. this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
  45355. }
  45356. /**
  45357. * Adds a cue to the segment-metadata track with some metadata information about the
  45358. * segment
  45359. *
  45360. * @private
  45361. * @param {Object} segmentInfo
  45362. * the object returned by loadSegment
  45363. * @method addSegmentMetadataCue_
  45364. */
  45365. }, {
  45366. key: 'addSegmentMetadataCue_',
  45367. value: function addSegmentMetadataCue_(segmentInfo) {
  45368. if (!this.segmentMetadataTrack_) {
  45369. return;
  45370. }
  45371. var segment = segmentInfo.segment;
  45372. var start = segment.start;
  45373. var end = segment.end; // Do not try adding the cue if the start and end times are invalid.
  45374. if (!finite(start) || !finite(end)) {
  45375. return;
  45376. }
  45377. removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
  45378. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  45379. var value = {
  45380. custom: segment.custom,
  45381. dateTimeObject: segment.dateTimeObject,
  45382. dateTimeString: segment.dateTimeString,
  45383. bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
  45384. resolution: segmentInfo.playlist.attributes.RESOLUTION,
  45385. codecs: segmentInfo.playlist.attributes.CODECS,
  45386. byteLength: segmentInfo.byteLength,
  45387. uri: segmentInfo.uri,
  45388. timeline: segmentInfo.timeline,
  45389. playlist: segmentInfo.playlist.uri,
  45390. start: start,
  45391. end: end
  45392. };
  45393. var data = JSON.stringify(value);
  45394. var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
  45395. // the differences of WebKitDataCue in safari and VTTCue in other browsers
  45396. cue.value = value;
  45397. this.segmentMetadataTrack_.addCue(cue);
  45398. }
  45399. }]);
  45400. return SegmentLoader;
  45401. }(videojs$1.EventTarget);
  45402. var uint8ToUtf8 = function uint8ToUtf8(uintArray) {
  45403. return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
  45404. };
  45405. /**
  45406. * @file vtt-segment-loader.js
  45407. */
  45408. var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (char) {
  45409. return char.charCodeAt(0);
  45410. }));
  45411. /**
  45412. * An object that manages segment loading and appending.
  45413. *
  45414. * @class VTTSegmentLoader
  45415. * @param {Object} options required and optional options
  45416. * @extends videojs.EventTarget
  45417. */
  45418. var VTTSegmentLoader = function (_SegmentLoader) {
  45419. inherits$1(VTTSegmentLoader, _SegmentLoader);
  45420. function VTTSegmentLoader(settings) {
  45421. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  45422. classCallCheck$1(this, VTTSegmentLoader); // SegmentLoader requires a MediaSource be specified or it will throw an error;
  45423. // however, VTTSegmentLoader has no need of a media source, so delete the reference
  45424. var _this = possibleConstructorReturn$1(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options));
  45425. _this.mediaSource_ = null;
  45426. _this.subtitlesTrack_ = null;
  45427. return _this;
  45428. }
  45429. /**
  45430. * Indicates which time ranges are buffered
  45431. *
  45432. * @return {TimeRange}
  45433. * TimeRange object representing the current buffered ranges
  45434. */
  45435. createClass$1(VTTSegmentLoader, [{
  45436. key: 'buffered_',
  45437. value: function buffered_() {
  45438. if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) {
  45439. return videojs$1.createTimeRanges();
  45440. }
  45441. var cues = this.subtitlesTrack_.cues;
  45442. var start = cues[0].startTime;
  45443. var end = cues[cues.length - 1].startTime;
  45444. return videojs$1.createTimeRanges([[start, end]]);
  45445. }
  45446. /**
  45447. * Gets and sets init segment for the provided map
  45448. *
  45449. * @param {Object} map
  45450. * The map object representing the init segment to get or set
  45451. * @param {Boolean=} set
  45452. * If true, the init segment for the provided map should be saved
  45453. * @return {Object}
  45454. * map object for desired init segment
  45455. */
  45456. }, {
  45457. key: 'initSegment',
  45458. value: function initSegment(map) {
  45459. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  45460. if (!map) {
  45461. return null;
  45462. }
  45463. var id = initSegmentId(map);
  45464. var storedMap = this.initSegments_[id];
  45465. if (set$$1 && !storedMap && map.bytes) {
  45466. // append WebVTT line terminators to the media initialization segment if it exists
  45467. // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
  45468. // requires two or more WebVTT line terminators between the WebVTT header and the
  45469. // rest of the file
  45470. var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
  45471. var combinedSegment = new Uint8Array(combinedByteLength);
  45472. combinedSegment.set(map.bytes);
  45473. combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
  45474. this.initSegments_[id] = storedMap = {
  45475. resolvedUri: map.resolvedUri,
  45476. byterange: map.byterange,
  45477. bytes: combinedSegment
  45478. };
  45479. }
  45480. return storedMap || map;
  45481. }
  45482. /**
  45483. * Returns true if all configuration required for loading is present, otherwise false.
  45484. *
  45485. * @return {Boolean} True if the all configuration is ready for loading
  45486. * @private
  45487. */
  45488. }, {
  45489. key: 'couldBeginLoading_',
  45490. value: function couldBeginLoading_() {
  45491. return this.playlist_ && this.subtitlesTrack_ && !this.paused();
  45492. }
  45493. /**
  45494. * Once all the starting parameters have been specified, begin
  45495. * operation. This method should only be invoked from the INIT
  45496. * state.
  45497. *
  45498. * @private
  45499. */
  45500. }, {
  45501. key: 'init_',
  45502. value: function init_() {
  45503. this.state = 'READY';
  45504. this.resetEverything();
  45505. return this.monitorBuffer_();
  45506. }
  45507. /**
  45508. * Set a subtitle track on the segment loader to add subtitles to
  45509. *
  45510. * @param {TextTrack=} track
  45511. * The text track to add loaded subtitles to
  45512. * @return {TextTrack}
  45513. * Returns the subtitles track
  45514. */
  45515. }, {
  45516. key: 'track',
  45517. value: function track(_track) {
  45518. if (typeof _track === 'undefined') {
  45519. return this.subtitlesTrack_;
  45520. }
  45521. this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start
  45522. // buffering now
  45523. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  45524. this.init_();
  45525. }
  45526. return this.subtitlesTrack_;
  45527. }
  45528. /**
  45529. * Remove any data in the source buffer between start and end times
  45530. * @param {Number} start - the start time of the region to remove from the buffer
  45531. * @param {Number} end - the end time of the region to remove from the buffer
  45532. */
  45533. }, {
  45534. key: 'remove',
  45535. value: function remove(start, end) {
  45536. removeCuesFromTrack(start, end, this.subtitlesTrack_);
  45537. }
  45538. /**
  45539. * fill the buffer with segements unless the sourceBuffers are
  45540. * currently updating
  45541. *
  45542. * Note: this function should only ever be called by monitorBuffer_
  45543. * and never directly
  45544. *
  45545. * @private
  45546. */
  45547. }, {
  45548. key: 'fillBuffer_',
  45549. value: function fillBuffer_() {
  45550. var _this2 = this;
  45551. if (!this.syncPoint_) {
  45552. this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  45553. } // see if we need to begin loading immediately
  45554. var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
  45555. segmentInfo = this.skipEmptySegments_(segmentInfo);
  45556. if (!segmentInfo) {
  45557. return;
  45558. }
  45559. if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
  45560. // We don't have the timestamp offset that we need to sync subtitles.
  45561. // Rerun on a timestamp offset or user interaction.
  45562. var checkTimestampOffset = function checkTimestampOffset() {
  45563. _this2.state = 'READY';
  45564. if (!_this2.paused()) {
  45565. // if not paused, queue a buffer check as soon as possible
  45566. _this2.monitorBuffer_();
  45567. }
  45568. };
  45569. this.syncController_.one('timestampoffset', checkTimestampOffset);
  45570. this.state = 'WAITING_ON_TIMELINE';
  45571. return;
  45572. }
  45573. this.loadSegment_(segmentInfo);
  45574. }
  45575. /**
  45576. * Prevents the segment loader from requesting segments we know contain no subtitles
  45577. * by walking forward until we find the next segment that we don't know whether it is
  45578. * empty or not.
  45579. *
  45580. * @param {Object} segmentInfo
  45581. * a segment info object that describes the current segment
  45582. * @return {Object}
  45583. * a segment info object that describes the current segment
  45584. */
  45585. }, {
  45586. key: 'skipEmptySegments_',
  45587. value: function skipEmptySegments_(segmentInfo) {
  45588. while (segmentInfo && segmentInfo.segment.empty) {
  45589. segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest);
  45590. }
  45591. return segmentInfo;
  45592. }
  45593. /**
  45594. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  45595. *
  45596. * @private
  45597. */
  45598. }, {
  45599. key: 'handleSegment_',
  45600. value: function handleSegment_() {
  45601. var _this3 = this;
  45602. if (!this.pendingSegment_ || !this.subtitlesTrack_) {
  45603. this.state = 'READY';
  45604. return;
  45605. }
  45606. this.state = 'APPENDING';
  45607. var segmentInfo = this.pendingSegment_;
  45608. var segment = segmentInfo.segment; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  45609. if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) {
  45610. var loadHandler = function loadHandler() {
  45611. _this3.handleSegment_();
  45612. };
  45613. this.state = 'WAITING_ON_VTTJS';
  45614. this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler);
  45615. this.subtitlesTrack_.tech_.one('vttjserror', function () {
  45616. _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler);
  45617. _this3.error({
  45618. message: 'Error loading vtt.js'
  45619. });
  45620. _this3.state = 'READY';
  45621. _this3.pause();
  45622. _this3.trigger('error');
  45623. });
  45624. return;
  45625. }
  45626. segment.requested = true;
  45627. try {
  45628. this.parseVTTCues_(segmentInfo);
  45629. } catch (e) {
  45630. this.error({
  45631. message: e.message
  45632. });
  45633. this.state = 'READY';
  45634. this.pause();
  45635. return this.trigger('error');
  45636. }
  45637. this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
  45638. if (segmentInfo.isSyncRequest) {
  45639. this.trigger('syncinfoupdate');
  45640. this.pendingSegment_ = null;
  45641. this.state = 'READY';
  45642. return;
  45643. }
  45644. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  45645. this.mediaSecondsLoaded += segment.duration;
  45646. if (segmentInfo.cues.length) {
  45647. // remove any overlapping cues to prevent doubling
  45648. this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime);
  45649. }
  45650. segmentInfo.cues.forEach(function (cue) {
  45651. _this3.subtitlesTrack_.addCue(cue);
  45652. });
  45653. this.handleUpdateEnd_();
  45654. }
  45655. /**
  45656. * Uses the WebVTT parser to parse the segment response
  45657. *
  45658. * @param {Object} segmentInfo
  45659. * a segment info object that describes the current segment
  45660. * @private
  45661. */
  45662. }, {
  45663. key: 'parseVTTCues_',
  45664. value: function parseVTTCues_(segmentInfo) {
  45665. var decoder = void 0;
  45666. var decodeBytesToString = false;
  45667. if (typeof window$1.TextDecoder === 'function') {
  45668. decoder = new window$1.TextDecoder('utf8');
  45669. } else {
  45670. decoder = window$1.WebVTT.StringDecoder();
  45671. decodeBytesToString = true;
  45672. }
  45673. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder);
  45674. segmentInfo.cues = [];
  45675. segmentInfo.timestampmap = {
  45676. MPEGTS: 0,
  45677. LOCAL: 0
  45678. };
  45679. parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
  45680. parser.ontimestampmap = function (map) {
  45681. return segmentInfo.timestampmap = map;
  45682. };
  45683. parser.onparsingerror = function (error) {
  45684. videojs$1.log.warn('Error encountered when parsing cues: ' + error.message);
  45685. };
  45686. if (segmentInfo.segment.map) {
  45687. var mapData = segmentInfo.segment.map.bytes;
  45688. if (decodeBytesToString) {
  45689. mapData = uint8ToUtf8(mapData);
  45690. }
  45691. parser.parse(mapData);
  45692. }
  45693. var segmentData = segmentInfo.bytes;
  45694. if (decodeBytesToString) {
  45695. segmentData = uint8ToUtf8(segmentData);
  45696. }
  45697. parser.parse(segmentData);
  45698. parser.flush();
  45699. }
  45700. /**
  45701. * Updates the start and end times of any cues parsed by the WebVTT parser using
  45702. * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
  45703. * from the SyncController
  45704. *
  45705. * @param {Object} segmentInfo
  45706. * a segment info object that describes the current segment
  45707. * @param {Object} mappingObj
  45708. * object containing a mapping from TS to media time
  45709. * @param {Object} playlist
  45710. * the playlist object containing the segment
  45711. * @private
  45712. */
  45713. }, {
  45714. key: 'updateTimeMapping_',
  45715. value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
  45716. var segment = segmentInfo.segment;
  45717. if (!mappingObj) {
  45718. // If the sync controller does not have a mapping of TS to Media Time for the
  45719. // timeline, then we don't have enough information to update the cue
  45720. // start/end times
  45721. return;
  45722. }
  45723. if (!segmentInfo.cues.length) {
  45724. // If there are no cues, we also do not have enough information to figure out
  45725. // segment timing. Mark that the segment contains no cues so we don't re-request
  45726. // an empty segment.
  45727. segment.empty = true;
  45728. return;
  45729. }
  45730. var timestampmap = segmentInfo.timestampmap;
  45731. var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping;
  45732. segmentInfo.cues.forEach(function (cue) {
  45733. // First convert cue time to TS time using the timestamp-map provided within the vtt
  45734. cue.startTime += diff;
  45735. cue.endTime += diff;
  45736. });
  45737. if (!playlist.syncInfo) {
  45738. var firstStart = segmentInfo.cues[0].startTime;
  45739. var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
  45740. playlist.syncInfo = {
  45741. mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
  45742. time: Math.min(firstStart, lastStart - segment.duration)
  45743. };
  45744. }
  45745. }
  45746. }]);
  45747. return VTTSegmentLoader;
  45748. }(SegmentLoader);
  45749. /**
  45750. * @file ad-cue-tags.js
  45751. */
  45752. /**
  45753. * Searches for an ad cue that overlaps with the given mediaTime
  45754. */
  45755. var findAdCue = function findAdCue(track, mediaTime) {
  45756. var cues = track.cues;
  45757. for (var i = 0; i < cues.length; i++) {
  45758. var cue = cues[i];
  45759. if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
  45760. return cue;
  45761. }
  45762. }
  45763. return null;
  45764. };
  45765. var updateAdCues = function updateAdCues(media, track) {
  45766. var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
  45767. if (!media.segments) {
  45768. return;
  45769. }
  45770. var mediaTime = offset;
  45771. var cue = void 0;
  45772. for (var i = 0; i < media.segments.length; i++) {
  45773. var segment = media.segments[i];
  45774. if (!cue) {
  45775. // Since the cues will span for at least the segment duration, adding a fudge
  45776. // factor of half segment duration will prevent duplicate cues from being
  45777. // created when timing info is not exact (e.g. cue start time initialized
  45778. // at 10.006677, but next call mediaTime is 10.003332 )
  45779. cue = findAdCue(track, mediaTime + segment.duration / 2);
  45780. }
  45781. if (cue) {
  45782. if ('cueIn' in segment) {
  45783. // Found a CUE-IN so end the cue
  45784. cue.endTime = mediaTime;
  45785. cue.adEndTime = mediaTime;
  45786. mediaTime += segment.duration;
  45787. cue = null;
  45788. continue;
  45789. }
  45790. if (mediaTime < cue.endTime) {
  45791. // Already processed this mediaTime for this cue
  45792. mediaTime += segment.duration;
  45793. continue;
  45794. } // otherwise extend cue until a CUE-IN is found
  45795. cue.endTime += segment.duration;
  45796. } else {
  45797. if ('cueOut' in segment) {
  45798. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
  45799. cue.adStartTime = mediaTime; // Assumes tag format to be
  45800. // #EXT-X-CUE-OUT:30
  45801. cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
  45802. track.addCue(cue);
  45803. }
  45804. if ('cueOutCont' in segment) {
  45805. // Entered into the middle of an ad cue
  45806. var adOffset = void 0;
  45807. var adTotal = void 0; // Assumes tag formate to be
  45808. // #EXT-X-CUE-OUT-CONT:10/30
  45809. var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat);
  45810. var _segment$cueOutCont$s2 = slicedToArray(_segment$cueOutCont$s, 2);
  45811. adOffset = _segment$cueOutCont$s2[0];
  45812. adTotal = _segment$cueOutCont$s2[1];
  45813. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, '');
  45814. cue.adStartTime = mediaTime - adOffset;
  45815. cue.adEndTime = cue.adStartTime + adTotal;
  45816. track.addCue(cue);
  45817. }
  45818. }
  45819. mediaTime += segment.duration;
  45820. }
  45821. };
  45822. /**
  45823. * @file sync-controller.js
  45824. */
  45825. var tsprobe = tsInspector.inspect;
  45826. var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
  45827. // the equivalence display-time 0 === segment-index 0
  45828. {
  45829. name: 'VOD',
  45830. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  45831. if (duration$$1 !== Infinity) {
  45832. var syncPoint = {
  45833. time: 0,
  45834. segmentIndex: 0
  45835. };
  45836. return syncPoint;
  45837. }
  45838. return null;
  45839. }
  45840. }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
  45841. {
  45842. name: 'ProgramDateTime',
  45843. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  45844. if (!syncController.datetimeToDisplayTime) {
  45845. return null;
  45846. }
  45847. var segments = playlist.segments || [];
  45848. var syncPoint = null;
  45849. var lastDistance = null;
  45850. currentTime = currentTime || 0;
  45851. for (var i = 0; i < segments.length; i++) {
  45852. var segment = segments[i];
  45853. if (segment.dateTimeObject) {
  45854. var segmentTime = segment.dateTimeObject.getTime() / 1000;
  45855. var segmentStart = segmentTime + syncController.datetimeToDisplayTime;
  45856. var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, we have passed
  45857. // currentTime and can stop looking for better candidates
  45858. if (lastDistance !== null && lastDistance < distance) {
  45859. break;
  45860. }
  45861. lastDistance = distance;
  45862. syncPoint = {
  45863. time: segmentStart,
  45864. segmentIndex: i
  45865. };
  45866. }
  45867. }
  45868. return syncPoint;
  45869. }
  45870. }, // Stategy "Segment": We have a known time mapping for a timeline and a
  45871. // segment in the current timeline with timing data
  45872. {
  45873. name: 'Segment',
  45874. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  45875. var segments = playlist.segments || [];
  45876. var syncPoint = null;
  45877. var lastDistance = null;
  45878. currentTime = currentTime || 0;
  45879. for (var i = 0; i < segments.length; i++) {
  45880. var segment = segments[i];
  45881. if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') {
  45882. var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed
  45883. // currentTime and can stop looking for better candidates
  45884. if (lastDistance !== null && lastDistance < distance) {
  45885. break;
  45886. }
  45887. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  45888. lastDistance = distance;
  45889. syncPoint = {
  45890. time: segment.start,
  45891. segmentIndex: i
  45892. };
  45893. }
  45894. }
  45895. }
  45896. return syncPoint;
  45897. }
  45898. }, // Stategy "Discontinuity": We have a discontinuity with a known
  45899. // display-time
  45900. {
  45901. name: 'Discontinuity',
  45902. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  45903. var syncPoint = null;
  45904. currentTime = currentTime || 0;
  45905. if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  45906. var lastDistance = null;
  45907. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  45908. var segmentIndex = playlist.discontinuityStarts[i];
  45909. var discontinuity = playlist.discontinuitySequence + i + 1;
  45910. var discontinuitySync = syncController.discontinuities[discontinuity];
  45911. if (discontinuitySync) {
  45912. var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
  45913. // currentTime and can stop looking for better candidates
  45914. if (lastDistance !== null && lastDistance < distance) {
  45915. break;
  45916. }
  45917. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  45918. lastDistance = distance;
  45919. syncPoint = {
  45920. time: discontinuitySync.time,
  45921. segmentIndex: segmentIndex
  45922. };
  45923. }
  45924. }
  45925. }
  45926. }
  45927. return syncPoint;
  45928. }
  45929. }, // Stategy "Playlist": We have a playlist with a known mapping of
  45930. // segment index to display time
  45931. {
  45932. name: 'Playlist',
  45933. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  45934. if (playlist.syncInfo) {
  45935. var syncPoint = {
  45936. time: playlist.syncInfo.time,
  45937. segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence
  45938. };
  45939. return syncPoint;
  45940. }
  45941. return null;
  45942. }
  45943. }];
  45944. var SyncController = function (_videojs$EventTarget) {
  45945. inherits$1(SyncController, _videojs$EventTarget);
  45946. function SyncController() {
  45947. classCallCheck$1(this, SyncController); // Segment Loader state variables...
  45948. // ...for synching across variants
  45949. var _this = possibleConstructorReturn$1(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this));
  45950. _this.inspectCache_ = undefined; // ...for synching across variants
  45951. _this.timelines = [];
  45952. _this.discontinuities = [];
  45953. _this.datetimeToDisplayTime = null;
  45954. _this.logger_ = logger('SyncController');
  45955. return _this;
  45956. }
  45957. /**
  45958. * Find a sync-point for the playlist specified
  45959. *
  45960. * A sync-point is defined as a known mapping from display-time to
  45961. * a segment-index in the current playlist.
  45962. *
  45963. * @param {Playlist} playlist
  45964. * The playlist that needs a sync-point
  45965. * @param {Number} duration
  45966. * Duration of the MediaSource (Infinite if playing a live source)
  45967. * @param {Number} currentTimeline
  45968. * The last timeline from which a segment was loaded
  45969. * @returns {Object}
  45970. * A sync-point object
  45971. */
  45972. createClass$1(SyncController, [{
  45973. key: 'getSyncPoint',
  45974. value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) {
  45975. var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime);
  45976. if (!syncPoints.length) {
  45977. // Signal that we need to attempt to get a sync-point manually
  45978. // by fetching a segment in the playlist and constructing
  45979. // a sync-point from that information
  45980. return null;
  45981. } // Now find the sync-point that is closest to the currentTime because
  45982. // that should result in the most accurate guess about which segment
  45983. // to fetch
  45984. return this.selectSyncPoint_(syncPoints, {
  45985. key: 'time',
  45986. value: currentTime
  45987. });
  45988. }
  45989. /**
  45990. * Calculate the amount of time that has expired off the playlist during playback
  45991. *
  45992. * @param {Playlist} playlist
  45993. * Playlist object to calculate expired from
  45994. * @param {Number} duration
  45995. * Duration of the MediaSource (Infinity if playling a live source)
  45996. * @returns {Number|null}
  45997. * The amount of time that has expired off the playlist during playback. Null
  45998. * if no sync-points for the playlist can be found.
  45999. */
  46000. }, {
  46001. key: 'getExpiredTime',
  46002. value: function getExpiredTime(playlist, duration$$1) {
  46003. if (!playlist || !playlist.segments) {
  46004. return null;
  46005. }
  46006. var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
  46007. if (!syncPoints.length) {
  46008. return null;
  46009. }
  46010. var syncPoint = this.selectSyncPoint_(syncPoints, {
  46011. key: 'segmentIndex',
  46012. value: 0
  46013. }); // If the sync-point is beyond the start of the playlist, we want to subtract the
  46014. // duration from index 0 to syncPoint.segmentIndex instead of adding.
  46015. if (syncPoint.segmentIndex > 0) {
  46016. syncPoint.time *= -1;
  46017. }
  46018. return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0));
  46019. }
  46020. /**
  46021. * Runs each sync-point strategy and returns a list of sync-points returned by the
  46022. * strategies
  46023. *
  46024. * @private
  46025. * @param {Playlist} playlist
  46026. * The playlist that needs a sync-point
  46027. * @param {Number} duration
  46028. * Duration of the MediaSource (Infinity if playing a live source)
  46029. * @param {Number} currentTimeline
  46030. * The last timeline from which a segment was loaded
  46031. * @returns {Array}
  46032. * A list of sync-point objects
  46033. */
  46034. }, {
  46035. key: 'runStrategies_',
  46036. value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) {
  46037. var syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
  46038. for (var i = 0; i < syncPointStrategies.length; i++) {
  46039. var strategy = syncPointStrategies[i];
  46040. var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime);
  46041. if (syncPoint) {
  46042. syncPoint.strategy = strategy.name;
  46043. syncPoints.push({
  46044. strategy: strategy.name,
  46045. syncPoint: syncPoint
  46046. });
  46047. }
  46048. }
  46049. return syncPoints;
  46050. }
  46051. /**
  46052. * Selects the sync-point nearest the specified target
  46053. *
  46054. * @private
  46055. * @param {Array} syncPoints
  46056. * List of sync-points to select from
  46057. * @param {Object} target
  46058. * Object specifying the property and value we are targeting
  46059. * @param {String} target.key
  46060. * Specifies the property to target. Must be either 'time' or 'segmentIndex'
  46061. * @param {Number} target.value
  46062. * The value to target for the specified key.
  46063. * @returns {Object}
  46064. * The sync-point nearest the target
  46065. */
  46066. }, {
  46067. key: 'selectSyncPoint_',
  46068. value: function selectSyncPoint_(syncPoints, target) {
  46069. var bestSyncPoint = syncPoints[0].syncPoint;
  46070. var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
  46071. var bestStrategy = syncPoints[0].strategy;
  46072. for (var i = 1; i < syncPoints.length; i++) {
  46073. var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
  46074. if (newDistance < bestDistance) {
  46075. bestDistance = newDistance;
  46076. bestSyncPoint = syncPoints[i].syncPoint;
  46077. bestStrategy = syncPoints[i].strategy;
  46078. }
  46079. }
  46080. this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']'));
  46081. return bestSyncPoint;
  46082. }
  46083. /**
  46084. * Save any meta-data present on the segments when segments leave
  46085. * the live window to the playlist to allow for synchronization at the
  46086. * playlist level later.
  46087. *
  46088. * @param {Playlist} oldPlaylist - The previous active playlist
  46089. * @param {Playlist} newPlaylist - The updated and most current playlist
  46090. */
  46091. }, {
  46092. key: 'saveExpiredSegmentInfo',
  46093. value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
  46094. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time
  46095. // save that information as a possible sync-point reference in future
  46096. for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
  46097. var lastRemovedSegment = oldPlaylist.segments[i];
  46098. if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
  46099. newPlaylist.syncInfo = {
  46100. mediaSequence: oldPlaylist.mediaSequence + i,
  46101. time: lastRemovedSegment.start
  46102. };
  46103. this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']'));
  46104. this.trigger('syncinfoupdate');
  46105. break;
  46106. }
  46107. }
  46108. }
  46109. /**
  46110. * Save the mapping from playlist's ProgramDateTime to display. This should
  46111. * only ever happen once at the start of playback.
  46112. *
  46113. * @param {Playlist} playlist - The currently active playlist
  46114. */
  46115. }, {
  46116. key: 'setDateTimeMapping',
  46117. value: function setDateTimeMapping(playlist) {
  46118. if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
  46119. var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
  46120. this.datetimeToDisplayTime = -playlistTimestamp;
  46121. }
  46122. }
  46123. /**
  46124. * Reset the state of the inspection cache when we do a rendition
  46125. * switch
  46126. */
  46127. }, {
  46128. key: 'reset',
  46129. value: function reset() {
  46130. this.inspectCache_ = undefined;
  46131. }
  46132. /**
  46133. * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start
  46134. * and end of the segment in it's internal "media time". Used to generate
  46135. * mappings from that internal "media time" to the display time that is
  46136. * shown on the player.
  46137. *
  46138. * @param {SegmentInfo} segmentInfo - The current active request information
  46139. */
  46140. }, {
  46141. key: 'probeSegmentInfo',
  46142. value: function probeSegmentInfo(segmentInfo) {
  46143. var segment = segmentInfo.segment;
  46144. var playlist = segmentInfo.playlist;
  46145. var timingInfo = void 0;
  46146. if (segment.map) {
  46147. timingInfo = this.probeMp4Segment_(segmentInfo);
  46148. } else {
  46149. timingInfo = this.probeTsSegment_(segmentInfo);
  46150. }
  46151. if (timingInfo) {
  46152. if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) {
  46153. this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
  46154. // now with segment timing information
  46155. if (!playlist.syncInfo) {
  46156. playlist.syncInfo = {
  46157. mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
  46158. time: segment.start
  46159. };
  46160. }
  46161. }
  46162. }
  46163. return timingInfo;
  46164. }
  46165. /**
  46166. * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment
  46167. * in it's internal "media time".
  46168. *
  46169. * @private
  46170. * @param {SegmentInfo} segmentInfo - The current active request information
  46171. * @return {object} The start and end time of the current segment in "media time"
  46172. */
  46173. }, {
  46174. key: 'probeMp4Segment_',
  46175. value: function probeMp4Segment_(segmentInfo) {
  46176. var segment = segmentInfo.segment;
  46177. var timescales = probe.timescale(segment.map.bytes);
  46178. var startTime = probe.startTime(timescales, segmentInfo.bytes);
  46179. if (segmentInfo.timestampOffset !== null) {
  46180. segmentInfo.timestampOffset -= startTime;
  46181. }
  46182. return {
  46183. start: startTime,
  46184. end: startTime + segment.duration
  46185. };
  46186. }
  46187. /**
  46188. * Probe an mpeg2-ts segment to determine the start and end of the segment
  46189. * in it's internal "media time".
  46190. *
  46191. * @private
  46192. * @param {SegmentInfo} segmentInfo - The current active request information
  46193. * @return {object} The start and end time of the current segment in "media time"
  46194. */
  46195. }, {
  46196. key: 'probeTsSegment_',
  46197. value: function probeTsSegment_(segmentInfo) {
  46198. var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_);
  46199. var segmentStartTime = void 0;
  46200. var segmentEndTime = void 0;
  46201. if (!timeInfo) {
  46202. return null;
  46203. }
  46204. if (timeInfo.video && timeInfo.video.length === 2) {
  46205. this.inspectCache_ = timeInfo.video[1].dts;
  46206. segmentStartTime = timeInfo.video[0].dtsTime;
  46207. segmentEndTime = timeInfo.video[1].dtsTime;
  46208. } else if (timeInfo.audio && timeInfo.audio.length === 2) {
  46209. this.inspectCache_ = timeInfo.audio[1].dts;
  46210. segmentStartTime = timeInfo.audio[0].dtsTime;
  46211. segmentEndTime = timeInfo.audio[1].dtsTime;
  46212. }
  46213. var probedInfo = {
  46214. start: segmentStartTime,
  46215. end: segmentEndTime,
  46216. containsVideo: timeInfo.video && timeInfo.video.length === 2,
  46217. containsAudio: timeInfo.audio && timeInfo.audio.length === 2
  46218. };
  46219. return probedInfo;
  46220. }
  46221. }, {
  46222. key: 'timestampOffsetForTimeline',
  46223. value: function timestampOffsetForTimeline(timeline) {
  46224. if (typeof this.timelines[timeline] === 'undefined') {
  46225. return null;
  46226. }
  46227. return this.timelines[timeline].time;
  46228. }
  46229. }, {
  46230. key: 'mappingForTimeline',
  46231. value: function mappingForTimeline(timeline) {
  46232. if (typeof this.timelines[timeline] === 'undefined') {
  46233. return null;
  46234. }
  46235. return this.timelines[timeline].mapping;
  46236. }
  46237. /**
  46238. * Use the "media time" for a segment to generate a mapping to "display time" and
  46239. * save that display time to the segment.
  46240. *
  46241. * @private
  46242. * @param {SegmentInfo} segmentInfo
  46243. * The current active request information
  46244. * @param {object} timingInfo
  46245. * The start and end time of the current segment in "media time"
  46246. * @returns {Boolean}
  46247. * Returns false if segment time mapping could not be calculated
  46248. */
  46249. }, {
  46250. key: 'calculateSegmentTimeMapping_',
  46251. value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) {
  46252. var segment = segmentInfo.segment;
  46253. var mappingObj = this.timelines[segmentInfo.timeline];
  46254. if (segmentInfo.timestampOffset !== null) {
  46255. mappingObj = {
  46256. time: segmentInfo.startOfSegment,
  46257. mapping: segmentInfo.startOfSegment - timingInfo.start
  46258. };
  46259. this.timelines[segmentInfo.timeline] = mappingObj;
  46260. this.trigger('timestampoffset');
  46261. this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']'));
  46262. segment.start = segmentInfo.startOfSegment;
  46263. segment.end = timingInfo.end + mappingObj.mapping;
  46264. } else if (mappingObj) {
  46265. segment.start = timingInfo.start + mappingObj.mapping;
  46266. segment.end = timingInfo.end + mappingObj.mapping;
  46267. } else {
  46268. return false;
  46269. }
  46270. return true;
  46271. }
  46272. /**
  46273. * Each time we have discontinuity in the playlist, attempt to calculate the location
  46274. * in display of the start of the discontinuity and save that. We also save an accuracy
  46275. * value so that we save values with the most accuracy (closest to 0.)
  46276. *
  46277. * @private
  46278. * @param {SegmentInfo} segmentInfo - The current active request information
  46279. */
  46280. }, {
  46281. key: 'saveDiscontinuitySyncInfo_',
  46282. value: function saveDiscontinuitySyncInfo_(segmentInfo) {
  46283. var playlist = segmentInfo.playlist;
  46284. var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
  46285. // the start of the range and it's accuracy is 0 (greater accuracy values
  46286. // mean more approximation)
  46287. if (segment.discontinuity) {
  46288. this.discontinuities[segment.timeline] = {
  46289. time: segment.start,
  46290. accuracy: 0
  46291. };
  46292. } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  46293. // Search for future discontinuities that we can provide better timing
  46294. // information for and save that information for sync purposes
  46295. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  46296. var segmentIndex = playlist.discontinuityStarts[i];
  46297. var discontinuity = playlist.discontinuitySequence + i + 1;
  46298. var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
  46299. var accuracy = Math.abs(mediaIndexDiff);
  46300. if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
  46301. var time = void 0;
  46302. if (mediaIndexDiff < 0) {
  46303. time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex);
  46304. } else {
  46305. time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex);
  46306. }
  46307. this.discontinuities[discontinuity] = {
  46308. time: time,
  46309. accuracy: accuracy
  46310. };
  46311. }
  46312. }
  46313. }
  46314. }
  46315. }]);
  46316. return SyncController;
  46317. }(videojs$1.EventTarget);
  46318. var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) {
  46319. var self = this;
  46320. var decrypterWorker = function () {
  46321. /*
  46322. * pkcs7.pad
  46323. * https://github.com/brightcove/pkcs7
  46324. *
  46325. * Copyright (c) 2014 Brightcove
  46326. * Licensed under the apache2 license.
  46327. */
  46328. /**
  46329. * Returns the subarray of a Uint8Array without PKCS#7 padding.
  46330. * @param padded {Uint8Array} unencrypted bytes that have been padded
  46331. * @return {Uint8Array} the unpadded bytes
  46332. * @see http://tools.ietf.org/html/rfc5652
  46333. */
  46334. function unpad(padded) {
  46335. return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
  46336. }
  46337. var classCallCheck = function classCallCheck(instance, Constructor) {
  46338. if (!(instance instanceof Constructor)) {
  46339. throw new TypeError("Cannot call a class as a function");
  46340. }
  46341. };
  46342. var createClass = function () {
  46343. function defineProperties(target, props) {
  46344. for (var i = 0; i < props.length; i++) {
  46345. var descriptor = props[i];
  46346. descriptor.enumerable = descriptor.enumerable || false;
  46347. descriptor.configurable = true;
  46348. if ("value" in descriptor) descriptor.writable = true;
  46349. Object.defineProperty(target, descriptor.key, descriptor);
  46350. }
  46351. }
  46352. return function (Constructor, protoProps, staticProps) {
  46353. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  46354. if (staticProps) defineProperties(Constructor, staticProps);
  46355. return Constructor;
  46356. };
  46357. }();
  46358. var inherits = function inherits(subClass, superClass) {
  46359. if (typeof superClass !== "function" && superClass !== null) {
  46360. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  46361. }
  46362. subClass.prototype = Object.create(superClass && superClass.prototype, {
  46363. constructor: {
  46364. value: subClass,
  46365. enumerable: false,
  46366. writable: true,
  46367. configurable: true
  46368. }
  46369. });
  46370. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  46371. };
  46372. var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
  46373. if (!self) {
  46374. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  46375. }
  46376. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  46377. };
  46378. /**
  46379. * @file aes.js
  46380. *
  46381. * This file contains an adaptation of the AES decryption algorithm
  46382. * from the Standford Javascript Cryptography Library. That work is
  46383. * covered by the following copyright and permissions notice:
  46384. *
  46385. * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
  46386. * All rights reserved.
  46387. *
  46388. * Redistribution and use in source and binary forms, with or without
  46389. * modification, are permitted provided that the following conditions are
  46390. * met:
  46391. *
  46392. * 1. Redistributions of source code must retain the above copyright
  46393. * notice, this list of conditions and the following disclaimer.
  46394. *
  46395. * 2. Redistributions in binary form must reproduce the above
  46396. * copyright notice, this list of conditions and the following
  46397. * disclaimer in the documentation and/or other materials provided
  46398. * with the distribution.
  46399. *
  46400. * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
  46401. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  46402. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  46403. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
  46404. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  46405. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  46406. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  46407. * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  46408. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  46409. * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
  46410. * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  46411. *
  46412. * The views and conclusions contained in the software and documentation
  46413. * are those of the authors and should not be interpreted as representing
  46414. * official policies, either expressed or implied, of the authors.
  46415. */
  46416. /**
  46417. * Expand the S-box tables.
  46418. *
  46419. * @private
  46420. */
  46421. var precompute = function precompute() {
  46422. var tables = [[[], [], [], [], []], [[], [], [], [], []]];
  46423. var encTable = tables[0];
  46424. var decTable = tables[1];
  46425. var sbox = encTable[4];
  46426. var sboxInv = decTable[4];
  46427. var i = void 0;
  46428. var x = void 0;
  46429. var xInv = void 0;
  46430. var d = [];
  46431. var th = [];
  46432. var x2 = void 0;
  46433. var x4 = void 0;
  46434. var x8 = void 0;
  46435. var s = void 0;
  46436. var tEnc = void 0;
  46437. var tDec = void 0; // Compute double and third tables
  46438. for (i = 0; i < 256; i++) {
  46439. th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
  46440. }
  46441. for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
  46442. // Compute sbox
  46443. s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
  46444. s = s >> 8 ^ s & 255 ^ 99;
  46445. sbox[x] = s;
  46446. sboxInv[s] = x; // Compute MixColumns
  46447. x8 = d[x4 = d[x2 = d[x]]];
  46448. tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
  46449. tEnc = d[s] * 0x101 ^ s * 0x1010100;
  46450. for (i = 0; i < 4; i++) {
  46451. encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
  46452. decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
  46453. }
  46454. } // Compactify. Considerable speedup on Firefox.
  46455. for (i = 0; i < 5; i++) {
  46456. encTable[i] = encTable[i].slice(0);
  46457. decTable[i] = decTable[i].slice(0);
  46458. }
  46459. return tables;
  46460. };
  46461. var aesTables = null;
  46462. /**
  46463. * Schedule out an AES key for both encryption and decryption. This
  46464. * is a low-level class. Use a cipher mode to do bulk encryption.
  46465. *
  46466. * @class AES
  46467. * @param key {Array} The key as an array of 4, 6 or 8 words.
  46468. */
  46469. var AES = function () {
  46470. function AES(key) {
  46471. classCallCheck(this, AES);
  46472. /**
  46473. * The expanded S-box and inverse S-box tables. These will be computed
  46474. * on the client so that we don't have to send them down the wire.
  46475. *
  46476. * There are two tables, _tables[0] is for encryption and
  46477. * _tables[1] is for decryption.
  46478. *
  46479. * The first 4 sub-tables are the expanded S-box with MixColumns. The
  46480. * last (_tables[01][4]) is the S-box itself.
  46481. *
  46482. * @private
  46483. */
  46484. // if we have yet to precompute the S-box tables
  46485. // do so now
  46486. if (!aesTables) {
  46487. aesTables = precompute();
  46488. } // then make a copy of that object for use
  46489. 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()]];
  46490. var i = void 0;
  46491. var j = void 0;
  46492. var tmp = void 0;
  46493. var encKey = void 0;
  46494. var decKey = void 0;
  46495. var sbox = this._tables[0][4];
  46496. var decTable = this._tables[1];
  46497. var keyLen = key.length;
  46498. var rcon = 1;
  46499. if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
  46500. throw new Error('Invalid aes key size');
  46501. }
  46502. encKey = key.slice(0);
  46503. decKey = [];
  46504. this._key = [encKey, decKey]; // schedule encryption keys
  46505. for (i = keyLen; i < 4 * keyLen + 28; i++) {
  46506. tmp = encKey[i - 1]; // apply sbox
  46507. if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
  46508. tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
  46509. if (i % keyLen === 0) {
  46510. tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
  46511. rcon = rcon << 1 ^ (rcon >> 7) * 283;
  46512. }
  46513. }
  46514. encKey[i] = encKey[i - keyLen] ^ tmp;
  46515. } // schedule decryption keys
  46516. for (j = 0; i; j++, i--) {
  46517. tmp = encKey[j & 3 ? i : i - 4];
  46518. if (i <= 4 || j < 4) {
  46519. decKey[j] = tmp;
  46520. } else {
  46521. decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
  46522. }
  46523. }
  46524. }
  46525. /**
  46526. * Decrypt 16 bytes, specified as four 32-bit words.
  46527. *
  46528. * @param {Number} encrypted0 the first word to decrypt
  46529. * @param {Number} encrypted1 the second word to decrypt
  46530. * @param {Number} encrypted2 the third word to decrypt
  46531. * @param {Number} encrypted3 the fourth word to decrypt
  46532. * @param {Int32Array} out the array to write the decrypted words
  46533. * into
  46534. * @param {Number} offset the offset into the output array to start
  46535. * writing results
  46536. * @return {Array} The plaintext.
  46537. */
  46538. AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
  46539. var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
  46540. var a = encrypted0 ^ key[0];
  46541. var b = encrypted3 ^ key[1];
  46542. var c = encrypted2 ^ key[2];
  46543. var d = encrypted1 ^ key[3];
  46544. var a2 = void 0;
  46545. var b2 = void 0;
  46546. var c2 = void 0; // key.length === 2 ?
  46547. var nInnerRounds = key.length / 4 - 2;
  46548. var i = void 0;
  46549. var kIndex = 4;
  46550. var table = this._tables[1]; // load up the tables
  46551. var table0 = table[0];
  46552. var table1 = table[1];
  46553. var table2 = table[2];
  46554. var table3 = table[3];
  46555. var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
  46556. for (i = 0; i < nInnerRounds; i++) {
  46557. a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
  46558. b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
  46559. c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
  46560. d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
  46561. kIndex += 4;
  46562. a = a2;
  46563. b = b2;
  46564. c = c2;
  46565. } // Last round.
  46566. for (i = 0; i < 4; i++) {
  46567. out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
  46568. a2 = a;
  46569. a = b;
  46570. b = c;
  46571. c = d;
  46572. d = a2;
  46573. }
  46574. };
  46575. return AES;
  46576. }();
  46577. /**
  46578. * @file stream.js
  46579. */
  46580. /**
  46581. * A lightweight readable stream implemention that handles event dispatching.
  46582. *
  46583. * @class Stream
  46584. */
  46585. var Stream = function () {
  46586. function Stream() {
  46587. classCallCheck(this, Stream);
  46588. this.listeners = {};
  46589. }
  46590. /**
  46591. * Add a listener for a specified event type.
  46592. *
  46593. * @param {String} type the event name
  46594. * @param {Function} listener the callback to be invoked when an event of
  46595. * the specified type occurs
  46596. */
  46597. Stream.prototype.on = function on(type, listener) {
  46598. if (!this.listeners[type]) {
  46599. this.listeners[type] = [];
  46600. }
  46601. this.listeners[type].push(listener);
  46602. };
  46603. /**
  46604. * Remove a listener for a specified event type.
  46605. *
  46606. * @param {String} type the event name
  46607. * @param {Function} listener a function previously registered for this
  46608. * type of event through `on`
  46609. * @return {Boolean} if we could turn it off or not
  46610. */
  46611. Stream.prototype.off = function off(type, listener) {
  46612. if (!this.listeners[type]) {
  46613. return false;
  46614. }
  46615. var index = this.listeners[type].indexOf(listener);
  46616. this.listeners[type].splice(index, 1);
  46617. return index > -1;
  46618. };
  46619. /**
  46620. * Trigger an event of the specified type on this stream. Any additional
  46621. * arguments to this function are passed as parameters to event listeners.
  46622. *
  46623. * @param {String} type the event name
  46624. */
  46625. Stream.prototype.trigger = function trigger(type) {
  46626. var callbacks = this.listeners[type];
  46627. if (!callbacks) {
  46628. return;
  46629. } // Slicing the arguments on every invocation of this method
  46630. // can add a significant amount of overhead. Avoid the
  46631. // intermediate object creation for the common case of a
  46632. // single callback argument
  46633. if (arguments.length === 2) {
  46634. var length = callbacks.length;
  46635. for (var i = 0; i < length; ++i) {
  46636. callbacks[i].call(this, arguments[1]);
  46637. }
  46638. } else {
  46639. var args = Array.prototype.slice.call(arguments, 1);
  46640. var _length = callbacks.length;
  46641. for (var _i = 0; _i < _length; ++_i) {
  46642. callbacks[_i].apply(this, args);
  46643. }
  46644. }
  46645. };
  46646. /**
  46647. * Destroys the stream and cleans up.
  46648. */
  46649. Stream.prototype.dispose = function dispose() {
  46650. this.listeners = {};
  46651. };
  46652. /**
  46653. * Forwards all `data` events on this stream to the destination stream. The
  46654. * destination stream should provide a method `push` to receive the data
  46655. * events as they arrive.
  46656. *
  46657. * @param {Stream} destination the stream that will receive all `data` events
  46658. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  46659. */
  46660. Stream.prototype.pipe = function pipe(destination) {
  46661. this.on('data', function (data) {
  46662. destination.push(data);
  46663. });
  46664. };
  46665. return Stream;
  46666. }();
  46667. /**
  46668. * @file async-stream.js
  46669. */
  46670. /**
  46671. * A wrapper around the Stream class to use setTiemout
  46672. * and run stream "jobs" Asynchronously
  46673. *
  46674. * @class AsyncStream
  46675. * @extends Stream
  46676. */
  46677. var AsyncStream$$1 = function (_Stream) {
  46678. inherits(AsyncStream$$1, _Stream);
  46679. function AsyncStream$$1() {
  46680. classCallCheck(this, AsyncStream$$1);
  46681. var _this = possibleConstructorReturn(this, _Stream.call(this, Stream));
  46682. _this.jobs = [];
  46683. _this.delay = 1;
  46684. _this.timeout_ = null;
  46685. return _this;
  46686. }
  46687. /**
  46688. * process an async job
  46689. *
  46690. * @private
  46691. */
  46692. AsyncStream$$1.prototype.processJob_ = function processJob_() {
  46693. this.jobs.shift()();
  46694. if (this.jobs.length) {
  46695. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  46696. } else {
  46697. this.timeout_ = null;
  46698. }
  46699. };
  46700. /**
  46701. * push a job into the stream
  46702. *
  46703. * @param {Function} job the job to push into the stream
  46704. */
  46705. AsyncStream$$1.prototype.push = function push(job) {
  46706. this.jobs.push(job);
  46707. if (!this.timeout_) {
  46708. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  46709. }
  46710. };
  46711. return AsyncStream$$1;
  46712. }(Stream);
  46713. /**
  46714. * @file decrypter.js
  46715. *
  46716. * An asynchronous implementation of AES-128 CBC decryption with
  46717. * PKCS#7 padding.
  46718. */
  46719. /**
  46720. * Convert network-order (big-endian) bytes into their little-endian
  46721. * representation.
  46722. */
  46723. var ntoh = function ntoh(word) {
  46724. return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
  46725. };
  46726. /**
  46727. * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
  46728. *
  46729. * @param {Uint8Array} encrypted the encrypted bytes
  46730. * @param {Uint32Array} key the bytes of the decryption key
  46731. * @param {Uint32Array} initVector the initialization vector (IV) to
  46732. * use for the first round of CBC.
  46733. * @return {Uint8Array} the decrypted bytes
  46734. *
  46735. * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
  46736. * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
  46737. * @see https://tools.ietf.org/html/rfc2315
  46738. */
  46739. var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) {
  46740. // word-level access to the encrypted bytes
  46741. var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
  46742. var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
  46743. var decrypted = new Uint8Array(encrypted.byteLength);
  46744. var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
  46745. // decrypted data
  46746. var init0 = void 0;
  46747. var init1 = void 0;
  46748. var init2 = void 0;
  46749. var init3 = void 0;
  46750. var encrypted0 = void 0;
  46751. var encrypted1 = void 0;
  46752. var encrypted2 = void 0;
  46753. var encrypted3 = void 0; // iteration variable
  46754. var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
  46755. // passed-in reference and easier access
  46756. init0 = initVector[0];
  46757. init1 = initVector[1];
  46758. init2 = initVector[2];
  46759. init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
  46760. // to each decrypted block
  46761. for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
  46762. // convert big-endian (network order) words into little-endian
  46763. // (javascript order)
  46764. encrypted0 = ntoh(encrypted32[wordIx]);
  46765. encrypted1 = ntoh(encrypted32[wordIx + 1]);
  46766. encrypted2 = ntoh(encrypted32[wordIx + 2]);
  46767. encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
  46768. decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
  46769. // plaintext
  46770. decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
  46771. decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
  46772. decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
  46773. decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
  46774. init0 = encrypted0;
  46775. init1 = encrypted1;
  46776. init2 = encrypted2;
  46777. init3 = encrypted3;
  46778. }
  46779. return decrypted;
  46780. };
  46781. /**
  46782. * The `Decrypter` class that manages decryption of AES
  46783. * data through `AsyncStream` objects and the `decrypt`
  46784. * function
  46785. *
  46786. * @param {Uint8Array} encrypted the encrypted bytes
  46787. * @param {Uint32Array} key the bytes of the decryption key
  46788. * @param {Uint32Array} initVector the initialization vector (IV) to
  46789. * @param {Function} done the function to run when done
  46790. * @class Decrypter
  46791. */
  46792. var Decrypter$$1 = function () {
  46793. function Decrypter$$1(encrypted, key, initVector, done) {
  46794. classCallCheck(this, Decrypter$$1);
  46795. var step = Decrypter$$1.STEP;
  46796. var encrypted32 = new Int32Array(encrypted.buffer);
  46797. var decrypted = new Uint8Array(encrypted.byteLength);
  46798. var i = 0;
  46799. this.asyncStream_ = new AsyncStream$$1(); // split up the encryption job and do the individual chunks asynchronously
  46800. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  46801. for (i = step; i < encrypted32.length; i += step) {
  46802. initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
  46803. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  46804. } // invoke the done() callback when everything is finished
  46805. this.asyncStream_.push(function () {
  46806. // remove pkcs#7 padding from the decrypted bytes
  46807. done(null, unpad(decrypted));
  46808. });
  46809. }
  46810. /**
  46811. * a getter for step the maximum number of bytes to process at one time
  46812. *
  46813. * @return {Number} the value of step 32000
  46814. */
  46815. /**
  46816. * @private
  46817. */
  46818. Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
  46819. return function () {
  46820. var bytes = decrypt$$1(encrypted, key, initVector);
  46821. decrypted.set(bytes, encrypted.byteOffset);
  46822. };
  46823. };
  46824. createClass(Decrypter$$1, null, [{
  46825. key: 'STEP',
  46826. get: function get$$1() {
  46827. // 4 * 8000;
  46828. return 32000;
  46829. }
  46830. }]);
  46831. return Decrypter$$1;
  46832. }();
  46833. /**
  46834. * @file bin-utils.js
  46835. */
  46836. /**
  46837. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  46838. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  46839. *
  46840. * @param {Object} message
  46841. * Object of properties and values to send to the web worker
  46842. * @return {Object}
  46843. * Modified message with TypedArray values expanded
  46844. * @function createTransferableMessage
  46845. */
  46846. var createTransferableMessage = function createTransferableMessage(message) {
  46847. var transferable = {};
  46848. Object.keys(message).forEach(function (key) {
  46849. var value = message[key];
  46850. if (ArrayBuffer.isView(value)) {
  46851. transferable[key] = {
  46852. bytes: value.buffer,
  46853. byteOffset: value.byteOffset,
  46854. byteLength: value.byteLength
  46855. };
  46856. } else {
  46857. transferable[key] = value;
  46858. }
  46859. });
  46860. return transferable;
  46861. };
  46862. /**
  46863. * Our web worker interface so that things can talk to aes-decrypter
  46864. * that will be running in a web worker. the scope is passed to this by
  46865. * webworkify.
  46866. *
  46867. * @param {Object} self
  46868. * the scope for the web worker
  46869. */
  46870. var DecrypterWorker = function DecrypterWorker(self) {
  46871. self.onmessage = function (event) {
  46872. var data = event.data;
  46873. var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
  46874. var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
  46875. var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
  46876. /* eslint-disable no-new, handle-callback-err */
  46877. new Decrypter$$1(encrypted, key, iv, function (err, bytes) {
  46878. self.postMessage(createTransferableMessage({
  46879. source: data.source,
  46880. decrypted: bytes
  46881. }), [bytes.buffer]);
  46882. });
  46883. /* eslint-enable */
  46884. };
  46885. };
  46886. var decrypterWorker = new DecrypterWorker(self);
  46887. return decrypterWorker;
  46888. }();
  46889. });
  46890. /**
  46891. * Convert the properties of an HLS track into an audioTrackKind.
  46892. *
  46893. * @private
  46894. */
  46895. var audioTrackKind_ = function audioTrackKind_(properties) {
  46896. var kind = properties.default ? 'main' : 'alternative';
  46897. if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
  46898. kind = 'main-desc';
  46899. }
  46900. return kind;
  46901. };
  46902. /**
  46903. * Pause provided segment loader and playlist loader if active
  46904. *
  46905. * @param {SegmentLoader} segmentLoader
  46906. * SegmentLoader to pause
  46907. * @param {Object} mediaType
  46908. * Active media type
  46909. * @function stopLoaders
  46910. */
  46911. var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
  46912. segmentLoader.abort();
  46913. segmentLoader.pause();
  46914. if (mediaType && mediaType.activePlaylistLoader) {
  46915. mediaType.activePlaylistLoader.pause();
  46916. mediaType.activePlaylistLoader = null;
  46917. }
  46918. };
  46919. /**
  46920. * Start loading provided segment loader and playlist loader
  46921. *
  46922. * @param {PlaylistLoader} playlistLoader
  46923. * PlaylistLoader to start loading
  46924. * @param {Object} mediaType
  46925. * Active media type
  46926. * @function startLoaders
  46927. */
  46928. var startLoaders = function startLoaders(playlistLoader, mediaType) {
  46929. // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
  46930. // playlist loader
  46931. mediaType.activePlaylistLoader = playlistLoader;
  46932. playlistLoader.load();
  46933. };
  46934. /**
  46935. * Returns a function to be called when the media group changes. It performs a
  46936. * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
  46937. * change of group is merely a rendition switch of the same content at another encoding,
  46938. * rather than a change of content, such as switching audio from English to Spanish.
  46939. *
  46940. * @param {String} type
  46941. * MediaGroup type
  46942. * @param {Object} settings
  46943. * Object containing required information for media groups
  46944. * @return {Function}
  46945. * Handler for a non-destructive resync of SegmentLoader when the active media
  46946. * group changes.
  46947. * @function onGroupChanged
  46948. */
  46949. var onGroupChanged = function onGroupChanged(type, settings) {
  46950. return function () {
  46951. var _settings$segmentLoad = settings.segmentLoaders,
  46952. segmentLoader = _settings$segmentLoad[type],
  46953. mainSegmentLoader = _settings$segmentLoad.main,
  46954. mediaType = settings.mediaTypes[type];
  46955. var activeTrack = mediaType.activeTrack();
  46956. var activeGroup = mediaType.activeGroup(activeTrack);
  46957. var previousActiveLoader = mediaType.activePlaylistLoader;
  46958. stopLoaders(segmentLoader, mediaType);
  46959. if (!activeGroup) {
  46960. // there is no group active
  46961. return;
  46962. }
  46963. if (!activeGroup.playlistLoader) {
  46964. if (previousActiveLoader) {
  46965. // The previous group had a playlist loader but the new active group does not
  46966. // this means we are switching from demuxed to muxed audio. In this case we want to
  46967. // do a destructive reset of the main segment loader and not restart the audio
  46968. // loaders.
  46969. mainSegmentLoader.resetEverything();
  46970. }
  46971. return;
  46972. } // Non-destructive resync
  46973. segmentLoader.resyncLoader();
  46974. startLoaders(activeGroup.playlistLoader, mediaType);
  46975. };
  46976. };
  46977. /**
  46978. * Returns a function to be called when the media track changes. It performs a
  46979. * destructive reset of the SegmentLoader to ensure we start loading as close to
  46980. * currentTime as possible.
  46981. *
  46982. * @param {String} type
  46983. * MediaGroup type
  46984. * @param {Object} settings
  46985. * Object containing required information for media groups
  46986. * @return {Function}
  46987. * Handler for a destructive reset of SegmentLoader when the active media
  46988. * track changes.
  46989. * @function onTrackChanged
  46990. */
  46991. var onTrackChanged = function onTrackChanged(type, settings) {
  46992. return function () {
  46993. var _settings$segmentLoad2 = settings.segmentLoaders,
  46994. segmentLoader = _settings$segmentLoad2[type],
  46995. mainSegmentLoader = _settings$segmentLoad2.main,
  46996. mediaType = settings.mediaTypes[type];
  46997. var activeTrack = mediaType.activeTrack();
  46998. var activeGroup = mediaType.activeGroup(activeTrack);
  46999. var previousActiveLoader = mediaType.activePlaylistLoader;
  47000. stopLoaders(segmentLoader, mediaType);
  47001. if (!activeGroup) {
  47002. // there is no group active so we do not want to restart loaders
  47003. return;
  47004. }
  47005. if (!activeGroup.playlistLoader) {
  47006. // when switching from demuxed audio/video to muxed audio/video (noted by no playlist
  47007. // loader for the audio group), we want to do a destructive reset of the main segment
  47008. // loader and not restart the audio loaders
  47009. mainSegmentLoader.resetEverything();
  47010. return;
  47011. }
  47012. if (previousActiveLoader === activeGroup.playlistLoader) {
  47013. // Nothing has actually changed. This can happen because track change events can fire
  47014. // multiple times for a "single" change. One for enabling the new active track, and
  47015. // one for disabling the track that was active
  47016. startLoaders(activeGroup.playlistLoader, mediaType);
  47017. return;
  47018. }
  47019. if (segmentLoader.track) {
  47020. // For WebVTT, set the new text track in the segmentloader
  47021. segmentLoader.track(activeTrack);
  47022. } // destructive reset
  47023. segmentLoader.resetEverything();
  47024. startLoaders(activeGroup.playlistLoader, mediaType);
  47025. };
  47026. };
  47027. var onError = {
  47028. /**
  47029. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  47030. * an error.
  47031. *
  47032. * @param {String} type
  47033. * MediaGroup type
  47034. * @param {Object} settings
  47035. * Object containing required information for media groups
  47036. * @return {Function}
  47037. * Error handler. Logs warning (or error if the playlist is blacklisted) to
  47038. * console and switches back to default audio track.
  47039. * @function onError.AUDIO
  47040. */
  47041. AUDIO: function AUDIO(type, settings) {
  47042. return function () {
  47043. var segmentLoader = settings.segmentLoaders[type],
  47044. mediaType = settings.mediaTypes[type],
  47045. blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
  47046. stopLoaders(segmentLoader, mediaType); // switch back to default audio track
  47047. var activeTrack = mediaType.activeTrack();
  47048. var activeGroup = mediaType.activeGroup();
  47049. var id = (activeGroup.filter(function (group) {
  47050. return group.default;
  47051. })[0] || activeGroup[0]).id;
  47052. var defaultTrack = mediaType.tracks[id];
  47053. if (activeTrack === defaultTrack) {
  47054. // Default track encountered an error. All we can do now is blacklist the current
  47055. // rendition and hope another will switch audio groups
  47056. blacklistCurrentPlaylist({
  47057. message: 'Problem encountered loading the default audio track.'
  47058. });
  47059. return;
  47060. }
  47061. videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
  47062. for (var trackId in mediaType.tracks) {
  47063. mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
  47064. }
  47065. mediaType.onTrackChanged();
  47066. };
  47067. },
  47068. /**
  47069. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  47070. * an error.
  47071. *
  47072. * @param {String} type
  47073. * MediaGroup type
  47074. * @param {Object} settings
  47075. * Object containing required information for media groups
  47076. * @return {Function}
  47077. * Error handler. Logs warning to console and disables the active subtitle track
  47078. * @function onError.SUBTITLES
  47079. */
  47080. SUBTITLES: function SUBTITLES(type, settings) {
  47081. return function () {
  47082. var segmentLoader = settings.segmentLoaders[type],
  47083. mediaType = settings.mediaTypes[type];
  47084. videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
  47085. stopLoaders(segmentLoader, mediaType);
  47086. var track = mediaType.activeTrack();
  47087. if (track) {
  47088. track.mode = 'disabled';
  47089. }
  47090. mediaType.onTrackChanged();
  47091. };
  47092. }
  47093. };
  47094. var setupListeners = {
  47095. /**
  47096. * Setup event listeners for audio playlist loader
  47097. *
  47098. * @param {String} type
  47099. * MediaGroup type
  47100. * @param {PlaylistLoader|null} playlistLoader
  47101. * PlaylistLoader to register listeners on
  47102. * @param {Object} settings
  47103. * Object containing required information for media groups
  47104. * @function setupListeners.AUDIO
  47105. */
  47106. AUDIO: function AUDIO(type, playlistLoader, settings) {
  47107. if (!playlistLoader) {
  47108. // no playlist loader means audio will be muxed with the video
  47109. return;
  47110. }
  47111. var tech = settings.tech,
  47112. requestOptions = settings.requestOptions,
  47113. segmentLoader = settings.segmentLoaders[type];
  47114. playlistLoader.on('loadedmetadata', function () {
  47115. var media = playlistLoader.media();
  47116. segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
  47117. // permits, start downloading segments
  47118. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  47119. segmentLoader.load();
  47120. }
  47121. });
  47122. playlistLoader.on('loadedplaylist', function () {
  47123. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  47124. if (!tech.paused()) {
  47125. segmentLoader.load();
  47126. }
  47127. });
  47128. playlistLoader.on('error', onError[type](type, settings));
  47129. },
  47130. /**
  47131. * Setup event listeners for subtitle playlist loader
  47132. *
  47133. * @param {String} type
  47134. * MediaGroup type
  47135. * @param {PlaylistLoader|null} playlistLoader
  47136. * PlaylistLoader to register listeners on
  47137. * @param {Object} settings
  47138. * Object containing required information for media groups
  47139. * @function setupListeners.SUBTITLES
  47140. */
  47141. SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
  47142. var tech = settings.tech,
  47143. requestOptions = settings.requestOptions,
  47144. segmentLoader = settings.segmentLoaders[type],
  47145. mediaType = settings.mediaTypes[type];
  47146. playlistLoader.on('loadedmetadata', function () {
  47147. var media = playlistLoader.media();
  47148. segmentLoader.playlist(media, requestOptions);
  47149. segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
  47150. // permits, start downloading segments
  47151. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  47152. segmentLoader.load();
  47153. }
  47154. });
  47155. playlistLoader.on('loadedplaylist', function () {
  47156. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  47157. if (!tech.paused()) {
  47158. segmentLoader.load();
  47159. }
  47160. });
  47161. playlistLoader.on('error', onError[type](type, settings));
  47162. }
  47163. };
  47164. var byGroupId = function byGroupId(type, groupId) {
  47165. return function (playlist) {
  47166. return playlist.attributes[type] === groupId;
  47167. };
  47168. };
  47169. var byResolvedUri = function byResolvedUri(resolvedUri) {
  47170. return function (playlist) {
  47171. return playlist.resolvedUri === resolvedUri;
  47172. };
  47173. };
  47174. var initialize = {
  47175. /**
  47176. * Setup PlaylistLoaders and AudioTracks for the audio groups
  47177. *
  47178. * @param {String} type
  47179. * MediaGroup type
  47180. * @param {Object} settings
  47181. * Object containing required information for media groups
  47182. * @function initialize.AUDIO
  47183. */
  47184. 'AUDIO': function AUDIO(type, settings) {
  47185. var hls = settings.hls,
  47186. sourceType = settings.sourceType,
  47187. segmentLoader = settings.segmentLoaders[type],
  47188. requestOptions = settings.requestOptions,
  47189. _settings$master = settings.master,
  47190. mediaGroups = _settings$master.mediaGroups,
  47191. playlists = _settings$master.playlists,
  47192. _settings$mediaTypes$ = settings.mediaTypes[type],
  47193. groups = _settings$mediaTypes$.groups,
  47194. tracks = _settings$mediaTypes$.tracks,
  47195. masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none
  47196. if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
  47197. mediaGroups[type] = {
  47198. main: {
  47199. default: {
  47200. default: true
  47201. }
  47202. }
  47203. };
  47204. }
  47205. for (var groupId in mediaGroups[type]) {
  47206. if (!groups[groupId]) {
  47207. groups[groupId] = [];
  47208. } // List of playlists that have an AUDIO attribute value matching the current
  47209. // group ID
  47210. var groupPlaylists = playlists.filter(byGroupId(type, groupId));
  47211. for (var variantLabel in mediaGroups[type][groupId]) {
  47212. var properties = mediaGroups[type][groupId][variantLabel]; // List of playlists for the current group ID that have a matching uri with
  47213. // this alternate audio variant
  47214. var matchingPlaylists = groupPlaylists.filter(byResolvedUri(properties.resolvedUri));
  47215. if (matchingPlaylists.length) {
  47216. // If there is a playlist that has the same uri as this audio variant, assume
  47217. // that the playlist is audio only. We delete the resolvedUri property here
  47218. // to prevent a playlist loader from being created so that we don't have
  47219. // both the main and audio segment loaders loading the same audio segments
  47220. // from the same playlist.
  47221. delete properties.resolvedUri;
  47222. }
  47223. var playlistLoader = void 0;
  47224. if (properties.resolvedUri) {
  47225. playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
  47226. } else if (properties.playlists && sourceType === 'dash') {
  47227. playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
  47228. } else {
  47229. // no resolvedUri means the audio is muxed with the video when using this
  47230. // audio track
  47231. playlistLoader = null;
  47232. }
  47233. properties = videojs$1.mergeOptions({
  47234. id: variantLabel,
  47235. playlistLoader: playlistLoader
  47236. }, properties);
  47237. setupListeners[type](type, properties.playlistLoader, settings);
  47238. groups[groupId].push(properties);
  47239. if (typeof tracks[variantLabel] === 'undefined') {
  47240. var track = new videojs$1.AudioTrack({
  47241. id: variantLabel,
  47242. kind: audioTrackKind_(properties),
  47243. enabled: false,
  47244. language: properties.language,
  47245. default: properties.default,
  47246. label: variantLabel
  47247. });
  47248. tracks[variantLabel] = track;
  47249. }
  47250. }
  47251. } // setup single error event handler for the segment loader
  47252. segmentLoader.on('error', onError[type](type, settings));
  47253. },
  47254. /**
  47255. * Setup PlaylistLoaders and TextTracks for the subtitle groups
  47256. *
  47257. * @param {String} type
  47258. * MediaGroup type
  47259. * @param {Object} settings
  47260. * Object containing required information for media groups
  47261. * @function initialize.SUBTITLES
  47262. */
  47263. 'SUBTITLES': function SUBTITLES(type, settings) {
  47264. var tech = settings.tech,
  47265. hls = settings.hls,
  47266. sourceType = settings.sourceType,
  47267. segmentLoader = settings.segmentLoaders[type],
  47268. requestOptions = settings.requestOptions,
  47269. mediaGroups = settings.master.mediaGroups,
  47270. _settings$mediaTypes$2 = settings.mediaTypes[type],
  47271. groups = _settings$mediaTypes$2.groups,
  47272. tracks = _settings$mediaTypes$2.tracks,
  47273. masterPlaylistLoader = settings.masterPlaylistLoader;
  47274. for (var groupId in mediaGroups[type]) {
  47275. if (!groups[groupId]) {
  47276. groups[groupId] = [];
  47277. }
  47278. for (var variantLabel in mediaGroups[type][groupId]) {
  47279. if (mediaGroups[type][groupId][variantLabel].forced) {
  47280. // Subtitle playlists with the forced attribute are not selectable in Safari.
  47281. // According to Apple's HLS Authoring Specification:
  47282. // If content has forced subtitles and regular subtitles in a given language,
  47283. // the regular subtitles track in that language MUST contain both the forced
  47284. // subtitles and the regular subtitles for that language.
  47285. // Because of this requirement and that Safari does not add forced subtitles,
  47286. // forced subtitles are skipped here to maintain consistent experience across
  47287. // all platforms
  47288. continue;
  47289. }
  47290. var properties = mediaGroups[type][groupId][variantLabel];
  47291. var playlistLoader = void 0;
  47292. if (sourceType === 'hls') {
  47293. playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
  47294. } else if (sourceType === 'dash') {
  47295. playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
  47296. }
  47297. properties = videojs$1.mergeOptions({
  47298. id: variantLabel,
  47299. playlistLoader: playlistLoader
  47300. }, properties);
  47301. setupListeners[type](type, properties.playlistLoader, settings);
  47302. groups[groupId].push(properties);
  47303. if (typeof tracks[variantLabel] === 'undefined') {
  47304. var track = tech.addRemoteTextTrack({
  47305. id: variantLabel,
  47306. kind: 'subtitles',
  47307. default: properties.default && properties.autoselect,
  47308. language: properties.language,
  47309. label: variantLabel
  47310. }, false).track;
  47311. tracks[variantLabel] = track;
  47312. }
  47313. }
  47314. } // setup single error event handler for the segment loader
  47315. segmentLoader.on('error', onError[type](type, settings));
  47316. },
  47317. /**
  47318. * Setup TextTracks for the closed-caption groups
  47319. *
  47320. * @param {String} type
  47321. * MediaGroup type
  47322. * @param {Object} settings
  47323. * Object containing required information for media groups
  47324. * @function initialize['CLOSED-CAPTIONS']
  47325. */
  47326. 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
  47327. var tech = settings.tech,
  47328. mediaGroups = settings.master.mediaGroups,
  47329. _settings$mediaTypes$3 = settings.mediaTypes[type],
  47330. groups = _settings$mediaTypes$3.groups,
  47331. tracks = _settings$mediaTypes$3.tracks;
  47332. for (var groupId in mediaGroups[type]) {
  47333. if (!groups[groupId]) {
  47334. groups[groupId] = [];
  47335. }
  47336. for (var variantLabel in mediaGroups[type][groupId]) {
  47337. var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that
  47338. // doesn't use a CCx INSTREAM-ID
  47339. if (!properties.instreamId.match(/CC\d/)) {
  47340. continue;
  47341. } // No PlaylistLoader is required for Closed-Captions because the captions are
  47342. // embedded within the video stream
  47343. groups[groupId].push(videojs$1.mergeOptions({
  47344. id: variantLabel
  47345. }, properties));
  47346. if (typeof tracks[variantLabel] === 'undefined') {
  47347. var track = tech.addRemoteTextTrack({
  47348. id: properties.instreamId,
  47349. kind: 'captions',
  47350. default: properties.default && properties.autoselect,
  47351. language: properties.language,
  47352. label: variantLabel
  47353. }, false).track;
  47354. tracks[variantLabel] = track;
  47355. }
  47356. }
  47357. }
  47358. }
  47359. };
  47360. /**
  47361. * Returns a function used to get the active group of the provided type
  47362. *
  47363. * @param {String} type
  47364. * MediaGroup type
  47365. * @param {Object} settings
  47366. * Object containing required information for media groups
  47367. * @return {Function}
  47368. * Function that returns the active media group for the provided type. Takes an
  47369. * optional parameter {TextTrack} track. If no track is provided, a list of all
  47370. * variants in the group, otherwise the variant corresponding to the provided
  47371. * track is returned.
  47372. * @function activeGroup
  47373. */
  47374. var activeGroup = function activeGroup(type, settings) {
  47375. return function (track) {
  47376. var masterPlaylistLoader = settings.masterPlaylistLoader,
  47377. groups = settings.mediaTypes[type].groups;
  47378. var media = masterPlaylistLoader.media();
  47379. if (!media) {
  47380. return null;
  47381. }
  47382. var variants = null;
  47383. if (media.attributes[type]) {
  47384. variants = groups[media.attributes[type]];
  47385. }
  47386. variants = variants || groups.main;
  47387. if (typeof track === 'undefined') {
  47388. return variants;
  47389. }
  47390. if (track === null) {
  47391. // An active track was specified so a corresponding group is expected. track === null
  47392. // means no track is currently active so there is no corresponding group
  47393. return null;
  47394. }
  47395. return variants.filter(function (props) {
  47396. return props.id === track.id;
  47397. })[0] || null;
  47398. };
  47399. };
  47400. var activeTrack = {
  47401. /**
  47402. * Returns a function used to get the active track of type provided
  47403. *
  47404. * @param {String} type
  47405. * MediaGroup type
  47406. * @param {Object} settings
  47407. * Object containing required information for media groups
  47408. * @return {Function}
  47409. * Function that returns the active media track for the provided type. Returns
  47410. * null if no track is active
  47411. * @function activeTrack.AUDIO
  47412. */
  47413. AUDIO: function AUDIO(type, settings) {
  47414. return function () {
  47415. var tracks = settings.mediaTypes[type].tracks;
  47416. for (var id in tracks) {
  47417. if (tracks[id].enabled) {
  47418. return tracks[id];
  47419. }
  47420. }
  47421. return null;
  47422. };
  47423. },
  47424. /**
  47425. * Returns a function used to get the active track of type provided
  47426. *
  47427. * @param {String} type
  47428. * MediaGroup type
  47429. * @param {Object} settings
  47430. * Object containing required information for media groups
  47431. * @return {Function}
  47432. * Function that returns the active media track for the provided type. Returns
  47433. * null if no track is active
  47434. * @function activeTrack.SUBTITLES
  47435. */
  47436. SUBTITLES: function SUBTITLES(type, settings) {
  47437. return function () {
  47438. var tracks = settings.mediaTypes[type].tracks;
  47439. for (var id in tracks) {
  47440. if (tracks[id].mode === 'showing') {
  47441. return tracks[id];
  47442. }
  47443. }
  47444. return null;
  47445. };
  47446. }
  47447. };
  47448. /**
  47449. * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
  47450. * Closed-Captions) specified in the master manifest.
  47451. *
  47452. * @param {Object} settings
  47453. * Object containing required information for setting up the media groups
  47454. * @param {SegmentLoader} settings.segmentLoaders.AUDIO
  47455. * Audio segment loader
  47456. * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES
  47457. * Subtitle segment loader
  47458. * @param {SegmentLoader} settings.segmentLoaders.main
  47459. * Main segment loader
  47460. * @param {Tech} settings.tech
  47461. * The tech of the player
  47462. * @param {Object} settings.requestOptions
  47463. * XHR request options used by the segment loaders
  47464. * @param {PlaylistLoader} settings.masterPlaylistLoader
  47465. * PlaylistLoader for the master source
  47466. * @param {HlsHandler} settings.hls
  47467. * HLS SourceHandler
  47468. * @param {Object} settings.master
  47469. * The parsed master manifest
  47470. * @param {Object} settings.mediaTypes
  47471. * Object to store the loaders, tracks, and utility methods for each media type
  47472. * @param {Function} settings.blacklistCurrentPlaylist
  47473. * Blacklists the current rendition and forces a rendition switch.
  47474. * @function setupMediaGroups
  47475. */
  47476. var setupMediaGroups = function setupMediaGroups(settings) {
  47477. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  47478. initialize[type](type, settings);
  47479. });
  47480. var mediaTypes = settings.mediaTypes,
  47481. masterPlaylistLoader = settings.masterPlaylistLoader,
  47482. tech = settings.tech,
  47483. hls = settings.hls; // setup active group and track getters and change event handlers
  47484. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  47485. mediaTypes[type].activeGroup = activeGroup(type, settings);
  47486. mediaTypes[type].activeTrack = activeTrack[type](type, settings);
  47487. mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
  47488. mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
  47489. }); // DO NOT enable the default subtitle or caption track.
  47490. // DO enable the default audio track
  47491. var audioGroup = mediaTypes.AUDIO.activeGroup();
  47492. var groupId = (audioGroup.filter(function (group) {
  47493. return group.default;
  47494. })[0] || audioGroup[0]).id;
  47495. mediaTypes.AUDIO.tracks[groupId].enabled = true;
  47496. mediaTypes.AUDIO.onTrackChanged();
  47497. masterPlaylistLoader.on('mediachange', function () {
  47498. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  47499. return mediaTypes[type].onGroupChanged();
  47500. });
  47501. }); // custom audio track change event handler for usage event
  47502. var onAudioTrackChanged = function onAudioTrackChanged() {
  47503. mediaTypes.AUDIO.onTrackChanged();
  47504. tech.trigger({
  47505. type: 'usage',
  47506. name: 'hls-audio-change'
  47507. });
  47508. };
  47509. tech.audioTracks().addEventListener('change', onAudioTrackChanged);
  47510. tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  47511. hls.on('dispose', function () {
  47512. tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
  47513. tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  47514. }); // clear existing audio tracks and add the ones we just created
  47515. tech.clearTracks('audio');
  47516. for (var id in mediaTypes.AUDIO.tracks) {
  47517. tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
  47518. }
  47519. };
  47520. /**
  47521. * Creates skeleton object used to store the loaders, tracks, and utility methods for each
  47522. * media type
  47523. *
  47524. * @return {Object}
  47525. * Object to store the loaders, tracks, and utility methods for each media type
  47526. * @function createMediaTypes
  47527. */
  47528. var createMediaTypes = function createMediaTypes() {
  47529. var mediaTypes = {};
  47530. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  47531. mediaTypes[type] = {
  47532. groups: {},
  47533. tracks: {},
  47534. activePlaylistLoader: null,
  47535. activeGroup: noop$1,
  47536. activeTrack: noop$1,
  47537. onGroupChanged: noop$1,
  47538. onTrackChanged: noop$1
  47539. };
  47540. });
  47541. return mediaTypes;
  47542. };
  47543. /**
  47544. * @file master-playlist-controller.js
  47545. */
  47546. var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
  47547. var Hls = void 0; // SegmentLoader stats that need to have each loader's
  47548. // values summed to calculate the final value
  47549. var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred'];
  47550. var sumLoaderStat = function sumLoaderStat(stat) {
  47551. return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
  47552. };
  47553. /**
  47554. * the master playlist controller controller all interactons
  47555. * between playlists and segmentloaders. At this time this mainly
  47556. * involves a master playlist and a series of audio playlists
  47557. * if they are available
  47558. *
  47559. * @class MasterPlaylistController
  47560. * @extends videojs.EventTarget
  47561. */
  47562. var MasterPlaylistController = function (_videojs$EventTarget) {
  47563. inherits$1(MasterPlaylistController, _videojs$EventTarget);
  47564. function MasterPlaylistController(options) {
  47565. classCallCheck$1(this, MasterPlaylistController);
  47566. var _this = possibleConstructorReturn$1(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this));
  47567. var url = options.url,
  47568. handleManifestRedirects = options.handleManifestRedirects,
  47569. withCredentials = options.withCredentials,
  47570. tech = options.tech,
  47571. bandwidth = options.bandwidth,
  47572. externHls = options.externHls,
  47573. useCueTags = options.useCueTags,
  47574. blacklistDuration = options.blacklistDuration,
  47575. enableLowInitialPlaylist = options.enableLowInitialPlaylist,
  47576. sourceType = options.sourceType,
  47577. seekTo = options.seekTo,
  47578. cacheEncryptionKeys = options.cacheEncryptionKeys;
  47579. if (!url) {
  47580. throw new Error('A non-empty playlist URL is required');
  47581. }
  47582. Hls = externHls;
  47583. _this.withCredentials = withCredentials;
  47584. _this.tech_ = tech;
  47585. _this.hls_ = tech.hls;
  47586. _this.seekTo_ = seekTo;
  47587. _this.sourceType_ = sourceType;
  47588. _this.useCueTags_ = useCueTags;
  47589. _this.blacklistDuration = blacklistDuration;
  47590. _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
  47591. if (_this.useCueTags_) {
  47592. _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
  47593. _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
  47594. }
  47595. _this.requestOptions_ = {
  47596. withCredentials: withCredentials,
  47597. handleManifestRedirects: handleManifestRedirects,
  47598. timeout: null
  47599. };
  47600. _this.mediaTypes_ = createMediaTypes();
  47601. _this.mediaSource = new videojs$1.MediaSource(); // load the media source into the player
  47602. _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this));
  47603. _this.seekable_ = videojs$1.createTimeRanges();
  47604. _this.hasPlayed_ = function () {
  47605. return false;
  47606. };
  47607. _this.syncController_ = new SyncController(options);
  47608. _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
  47609. kind: 'metadata',
  47610. label: 'segment-metadata'
  47611. }, false).track;
  47612. _this.decrypter_ = new Decrypter$1();
  47613. _this.inbandTextTracks_ = {};
  47614. var segmentLoaderSettings = {
  47615. hls: _this.hls_,
  47616. mediaSource: _this.mediaSource,
  47617. currentTime: _this.tech_.currentTime.bind(_this.tech_),
  47618. seekable: function seekable$$1() {
  47619. return _this.seekable();
  47620. },
  47621. seeking: function seeking() {
  47622. return _this.tech_.seeking();
  47623. },
  47624. duration: function duration$$1() {
  47625. return _this.mediaSource.duration;
  47626. },
  47627. hasPlayed: function hasPlayed() {
  47628. return _this.hasPlayed_();
  47629. },
  47630. goalBufferLength: function goalBufferLength() {
  47631. return _this.goalBufferLength();
  47632. },
  47633. bandwidth: bandwidth,
  47634. syncController: _this.syncController_,
  47635. decrypter: _this.decrypter_,
  47636. sourceType: _this.sourceType_,
  47637. inbandTextTracks: _this.inbandTextTracks_,
  47638. cacheEncryptionKeys: cacheEncryptionKeys
  47639. };
  47640. _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.requestOptions_) : new PlaylistLoader(url, _this.hls_, _this.requestOptions_);
  47641. _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders
  47642. // combined audio/video or just video when alternate audio track is selected
  47643. _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  47644. segmentMetadataTrack: _this.segmentMetadataTrack_,
  47645. loaderType: 'main'
  47646. }), options); // alternate audio track
  47647. _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  47648. loaderType: 'audio'
  47649. }), options);
  47650. _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  47651. loaderType: 'vtt'
  47652. }), options);
  47653. _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters
  47654. loaderStats.forEach(function (stat) {
  47655. _this[stat + '_'] = sumLoaderStat.bind(_this, stat);
  47656. });
  47657. _this.logger_ = logger('MPC');
  47658. _this.masterPlaylistLoader_.load();
  47659. return _this;
  47660. }
  47661. /**
  47662. * Register event handlers on the master playlist loader. A helper
  47663. * function for construction time.
  47664. *
  47665. * @private
  47666. */
  47667. createClass$1(MasterPlaylistController, [{
  47668. key: 'setupMasterPlaylistLoaderListeners_',
  47669. value: function setupMasterPlaylistLoaderListeners_() {
  47670. var _this2 = this;
  47671. this.masterPlaylistLoader_.on('loadedmetadata', function () {
  47672. var media = _this2.masterPlaylistLoader_.media();
  47673. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  47674. // timeout the request.
  47675. if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  47676. _this2.requestOptions_.timeout = 0;
  47677. } else {
  47678. _this2.requestOptions_.timeout = requestTimeout;
  47679. } // if this isn't a live video and preload permits, start
  47680. // downloading segments
  47681. if (media.endList && _this2.tech_.preload() !== 'none') {
  47682. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  47683. _this2.mainSegmentLoader_.load();
  47684. }
  47685. setupMediaGroups({
  47686. sourceType: _this2.sourceType_,
  47687. segmentLoaders: {
  47688. AUDIO: _this2.audioSegmentLoader_,
  47689. SUBTITLES: _this2.subtitleSegmentLoader_,
  47690. main: _this2.mainSegmentLoader_
  47691. },
  47692. tech: _this2.tech_,
  47693. requestOptions: _this2.requestOptions_,
  47694. masterPlaylistLoader: _this2.masterPlaylistLoader_,
  47695. hls: _this2.hls_,
  47696. master: _this2.master(),
  47697. mediaTypes: _this2.mediaTypes_,
  47698. blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2)
  47699. });
  47700. _this2.triggerPresenceUsage_(_this2.master(), media);
  47701. try {
  47702. _this2.setupSourceBuffers_();
  47703. } catch (e) {
  47704. videojs$1.log.warn('Failed to create SourceBuffers', e);
  47705. return _this2.mediaSource.endOfStream('decode');
  47706. }
  47707. _this2.setupFirstPlay();
  47708. if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
  47709. _this2.trigger('selectedinitialmedia');
  47710. } else {
  47711. // We must wait for the active audio playlist loader to
  47712. // finish setting up before triggering this event so the
  47713. // representations API and EME setup is correct
  47714. _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () {
  47715. _this2.trigger('selectedinitialmedia');
  47716. });
  47717. }
  47718. });
  47719. this.masterPlaylistLoader_.on('loadedplaylist', function () {
  47720. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  47721. if (!updatedPlaylist) {
  47722. // blacklist any variants that are not supported by the browser before selecting
  47723. // an initial media as the playlist selectors do not consider browser support
  47724. _this2.excludeUnsupportedVariants_();
  47725. var selectedMedia = void 0;
  47726. if (_this2.enableLowInitialPlaylist) {
  47727. selectedMedia = _this2.selectInitialPlaylist();
  47728. }
  47729. if (!selectedMedia) {
  47730. selectedMedia = _this2.selectPlaylist();
  47731. }
  47732. _this2.initialMedia_ = selectedMedia;
  47733. _this2.masterPlaylistLoader_.media(_this2.initialMedia_);
  47734. return;
  47735. }
  47736. if (_this2.useCueTags_) {
  47737. _this2.updateAdCues_(updatedPlaylist);
  47738. } // TODO: Create a new event on the PlaylistLoader that signals
  47739. // that the segments have changed in some way and use that to
  47740. // update the SegmentLoader instead of doing it twice here and
  47741. // on `mediachange`
  47742. _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_);
  47743. _this2.updateDuration(); // If the player isn't paused, ensure that the segment loader is running,
  47744. // as it is possible that it was temporarily stopped while waiting for
  47745. // a playlist (e.g., in case the playlist errored and we re-requested it).
  47746. if (!_this2.tech_.paused()) {
  47747. _this2.mainSegmentLoader_.load();
  47748. if (_this2.audioSegmentLoader_) {
  47749. _this2.audioSegmentLoader_.load();
  47750. }
  47751. }
  47752. if (!updatedPlaylist.endList) {
  47753. var addSeekableRange = function addSeekableRange() {
  47754. var seekable$$1 = _this2.seekable();
  47755. if (seekable$$1.length !== 0) {
  47756. _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0));
  47757. }
  47758. };
  47759. if (_this2.duration() !== Infinity) {
  47760. var onDurationchange = function onDurationchange() {
  47761. if (_this2.duration() === Infinity) {
  47762. addSeekableRange();
  47763. } else {
  47764. _this2.tech_.one('durationchange', onDurationchange);
  47765. }
  47766. };
  47767. _this2.tech_.one('durationchange', onDurationchange);
  47768. } else {
  47769. addSeekableRange();
  47770. }
  47771. }
  47772. });
  47773. this.masterPlaylistLoader_.on('error', function () {
  47774. _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error);
  47775. });
  47776. this.masterPlaylistLoader_.on('mediachanging', function () {
  47777. _this2.mainSegmentLoader_.abort();
  47778. _this2.mainSegmentLoader_.pause();
  47779. });
  47780. this.masterPlaylistLoader_.on('mediachange', function () {
  47781. var media = _this2.masterPlaylistLoader_.media();
  47782. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  47783. // timeout the request.
  47784. if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  47785. _this2.requestOptions_.timeout = 0;
  47786. } else {
  47787. _this2.requestOptions_.timeout = requestTimeout;
  47788. } // TODO: Create a new event on the PlaylistLoader that signals
  47789. // that the segments have changed in some way and use that to
  47790. // update the SegmentLoader instead of doing it twice here and
  47791. // on `loadedplaylist`
  47792. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  47793. _this2.mainSegmentLoader_.load();
  47794. _this2.tech_.trigger({
  47795. type: 'mediachange',
  47796. bubbles: true
  47797. });
  47798. });
  47799. this.masterPlaylistLoader_.on('playlistunchanged', function () {
  47800. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  47801. var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist);
  47802. if (playlistOutdated) {
  47803. // Playlist has stopped updating and we're stuck at its end. Try to
  47804. // blacklist it and switch to another playlist in the hope that that
  47805. // one is updating (and give the player a chance to re-adjust to the
  47806. // safe live point).
  47807. _this2.blacklistCurrentPlaylist({
  47808. message: 'Playlist no longer updating.'
  47809. }); // useful for monitoring QoS
  47810. _this2.tech_.trigger('playliststuck');
  47811. }
  47812. });
  47813. this.masterPlaylistLoader_.on('renditiondisabled', function () {
  47814. _this2.tech_.trigger({
  47815. type: 'usage',
  47816. name: 'hls-rendition-disabled'
  47817. });
  47818. });
  47819. this.masterPlaylistLoader_.on('renditionenabled', function () {
  47820. _this2.tech_.trigger({
  47821. type: 'usage',
  47822. name: 'hls-rendition-enabled'
  47823. });
  47824. });
  47825. }
  47826. /**
  47827. * A helper function for triggerring presence usage events once per source
  47828. *
  47829. * @private
  47830. */
  47831. }, {
  47832. key: 'triggerPresenceUsage_',
  47833. value: function triggerPresenceUsage_(master, media) {
  47834. var mediaGroups = master.mediaGroups || {};
  47835. var defaultDemuxed = true;
  47836. var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
  47837. for (var mediaGroup in mediaGroups.AUDIO) {
  47838. for (var label in mediaGroups.AUDIO[mediaGroup]) {
  47839. var properties = mediaGroups.AUDIO[mediaGroup][label];
  47840. if (!properties.uri) {
  47841. defaultDemuxed = false;
  47842. }
  47843. }
  47844. }
  47845. if (defaultDemuxed) {
  47846. this.tech_.trigger({
  47847. type: 'usage',
  47848. name: 'hls-demuxed'
  47849. });
  47850. }
  47851. if (Object.keys(mediaGroups.SUBTITLES).length) {
  47852. this.tech_.trigger({
  47853. type: 'usage',
  47854. name: 'hls-webvtt'
  47855. });
  47856. }
  47857. if (Hls.Playlist.isAes(media)) {
  47858. this.tech_.trigger({
  47859. type: 'usage',
  47860. name: 'hls-aes'
  47861. });
  47862. }
  47863. if (Hls.Playlist.isFmp4(media)) {
  47864. this.tech_.trigger({
  47865. type: 'usage',
  47866. name: 'hls-fmp4'
  47867. });
  47868. }
  47869. if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
  47870. this.tech_.trigger({
  47871. type: 'usage',
  47872. name: 'hls-alternate-audio'
  47873. });
  47874. }
  47875. if (this.useCueTags_) {
  47876. this.tech_.trigger({
  47877. type: 'usage',
  47878. name: 'hls-playlist-cue-tags'
  47879. });
  47880. }
  47881. }
  47882. /**
  47883. * Register event handlers on the segment loaders. A helper function
  47884. * for construction time.
  47885. *
  47886. * @private
  47887. */
  47888. }, {
  47889. key: 'setupSegmentLoaderListeners_',
  47890. value: function setupSegmentLoaderListeners_() {
  47891. var _this3 = this;
  47892. this.mainSegmentLoader_.on('bandwidthupdate', function () {
  47893. var nextPlaylist = _this3.selectPlaylist();
  47894. var currentPlaylist = _this3.masterPlaylistLoader_.media();
  47895. var buffered = _this3.tech_.buffered();
  47896. var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0;
  47897. var bufferLowWaterLine = _this3.bufferLowWaterLine(); // If the playlist is live, then we want to not take low water line into account.
  47898. // This is because in LIVE, the player plays 3 segments from the end of the
  47899. // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
  47900. // in those segments, a viewer will never experience a rendition upswitch.
  47901. if (!currentPlaylist.endList || // For the same reason as LIVE, we ignore the low water line when the VOD
  47902. // duration is below the max potential low water line
  47903. _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE || // we want to switch down to lower resolutions quickly to continue playback, but
  47904. nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH || // ensure we have some buffer before we switch up to prevent us running out of
  47905. // buffer while loading a higher rendition.
  47906. forwardBuffer >= bufferLowWaterLine) {
  47907. _this3.masterPlaylistLoader_.media(nextPlaylist);
  47908. }
  47909. _this3.tech_.trigger('bandwidthupdate');
  47910. });
  47911. this.mainSegmentLoader_.on('progress', function () {
  47912. _this3.trigger('progress');
  47913. });
  47914. this.mainSegmentLoader_.on('error', function () {
  47915. _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error());
  47916. });
  47917. this.mainSegmentLoader_.on('syncinfoupdate', function () {
  47918. _this3.onSyncInfoUpdate_();
  47919. });
  47920. this.mainSegmentLoader_.on('timestampoffset', function () {
  47921. _this3.tech_.trigger({
  47922. type: 'usage',
  47923. name: 'hls-timestamp-offset'
  47924. });
  47925. });
  47926. this.audioSegmentLoader_.on('syncinfoupdate', function () {
  47927. _this3.onSyncInfoUpdate_();
  47928. });
  47929. this.mainSegmentLoader_.on('ended', function () {
  47930. _this3.onEndOfStream();
  47931. });
  47932. this.mainSegmentLoader_.on('earlyabort', function () {
  47933. _this3.blacklistCurrentPlaylist({
  47934. message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
  47935. }, ABORT_EARLY_BLACKLIST_SECONDS);
  47936. });
  47937. this.mainSegmentLoader_.on('reseteverything', function () {
  47938. // If playing an MTS stream, a videojs.MediaSource is listening for
  47939. // hls-reset to reset caption parsing state in the transmuxer
  47940. _this3.tech_.trigger('hls-reset');
  47941. });
  47942. this.mainSegmentLoader_.on('segmenttimemapping', function (event) {
  47943. // If playing an MTS stream in html, a videojs.MediaSource is listening for
  47944. // hls-segment-time-mapping update its internal mapping of stream to display time
  47945. _this3.tech_.trigger({
  47946. type: 'hls-segment-time-mapping',
  47947. mapping: event.mapping
  47948. });
  47949. });
  47950. this.audioSegmentLoader_.on('ended', function () {
  47951. _this3.onEndOfStream();
  47952. });
  47953. }
  47954. }, {
  47955. key: 'mediaSecondsLoaded_',
  47956. value: function mediaSecondsLoaded_() {
  47957. return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
  47958. }
  47959. /**
  47960. * Call load on our SegmentLoaders
  47961. */
  47962. }, {
  47963. key: 'load',
  47964. value: function load() {
  47965. this.mainSegmentLoader_.load();
  47966. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  47967. this.audioSegmentLoader_.load();
  47968. }
  47969. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  47970. this.subtitleSegmentLoader_.load();
  47971. }
  47972. }
  47973. /**
  47974. * Re-tune playback quality level for the current player
  47975. * conditions without performing destructive actions, like
  47976. * removing already buffered content
  47977. *
  47978. * @private
  47979. */
  47980. }, {
  47981. key: 'smoothQualityChange_',
  47982. value: function smoothQualityChange_() {
  47983. var media = this.selectPlaylist();
  47984. if (media !== this.masterPlaylistLoader_.media()) {
  47985. this.masterPlaylistLoader_.media(media);
  47986. this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes
  47987. }
  47988. }
  47989. /**
  47990. * Re-tune playback quality level for the current player
  47991. * conditions. This method will perform destructive actions like removing
  47992. * already buffered content in order to readjust the currently active
  47993. * playlist quickly. This is good for manual quality changes
  47994. *
  47995. * @private
  47996. */
  47997. }, {
  47998. key: 'fastQualityChange_',
  47999. value: function fastQualityChange_() {
  48000. var _this4 = this;
  48001. var media = this.selectPlaylist();
  48002. if (media === this.masterPlaylistLoader_.media()) {
  48003. return;
  48004. }
  48005. this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give
  48006. // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
  48007. // ahead is roughly the minimum that will accomplish this across a variety of content
  48008. // in IE and Edge, but seeking in place is sufficient on all other browsers)
  48009. // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
  48010. // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
  48011. this.mainSegmentLoader_.resetEverything(function () {
  48012. // Since this is not a typical seek, we avoid the seekTo method which can cause segments
  48013. // from the previously enabled rendition to load before the new playlist has finished loading
  48014. if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) {
  48015. _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04);
  48016. } else {
  48017. _this4.tech_.setCurrentTime(_this4.tech_.currentTime());
  48018. }
  48019. }); // don't need to reset audio as it is reset when media changes
  48020. }
  48021. /**
  48022. * Begin playback.
  48023. */
  48024. }, {
  48025. key: 'play',
  48026. value: function play() {
  48027. if (this.setupFirstPlay()) {
  48028. return;
  48029. }
  48030. if (this.tech_.ended()) {
  48031. this.seekTo_(0);
  48032. }
  48033. if (this.hasPlayed_()) {
  48034. this.load();
  48035. }
  48036. var seekable$$1 = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
  48037. // seek forward to the live point
  48038. if (this.tech_.duration() === Infinity) {
  48039. if (this.tech_.currentTime() < seekable$$1.start(0)) {
  48040. return this.seekTo_(seekable$$1.end(seekable$$1.length - 1));
  48041. }
  48042. }
  48043. }
  48044. /**
  48045. * Seek to the latest media position if this is a live video and the
  48046. * player and video are loaded and initialized.
  48047. */
  48048. }, {
  48049. key: 'setupFirstPlay',
  48050. value: function setupFirstPlay() {
  48051. var _this5 = this;
  48052. var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
  48053. // If 1) there is no active media
  48054. // 2) the player is paused
  48055. // 3) the first play has already been setup
  48056. // then exit early
  48057. if (!media || this.tech_.paused() || this.hasPlayed_()) {
  48058. return false;
  48059. } // when the video is a live stream
  48060. if (!media.endList) {
  48061. var seekable$$1 = this.seekable();
  48062. if (!seekable$$1.length) {
  48063. // without a seekable range, the player cannot seek to begin buffering at the live
  48064. // point
  48065. return false;
  48066. }
  48067. if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) {
  48068. // IE11 throws an InvalidStateError if you try to set currentTime while the
  48069. // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
  48070. this.tech_.one('loadedmetadata', function () {
  48071. _this5.trigger('firstplay');
  48072. _this5.seekTo_(seekable$$1.end(0));
  48073. _this5.hasPlayed_ = function () {
  48074. return true;
  48075. };
  48076. });
  48077. return false;
  48078. } // trigger firstplay to inform the source handler to ignore the next seek event
  48079. this.trigger('firstplay'); // seek to the live point
  48080. this.seekTo_(seekable$$1.end(0));
  48081. }
  48082. this.hasPlayed_ = function () {
  48083. return true;
  48084. }; // we can begin loading now that everything is ready
  48085. this.load();
  48086. return true;
  48087. }
  48088. /**
  48089. * handle the sourceopen event on the MediaSource
  48090. *
  48091. * @private
  48092. */
  48093. }, {
  48094. key: 'handleSourceOpen_',
  48095. value: function handleSourceOpen_() {
  48096. // Only attempt to create the source buffer if none already exist.
  48097. // handleSourceOpen is also called when we are "re-opening" a source buffer
  48098. // after `endOfStream` has been called (in response to a seek for instance)
  48099. try {
  48100. this.setupSourceBuffers_();
  48101. } catch (e) {
  48102. videojs$1.log.warn('Failed to create Source Buffers', e);
  48103. return this.mediaSource.endOfStream('decode');
  48104. } // if autoplay is enabled, begin playback. This is duplicative of
  48105. // code in video.js but is required because play() must be invoked
  48106. // *after* the media source has opened.
  48107. if (this.tech_.autoplay()) {
  48108. var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
  48109. // on browsers which return a promise
  48110. if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
  48111. playPromise.then(null, function (e) {});
  48112. }
  48113. }
  48114. this.trigger('sourceopen');
  48115. }
  48116. /**
  48117. * Calls endOfStream on the media source when all active stream types have called
  48118. * endOfStream
  48119. *
  48120. * @param {string} streamType
  48121. * Stream type of the segment loader that called endOfStream
  48122. * @private
  48123. */
  48124. }, {
  48125. key: 'onEndOfStream',
  48126. value: function onEndOfStream() {
  48127. var isEndOfStream = this.mainSegmentLoader_.ended_;
  48128. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  48129. // if the audio playlist loader exists, then alternate audio is active
  48130. if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) {
  48131. // if we do not know if the main segment loader contains video yet or if we
  48132. // definitively know the main segment loader contains video, then we need to wait
  48133. // for both main and audio segment loaders to call endOfStream
  48134. isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
  48135. } else {
  48136. // otherwise just rely on the audio loader
  48137. isEndOfStream = this.audioSegmentLoader_.ended_;
  48138. }
  48139. }
  48140. if (!isEndOfStream) {
  48141. return;
  48142. }
  48143. this.logger_('calling mediaSource.endOfStream()'); // on chrome calling endOfStream can sometimes cause an exception,
  48144. // even when the media source is in a valid state.
  48145. try {
  48146. this.mediaSource.endOfStream();
  48147. } catch (e) {
  48148. videojs$1.log.warn('Failed to call media source endOfStream', e);
  48149. }
  48150. }
  48151. /**
  48152. * Check if a playlist has stopped being updated
  48153. * @param {Object} playlist the media playlist object
  48154. * @return {boolean} whether the playlist has stopped being updated or not
  48155. */
  48156. }, {
  48157. key: 'stuckAtPlaylistEnd_',
  48158. value: function stuckAtPlaylistEnd_(playlist) {
  48159. var seekable$$1 = this.seekable();
  48160. if (!seekable$$1.length) {
  48161. // playlist doesn't have enough information to determine whether we are stuck
  48162. return false;
  48163. }
  48164. var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration);
  48165. if (expired === null) {
  48166. return false;
  48167. } // does not use the safe live end to calculate playlist end, since we
  48168. // don't want to say we are stuck while there is still content
  48169. var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired);
  48170. var currentTime = this.tech_.currentTime();
  48171. var buffered = this.tech_.buffered();
  48172. if (!buffered.length) {
  48173. // return true if the playhead reached the absolute end of the playlist
  48174. return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
  48175. }
  48176. var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
  48177. // end of playlist
  48178. return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
  48179. }
  48180. /**
  48181. * Blacklists a playlist when an error occurs for a set amount of time
  48182. * making it unavailable for selection by the rendition selection algorithm
  48183. * and then forces a new playlist (rendition) selection.
  48184. *
  48185. * @param {Object=} error an optional error that may include the playlist
  48186. * to blacklist
  48187. * @param {Number=} blacklistDuration an optional number of seconds to blacklist the
  48188. * playlist
  48189. */
  48190. }, {
  48191. key: 'blacklistCurrentPlaylist',
  48192. value: function blacklistCurrentPlaylist() {
  48193. var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  48194. var blacklistDuration = arguments[1];
  48195. var currentPlaylist = void 0;
  48196. var nextPlaylist = void 0; // If the `error` was generated by the playlist loader, it will contain
  48197. // the playlist we were trying to load (but failed) and that should be
  48198. // blacklisted instead of the currently selected playlist which is likely
  48199. // out-of-date in this scenario
  48200. currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
  48201. blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were
  48202. // trying to load the master OR while we were disposing of the tech
  48203. if (!currentPlaylist) {
  48204. this.error = error;
  48205. try {
  48206. return this.mediaSource.endOfStream('network');
  48207. } catch (e) {
  48208. return this.trigger('error');
  48209. }
  48210. }
  48211. var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1;
  48212. if (isFinalRendition) {
  48213. // Never blacklisting this playlist because it's final rendition
  48214. videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the final playlist.');
  48215. this.tech_.trigger('retryplaylist');
  48216. return this.masterPlaylistLoader_.load(isFinalRendition);
  48217. } // Blacklist this playlist
  48218. currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000;
  48219. this.tech_.trigger('blacklistplaylist');
  48220. this.tech_.trigger({
  48221. type: 'usage',
  48222. name: 'hls-rendition-blacklisted'
  48223. }); // Select a new playlist
  48224. nextPlaylist = this.selectPlaylist();
  48225. videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.');
  48226. return this.masterPlaylistLoader_.media(nextPlaylist);
  48227. }
  48228. /**
  48229. * Pause all segment loaders
  48230. */
  48231. }, {
  48232. key: 'pauseLoading',
  48233. value: function pauseLoading() {
  48234. this.mainSegmentLoader_.pause();
  48235. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  48236. this.audioSegmentLoader_.pause();
  48237. }
  48238. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  48239. this.subtitleSegmentLoader_.pause();
  48240. }
  48241. }
  48242. /**
  48243. * set the current time on all segment loaders
  48244. *
  48245. * @param {TimeRange} currentTime the current time to set
  48246. * @return {TimeRange} the current time
  48247. */
  48248. }, {
  48249. key: 'setCurrentTime',
  48250. value: function setCurrentTime(currentTime) {
  48251. var buffered = findRange(this.tech_.buffered(), currentTime);
  48252. if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
  48253. // return immediately if the metadata is not ready yet
  48254. return 0;
  48255. } // it's clearly an edge-case but don't thrown an error if asked to
  48256. // seek within an empty playlist
  48257. if (!this.masterPlaylistLoader_.media().segments) {
  48258. return 0;
  48259. } // In flash playback, the segment loaders should be reset on every seek, even
  48260. // in buffer seeks. If the seek location is already buffered, continue buffering as
  48261. // usual
  48262. // TODO: redo this comment
  48263. if (buffered && buffered.length) {
  48264. return currentTime;
  48265. } // cancel outstanding requests so we begin buffering at the new
  48266. // location
  48267. this.mainSegmentLoader_.resetEverything();
  48268. this.mainSegmentLoader_.abort();
  48269. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  48270. this.audioSegmentLoader_.resetEverything();
  48271. this.audioSegmentLoader_.abort();
  48272. }
  48273. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  48274. this.subtitleSegmentLoader_.resetEverything();
  48275. this.subtitleSegmentLoader_.abort();
  48276. } // start segment loader loading in case they are paused
  48277. this.load();
  48278. }
  48279. /**
  48280. * get the current duration
  48281. *
  48282. * @return {TimeRange} the duration
  48283. */
  48284. }, {
  48285. key: 'duration',
  48286. value: function duration$$1() {
  48287. if (!this.masterPlaylistLoader_) {
  48288. return 0;
  48289. }
  48290. if (this.mediaSource) {
  48291. return this.mediaSource.duration;
  48292. }
  48293. return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  48294. }
  48295. /**
  48296. * check the seekable range
  48297. *
  48298. * @return {TimeRange} the seekable range
  48299. */
  48300. }, {
  48301. key: 'seekable',
  48302. value: function seekable$$1() {
  48303. return this.seekable_;
  48304. }
  48305. }, {
  48306. key: 'onSyncInfoUpdate_',
  48307. value: function onSyncInfoUpdate_() {
  48308. var mainSeekable = void 0;
  48309. var audioSeekable = void 0;
  48310. if (!this.masterPlaylistLoader_) {
  48311. return;
  48312. }
  48313. var media = this.masterPlaylistLoader_.media();
  48314. if (!media) {
  48315. return;
  48316. }
  48317. var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  48318. if (expired === null) {
  48319. // not enough information to update seekable
  48320. return;
  48321. }
  48322. mainSeekable = Hls.Playlist.seekable(media, expired);
  48323. if (mainSeekable.length === 0) {
  48324. return;
  48325. }
  48326. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  48327. media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
  48328. expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  48329. if (expired === null) {
  48330. return;
  48331. }
  48332. audioSeekable = Hls.Playlist.seekable(media, expired);
  48333. if (audioSeekable.length === 0) {
  48334. return;
  48335. }
  48336. }
  48337. var oldEnd = void 0;
  48338. var oldStart = void 0;
  48339. if (this.seekable_ && this.seekable_.length) {
  48340. oldEnd = this.seekable_.end(0);
  48341. oldStart = this.seekable_.start(0);
  48342. }
  48343. if (!audioSeekable) {
  48344. // seekable has been calculated based on buffering video data so it
  48345. // can be returned directly
  48346. this.seekable_ = mainSeekable;
  48347. } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
  48348. // seekables are pretty far off, rely on main
  48349. this.seekable_ = mainSeekable;
  48350. } else {
  48351. this.seekable_ = videojs$1.createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]);
  48352. } // seekable is the same as last time
  48353. if (this.seekable_ && this.seekable_.length) {
  48354. if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
  48355. return;
  48356. }
  48357. }
  48358. this.logger_('seekable updated [' + printableRange(this.seekable_) + ']');
  48359. this.tech_.trigger('seekablechanged');
  48360. }
  48361. /**
  48362. * Update the player duration
  48363. */
  48364. }, {
  48365. key: 'updateDuration',
  48366. value: function updateDuration() {
  48367. var _this6 = this;
  48368. var oldDuration = this.mediaSource.duration;
  48369. var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  48370. var buffered = this.tech_.buffered();
  48371. var setDuration = function setDuration() {
  48372. // on firefox setting the duration may sometimes cause an exception
  48373. // even if the media source is open and source buffers are not
  48374. // updating, something about the media source being in an invalid state.
  48375. _this6.logger_('Setting duration from ' + _this6.mediaSource.duration + ' => ' + newDuration);
  48376. try {
  48377. _this6.mediaSource.duration = newDuration;
  48378. } catch (e) {
  48379. videojs$1.log.warn('Failed to set media source duration', e);
  48380. }
  48381. _this6.tech_.trigger('durationchange');
  48382. _this6.mediaSource.removeEventListener('sourceopen', setDuration);
  48383. };
  48384. if (buffered.length > 0) {
  48385. newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
  48386. } // if the duration has changed, invalidate the cached value
  48387. if (oldDuration !== newDuration) {
  48388. // update the duration
  48389. if (this.mediaSource.readyState !== 'open') {
  48390. this.mediaSource.addEventListener('sourceopen', setDuration);
  48391. } else {
  48392. setDuration();
  48393. }
  48394. }
  48395. }
  48396. /**
  48397. * dispose of the MasterPlaylistController and everything
  48398. * that it controls
  48399. */
  48400. }, {
  48401. key: 'dispose',
  48402. value: function dispose() {
  48403. var _this7 = this;
  48404. this.decrypter_.terminate();
  48405. this.masterPlaylistLoader_.dispose();
  48406. this.mainSegmentLoader_.dispose();
  48407. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  48408. var groups = _this7.mediaTypes_[type].groups;
  48409. for (var id in groups) {
  48410. groups[id].forEach(function (group) {
  48411. if (group.playlistLoader) {
  48412. group.playlistLoader.dispose();
  48413. }
  48414. });
  48415. }
  48416. });
  48417. this.audioSegmentLoader_.dispose();
  48418. this.subtitleSegmentLoader_.dispose();
  48419. }
  48420. /**
  48421. * return the master playlist object if we have one
  48422. *
  48423. * @return {Object} the master playlist object that we parsed
  48424. */
  48425. }, {
  48426. key: 'master',
  48427. value: function master() {
  48428. return this.masterPlaylistLoader_.master;
  48429. }
  48430. /**
  48431. * return the currently selected playlist
  48432. *
  48433. * @return {Object} the currently selected playlist object that we parsed
  48434. */
  48435. }, {
  48436. key: 'media',
  48437. value: function media() {
  48438. // playlist loader will not return media if it has not been fully loaded
  48439. return this.masterPlaylistLoader_.media() || this.initialMedia_;
  48440. }
  48441. /**
  48442. * setup our internal source buffers on our segment Loaders
  48443. *
  48444. * @private
  48445. */
  48446. }, {
  48447. key: 'setupSourceBuffers_',
  48448. value: function setupSourceBuffers_() {
  48449. var media = this.masterPlaylistLoader_.media();
  48450. var mimeTypes = void 0; // wait until a media playlist is available and the Media Source is
  48451. // attached
  48452. if (!media || this.mediaSource.readyState !== 'open') {
  48453. return;
  48454. }
  48455. mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media);
  48456. if (mimeTypes.length < 1) {
  48457. this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri;
  48458. return this.mediaSource.endOfStream('decode');
  48459. }
  48460. this.configureLoaderMimeTypes_(mimeTypes); // exclude any incompatible variant streams from future playlist
  48461. // selection
  48462. this.excludeIncompatibleVariants_(media);
  48463. }
  48464. }, {
  48465. key: 'configureLoaderMimeTypes_',
  48466. value: function configureLoaderMimeTypes_(mimeTypes) {
  48467. // If the content is demuxed, we can't start appending segments to a source buffer
  48468. // until both source buffers are set up, or else the browser may not let us add the
  48469. // second source buffer (it will assume we are playing either audio only or video
  48470. // only).
  48471. var sourceBufferEmitter = // If there is more than one mime type
  48472. mimeTypes.length > 1 && // and the first mime type does not have muxed video and audio
  48473. mimeTypes[0].indexOf(',') === -1 && // and the two mime types are different (they can be the same in the case of audio
  48474. // only with alternate audio)
  48475. mimeTypes[0] !== mimeTypes[1] ? // then we want to wait on the second source buffer
  48476. new videojs$1.EventTarget() : // otherwise there is no need to wait as the content is either audio only,
  48477. // video only, or muxed content.
  48478. null;
  48479. this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter);
  48480. if (mimeTypes[1]) {
  48481. this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter);
  48482. }
  48483. }
  48484. /**
  48485. * Blacklists playlists with codecs that are unsupported by the browser.
  48486. */
  48487. }, {
  48488. key: 'excludeUnsupportedVariants_',
  48489. value: function excludeUnsupportedVariants_() {
  48490. this.master().playlists.forEach(function (variant) {
  48491. if (variant.attributes.CODECS && window$1.MediaSource && window$1.MediaSource.isTypeSupported && !window$1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) {
  48492. variant.excludeUntil = Infinity;
  48493. }
  48494. });
  48495. }
  48496. /**
  48497. * Blacklist playlists that are known to be codec or
  48498. * stream-incompatible with the SourceBuffer configuration. For
  48499. * instance, Media Source Extensions would cause the video element to
  48500. * stall waiting for video data if you switched from a variant with
  48501. * video and audio to an audio-only one.
  48502. *
  48503. * @param {Object} media a media playlist compatible with the current
  48504. * set of SourceBuffers. Variants in the current master playlist that
  48505. * do not appear to have compatible codec or stream configurations
  48506. * will be excluded from the default playlist selection algorithm
  48507. * indefinitely.
  48508. * @private
  48509. */
  48510. }, {
  48511. key: 'excludeIncompatibleVariants_',
  48512. value: function excludeIncompatibleVariants_(media) {
  48513. var codecCount = 2;
  48514. var videoCodec = null;
  48515. var codecs = void 0;
  48516. if (media.attributes.CODECS) {
  48517. codecs = parseCodecs(media.attributes.CODECS);
  48518. videoCodec = codecs.videoCodec;
  48519. codecCount = codecs.codecCount;
  48520. }
  48521. this.master().playlists.forEach(function (variant) {
  48522. var variantCodecs = {
  48523. codecCount: 2,
  48524. videoCodec: null
  48525. };
  48526. if (variant.attributes.CODECS) {
  48527. variantCodecs = parseCodecs(variant.attributes.CODECS);
  48528. } // if the streams differ in the presence or absence of audio or
  48529. // video, they are incompatible
  48530. if (variantCodecs.codecCount !== codecCount) {
  48531. variant.excludeUntil = Infinity;
  48532. } // if h.264 is specified on the current playlist, some flavor of
  48533. // it must be specified on all compatible variants
  48534. if (variantCodecs.videoCodec !== videoCodec) {
  48535. variant.excludeUntil = Infinity;
  48536. }
  48537. });
  48538. }
  48539. }, {
  48540. key: 'updateAdCues_',
  48541. value: function updateAdCues_(media) {
  48542. var offset = 0;
  48543. var seekable$$1 = this.seekable();
  48544. if (seekable$$1.length) {
  48545. offset = seekable$$1.start(0);
  48546. }
  48547. updateAdCues(media, this.cueTagsTrack_, offset);
  48548. }
  48549. /**
  48550. * Calculates the desired forward buffer length based on current time
  48551. *
  48552. * @return {Number} Desired forward buffer length in seconds
  48553. */
  48554. }, {
  48555. key: 'goalBufferLength',
  48556. value: function goalBufferLength() {
  48557. var currentTime = this.tech_.currentTime();
  48558. var initial = Config.GOAL_BUFFER_LENGTH;
  48559. var rate = Config.GOAL_BUFFER_LENGTH_RATE;
  48560. var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
  48561. return Math.min(initial + currentTime * rate, max);
  48562. }
  48563. /**
  48564. * Calculates the desired buffer low water line based on current time
  48565. *
  48566. * @return {Number} Desired buffer low water line in seconds
  48567. */
  48568. }, {
  48569. key: 'bufferLowWaterLine',
  48570. value: function bufferLowWaterLine() {
  48571. var currentTime = this.tech_.currentTime();
  48572. var initial = Config.BUFFER_LOW_WATER_LINE;
  48573. var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
  48574. var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
  48575. return Math.min(initial + currentTime * rate, max);
  48576. }
  48577. }]);
  48578. return MasterPlaylistController;
  48579. }(videojs$1.EventTarget);
  48580. /**
  48581. * Returns a function that acts as the Enable/disable playlist function.
  48582. *
  48583. * @param {PlaylistLoader} loader - The master playlist loader
  48584. * @param {String} playlistUri - uri of the playlist
  48585. * @param {Function} changePlaylistFn - A function to be called after a
  48586. * playlist's enabled-state has been changed. Will NOT be called if a
  48587. * playlist's enabled-state is unchanged
  48588. * @param {Boolean=} enable - Value to set the playlist enabled-state to
  48589. * or if undefined returns the current enabled-state for the playlist
  48590. * @return {Function} Function for setting/getting enabled
  48591. */
  48592. var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) {
  48593. return function (enable) {
  48594. var playlist = loader.master.playlists[playlistUri];
  48595. var incompatible = isIncompatible(playlist);
  48596. var currentlyEnabled = isEnabled(playlist);
  48597. if (typeof enable === 'undefined') {
  48598. return currentlyEnabled;
  48599. }
  48600. if (enable) {
  48601. delete playlist.disabled;
  48602. } else {
  48603. playlist.disabled = true;
  48604. }
  48605. if (enable !== currentlyEnabled && !incompatible) {
  48606. // Ensure the outside world knows about our changes
  48607. changePlaylistFn();
  48608. if (enable) {
  48609. loader.trigger('renditionenabled');
  48610. } else {
  48611. loader.trigger('renditiondisabled');
  48612. }
  48613. }
  48614. return enable;
  48615. };
  48616. };
  48617. /**
  48618. * The representation object encapsulates the publicly visible information
  48619. * in a media playlist along with a setter/getter-type function (enabled)
  48620. * for changing the enabled-state of a particular playlist entry
  48621. *
  48622. * @class Representation
  48623. */
  48624. var Representation = function Representation(hlsHandler, playlist, id) {
  48625. classCallCheck$1(this, Representation);
  48626. var mpc = hlsHandler.masterPlaylistController_,
  48627. smoothQualityChange = hlsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function
  48628. var changeType = smoothQualityChange ? 'smooth' : 'fast';
  48629. var qualityChangeFunction = mpc[changeType + 'QualityChange_'].bind(mpc); // some playlist attributes are optional
  48630. if (playlist.attributes.RESOLUTION) {
  48631. var resolution = playlist.attributes.RESOLUTION;
  48632. this.width = resolution.width;
  48633. this.height = resolution.height;
  48634. }
  48635. this.bandwidth = playlist.attributes.BANDWIDTH; // The id is simply the ordinality of the media playlist
  48636. // within the master playlist
  48637. this.id = id; // Partially-apply the enableFunction to create a playlist-
  48638. // specific variant
  48639. this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, qualityChangeFunction);
  48640. };
  48641. /**
  48642. * A mixin function that adds the `representations` api to an instance
  48643. * of the HlsHandler class
  48644. * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the
  48645. * representation API into
  48646. */
  48647. var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) {
  48648. var playlists = hlsHandler.playlists; // Add a single API-specific function to the HlsHandler instance
  48649. hlsHandler.representations = function () {
  48650. return playlists.master.playlists.filter(function (media) {
  48651. return !isIncompatible(media);
  48652. }).map(function (e, i) {
  48653. return new Representation(hlsHandler, e, e.uri);
  48654. });
  48655. };
  48656. };
  48657. /**
  48658. * @file playback-watcher.js
  48659. *
  48660. * Playback starts, and now my watch begins. It shall not end until my death. I shall
  48661. * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
  48662. * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
  48663. * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
  48664. * my life and honor to the Playback Watch, for this Player and all the Players to come.
  48665. */
  48666. // Set of events that reset the playback-watcher time check logic and clear the timeout
  48667. var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
  48668. /**
  48669. * @class PlaybackWatcher
  48670. */
  48671. var PlaybackWatcher = function () {
  48672. /**
  48673. * Represents an PlaybackWatcher object.
  48674. * @constructor
  48675. * @param {object} options an object that includes the tech and settings
  48676. */
  48677. function PlaybackWatcher(options) {
  48678. var _this = this;
  48679. classCallCheck$1(this, PlaybackWatcher);
  48680. this.tech_ = options.tech;
  48681. this.seekable = options.seekable;
  48682. this.seekTo = options.seekTo;
  48683. this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
  48684. this.media = options.media;
  48685. this.consecutiveUpdates = 0;
  48686. this.lastRecordedTime = null;
  48687. this.timer_ = null;
  48688. this.checkCurrentTimeTimeout_ = null;
  48689. this.logger_ = logger('PlaybackWatcher');
  48690. this.logger_('initialize');
  48691. var canPlayHandler = function canPlayHandler() {
  48692. return _this.monitorCurrentTime_();
  48693. };
  48694. var waitingHandler = function waitingHandler() {
  48695. return _this.techWaiting_();
  48696. };
  48697. var cancelTimerHandler = function cancelTimerHandler() {
  48698. return _this.cancelTimer_();
  48699. };
  48700. var fixesBadSeeksHandler = function fixesBadSeeksHandler() {
  48701. return _this.fixesBadSeeks_();
  48702. };
  48703. this.tech_.on('seekablechanged', fixesBadSeeksHandler);
  48704. this.tech_.on('waiting', waitingHandler);
  48705. this.tech_.on(timerCancelEvents, cancelTimerHandler);
  48706. this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events
  48707. this.dispose = function () {
  48708. _this.logger_('dispose');
  48709. _this.tech_.off('seekablechanged', fixesBadSeeksHandler);
  48710. _this.tech_.off('waiting', waitingHandler);
  48711. _this.tech_.off(timerCancelEvents, cancelTimerHandler);
  48712. _this.tech_.off('canplay', canPlayHandler);
  48713. if (_this.checkCurrentTimeTimeout_) {
  48714. window$1.clearTimeout(_this.checkCurrentTimeTimeout_);
  48715. }
  48716. _this.cancelTimer_();
  48717. };
  48718. }
  48719. /**
  48720. * Periodically check current time to see if playback stopped
  48721. *
  48722. * @private
  48723. */
  48724. createClass$1(PlaybackWatcher, [{
  48725. key: 'monitorCurrentTime_',
  48726. value: function monitorCurrentTime_() {
  48727. this.checkCurrentTime_();
  48728. if (this.checkCurrentTimeTimeout_) {
  48729. window$1.clearTimeout(this.checkCurrentTimeTimeout_);
  48730. } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  48731. this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
  48732. }
  48733. /**
  48734. * The purpose of this function is to emulate the "waiting" event on
  48735. * browsers that do not emit it when they are waiting for more
  48736. * data to continue playback
  48737. *
  48738. * @private
  48739. */
  48740. }, {
  48741. key: 'checkCurrentTime_',
  48742. value: function checkCurrentTime_() {
  48743. if (this.tech_.seeking() && this.fixesBadSeeks_()) {
  48744. this.consecutiveUpdates = 0;
  48745. this.lastRecordedTime = this.tech_.currentTime();
  48746. return;
  48747. }
  48748. if (this.tech_.paused() || this.tech_.seeking()) {
  48749. return;
  48750. }
  48751. var currentTime = this.tech_.currentTime();
  48752. var buffered = this.tech_.buffered();
  48753. if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
  48754. // If current time is at the end of the final buffered region, then any playback
  48755. // stall is most likely caused by buffering in a low bandwidth environment. The tech
  48756. // should fire a `waiting` event in this scenario, but due to browser and tech
  48757. // inconsistencies. Calling `techWaiting_` here allows us to simulate
  48758. // responding to a native `waiting` event when the tech fails to emit one.
  48759. return this.techWaiting_();
  48760. }
  48761. if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
  48762. this.consecutiveUpdates++;
  48763. this.waiting_();
  48764. } else if (currentTime === this.lastRecordedTime) {
  48765. this.consecutiveUpdates++;
  48766. } else {
  48767. this.consecutiveUpdates = 0;
  48768. this.lastRecordedTime = currentTime;
  48769. }
  48770. }
  48771. /**
  48772. * Cancels any pending timers and resets the 'timeupdate' mechanism
  48773. * designed to detect that we are stalled
  48774. *
  48775. * @private
  48776. */
  48777. }, {
  48778. key: 'cancelTimer_',
  48779. value: function cancelTimer_() {
  48780. this.consecutiveUpdates = 0;
  48781. if (this.timer_) {
  48782. this.logger_('cancelTimer_');
  48783. clearTimeout(this.timer_);
  48784. }
  48785. this.timer_ = null;
  48786. }
  48787. /**
  48788. * Fixes situations where there's a bad seek
  48789. *
  48790. * @return {Boolean} whether an action was taken to fix the seek
  48791. * @private
  48792. */
  48793. }, {
  48794. key: 'fixesBadSeeks_',
  48795. value: function fixesBadSeeks_() {
  48796. var seeking = this.tech_.seeking();
  48797. if (!seeking) {
  48798. return false;
  48799. }
  48800. var seekable = this.seekable();
  48801. var currentTime = this.tech_.currentTime();
  48802. var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
  48803. var seekTo = void 0;
  48804. if (isAfterSeekableRange) {
  48805. var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
  48806. seekTo = seekableEnd;
  48807. }
  48808. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  48809. var seekableStart = seekable.start(0); // sync to the beginning of the live window
  48810. // provide a buffer of .1 seconds to handle rounding/imprecise numbers
  48811. seekTo = seekableStart + SAFE_TIME_DELTA;
  48812. }
  48813. if (typeof seekTo !== 'undefined') {
  48814. this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.'));
  48815. this.seekTo(seekTo);
  48816. return true;
  48817. }
  48818. return false;
  48819. }
  48820. /**
  48821. * Handler for situations when we determine the player is waiting.
  48822. *
  48823. * @private
  48824. */
  48825. }, {
  48826. key: 'waiting_',
  48827. value: function waiting_() {
  48828. if (this.techWaiting_()) {
  48829. return;
  48830. } // All tech waiting checks failed. Use last resort correction
  48831. var currentTime = this.tech_.currentTime();
  48832. var buffered = this.tech_.buffered();
  48833. var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
  48834. // region with no indication that anything is amiss (seen in Firefox). Seeking to
  48835. // currentTime is usually enough to kickstart the player. This checks that the player
  48836. // is currently within a buffered region before attempting a corrective seek.
  48837. // Chrome does not appear to continue `timeupdate` events after a `waiting` event
  48838. // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
  48839. // make sure there is ~3 seconds of forward buffer before taking any corrective action
  48840. // to avoid triggering an `unknownwaiting` event when the network is slow.
  48841. if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
  48842. this.cancelTimer_();
  48843. this.seekTo(currentTime);
  48844. 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
  48845. this.tech_.trigger({
  48846. type: 'usage',
  48847. name: 'hls-unknown-waiting'
  48848. });
  48849. return;
  48850. }
  48851. }
  48852. /**
  48853. * Handler for situations when the tech fires a `waiting` event
  48854. *
  48855. * @return {Boolean}
  48856. * True if an action (or none) was needed to correct the waiting. False if no
  48857. * checks passed
  48858. * @private
  48859. */
  48860. }, {
  48861. key: 'techWaiting_',
  48862. value: function techWaiting_() {
  48863. var seekable = this.seekable();
  48864. var currentTime = this.tech_.currentTime();
  48865. if (this.tech_.seeking() && this.fixesBadSeeks_()) {
  48866. // Tech is seeking or bad seek fixed, no action needed
  48867. return true;
  48868. }
  48869. if (this.tech_.seeking() || this.timer_ !== null) {
  48870. // Tech is seeking or already waiting on another action, no action needed
  48871. return true;
  48872. }
  48873. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  48874. var livePoint = seekable.end(seekable.length - 1);
  48875. this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint));
  48876. this.cancelTimer_();
  48877. this.seekTo(livePoint); // live window resyncs may be useful for monitoring QoS
  48878. this.tech_.trigger({
  48879. type: 'usage',
  48880. name: 'hls-live-resync'
  48881. });
  48882. return true;
  48883. }
  48884. var buffered = this.tech_.buffered();
  48885. var nextRange = findNextRange(buffered, currentTime);
  48886. if (this.videoUnderflow_(nextRange, buffered, currentTime)) {
  48887. // Even though the video underflowed and was stuck in a gap, the audio overplayed
  48888. // the gap, leading currentTime into a buffered range. Seeking to currentTime
  48889. // allows the video to catch up to the audio position without losing any audio
  48890. // (only suffering ~3 seconds of frozen video and a pause in audio playback).
  48891. this.cancelTimer_();
  48892. this.seekTo(currentTime); // video underflow may be useful for monitoring QoS
  48893. this.tech_.trigger({
  48894. type: 'usage',
  48895. name: 'hls-video-underflow'
  48896. });
  48897. return true;
  48898. } // check for gap
  48899. if (nextRange.length > 0) {
  48900. var difference = nextRange.start(0) - currentTime;
  48901. this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0)));
  48902. this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
  48903. return true;
  48904. } // All checks failed. Returning false to indicate failure to correct waiting
  48905. return false;
  48906. }
  48907. }, {
  48908. key: 'afterSeekableWindow_',
  48909. value: function afterSeekableWindow_(seekable, currentTime, playlist) {
  48910. var allowSeeksWithinUnsafeLiveWindow = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
  48911. if (!seekable.length) {
  48912. // we can't make a solid case if there's no seekable, default to false
  48913. return false;
  48914. }
  48915. var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
  48916. var isLive = !playlist.endList;
  48917. if (isLive && allowSeeksWithinUnsafeLiveWindow) {
  48918. allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
  48919. }
  48920. if (currentTime > allowedEnd) {
  48921. return true;
  48922. }
  48923. return false;
  48924. }
  48925. }, {
  48926. key: 'beforeSeekableWindow_',
  48927. value: function beforeSeekableWindow_(seekable, currentTime) {
  48928. if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream
  48929. seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) {
  48930. return true;
  48931. }
  48932. return false;
  48933. }
  48934. }, {
  48935. key: 'videoUnderflow_',
  48936. value: function videoUnderflow_(nextRange, buffered, currentTime) {
  48937. if (nextRange.length === 0) {
  48938. // Even if there is no available next range, there is still a possibility we are
  48939. // stuck in a gap due to video underflow.
  48940. var gap = this.gapFromVideoUnderflow_(buffered, currentTime);
  48941. if (gap) {
  48942. this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime));
  48943. return true;
  48944. }
  48945. }
  48946. return false;
  48947. }
  48948. /**
  48949. * Timer callback. If playback still has not proceeded, then we seek
  48950. * to the start of the next buffered region.
  48951. *
  48952. * @private
  48953. */
  48954. }, {
  48955. key: 'skipTheGap_',
  48956. value: function skipTheGap_(scheduledCurrentTime) {
  48957. var buffered = this.tech_.buffered();
  48958. var currentTime = this.tech_.currentTime();
  48959. var nextRange = findNextRange(buffered, currentTime);
  48960. this.cancelTimer_();
  48961. if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
  48962. return;
  48963. }
  48964. this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
  48965. this.seekTo(nextRange.start(0) + TIME_FUDGE_FACTOR);
  48966. this.tech_.trigger({
  48967. type: 'usage',
  48968. name: 'hls-gap-skip'
  48969. });
  48970. }
  48971. }, {
  48972. key: 'gapFromVideoUnderflow_',
  48973. value: function gapFromVideoUnderflow_(buffered, currentTime) {
  48974. // At least in Chrome, if there is a gap in the video buffer, the audio will continue
  48975. // playing for ~3 seconds after the video gap starts. This is done to account for
  48976. // video buffer underflow/underrun (note that this is not done when there is audio
  48977. // buffer underflow/underrun -- in that case the video will stop as soon as it
  48978. // encounters the gap, as audio stalls are more noticeable/jarring to a user than
  48979. // video stalls). The player's time will reflect the playthrough of audio, so the
  48980. // time will appear as if we are in a buffered region, even if we are stuck in a
  48981. // "gap."
  48982. //
  48983. // Example:
  48984. // video buffer: 0 => 10.1, 10.2 => 20
  48985. // audio buffer: 0 => 20
  48986. // overall buffer: 0 => 10.1, 10.2 => 20
  48987. // current time: 13
  48988. //
  48989. // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
  48990. // however, the audio continued playing until it reached ~3 seconds past the gap
  48991. // (13 seconds), at which point it stops as well. Since current time is past the
  48992. // gap, findNextRange will return no ranges.
  48993. //
  48994. // To check for this issue, we see if there is a gap that starts somewhere within
  48995. // a 3 second range (3 seconds +/- 1 second) back from our current time.
  48996. var gaps = findGaps(buffered);
  48997. for (var i = 0; i < gaps.length; i++) {
  48998. var start = gaps.start(i);
  48999. var end = gaps.end(i); // gap is starts no more than 4 seconds back
  49000. if (currentTime - start < 4 && currentTime - start > 2) {
  49001. return {
  49002. start: start,
  49003. end: end
  49004. };
  49005. }
  49006. }
  49007. return null;
  49008. }
  49009. }]);
  49010. return PlaybackWatcher;
  49011. }();
  49012. var defaultOptions = {
  49013. errorInterval: 30,
  49014. getSource: function getSource(next) {
  49015. var tech = this.tech({
  49016. IWillNotUseThisInPlugins: true
  49017. });
  49018. var sourceObj = tech.currentSource_;
  49019. return next(sourceObj);
  49020. }
  49021. };
  49022. /**
  49023. * Main entry point for the plugin
  49024. *
  49025. * @param {Player} player a reference to a videojs Player instance
  49026. * @param {Object} [options] an object with plugin options
  49027. * @private
  49028. */
  49029. var initPlugin = function initPlugin(player, options) {
  49030. var lastCalled = 0;
  49031. var seekTo = 0;
  49032. var localOptions = videojs$1.mergeOptions(defaultOptions, options);
  49033. player.ready(function () {
  49034. player.trigger({
  49035. type: 'usage',
  49036. name: 'hls-error-reload-initialized'
  49037. });
  49038. });
  49039. /**
  49040. * Player modifications to perform that must wait until `loadedmetadata`
  49041. * has been triggered
  49042. *
  49043. * @private
  49044. */
  49045. var loadedMetadataHandler = function loadedMetadataHandler() {
  49046. if (seekTo) {
  49047. player.currentTime(seekTo);
  49048. }
  49049. };
  49050. /**
  49051. * Set the source on the player element, play, and seek if necessary
  49052. *
  49053. * @param {Object} sourceObj An object specifying the source url and mime-type to play
  49054. * @private
  49055. */
  49056. var setSource = function setSource(sourceObj) {
  49057. if (sourceObj === null || sourceObj === undefined) {
  49058. return;
  49059. }
  49060. seekTo = player.duration() !== Infinity && player.currentTime() || 0;
  49061. player.one('loadedmetadata', loadedMetadataHandler);
  49062. player.src(sourceObj);
  49063. player.trigger({
  49064. type: 'usage',
  49065. name: 'hls-error-reload'
  49066. });
  49067. player.play();
  49068. };
  49069. /**
  49070. * Attempt to get a source from either the built-in getSource function
  49071. * or a custom function provided via the options
  49072. *
  49073. * @private
  49074. */
  49075. var errorHandler = function errorHandler() {
  49076. // Do not attempt to reload the source if a source-reload occurred before
  49077. // 'errorInterval' time has elapsed since the last source-reload
  49078. if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
  49079. player.trigger({
  49080. type: 'usage',
  49081. name: 'hls-error-reload-canceled'
  49082. });
  49083. return;
  49084. }
  49085. if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
  49086. videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
  49087. return;
  49088. }
  49089. lastCalled = Date.now();
  49090. return localOptions.getSource.call(player, setSource);
  49091. };
  49092. /**
  49093. * Unbind any event handlers that were bound by the plugin
  49094. *
  49095. * @private
  49096. */
  49097. var cleanupEvents = function cleanupEvents() {
  49098. player.off('loadedmetadata', loadedMetadataHandler);
  49099. player.off('error', errorHandler);
  49100. player.off('dispose', cleanupEvents);
  49101. };
  49102. /**
  49103. * Cleanup before re-initializing the plugin
  49104. *
  49105. * @param {Object} [newOptions] an object with plugin options
  49106. * @private
  49107. */
  49108. var reinitPlugin = function reinitPlugin(newOptions) {
  49109. cleanupEvents();
  49110. initPlugin(player, newOptions);
  49111. };
  49112. player.on('error', errorHandler);
  49113. player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
  49114. // initializing the plugin
  49115. player.reloadSourceOnError = reinitPlugin;
  49116. };
  49117. /**
  49118. * Reload the source when an error is detected as long as there
  49119. * wasn't an error previously within the last 30 seconds
  49120. *
  49121. * @param {Object} [options] an object with plugin options
  49122. */
  49123. var reloadSourceOnError = function reloadSourceOnError(options) {
  49124. initPlugin(this, options);
  49125. };
  49126. var version$3 = "1.10.1"; // since VHS handles HLS and DASH (and in the future, more types), use * to capture all
  49127. videojs$1.use('*', function (player) {
  49128. return {
  49129. setSource: function setSource(srcObj, next) {
  49130. // pass null as the first argument to indicate that the source is not rejected
  49131. next(null, srcObj);
  49132. },
  49133. // VHS needs to know when seeks happen. For external seeks (generated at the player
  49134. // level), this middleware will capture the action. For internal seeks (generated at
  49135. // the tech level), we use a wrapped function so that we can handle it on our own
  49136. // (specified elsewhere).
  49137. setCurrentTime: function setCurrentTime(time) {
  49138. if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
  49139. player.vhs.setCurrentTime(time);
  49140. }
  49141. return time;
  49142. },
  49143. // Sync VHS after play requests.
  49144. // This specifically handles replay where the order of actions is
  49145. // play, video element will seek to 0 (skipping the setCurrentTime middleware)
  49146. // then triggers a play event.
  49147. play: function play() {
  49148. if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
  49149. player.vhs.setCurrentTime(player.tech_.currentTime());
  49150. }
  49151. }
  49152. };
  49153. });
  49154. /**
  49155. * @file videojs-http-streaming.js
  49156. *
  49157. * The main file for the HLS project.
  49158. * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE
  49159. */
  49160. var Hls$1 = {
  49161. PlaylistLoader: PlaylistLoader,
  49162. Playlist: Playlist,
  49163. Decrypter: Decrypter,
  49164. AsyncStream: AsyncStream,
  49165. decrypt: decrypt,
  49166. utils: utils$1,
  49167. STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
  49168. INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
  49169. comparePlaylistBandwidth: comparePlaylistBandwidth,
  49170. comparePlaylistResolution: comparePlaylistResolution,
  49171. xhr: xhrFactory()
  49172. }; // Define getter/setters for config properites
  49173. ['GOAL_BUFFER_LENGTH', 'MAX_GOAL_BUFFER_LENGTH', 'GOAL_BUFFER_LENGTH_RATE', 'BUFFER_LOW_WATER_LINE', 'MAX_BUFFER_LOW_WATER_LINE', 'BUFFER_LOW_WATER_LINE_RATE', 'BANDWIDTH_VARIANCE'].forEach(function (prop) {
  49174. Object.defineProperty(Hls$1, prop, {
  49175. get: function get$$1() {
  49176. videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
  49177. return Config[prop];
  49178. },
  49179. set: function set$$1(value) {
  49180. videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
  49181. if (typeof value !== 'number' || value < 0) {
  49182. videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0');
  49183. return;
  49184. }
  49185. Config[prop] = value;
  49186. }
  49187. });
  49188. });
  49189. var LOCAL_STORAGE_KEY$1 = 'videojs-vhs';
  49190. var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
  49191. var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
  49192. if (mpegurlRE.test(type)) {
  49193. return 'hls';
  49194. }
  49195. var dashRE = /^application\/dash\+xml/i;
  49196. if (dashRE.test(type)) {
  49197. return 'dash';
  49198. }
  49199. return null;
  49200. };
  49201. /**
  49202. * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls.
  49203. *
  49204. * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
  49205. * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
  49206. * @function handleHlsMediaChange
  49207. */
  49208. var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) {
  49209. var newPlaylist = playlistLoader.media();
  49210. var selectedIndex = -1;
  49211. for (var i = 0; i < qualityLevels.length; i++) {
  49212. if (qualityLevels[i].id === newPlaylist.uri) {
  49213. selectedIndex = i;
  49214. break;
  49215. }
  49216. }
  49217. qualityLevels.selectedIndex_ = selectedIndex;
  49218. qualityLevels.trigger({
  49219. selectedIndex: selectedIndex,
  49220. type: 'change'
  49221. });
  49222. };
  49223. /**
  49224. * Adds quality levels to list once playlist metadata is available
  49225. *
  49226. * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
  49227. * @param {Object} hls Hls object to listen to for media events.
  49228. * @function handleHlsLoadedMetadata
  49229. */
  49230. var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
  49231. hls.representations().forEach(function (rep) {
  49232. qualityLevels.addQualityLevel(rep);
  49233. });
  49234. handleHlsMediaChange(qualityLevels, hls.playlists);
  49235. }; // HLS is a source handler, not a tech. Make sure attempts to use it
  49236. // as one do not cause exceptions.
  49237. Hls$1.canPlaySource = function () {
  49238. return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  49239. };
  49240. var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) {
  49241. if (!keySystemOptions) {
  49242. return keySystemOptions;
  49243. } // upsert the content types based on the selected playlist
  49244. var keySystemContentTypes = {};
  49245. for (var keySystem in keySystemOptions) {
  49246. keySystemContentTypes[keySystem] = {
  49247. audioContentType: 'audio/mp4; codecs="' + audioPlaylist.attributes.CODECS + '"',
  49248. videoContentType: 'video/mp4; codecs="' + videoPlaylist.attributes.CODECS + '"'
  49249. };
  49250. if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) {
  49251. keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh;
  49252. } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
  49253. // so we need to prevent overwriting the URL entirely
  49254. if (typeof keySystemOptions[keySystem] === 'string') {
  49255. keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
  49256. }
  49257. }
  49258. return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes);
  49259. };
  49260. var setupEmeOptions = function setupEmeOptions(hlsHandler) {
  49261. if (hlsHandler.options_.sourceType !== 'dash') {
  49262. return;
  49263. }
  49264. var player = videojs$1.players[hlsHandler.tech_.options_.playerId];
  49265. if (player.eme) {
  49266. var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, hlsHandler.playlists.media(), hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media());
  49267. if (sourceOptions) {
  49268. player.currentSource().keySystems = sourceOptions; // works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449
  49269. if (player.eme.initializeMediaKeys) {
  49270. player.eme.initializeMediaKeys();
  49271. }
  49272. }
  49273. }
  49274. };
  49275. var getVhsLocalStorage = function getVhsLocalStorage() {
  49276. if (!window.localStorage) {
  49277. return null;
  49278. }
  49279. var storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY$1);
  49280. if (!storedObject) {
  49281. return null;
  49282. }
  49283. try {
  49284. return JSON.parse(storedObject);
  49285. } catch (e) {
  49286. // someone may have tampered with the value
  49287. return null;
  49288. }
  49289. };
  49290. var updateVhsLocalStorage = function updateVhsLocalStorage(options) {
  49291. if (!window.localStorage) {
  49292. return false;
  49293. }
  49294. var objectToStore = getVhsLocalStorage();
  49295. objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options;
  49296. try {
  49297. window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore));
  49298. } catch (e) {
  49299. // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
  49300. // storage is set to 0).
  49301. // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
  49302. // No need to perform any operation.
  49303. return false;
  49304. }
  49305. return objectToStore;
  49306. };
  49307. /**
  49308. * Whether the browser has built-in HLS support.
  49309. */
  49310. Hls$1.supportsNativeHls = function () {
  49311. var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
  49312. if (!videojs$1.getTech('Html5').isSupported()) {
  49313. return false;
  49314. } // HLS manifests can go by many mime-types
  49315. var canPlay = [// Apple santioned
  49316. 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility
  49317. 'audio/mpegurl', // Very common
  49318. 'audio/x-mpegurl', // Very common
  49319. 'application/x-mpegurl', // Included for completeness
  49320. 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
  49321. return canPlay.some(function (canItPlay) {
  49322. return /maybe|probably/i.test(video.canPlayType(canItPlay));
  49323. });
  49324. }();
  49325. Hls$1.supportsNativeDash = function () {
  49326. if (!videojs$1.getTech('Html5').isSupported()) {
  49327. return false;
  49328. }
  49329. return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
  49330. }();
  49331. Hls$1.supportsTypeNatively = function (type) {
  49332. if (type === 'hls') {
  49333. return Hls$1.supportsNativeHls;
  49334. }
  49335. if (type === 'dash') {
  49336. return Hls$1.supportsNativeDash;
  49337. }
  49338. return false;
  49339. };
  49340. /**
  49341. * HLS is a source handler, not a tech. Make sure attempts to use it
  49342. * as one do not cause exceptions.
  49343. */
  49344. Hls$1.isSupported = function () {
  49345. return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  49346. };
  49347. var Component$1 = videojs$1.getComponent('Component');
  49348. /**
  49349. * The Hls Handler object, where we orchestrate all of the parts
  49350. * of HLS to interact with video.js
  49351. *
  49352. * @class HlsHandler
  49353. * @extends videojs.Component
  49354. * @param {Object} source the soruce object
  49355. * @param {Tech} tech the parent tech object
  49356. * @param {Object} options optional and required options
  49357. */
  49358. var HlsHandler = function (_Component) {
  49359. inherits$1(HlsHandler, _Component);
  49360. function HlsHandler(source, tech, options) {
  49361. classCallCheck$1(this, HlsHandler); // tech.player() is deprecated but setup a reference to HLS for
  49362. // backwards-compatibility
  49363. var _this = possibleConstructorReturn$1(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls));
  49364. if (tech.options_ && tech.options_.playerId) {
  49365. var _player = videojs$1(tech.options_.playerId);
  49366. if (!_player.hasOwnProperty('hls')) {
  49367. Object.defineProperty(_player, 'hls', {
  49368. get: function get$$1() {
  49369. videojs$1.log.warn('player.hls is deprecated. Use player.tech().hls instead.');
  49370. tech.trigger({
  49371. type: 'usage',
  49372. name: 'hls-player-access'
  49373. });
  49374. return _this;
  49375. },
  49376. configurable: true
  49377. });
  49378. } // Set up a reference to the HlsHandler from player.vhs. This allows users to start
  49379. // migrating from player.tech_.hls... to player.vhs... for API access. Although this
  49380. // isn't the most appropriate form of reference for video.js (since all APIs should
  49381. // be provided through core video.js), it is a common pattern for plugins, and vhs
  49382. // will act accordingly.
  49383. _player.vhs = _this; // deprecated, for backwards compatibility
  49384. _player.dash = _this;
  49385. _this.player_ = _player;
  49386. }
  49387. _this.tech_ = tech;
  49388. _this.source_ = source;
  49389. _this.stats = {};
  49390. _this.setOptions_();
  49391. if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
  49392. tech.overrideNativeAudioTracks(true);
  49393. tech.overrideNativeVideoTracks(true);
  49394. } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
  49395. // overriding native HLS only works if audio tracks have been emulated
  49396. // error early if we're misconfigured
  49397. throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
  49398. } // listen for fullscreenchange events for this player so that we
  49399. // can adjust our quality selection quickly
  49400. _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
  49401. var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
  49402. if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
  49403. _this.masterPlaylistController_.smoothQualityChange_();
  49404. }
  49405. }); // Handle seeking when looping - middleware doesn't handle this seek event from the tech
  49406. _this.on(_this.tech_, 'seeking', function () {
  49407. if (this.tech_.currentTime() === 0 && this.tech_.player_.loop()) {
  49408. this.setCurrentTime(0);
  49409. }
  49410. });
  49411. _this.on(_this.tech_, 'error', function () {
  49412. if (this.masterPlaylistController_) {
  49413. this.masterPlaylistController_.pauseLoading();
  49414. }
  49415. });
  49416. _this.on(_this.tech_, 'play', _this.play);
  49417. return _this;
  49418. }
  49419. createClass$1(HlsHandler, [{
  49420. key: 'setOptions_',
  49421. value: function setOptions_() {
  49422. var _this2 = this; // defaults
  49423. this.options_.withCredentials = this.options_.withCredentials || false;
  49424. this.options_.handleManifestRedirects = this.options_.handleManifestRedirects || false;
  49425. this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
  49426. this.options_.smoothQualityChange = this.options_.smoothQualityChange || false;
  49427. this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
  49428. this.options_.customTagParsers = this.options_.customTagParsers || [];
  49429. this.options_.customTagMappers = this.options_.customTagMappers || [];
  49430. this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
  49431. if (typeof this.options_.blacklistDuration !== 'number') {
  49432. this.options_.blacklistDuration = 5 * 60;
  49433. }
  49434. if (typeof this.options_.bandwidth !== 'number') {
  49435. if (this.options_.useBandwidthFromLocalStorage) {
  49436. var storedObject = getVhsLocalStorage();
  49437. if (storedObject && storedObject.bandwidth) {
  49438. this.options_.bandwidth = storedObject.bandwidth;
  49439. this.tech_.trigger({
  49440. type: 'usage',
  49441. name: 'hls-bandwidth-from-local-storage'
  49442. });
  49443. }
  49444. if (storedObject && storedObject.throughput) {
  49445. this.options_.throughput = storedObject.throughput;
  49446. this.tech_.trigger({
  49447. type: 'usage',
  49448. name: 'hls-throughput-from-local-storage'
  49449. });
  49450. }
  49451. }
  49452. } // if bandwidth was not set by options or pulled from local storage, start playlist
  49453. // selection at a reasonable bandwidth
  49454. if (typeof this.options_.bandwidth !== 'number') {
  49455. this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
  49456. } // If the bandwidth number is unchanged from the initial setting
  49457. // then this takes precedence over the enableLowInitialPlaylist option
  49458. this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
  49459. ['withCredentials', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys'].forEach(function (option) {
  49460. if (typeof _this2.source_[option] !== 'undefined') {
  49461. _this2.options_[option] = _this2.source_[option];
  49462. }
  49463. });
  49464. this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
  49465. }
  49466. /**
  49467. * called when player.src gets called, handle a new source
  49468. *
  49469. * @param {Object} src the source object to handle
  49470. */
  49471. }, {
  49472. key: 'src',
  49473. value: function src(_src, type) {
  49474. var _this3 = this; // do nothing if the src is falsey
  49475. if (!_src) {
  49476. return;
  49477. }
  49478. this.setOptions_(); // add master playlist controller options
  49479. this.options_.url = this.source_.src;
  49480. this.options_.tech = this.tech_;
  49481. this.options_.externHls = Hls$1;
  49482. this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update both the tech and call our own
  49483. // setCurrentTime function. This is needed because "seeking" events aren't always
  49484. // reliable. External seeks (via the player object) are handled via middleware.
  49485. this.options_.seekTo = function (time) {
  49486. _this3.tech_.setCurrentTime(time);
  49487. _this3.setCurrentTime(time);
  49488. };
  49489. this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
  49490. this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, {
  49491. seekable: function seekable$$1() {
  49492. return _this3.seekable();
  49493. },
  49494. media: function media() {
  49495. return _this3.masterPlaylistController_.media();
  49496. }
  49497. }));
  49498. this.masterPlaylistController_.on('error', function () {
  49499. var player = videojs$1.players[_this3.tech_.options_.playerId];
  49500. player.error(_this3.masterPlaylistController_.error);
  49501. }); // `this` in selectPlaylist should be the HlsHandler for backwards
  49502. // compatibility with < v2
  49503. this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this);
  49504. this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
  49505. this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
  49506. this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist
  49507. // controller. Using a custom property for backwards compatibility
  49508. // with < v2
  49509. Object.defineProperties(this, {
  49510. selectPlaylist: {
  49511. get: function get$$1() {
  49512. return this.masterPlaylistController_.selectPlaylist;
  49513. },
  49514. set: function set$$1(selectPlaylist) {
  49515. this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
  49516. }
  49517. },
  49518. throughput: {
  49519. get: function get$$1() {
  49520. return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
  49521. },
  49522. set: function set$$1(throughput) {
  49523. this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
  49524. // for the cumulative average
  49525. this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
  49526. }
  49527. },
  49528. bandwidth: {
  49529. get: function get$$1() {
  49530. return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
  49531. },
  49532. set: function set$$1(bandwidth) {
  49533. this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
  49534. // `count` is set to zero that current value of `rate` isn't included
  49535. // in the cumulative average
  49536. this.masterPlaylistController_.mainSegmentLoader_.throughput = {
  49537. rate: 0,
  49538. count: 0
  49539. };
  49540. }
  49541. },
  49542. /**
  49543. * `systemBandwidth` is a combination of two serial processes bit-rates. The first
  49544. * is the network bitrate provided by `bandwidth` and the second is the bitrate of
  49545. * the entire process after that - decryption, transmuxing, and appending - provided
  49546. * by `throughput`.
  49547. *
  49548. * Since the two process are serial, the overall system bandwidth is given by:
  49549. * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
  49550. */
  49551. systemBandwidth: {
  49552. get: function get$$1() {
  49553. var invBandwidth = 1 / (this.bandwidth || 1);
  49554. var invThroughput = void 0;
  49555. if (this.throughput > 0) {
  49556. invThroughput = 1 / this.throughput;
  49557. } else {
  49558. invThroughput = 0;
  49559. }
  49560. var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
  49561. return systemBitrate;
  49562. },
  49563. set: function set$$1() {
  49564. videojs$1.log.error('The "systemBandwidth" property is read-only');
  49565. }
  49566. }
  49567. });
  49568. if (this.options_.bandwidth) {
  49569. this.bandwidth = this.options_.bandwidth;
  49570. }
  49571. if (this.options_.throughput) {
  49572. this.throughput = this.options_.throughput;
  49573. }
  49574. Object.defineProperties(this.stats, {
  49575. bandwidth: {
  49576. get: function get$$1() {
  49577. return _this3.bandwidth || 0;
  49578. },
  49579. enumerable: true
  49580. },
  49581. mediaRequests: {
  49582. get: function get$$1() {
  49583. return _this3.masterPlaylistController_.mediaRequests_() || 0;
  49584. },
  49585. enumerable: true
  49586. },
  49587. mediaRequestsAborted: {
  49588. get: function get$$1() {
  49589. return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
  49590. },
  49591. enumerable: true
  49592. },
  49593. mediaRequestsTimedout: {
  49594. get: function get$$1() {
  49595. return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
  49596. },
  49597. enumerable: true
  49598. },
  49599. mediaRequestsErrored: {
  49600. get: function get$$1() {
  49601. return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
  49602. },
  49603. enumerable: true
  49604. },
  49605. mediaTransferDuration: {
  49606. get: function get$$1() {
  49607. return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
  49608. },
  49609. enumerable: true
  49610. },
  49611. mediaBytesTransferred: {
  49612. get: function get$$1() {
  49613. return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
  49614. },
  49615. enumerable: true
  49616. },
  49617. mediaSecondsLoaded: {
  49618. get: function get$$1() {
  49619. return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
  49620. },
  49621. enumerable: true
  49622. },
  49623. buffered: {
  49624. get: function get$$1() {
  49625. return timeRangesToArray(_this3.tech_.buffered());
  49626. },
  49627. enumerable: true
  49628. },
  49629. currentTime: {
  49630. get: function get$$1() {
  49631. return _this3.tech_.currentTime();
  49632. },
  49633. enumerable: true
  49634. },
  49635. currentSource: {
  49636. get: function get$$1() {
  49637. return _this3.tech_.currentSource_;
  49638. },
  49639. enumerable: true
  49640. },
  49641. currentTech: {
  49642. get: function get$$1() {
  49643. return _this3.tech_.name_;
  49644. },
  49645. enumerable: true
  49646. },
  49647. duration: {
  49648. get: function get$$1() {
  49649. return _this3.tech_.duration();
  49650. },
  49651. enumerable: true
  49652. },
  49653. master: {
  49654. get: function get$$1() {
  49655. return _this3.playlists.master;
  49656. },
  49657. enumerable: true
  49658. },
  49659. playerDimensions: {
  49660. get: function get$$1() {
  49661. return _this3.tech_.currentDimensions();
  49662. },
  49663. enumerable: true
  49664. },
  49665. seekable: {
  49666. get: function get$$1() {
  49667. return timeRangesToArray(_this3.tech_.seekable());
  49668. },
  49669. enumerable: true
  49670. },
  49671. timestamp: {
  49672. get: function get$$1() {
  49673. return Date.now();
  49674. },
  49675. enumerable: true
  49676. },
  49677. videoPlaybackQuality: {
  49678. get: function get$$1() {
  49679. return _this3.tech_.getVideoPlaybackQuality();
  49680. },
  49681. enumerable: true
  49682. }
  49683. });
  49684. this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
  49685. this.tech_.on('bandwidthupdate', function () {
  49686. if (_this3.options_.useBandwidthFromLocalStorage) {
  49687. updateVhsLocalStorage({
  49688. bandwidth: _this3.bandwidth,
  49689. throughput: Math.round(_this3.throughput)
  49690. });
  49691. }
  49692. });
  49693. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  49694. // Add the manual rendition mix-in to HlsHandler
  49695. renditionSelectionMixin(_this3);
  49696. setupEmeOptions(_this3);
  49697. }); // the bandwidth of the primary segment loader is our best
  49698. // estimate of overall bandwidth
  49699. this.on(this.masterPlaylistController_, 'progress', function () {
  49700. this.tech_.trigger('progress');
  49701. });
  49702. this.tech_.ready(function () {
  49703. return _this3.setupQualityLevels_();
  49704. }); // do nothing if the tech has been disposed already
  49705. // this can occur if someone sets the src in player.ready(), for instance
  49706. if (!this.tech_.el()) {
  49707. return;
  49708. }
  49709. this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource));
  49710. }
  49711. /**
  49712. * Initializes the quality levels and sets listeners to update them.
  49713. *
  49714. * @method setupQualityLevels_
  49715. * @private
  49716. */
  49717. }, {
  49718. key: 'setupQualityLevels_',
  49719. value: function setupQualityLevels_() {
  49720. var _this4 = this;
  49721. var player = videojs$1.players[this.tech_.options_.playerId];
  49722. if (player && player.qualityLevels) {
  49723. this.qualityLevels_ = player.qualityLevels();
  49724. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  49725. handleHlsLoadedMetadata(_this4.qualityLevels_, _this4);
  49726. });
  49727. this.playlists.on('mediachange', function () {
  49728. handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists);
  49729. });
  49730. }
  49731. }
  49732. /**
  49733. * Begin playing the video.
  49734. */
  49735. }, {
  49736. key: 'play',
  49737. value: function play() {
  49738. this.masterPlaylistController_.play();
  49739. }
  49740. /**
  49741. * a wrapper around the function in MasterPlaylistController
  49742. */
  49743. }, {
  49744. key: 'setCurrentTime',
  49745. value: function setCurrentTime(currentTime) {
  49746. this.masterPlaylistController_.setCurrentTime(currentTime);
  49747. }
  49748. /**
  49749. * a wrapper around the function in MasterPlaylistController
  49750. */
  49751. }, {
  49752. key: 'duration',
  49753. value: function duration$$1() {
  49754. return this.masterPlaylistController_.duration();
  49755. }
  49756. /**
  49757. * a wrapper around the function in MasterPlaylistController
  49758. */
  49759. }, {
  49760. key: 'seekable',
  49761. value: function seekable$$1() {
  49762. return this.masterPlaylistController_.seekable();
  49763. }
  49764. /**
  49765. * Abort all outstanding work and cleanup.
  49766. */
  49767. }, {
  49768. key: 'dispose',
  49769. value: function dispose() {
  49770. if (this.playbackWatcher_) {
  49771. this.playbackWatcher_.dispose();
  49772. }
  49773. if (this.masterPlaylistController_) {
  49774. this.masterPlaylistController_.dispose();
  49775. }
  49776. if (this.qualityLevels_) {
  49777. this.qualityLevels_.dispose();
  49778. }
  49779. if (this.player_) {
  49780. delete this.player_.vhs;
  49781. delete this.player_.dash;
  49782. delete this.player_.hls;
  49783. }
  49784. if (this.tech_ && this.tech_.hls) {
  49785. delete this.tech_.hls;
  49786. }
  49787. get$1(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this);
  49788. }
  49789. }, {
  49790. key: 'convertToProgramTime',
  49791. value: function convertToProgramTime(time, callback) {
  49792. return getProgramTime({
  49793. playlist: this.masterPlaylistController_.media(),
  49794. time: time,
  49795. callback: callback
  49796. });
  49797. } // the player must be playing before calling this
  49798. }, {
  49799. key: 'seekToProgramTime',
  49800. value: function seekToProgramTime$$1(programTime, callback) {
  49801. var pauseAfterSeek = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
  49802. var retryCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2;
  49803. return seekToProgramTime({
  49804. programTime: programTime,
  49805. playlist: this.masterPlaylistController_.media(),
  49806. retryCount: retryCount,
  49807. pauseAfterSeek: pauseAfterSeek,
  49808. seekTo: this.options_.seekTo,
  49809. tech: this.options_.tech,
  49810. callback: callback
  49811. });
  49812. }
  49813. }]);
  49814. return HlsHandler;
  49815. }(Component$1);
  49816. /**
  49817. * The Source Handler object, which informs video.js what additional
  49818. * MIME types are supported and sets up playback. It is registered
  49819. * automatically to the appropriate tech based on the capabilities of
  49820. * the browser it is running in. It is not necessary to use or modify
  49821. * this object in normal usage.
  49822. */
  49823. var HlsSourceHandler = {
  49824. name: 'videojs-http-streaming',
  49825. VERSION: version$3,
  49826. canHandleSource: function canHandleSource(srcObj) {
  49827. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  49828. var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
  49829. return HlsSourceHandler.canPlayType(srcObj.type, localOptions);
  49830. },
  49831. handleSource: function handleSource(source, tech) {
  49832. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  49833. var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
  49834. tech.hls = new HlsHandler(source, tech, localOptions);
  49835. tech.hls.xhr = xhrFactory();
  49836. tech.hls.src(source.src, source.type);
  49837. return tech.hls;
  49838. },
  49839. canPlayType: function canPlayType(type) {
  49840. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  49841. var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options),
  49842. overrideNative = _videojs$mergeOptions.hls.overrideNative;
  49843. var supportedType = simpleTypeFromSourceType(type);
  49844. var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative);
  49845. return canUseMsePlayback ? 'maybe' : '';
  49846. }
  49847. };
  49848. if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') {
  49849. videojs$1.MediaSource = MediaSource;
  49850. videojs$1.URL = URL$1;
  49851. } // register source handlers with the appropriate techs
  49852. if (MediaSource.supportsNativeMediaSources()) {
  49853. videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0);
  49854. }
  49855. videojs$1.HlsHandler = HlsHandler;
  49856. videojs$1.HlsSourceHandler = HlsSourceHandler;
  49857. videojs$1.Hls = Hls$1;
  49858. if (!videojs$1.use) {
  49859. videojs$1.registerComponent('Hls', Hls$1);
  49860. }
  49861. videojs$1.options.hls = videojs$1.options.hls || {};
  49862. if (videojs$1.registerPlugin) {
  49863. videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError);
  49864. } else {
  49865. videojs$1.plugin('reloadSourceOnError', reloadSourceOnError);
  49866. }
  49867. return videojs$1;
  49868. }));