video.js 1.8 MB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182351833518435185351863518735188351893519035191351923519335194351953519635197351983519935200352013520235203352043520535206352073520835209352103521135212352133521435215352163521735218352193522035221352223522335224352253522635227352283522935230352313523235233352343523535236352373523835239352403524135242352433524435245352463524735248352493525035251352523525335254352553525635257352583525935260352613526235263352643526535266352673526835269352703527135272352733527435275352763527735278352793528035281352823528335284352853528635287352883528935290352913529235293352943529535296352973529835299353003530135302353033530435305353063530735308353093531035311353123531335314353153531635317353183531935320353213532235323353243532535326353273532835329353303533135332353333533435335353363533735338353393534035341353423534335344353453534635347353483534935350353513535235353353543535535356353573535835359353603536135362353633536435365353663536735368353693537035371353723537335374353753537635377353783537935380353813538235383353843538535386353873538835389353903539135392353933539435395353963539735398353993540035401354023540335404354053540635407354083540935410354113541235413354143541535416354173541835419354203542135422354233542435425354263542735428354293543035431354323543335434354353543635437354383543935440354413544235443354443544535446354473544835449354503545135452354533545435455354563545735458354593546035461354623546335464354653546635467354683546935470354713547235473354743547535476354773547835479354803548135482354833548435485354863548735488354893549035491354923549335494354953549635497354983549935500355013550235503355043550535506355073550835509355103551135512355133551435515355163551735518355193552035521355223552335524355253552635527355283552935530355313553235533355343553535536355373553835539355403554135542355433554435545355463554735548355493555035551355523555335554355553555635557355583555935560355613556235563355643556535566355673556835569355703557135572355733557435575355763557735578355793558035581355823558335584355853558635587355883558935590355913559235593355943559535596355973559835599356003560135602356033560435605356063560735608356093561035611356123561335614356153561635617356183561935620356213562235623356243562535626356273562835629356303563135632356333563435635356363563735638356393564035641356423564335644356453564635647356483564935650356513565235653356543565535656356573565835659356603566135662356633566435665356663566735668356693567035671356723567335674356753567635677356783567935680356813568235683356843568535686356873568835689356903569135692356933569435695356963569735698356993570035701357023570335704357053570635707357083570935710357113571235713357143571535716357173571835719357203572135722357233572435725357263572735728357293573035731357323573335734357353573635737357383573935740357413574235743357443574535746357473574835749357503575135752357533575435755357563575735758357593576035761357623576335764357653576635767357683576935770357713577235773357743577535776357773577835779357803578135782357833578435785357863578735788357893579035791357923579335794357953579635797357983579935800358013580235803358043580535806358073580835809358103581135812358133581435815358163581735818358193582035821358223582335824358253582635827358283582935830358313583235833358343583535836358373583835839358403584135842358433584435845358463584735848358493585035851358523585335854358553585635857358583585935860358613586235863358643586535866358673586835869358703587135872358733587435875358763587735878358793588035881358823588335884358853588635887358883588935890358913589235893358943589535896358973589835899359003590135902359033590435905359063590735908359093591035911359123591335914359153591635917359183591935920359213592235923359243592535926359273592835929359303593135932359333593435935359363593735938359393594035941359423594335944359453594635947359483594935950359513595235953359543595535956359573595835959359603596135962359633596435965359663596735968359693597035971359723597335974359753597635977359783597935980359813598235983359843598535986359873598835989359903599135992359933599435995359963599735998359993600036001360023600336004360053600636007360083600936010360113601236013360143601536016360173601836019360203602136022360233602436025360263602736028360293603036031360323603336034360353603636037360383603936040360413604236043360443604536046360473604836049360503605136052360533605436055360563605736058360593606036061360623606336064360653606636067360683606936070360713607236073360743607536076360773607836079360803608136082360833608436085360863608736088360893609036091360923609336094360953609636097360983609936100361013610236103361043610536106361073610836109361103611136112361133611436115361163611736118361193612036121361223612336124361253612636127361283612936130361313613236133361343613536136361373613836139361403614136142361433614436145361463614736148361493615036151361523615336154361553615636157361583615936160361613616236163361643616536166361673616836169361703617136172361733617436175361763617736178361793618036181361823618336184361853618636187361883618936190361913619236193361943619536196361973619836199362003620136202362033620436205362063620736208362093621036211362123621336214362153621636217362183621936220362213622236223362243622536226362273622836229362303623136232362333623436235362363623736238362393624036241362423624336244362453624636247362483624936250362513625236253362543625536256362573625836259362603626136262362633626436265362663626736268362693627036271362723627336274362753627636277362783627936280362813628236283362843628536286362873628836289362903629136292362933629436295362963629736298362993630036301363023630336304363053630636307363083630936310363113631236313363143631536316363173631836319363203632136322363233632436325363263632736328363293633036331363323633336334363353633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353383543835538356383573835838359383603836138362383633836438365383663836738368383693837038371383723837338374383753837638377383783837938380383813838238383383843838538386383873838838389383903839138392383933839438395383963839738398383993840038401384023840338404384053840638407384083840938410384113841238413384143841538416384173841838419384203842138422384233842438425384263842738428384293843038431384323843338434384353843638437384383843938440384413844238443384443844538446384473844838449384503845138452384533845438455384563845738458384593846038461384623846338464384653846638467384683846938470384713847238473384743847538476384773847838479384803848138482384833848438485384863848738488384893849038491384923849338494384953849638497384983849938500385013850238503385043850538506385073850838509385103851138512385133851438515385163851738518385193852038521385223852338524385253852638527385283852938530385313853238533385343853538536385373853838539385403854138542385433854438545385463854738548385493855038551385523855338554385553855638557385583855938560385613856238563385643856538566385673856838569385703857138572385733857438575385763857738578385793858038581385823858338584385853858638587385883858938590385913859238593385943859538596385973859838599386003860138602386033860438605386063860738608386093861038611386123861338614386153861638617386183861938620386213862238623386243862538626386273862838629386303863138632386333863438635386363863738638386393864038641386423864338644386453864638647386483864938650386513865238653386543865538656386573865838659386603866138662386633866438665386663866738668386693867038671386723867338674386753867638677386783867938680386813868238683386843868538686386873868838689386903869138692386933869438695386963869738698386993870038701387023870338704387053870638707387083870938710387113871238713387143871538716387173871838719387203872138722387233872438725387263872738728387293873038731387323873338734387353873638737387383873938740387413874238743387443874538746387473874838749387503875138752387533875438755387563875738758387593876038761387623876338764387653876638767387683876938770387713877238773387743877538776387773877838779387803878138782387833878438785387863878738788387893879038791387923879338794387953879638797387983879938800388013880238803388043880538806388073880838809388103881138812388133881438815388163881738818388193882038821388223882338824388253882638827388283882938830388313883238833388343883538836388373883838839388403884138842388433884438845388463884738848388493885038851388523885338854388553885638857388583885938860388613886238863388643886538866388673886838869388703887138872388733887438875388763887738878388793888038881388823888338884388853888638887388883888938890388913889238893388943889538896388973889838899389003890138902389033890438905389063890738908389093891038911389123891338914389153891638917389183891938920389213892238923389243892538926389273892838929389303893138932389333893438935389363893738938389393894038941389423894338944389453894638947389483894938950389513895238953389543895538956389573895838959389603896138962389633896438965389663896738968389693897038971389723897338974389753897638977389783897938980389813898238983389843898538986389873898838989389903899138992389933899438995389963899738998389993900039001390023900339004390053900639007390083900939010390113901239013390143901539016390173901839019390203902139022390233902439025390263902739028390293903039031390323903339034390353903639037390383903939040390413904239043390443904539046390473904839049390503905139052390533905439055390563905739058390593906039061390623906339064390653906639067390683906939070390713907239073390743907539076390773907839079390803908139082390833908439085390863908739088390893909039091390923909339094390953909639097390983909939100391013910239103391043910539106391073910839109391103911139112391133911439115391163911739118391193912039121391223912339124391253912639127391283912939130391313913239133391343913539136391373913839139391403914139142391433914439145391463914739148391493915039151391523915339154391553915639157391583915939160391613916239163391643916539166391673916839169391703917139172391733917439175391763917739178391793918039181391823918339184391853918639187391883918939190391913919239193391943919539196391973919839199392003920139202392033920439205392063920739208392093921039211392123921339214392153921639217392183921939220392213922239223392243922539226392273922839229392303923139232392333923439235392363923739238392393924039241392423924339244392453924639247392483924939250392513925239253392543925539256392573925839259392603926139262392633926439265392663926739268392693927039271392723927339274392753927639277392783927939280392813928239283392843928539286392873928839289392903929139292392933929439295392963929739298392993930039301393023930339304393053930639307393083930939310393113931239313393143931539316393173931839319393203932139322393233932439325393263932739328393293933039331393323933339334393353933639337393383933939340393413934239343393443934539346393473934839349393503935139352393533935439355393563935739358393593936039361393623936339364393653936639367393683936939370393713937239373393743937539376393773937839379393803938139382393833938439385393863938739388393893939039391393923939339394393953939639397393983939939400394013940239403394043940539406394073940839409394103941139412394133941439415394163941739418394193942039421394223942339424394253942639427394283942939430394313943239433394343943539436394373943839439394403944139442394433944439445394463944739448394493945039451394523945339454394553945639457394583945939460394613946239463394643946539466394673946839469394703947139472394733947439475394763947739478394793948039481394823948339484394853948639487394883948939490394913949239493394943949539496394973949839499395003950139502395033950439505395063950739508395093951039511395123951339514395153951639517395183951939520395213952239523395243952539526395273952839529395303953139532395333953439535395363953739538395393954039541395423954339544395453954639547395483954939550395513955239553395543955539556395573955839559395603956139562395633956439565395663956739568395693957039571395723957339574395753957639577395783957939580395813958239583395843958539586395873958839589395903959139592395933959439595395963959739598395993960039601396023960339604396053960639607396083960939610396113961239613396143961539616396173961839619396203962139622396233962439625396263962739628396293963039631396323963339634396353963639637396383963939640396413964239643396443964539646396473964839649396503965139652396533965439655396563965739658396593966039661396623966339664396653966639667396683966939670396713967239673396743967539676396773967839679396803968139682396833968439685396863968739688396893969039691396923969339694396953969639697396983969939700397013970239703397043970539706397073970839709397103971139712397133971439715397163971739718397193972039721397223972339724397253972639727397283972939730397313973239733397343973539736397373973839739397403974139742397433974439745397463974739748397493975039751397523975339754397553975639757397583975939760397613976239763397643976539766397673976839769397703977139772397733977439775397763977739778397793978039781397823978339784397853978639787397883978939790397913979239793397943979539796397973979839799398003980139802398033980439805398063980739808398093981039811398123981339814398153981639817398183981939820398213982239823398243982539826398273982839829398303983139832398333983439835398363983739838398393984039841398423984339844398453984639847398483984939850398513985239853398543985539856398573985839859398603986139862398633986439865398663986739868398693987039871398723987339874398753987639877398783987939880398813988239883398843988539886398873988839889398903989139892398933989439895398963989739898398993990039901399023990339904399053990639907399083990939910399113991239913399143991539916399173991839919399203992139922399233992439925399263992739928399293993039931399323993339934399353993639937399383993939940399413994239943399443994539946399473994839949399503995139952399533995439955399563995739958399593996039961399623996339964399653996639967399683996939970399713997239973399743997539976399773997839979399803998139982399833998439985399863998739988399893999039991399923999339994399953999639997399983999940000400014000240003400044000540006400074000840009400104001140012400134001440015400164001740018400194002040021400224002340024400254002640027400284002940030400314003240033400344003540036400374003840039400404004140042400434004440045400464004740048400494005040051400524005340054400554005640057400584005940060400614006240063400644006540066400674006840069400704007140072400734007440075400764007740078400794008040081400824008340084400854008640087400884008940090400914009240093400944009540096400974009840099401004010140102401034010440105401064010740108401094011040111401124011340114401154011640117401184011940120401214012240123401244012540126401274012840129401304013140132401334013440135401364013740138401394014040141401424014340144401454014640147401484014940150401514015240153401544015540156401574015840159401604016140162401634016440165401664016740168401694017040171401724017340174401754017640177401784017940180401814018240183401844018540186401874018840189401904019140192401934019440195401964019740198401994020040201402024020340204402054020640207402084020940210402114021240213402144021540216402174021840219402204022140222402234022440225402264022740228402294023040231402324023340234402354023640237402384023940240402414024240243402444024540246402474024840249402504025140252402534025440255402564025740258402594026040261402624026340264402654026640267402684026940270402714027240273402744027540276402774027840279402804028140282402834028440285402864028740288402894029040291402924029340294402954029640297402984029940300403014030240303403044030540306403074030840309403104031140312403134031440315403164031740318403194032040321403224032340324403254032640327403284032940330403314033240333403344033540336403374033840339403404034140342403434034440345403464034740348403494035040351403524035340354403554035640357403584035940360403614036240363403644036540366403674036840369403704037140372403734037440375403764037740378403794038040381403824038340384403854038640387403884038940390403914039240393403944039540396403974039840399404004040140402404034040440405404064040740408404094041040411404124041340414404154041640417404184041940420404214042240423404244042540426404274042840429404304043140432404334043440435404364043740438404394044040441404424044340444404454044640447404484044940450404514045240453404544045540456404574045840459404604046140462404634046440465404664046740468404694047040471404724047340474404754047640477404784047940480404814048240483404844048540486404874048840489404904049140492404934049440495404964049740498404994050040501405024050340504405054050640507405084050940510405114051240513405144051540516405174051840519405204052140522405234052440525405264052740528405294053040531405324053340534405354053640537405384053940540405414054240543405444054540546405474054840549405504055140552405534055440555405564055740558405594056040561405624056340564405654056640567405684056940570405714057240573405744057540576405774057840579405804058140582405834058440585405864058740588405894059040591405924059340594405954059640597405984059940600406014060240603406044060540606406074060840609406104061140612406134061440615406164061740618406194062040621406224062340624406254062640627406284062940630406314063240633406344063540636406374063840639406404064140642406434064440645406464064740648406494065040651406524065340654406554065640657406584065940660406614066240663406644066540666406674066840669406704067140672406734067440675406764067740678406794068040681406824068340684406854068640687406884068940690406914069240693406944069540696406974069840699407004070140702407034070440705407064070740708407094071040711407124071340714407154071640717407184071940720407214072240723407244072540726407274072840729407304073140732407334073440735407364073740738407394074040741407424074340744407454074640747407484074940750407514075240753407544075540756407574075840759407604076140762407634076440765407664076740768407694077040771407724077340774407754077640777407784077940780407814078240783407844078540786407874078840789407904079140792407934079440795407964079740798407994080040801408024080340804408054080640807408084080940810408114081240813408144081540816408174081840819408204082140822408234082440825408264082740828408294083040831408324083340834408354083640837408384083940840408414084240843408444084540846408474084840849408504085140852408534085440855408564085740858408594086040861408624086340864408654086640867408684086940870408714087240873408744087540876408774087840879408804088140882408834088440885408864088740888408894089040891408924089340894408954089640897408984089940900409014090240903409044090540906409074090840909409104091140912409134091440915409164091740918409194092040921409224092340924409254092640927409284092940930409314093240933409344093540936409374093840939409404094140942409434094440945409464094740948409494095040951409524095340954409554095640957409584095940960409614096240963409644096540966409674096840969409704097140972409734097440975409764097740978409794098040981409824098340984409854098640987409884098940990409914099240993409944099540996409974099840999410004100141002410034100441005410064100741008410094101041011410124101341014410154101641017410184101941020410214102241023410244102541026410274102841029410304103141032410334103441035410364103741038410394104041041410424104341044410454104641047410484104941050410514105241053410544105541056410574105841059410604106141062410634106441065410664106741068410694107041071410724107341074410754107641077410784107941080410814108241083410844108541086410874108841089410904109141092410934109441095410964109741098410994110041101411024110341104411054110641107411084110941110411114111241113411144111541116411174111841119411204112141122411234112441125411264112741128411294113041131411324113341134411354113641137411384113941140411414114241143411444114541146411474114841149411504115141152411534115441155411564115741158411594116041161411624116341164411654116641167411684116941170411714117241173411744117541176411774117841179411804118141182411834118441185411864118741188411894119041191411924119341194411954119641197411984119941200412014120241203412044120541206412074120841209412104121141212412134121441215412164121741218412194122041221412224122341224412254122641227412284122941230412314123241233412344123541236412374123841239412404124141242412434124441245412464124741248412494125041251412524125341254412554125641257412584125941260412614126241263412644126541266412674126841269412704127141272412734127441275412764127741278412794128041281412824128341284412854128641287412884128941290412914129241293412944129541296412974129841299413004130141302413034130441305413064130741308413094131041311413124131341314413154131641317413184131941320413214132241323413244132541326413274132841329413304133141332413334133441335413364133741338413394134041341413424134341344413454134641347413484134941350413514135241353413544135541356413574135841359413604136141362413634136441365413664136741368413694137041371413724137341374413754137641377413784137941380413814138241383413844138541386413874138841389413904139141392413934139441395413964139741398413994140041401414024140341404414054140641407414084140941410414114141241413414144141541416414174141841419414204142141422414234142441425414264142741428414294143041431414324143341434414354143641437414384143941440414414144241443414444144541446414474144841449414504145141452414534145441455414564145741458414594146041461414624146341464414654146641467414684146941470414714147241473414744147541476414774147841479414804148141482414834148441485414864148741488414894149041491414924149341494414954149641497414984149941500415014150241503415044150541506415074150841509415104151141512415134151441515415164151741518415194152041521415224152341524415254152641527415284152941530415314153241533415344153541536415374153841539415404154141542415434154441545415464154741548415494155041551415524155341554415554155641557415584155941560415614156241563415644156541566415674156841569415704157141572415734157441575415764157741578415794158041581415824158341584415854158641587415884158941590415914159241593415944159541596415974159841599416004160141602416034160441605416064160741608416094161041611416124161341614416154161641617416184161941620416214162241623416244162541626416274162841629416304163141632416334163441635416364163741638416394164041641416424164341644416454164641647416484164941650416514165241653416544165541656416574165841659416604166141662416634166441665416664166741668416694167041671416724167341674416754167641677416784167941680416814168241683416844168541686416874168841689416904169141692416934169441695416964169741698416994170041701417024170341704417054170641707417084170941710417114171241713417144171541716417174171841719417204172141722417234172441725417264172741728417294173041731417324173341734417354173641737417384173941740417414174241743417444174541746417474174841749417504175141752417534175441755417564175741758417594176041761417624176341764417654176641767417684176941770417714177241773417744177541776417774177841779417804178141782417834178441785417864178741788417894179041791417924179341794417954179641797417984179941800418014180241803418044180541806418074180841809418104181141812418134181441815418164181741818418194182041821418224182341824418254182641827418284182941830418314183241833418344183541836418374183841839418404184141842418434184441845418464184741848418494185041851418524185341854418554185641857418584185941860418614186241863418644186541866418674186841869418704187141872418734187441875418764187741878418794188041881418824188341884418854188641887418884188941890418914189241893418944189541896418974189841899419004190141902419034190441905419064190741908419094191041911419124191341914419154191641917419184191941920419214192241923419244192541926419274192841929419304193141932419334193441935419364193741938419394194041941419424194341944419454194641947419484194941950419514195241953419544195541956419574195841959419604196141962419634196441965419664196741968419694197041971419724197341974419754197641977419784197941980419814198241983419844198541986419874198841989419904199141992419934199441995419964199741998419994200042001420024200342004420054200642007420084200942010420114201242013420144201542016420174201842019420204202142022420234202442025420264202742028420294203042031420324203342034420354203642037420384203942040420414204242043420444204542046420474204842049420504205142052420534205442055420564205742058420594206042061420624206342064420654206642067420684206942070420714207242073420744207542076420774207842079420804208142082420834208442085420864208742088420894209042091420924209342094420954209642097420984209942100421014210242103421044210542106421074210842109421104211142112421134211442115421164211742118421194212042121421224212342124421254212642127421284212942130421314213242133421344213542136421374213842139421404214142142421434214442145421464214742148421494215042151421524215342154421554215642157421584215942160421614216242163421644216542166421674216842169421704217142172421734217442175421764217742178421794218042181421824218342184421854218642187421884218942190421914219242193421944219542196421974219842199422004220142202422034220442205422064220742208422094221042211422124221342214422154221642217422184221942220422214222242223422244222542226422274222842229422304223142232422334223442235422364223742238422394224042241422424224342244422454224642247422484224942250422514225242253422544225542256422574225842259422604226142262422634226442265422664226742268422694227042271422724227342274422754227642277422784227942280422814228242283422844228542286422874228842289422904229142292422934229442295422964229742298422994230042301423024230342304423054230642307423084230942310423114231242313423144231542316423174231842319423204232142322423234232442325423264232742328423294233042331423324233342334423354233642337423384233942340423414234242343423444234542346423474234842349423504235142352423534235442355423564235742358423594236042361423624236342364423654236642367423684236942370423714237242373423744237542376423774237842379423804238142382423834238442385423864238742388423894239042391423924239342394423954239642397423984239942400424014240242403424044240542406424074240842409424104241142412424134241442415424164241742418424194242042421424224242342424424254242642427424284242942430424314243242433424344243542436424374243842439424404244142442424434244442445424464244742448424494245042451424524245342454424554245642457424584245942460424614246242463424644246542466424674246842469424704247142472424734247442475424764247742478424794248042481424824248342484424854248642487424884248942490424914249242493424944249542496424974249842499425004250142502425034250442505425064250742508425094251042511425124251342514425154251642517425184251942520425214252242523425244252542526425274252842529425304253142532425334253442535425364253742538425394254042541425424254342544425454254642547425484254942550425514255242553425544255542556425574255842559425604256142562425634256442565425664256742568425694257042571425724257342574425754257642577425784257942580425814258242583425844258542586425874258842589425904259142592425934259442595425964259742598425994260042601426024260342604426054260642607426084260942610426114261242613426144261542616426174261842619426204262142622426234262442625426264262742628426294263042631426324263342634426354263642637426384263942640426414264242643426444264542646426474264842649426504265142652426534265442655426564265742658426594266042661426624266342664426654266642667426684266942670426714267242673426744267542676426774267842679426804268142682426834268442685426864268742688426894269042691426924269342694426954269642697426984269942700427014270242703427044270542706427074270842709427104271142712427134271442715427164271742718427194272042721427224272342724427254272642727427284272942730427314273242733427344273542736427374273842739427404274142742427434274442745427464274742748427494275042751427524275342754427554275642757427584275942760427614276242763427644276542766427674276842769427704277142772427734277442775427764277742778427794278042781427824278342784427854278642787427884278942790427914279242793427944279542796427974279842799428004280142802428034280442805428064280742808428094281042811428124281342814428154281642817428184281942820428214282242823428244282542826428274282842829428304283142832428334283442835428364283742838428394284042841428424284342844428454284642847428484284942850428514285242853428544285542856428574285842859428604286142862428634286442865428664286742868428694287042871428724287342874428754287642877428784287942880428814288242883428844288542886428874288842889428904289142892428934289442895428964289742898428994290042901429024290342904429054290642907429084290942910429114291242913429144291542916429174291842919429204292142922429234292442925429264292742928429294293042931429324293342934429354293642937429384293942940429414294242943429444294542946429474294842949429504295142952429534295442955429564295742958429594296042961429624296342964429654296642967429684296942970429714297242973429744297542976429774297842979429804298142982429834298442985429864298742988429894299042991429924299342994429954299642997429984299943000430014300243003430044300543006430074300843009430104301143012430134301443015430164301743018430194302043021430224302343024430254302643027430284302943030430314303243033430344303543036430374303843039430404304143042430434304443045430464304743048430494305043051430524305343054430554305643057430584305943060430614306243063430644306543066430674306843069430704307143072430734307443075430764307743078430794308043081430824308343084430854308643087430884308943090430914309243093430944309543096430974309843099431004310143102431034310443105431064310743108431094311043111431124311343114431154311643117431184311943120431214312243123431244312543126431274312843129431304313143132431334313443135431364313743138431394314043141431424314343144431454314643147431484314943150431514315243153431544315543156431574315843159431604316143162431634316443165431664316743168431694317043171431724317343174431754317643177431784317943180431814318243183431844318543186431874318843189431904319143192431934319443195431964319743198431994320043201432024320343204432054320643207432084320943210432114321243213432144321543216432174321843219432204322143222432234322443225432264322743228432294323043231432324323343234432354323643237432384323943240432414324243243432444324543246432474324843249432504325143252432534325443255432564325743258432594326043261432624326343264432654326643267432684326943270432714327243273432744327543276432774327843279432804328143282432834328443285432864328743288432894329043291432924329343294432954329643297432984329943300433014330243303433044330543306433074330843309433104331143312433134331443315433164331743318433194332043321433224332343324433254332643327433284332943330433314333243333433344333543336433374333843339433404334143342433434334443345433464334743348433494335043351433524335343354433554335643357433584335943360433614336243363433644336543366433674336843369433704337143372433734337443375433764337743378433794338043381433824338343384433854338643387433884338943390433914339243393433944339543396433974339843399434004340143402434034340443405434064340743408434094341043411434124341343414434154341643417434184341943420434214342243423434244342543426434274342843429434304343143432434334343443435434364343743438434394344043441434424344343444434454344643447434484344943450434514345243453434544345543456434574345843459434604346143462434634346443465434664346743468434694347043471434724347343474434754347643477434784347943480434814348243483434844348543486434874348843489434904349143492434934349443495434964349743498434994350043501435024350343504435054350643507435084350943510435114351243513435144351543516435174351843519435204352143522435234352443525435264352743528435294353043531435324353343534435354353643537435384353943540435414354243543435444354543546435474354843549435504355143552435534355443555435564355743558435594356043561435624356343564435654356643567435684356943570435714357243573435744357543576435774357843579435804358143582435834358443585435864358743588435894359043591435924359343594435954359643597435984359943600436014360243603436044360543606436074360843609436104361143612436134361443615436164361743618436194362043621436224362343624436254362643627436284362943630436314363243633436344363543636436374363843639436404364143642436434364443645436464364743648436494365043651436524365343654436554365643657436584365943660436614366243663436644366543666436674366843669436704367143672436734367443675436764367743678436794368043681436824368343684436854368643687436884368943690436914369243693436944369543696436974369843699437004370143702437034370443705437064370743708437094371043711437124371343714437154371643717437184371943720437214372243723437244372543726437274372843729437304373143732437334373443735437364373743738437394374043741437424374343744437454374643747437484374943750437514375243753437544375543756437574375843759437604376143762437634376443765437664376743768437694377043771437724377343774437754377643777437784377943780437814378243783437844378543786437874378843789437904379143792437934379443795437964379743798437994380043801438024380343804438054380643807438084380943810438114381243813438144381543816438174381843819438204382143822438234382443825438264382743828438294383043831438324383343834438354383643837438384383943840438414384243843438444384543846438474384843849438504385143852438534385443855438564385743858438594386043861438624386343864438654386643867438684386943870438714387243873438744387543876438774387843879438804388143882438834388443885438864388743888438894389043891438924389343894438954389643897438984389943900439014390243903439044390543906439074390843909439104391143912439134391443915439164391743918439194392043921439224392343924439254392643927439284392943930439314393243933439344393543936439374393843939439404394143942439434394443945439464394743948439494395043951439524395343954439554395643957439584395943960439614396243963439644396543966439674396843969439704397143972439734397443975439764397743978439794398043981439824398343984439854398643987439884398943990439914399243993439944399543996439974399843999440004400144002440034400444005440064400744008440094401044011440124401344014440154401644017440184401944020440214402244023440244402544026440274402844029440304403144032440334403444035440364403744038440394404044041440424404344044440454404644047440484404944050440514405244053440544405544056440574405844059440604406144062440634406444065440664406744068440694407044071440724407344074440754407644077440784407944080440814408244083440844408544086440874408844089440904409144092440934409444095440964409744098440994410044101441024410344104441054410644107441084410944110441114411244113441144411544116441174411844119441204412144122441234412444125441264412744128441294413044131441324413344134441354413644137441384413944140441414414244143441444414544146441474414844149441504415144152441534415444155441564415744158441594416044161441624416344164441654416644167441684416944170441714417244173441744417544176441774417844179441804418144182441834418444185441864418744188441894419044191441924419344194441954419644197441984419944200442014420244203442044420544206442074420844209442104421144212442134421444215442164421744218442194422044221442224422344224442254422644227442284422944230442314423244233442344423544236442374423844239442404424144242442434424444245442464424744248442494425044251442524425344254442554425644257442584425944260442614426244263442644426544266442674426844269442704427144272442734427444275442764427744278442794428044281442824428344284442854428644287442884428944290442914429244293442944429544296442974429844299443004430144302443034430444305443064430744308443094431044311443124431344314443154431644317443184431944320443214432244323443244432544326443274432844329443304433144332443334433444335443364433744338443394434044341443424434344344443454434644347443484434944350443514435244353443544435544356443574435844359443604436144362443634436444365443664436744368443694437044371443724437344374443754437644377443784437944380443814438244383443844438544386443874438844389443904439144392443934439444395443964439744398443994440044401444024440344404444054440644407444084440944410444114441244413444144441544416444174441844419444204442144422444234442444425444264442744428444294443044431444324443344434444354443644437444384443944440444414444244443444444444544446444474444844449444504445144452444534445444455444564445744458444594446044461444624446344464444654446644467444684446944470444714447244473444744447544476444774447844479444804448144482444834448444485444864448744488444894449044491444924449344494444954449644497444984449944500445014450244503445044450544506445074450844509445104451144512445134451444515445164451744518445194452044521445224452344524445254452644527445284452944530445314453244533445344453544536445374453844539445404454144542445434454444545445464454744548445494455044551445524455344554445554455644557445584455944560445614456244563445644456544566445674456844569445704457144572445734457444575445764457744578445794458044581445824458344584445854458644587445884458944590445914459244593445944459544596445974459844599446004460144602446034460444605446064460744608446094461044611446124461344614446154461644617446184461944620446214462244623446244462544626446274462844629446304463144632446334463444635446364463744638446394464044641446424464344644446454464644647446484464944650446514465244653446544465544656446574465844659446604466144662446634466444665446664466744668446694467044671446724467344674446754467644677446784467944680446814468244683446844468544686446874468844689446904469144692446934469444695446964469744698446994470044701447024470344704447054470644707447084470944710447114471244713447144471544716447174471844719447204472144722447234472444725447264472744728447294473044731447324473344734447354473644737447384473944740447414474244743447444474544746447474474844749447504475144752447534475444755447564475744758447594476044761447624476344764447654476644767447684476944770447714477244773447744477544776447774477844779447804478144782447834478444785447864478744788447894479044791447924479344794447954479644797447984479944800448014480244803448044480544806448074480844809448104481144812448134481444815448164481744818448194482044821448224482344824448254482644827448284482944830448314483244833448344483544836448374483844839448404484144842448434484444845448464484744848448494485044851448524485344854448554485644857448584485944860448614486244863448644486544866448674486844869448704487144872448734487444875448764487744878448794488044881448824488344884448854488644887448884488944890448914489244893448944489544896448974489844899449004490144902449034490444905449064490744908449094491044911449124491344914449154491644917449184491944920449214492244923449244492544926449274492844929449304493144932449334493444935449364493744938449394494044941449424494344944449454494644947449484494944950449514495244953449544495544956449574495844959449604496144962449634496444965449664496744968449694497044971449724497344974449754497644977449784497944980449814498244983449844498544986449874498844989449904499144992449934499444995449964499744998449994500045001450024500345004450054500645007450084500945010450114501245013450144501545016450174501845019450204502145022450234502445025450264502745028450294503045031450324503345034450354503645037450384503945040450414504245043450444504545046450474504845049450504505145052450534505445055450564505745058450594506045061450624506345064450654506645067450684506945070450714507245073450744507545076450774507845079450804508145082450834508445085450864508745088450894509045091450924509345094450954509645097450984509945100451014510245103451044510545106451074510845109451104511145112451134511445115451164511745118451194512045121451224512345124451254512645127451284512945130451314513245133451344513545136451374513845139451404514145142451434514445145451464514745148451494515045151451524515345154451554515645157451584515945160451614516245163451644516545166451674516845169451704517145172451734517445175451764517745178451794518045181451824518345184451854518645187451884518945190451914519245193451944519545196451974519845199452004520145202452034520445205452064520745208452094521045211452124521345214452154521645217452184521945220452214522245223452244522545226452274522845229452304523145232452334523445235452364523745238452394524045241452424524345244452454524645247452484524945250452514525245253452544525545256452574525845259452604526145262452634526445265452664526745268452694527045271452724527345274452754527645277452784527945280452814528245283452844528545286452874528845289452904529145292452934529445295452964529745298452994530045301453024530345304453054530645307453084530945310453114531245313453144531545316453174531845319453204532145322453234532445325453264532745328453294533045331453324533345334453354533645337453384533945340453414534245343453444534545346453474534845349453504535145352453534535445355453564535745358453594536045361453624536345364453654536645367453684536945370453714537245373453744537545376453774537845379453804538145382453834538445385453864538745388453894539045391453924539345394453954539645397453984539945400454014540245403454044540545406454074540845409454104541145412454134541445415454164541745418454194542045421454224542345424454254542645427454284542945430454314543245433454344543545436454374543845439454404544145442454434544445445454464544745448454494545045451454524545345454454554545645457454584545945460454614546245463454644546545466454674546845469454704547145472454734547445475454764547745478454794548045481454824548345484454854548645487454884548945490454914549245493454944549545496454974549845499455004550145502455034550445505455064550745508455094551045511455124551345514455154551645517455184551945520455214552245523455244552545526455274552845529455304553145532455334553445535455364553745538455394554045541455424554345544455454554645547455484554945550455514555245553455544555545556455574555845559455604556145562455634556445565455664556745568455694557045571455724557345574455754557645577455784557945580455814558245583455844558545586455874558845589455904559145592455934559445595455964559745598455994560045601456024560345604456054560645607456084560945610456114561245613456144561545616456174561845619456204562145622456234562445625456264562745628456294563045631456324563345634456354563645637456384563945640456414564245643456444564545646456474564845649456504565145652456534565445655456564565745658456594566045661456624566345664456654566645667456684566945670456714567245673456744567545676456774567845679456804568145682456834568445685456864568745688456894569045691456924569345694456954569645697456984569945700457014570245703457044570545706457074570845709457104571145712457134571445715457164571745718457194572045721457224572345724457254572645727457284572945730457314573245733457344573545736457374573845739457404574145742457434574445745457464574745748457494575045751457524575345754457554575645757457584575945760457614576245763457644576545766457674576845769457704577145772457734577445775457764577745778457794578045781457824578345784457854578645787457884578945790457914579245793457944579545796457974579845799458004580145802458034580445805458064580745808458094581045811458124581345814458154581645817458184581945820458214582245823458244582545826458274582845829458304583145832458334583445835458364583745838458394584045841458424584345844458454584645847458484584945850458514585245853458544585545856458574585845859458604586145862458634586445865458664586745868458694587045871458724587345874458754587645877458784587945880458814588245883458844588545886458874588845889458904589145892458934589445895458964589745898458994590045901459024590345904459054590645907459084590945910459114591245913459144591545916459174591845919459204592145922459234592445925459264592745928459294593045931459324593345934459354593645937459384593945940459414594245943459444594545946459474594845949459504595145952459534595445955459564595745958459594596045961459624596345964459654596645967459684596945970459714597245973459744597545976459774597845979459804598145982459834598445985459864598745988459894599045991459924599345994459954599645997459984599946000460014600246003460044600546006460074600846009460104601146012460134601446015460164601746018460194602046021460224602346024460254602646027460284602946030460314603246033460344603546036460374603846039460404604146042460434604446045460464604746048460494605046051460524605346054460554605646057460584605946060460614606246063460644606546066460674606846069460704607146072460734607446075460764607746078460794608046081460824608346084460854608646087460884608946090460914609246093460944609546096460974609846099461004610146102461034610446105461064610746108461094611046111461124611346114461154611646117461184611946120461214612246123461244612546126461274612846129461304613146132461334613446135461364613746138461394614046141461424614346144461454614646147461484614946150461514615246153461544615546156461574615846159461604616146162461634616446165461664616746168461694617046171461724617346174461754617646177461784617946180461814618246183461844618546186461874618846189461904619146192461934619446195461964619746198461994620046201462024620346204462054620646207462084620946210462114621246213462144621546216462174621846219462204622146222462234622446225462264622746228462294623046231462324623346234462354623646237462384623946240462414624246243462444624546246462474624846249462504625146252462534625446255462564625746258462594626046261462624626346264462654626646267462684626946270462714627246273462744627546276462774627846279462804628146282462834628446285462864628746288462894629046291462924629346294462954629646297462984629946300463014630246303463044630546306463074630846309463104631146312463134631446315463164631746318463194632046321463224632346324463254632646327463284632946330463314633246333463344633546336463374633846339463404634146342463434634446345463464634746348463494635046351463524635346354463554635646357463584635946360463614636246363463644636546366463674636846369463704637146372463734637446375463764637746378463794638046381463824638346384463854638646387463884638946390463914639246393463944639546396463974639846399464004640146402464034640446405464064640746408464094641046411464124641346414464154641646417464184641946420464214642246423464244642546426464274642846429464304643146432464334643446435464364643746438464394644046441464424644346444464454644646447464484644946450464514645246453464544645546456464574645846459464604646146462464634646446465464664646746468464694647046471464724647346474464754647646477464784647946480464814648246483464844648546486464874648846489464904649146492464934649446495464964649746498464994650046501465024650346504465054650646507465084650946510465114651246513465144651546516465174651846519465204652146522465234652446525465264652746528465294653046531465324653346534465354653646537465384653946540465414654246543465444654546546465474654846549465504655146552465534655446555465564655746558465594656046561465624656346564465654656646567465684656946570465714657246573465744657546576465774657846579465804658146582465834658446585465864658746588465894659046591465924659346594465954659646597465984659946600466014660246603466044660546606466074660846609466104661146612466134661446615466164661746618466194662046621466224662346624466254662646627466284662946630466314663246633466344663546636466374663846639466404664146642466434664446645466464664746648466494665046651466524665346654466554665646657466584665946660466614666246663466644666546666466674666846669466704667146672466734667446675466764667746678466794668046681466824668346684466854668646687466884668946690466914669246693466944669546696466974669846699467004670146702467034670446705467064670746708467094671046711467124671346714467154671646717467184671946720467214672246723467244672546726467274672846729467304673146732467334673446735467364673746738467394674046741467424674346744467454674646747467484674946750467514675246753467544675546756467574675846759467604676146762467634676446765467664676746768467694677046771467724677346774467754677646777467784677946780467814678246783467844678546786467874678846789467904679146792467934679446795467964679746798467994680046801468024680346804468054680646807468084680946810468114681246813468144681546816468174681846819468204682146822468234682446825468264682746828468294683046831468324683346834468354683646837468384683946840468414684246843468444684546846468474684846849468504685146852468534685446855468564685746858468594686046861468624686346864468654686646867468684686946870468714687246873468744687546876468774687846879468804688146882468834688446885468864688746888468894689046891468924689346894468954689646897468984689946900469014690246903469044690546906469074690846909469104691146912469134691446915469164691746918469194692046921469224692346924469254692646927469284692946930469314693246933469344693546936469374693846939469404694146942469434694446945469464694746948469494695046951469524695346954469554695646957469584695946960469614696246963469644696546966469674696846969469704697146972469734697446975469764697746978469794698046981469824698346984469854698646987469884698946990469914699246993469944699546996469974699846999470004700147002470034700447005470064700747008470094701047011470124701347014470154701647017470184701947020470214702247023470244702547026470274702847029470304703147032470334703447035470364703747038470394704047041470424704347044470454704647047470484704947050470514705247053470544705547056470574705847059470604706147062470634706447065470664706747068470694707047071470724707347074470754707647077470784707947080470814708247083470844708547086470874708847089470904709147092470934709447095470964709747098470994710047101471024710347104471054710647107471084710947110471114711247113471144711547116471174711847119471204712147122471234712447125471264712747128471294713047131471324713347134471354713647137471384713947140471414714247143471444714547146471474714847149471504715147152471534715447155471564715747158471594716047161471624716347164471654716647167471684716947170471714717247173471744717547176471774717847179471804718147182471834718447185471864718747188471894719047191471924719347194471954719647197471984719947200472014720247203472044720547206472074720847209472104721147212472134721447215472164721747218472194722047221472224722347224472254722647227472284722947230472314723247233472344723547236472374723847239472404724147242472434724447245472464724747248472494725047251472524725347254472554725647257472584725947260472614726247263472644726547266472674726847269472704727147272472734727447275472764727747278472794728047281472824728347284472854728647287472884728947290472914729247293472944729547296472974729847299473004730147302473034730447305473064730747308473094731047311473124731347314473154731647317473184731947320473214732247323473244732547326473274732847329473304733147332473334733447335473364733747338473394734047341473424734347344473454734647347473484734947350473514735247353473544735547356473574735847359473604736147362473634736447365473664736747368473694737047371473724737347374473754737647377473784737947380473814738247383473844738547386473874738847389473904739147392473934739447395473964739747398473994740047401474024740347404474054740647407474084740947410474114741247413474144741547416474174741847419474204742147422474234742447425474264742747428474294743047431474324743347434474354743647437474384743947440474414744247443474444744547446474474744847449474504745147452474534745447455474564745747458474594746047461474624746347464474654746647467474684746947470474714747247473474744747547476474774747847479474804748147482474834748447485474864748747488474894749047491474924749347494474954749647497474984749947500475014750247503475044750547506475074750847509475104751147512475134751447515475164751747518475194752047521475224752347524475254752647527475284752947530475314753247533475344753547536475374753847539475404754147542475434754447545475464754747548475494755047551475524755347554475554755647557475584755947560475614756247563475644756547566475674756847569475704757147572475734757447575475764757747578475794758047581475824758347584475854758647587475884758947590475914759247593475944759547596475974759847599476004760147602476034760447605476064760747608476094761047611476124761347614476154761647617476184761947620476214762247623476244762547626476274762847629476304763147632476334763447635476364763747638476394764047641476424764347644476454764647647476484764947650476514765247653476544765547656476574765847659476604766147662476634766447665476664766747668476694767047671476724767347674476754767647677476784767947680476814768247683476844768547686476874768847689476904769147692476934769447695476964769747698476994770047701477024770347704477054770647707477084770947710477114771247713477144771547716477174771847719477204772147722477234772447725477264772747728477294773047731477324773347734477354773647737477384773947740477414774247743477444774547746477474774847749477504775147752477534775447755477564775747758477594776047761477624776347764477654776647767477684776947770477714777247773477744777547776477774777847779477804778147782477834778447785477864778747788477894779047791477924779347794477954779647797477984779947800478014780247803478044780547806478074780847809478104781147812478134781447815478164781747818478194782047821478224782347824478254782647827478284782947830478314783247833478344783547836478374783847839478404784147842478434784447845478464784747848478494785047851478524785347854478554785647857478584785947860478614786247863478644786547866478674786847869478704787147872478734787447875478764787747878478794788047881478824788347884478854788647887478884788947890478914789247893478944789547896478974789847899479004790147902479034790447905479064790747908479094791047911479124791347914479154791647917479184791947920479214792247923479244792547926479274792847929479304793147932479334793447935479364793747938479394794047941479424794347944479454794647947479484794947950479514795247953479544795547956479574795847959479604796147962479634796447965479664796747968479694797047971479724797347974479754797647977479784797947980479814798247983479844798547986479874798847989479904799147992479934799447995479964799747998479994800048001480024800348004480054800648007480084800948010480114801248013480144801548016480174801848019480204802148022480234802448025480264802748028480294803048031480324803348034480354803648037480384803948040480414804248043480444804548046480474804848049480504805148052480534805448055480564805748058480594806048061480624806348064480654806648067480684806948070480714807248073480744807548076480774807848079480804808148082480834808448085480864808748088480894809048091480924809348094480954809648097480984809948100481014810248103481044810548106481074810848109481104811148112481134811448115481164811748118481194812048121481224812348124481254812648127481284812948130481314813248133481344813548136481374813848139481404814148142481434814448145481464814748148481494815048151481524815348154481554815648157481584815948160481614816248163481644816548166481674816848169481704817148172481734817448175481764817748178481794818048181481824818348184481854818648187481884818948190481914819248193481944819548196481974819848199482004820148202482034820448205482064820748208482094821048211482124821348214482154821648217482184821948220482214822248223482244822548226482274822848229482304823148232482334823448235482364823748238482394824048241482424824348244482454824648247482484824948250482514825248253482544825548256482574825848259482604826148262482634826448265482664826748268482694827048271482724827348274482754827648277482784827948280482814828248283482844828548286482874828848289482904829148292482934829448295482964829748298482994830048301483024830348304483054830648307483084830948310483114831248313483144831548316483174831848319483204832148322483234832448325483264832748328483294833048331483324833348334483354833648337483384833948340483414834248343483444834548346483474834848349483504835148352483534835448355483564835748358483594836048361483624836348364483654836648367483684836948370483714837248373483744837548376483774837848379483804838148382483834838448385483864838748388483894839048391483924839348394483954839648397483984839948400484014840248403484044840548406484074840848409484104841148412484134841448415484164841748418484194842048421484224842348424484254842648427484284842948430484314843248433484344843548436484374843848439484404844148442484434844448445484464844748448484494845048451484524845348454484554845648457484584845948460484614846248463484644846548466484674846848469484704847148472484734847448475484764847748478484794848048481484824848348484484854848648487484884848948490484914849248493484944849548496484974849848499485004850148502485034850448505485064850748508485094851048511485124851348514485154851648517485184851948520485214852248523485244852548526485274852848529485304853148532485334853448535485364853748538485394854048541485424854348544485454854648547485484854948550485514855248553485544855548556485574855848559485604856148562485634856448565485664856748568485694857048571485724857348574485754857648577485784857948580485814858248583485844858548586485874858848589485904859148592485934859448595485964859748598485994860048601486024860348604486054860648607486084860948610486114861248613486144861548616486174861848619486204862148622486234862448625486264862748628486294863048631486324863348634486354863648637486384863948640486414864248643486444864548646486474864848649486504865148652486534865448655486564865748658486594866048661486624866348664486654866648667486684866948670486714867248673486744867548676486774867848679486804868148682486834868448685486864868748688486894869048691486924869348694486954869648697486984869948700487014870248703487044870548706487074870848709487104871148712487134871448715487164871748718487194872048721487224872348724487254872648727487284872948730487314873248733487344873548736487374873848739487404874148742487434874448745487464874748748487494875048751487524875348754487554875648757487584875948760487614876248763487644876548766487674876848769487704877148772487734877448775487764877748778487794878048781487824878348784487854878648787487884878948790487914879248793487944879548796487974879848799488004880148802488034880448805488064880748808488094881048811488124881348814488154881648817488184881948820488214882248823488244882548826488274882848829488304883148832488334883448835488364883748838488394884048841488424884348844488454884648847488484884948850488514885248853488544885548856488574885848859488604886148862488634886448865488664886748868488694887048871488724887348874488754887648877488784887948880488814888248883488844888548886488874888848889488904889148892488934889448895488964889748898488994890048901489024890348904489054890648907489084890948910489114891248913489144891548916489174891848919489204892148922489234892448925489264892748928489294893048931489324893348934489354893648937489384893948940489414894248943489444894548946489474894848949489504895148952489534895448955489564895748958489594896048961489624896348964489654896648967489684896948970489714897248973489744897548976489774897848979489804898148982489834898448985489864898748988489894899048991489924899348994489954899648997489984899949000490014900249003490044900549006490074900849009490104901149012490134901449015490164901749018490194902049021490224902349024490254902649027490284902949030490314903249033490344903549036490374903849039490404904149042490434904449045490464904749048490494905049051490524905349054490554905649057490584905949060490614906249063490644906549066490674906849069490704907149072490734907449075490764907749078490794908049081490824908349084490854908649087490884908949090490914909249093490944909549096490974909849099491004910149102491034910449105491064910749108491094911049111491124911349114491154911649117491184911949120491214912249123491244912549126491274912849129491304913149132491334913449135491364913749138491394914049141491424914349144491454914649147491484914949150491514915249153491544915549156491574915849159491604916149162491634916449165491664916749168491694917049171491724917349174491754917649177491784917949180491814918249183491844918549186491874918849189491904919149192491934919449195491964919749198491994920049201492024920349204492054920649207492084920949210492114921249213492144921549216492174921849219492204922149222492234922449225492264922749228492294923049231492324923349234492354923649237492384923949240492414924249243492444924549246492474924849249492504925149252492534925449255492564925749258492594926049261492624926349264492654926649267492684926949270492714927249273492744927549276492774927849279492804928149282492834928449285492864928749288492894929049291492924929349294492954929649297492984929949300493014930249303493044930549306493074930849309493104931149312493134931449315493164931749318493194932049321493224932349324493254932649327493284932949330493314933249333493344933549336493374933849339493404934149342493434934449345493464934749348493494935049351493524935349354493554935649357493584935949360493614936249363493644936549366493674936849369493704937149372493734937449375493764937749378493794938049381493824938349384493854938649387493884938949390493914939249393493944939549396493974939849399494004940149402494034940449405494064940749408494094941049411494124941349414494154941649417494184941949420494214942249423494244942549426494274942849429494304943149432494334943449435494364943749438494394944049441494424944349444494454944649447494484944949450494514945249453494544945549456494574945849459494604946149462494634946449465494664946749468494694947049471494724947349474494754947649477494784947949480494814948249483494844948549486494874948849489494904949149492494934949449495494964949749498494994950049501495024950349504495054950649507495084950949510495114951249513495144951549516495174951849519495204952149522495234952449525495264952749528495294953049531495324953349534495354953649537495384953949540495414954249543495444954549546495474954849549495504955149552495534955449555495564955749558495594956049561495624956349564495654956649567495684956949570495714957249573495744957549576495774957849579495804958149582495834958449585495864958749588495894959049591495924959349594495954959649597495984959949600496014960249603496044960549606496074960849609496104961149612496134961449615496164961749618496194962049621496224962349624496254962649627496284962949630496314963249633496344963549636496374963849639496404964149642496434964449645496464964749648496494965049651496524965349654496554965649657496584965949660496614966249663496644966549666496674966849669496704967149672496734967449675496764967749678496794968049681496824968349684496854968649687496884968949690496914969249693496944969549696496974969849699497004970149702497034970449705497064970749708497094971049711497124971349714497154971649717497184971949720497214972249723497244972549726497274972849729497304973149732497334973449735497364973749738497394974049741497424974349744497454974649747497484974949750497514975249753497544975549756497574975849759497604976149762497634976449765497664976749768497694977049771497724977349774497754977649777497784977949780497814978249783497844978549786497874978849789497904979149792497934979449795497964979749798497994980049801498024980349804498054980649807498084980949810498114981249813498144981549816498174981849819498204982149822498234982449825498264982749828498294983049831498324983349834498354983649837498384983949840498414984249843498444984549846498474984849849498504985149852498534985449855498564985749858498594986049861498624986349864498654986649867498684986949870498714987249873498744987549876498774987849879498804988149882498834988449885498864988749888498894989049891498924989349894498954989649897498984989949900499014990249903499044990549906499074990849909499104991149912499134991449915499164991749918499194992049921499224992349924499254992649927499284992949930499314993249933499344993549936499374993849939499404994149942499434994449945499464994749948499494995049951499524995349954499554995649957499584995949960499614996249963499644996549966499674996849969499704997149972499734997449975499764997749978499794998049981499824998349984499854998649987499884998949990499914999249993499944999549996499974999849999500005000150002500035000450005500065000750008500095001050011500125001350014500155001650017500185001950020500215002250023500245002550026500275002850029500305003150032500335003450035500365003750038500395004050041500425004350044500455004650047500485004950050500515005250053500545005550056500575005850059500605006150062500635006450065500665006750068500695007050071500725007350074500755007650077500785007950080500815008250083500845008550086500875008850089500905009150092500935009450095500965009750098500995010050101501025010350104501055010650107501085010950110501115011250113501145011550116501175011850119501205012150122501235012450125501265012750128501295013050131501325013350134501355013650137501385013950140501415014250143501445014550146501475014850149501505015150152501535015450155501565015750158501595016050161501625016350164501655016650167501685016950170501715017250173501745017550176501775017850179501805018150182501835018450185501865018750188501895019050191501925019350194501955019650197501985019950200502015020250203502045020550206502075020850209502105021150212502135021450215502165021750218502195022050221502225022350224502255022650227502285022950230502315023250233502345023550236502375023850239502405024150242502435024450245502465024750248502495025050251502525025350254502555025650257502585025950260502615026250263502645026550266502675026850269502705027150272502735027450275502765027750278502795028050281502825028350284502855028650287502885028950290502915029250293502945029550296502975029850299503005030150302503035030450305503065030750308503095031050311503125031350314503155031650317503185031950320503215032250323503245032550326503275032850329503305033150332503335033450335503365033750338503395034050341503425034350344503455034650347503485034950350503515035250353503545035550356503575035850359503605036150362503635036450365503665036750368503695037050371503725037350374503755037650377503785037950380503815038250383503845038550386503875038850389503905039150392503935039450395503965039750398503995040050401504025040350404504055040650407504085040950410504115041250413504145041550416504175041850419504205042150422504235042450425504265042750428504295043050431504325043350434504355043650437504385043950440504415044250443504445044550446504475044850449504505045150452504535045450455504565045750458504595046050461504625046350464504655046650467504685046950470504715047250473504745047550476504775047850479504805048150482504835048450485504865048750488504895049050491504925049350494504955049650497504985049950500505015050250503505045050550506505075050850509505105051150512505135051450515505165051750518505195052050521505225052350524505255052650527505285052950530505315053250533505345053550536505375053850539505405054150542505435054450545505465054750548505495055050551505525055350554505555055650557505585055950560505615056250563505645056550566505675056850569505705057150572505735057450575505765057750578505795058050581505825058350584505855058650587505885058950590505915059250593505945059550596505975059850599506005060150602506035060450605506065060750608506095061050611506125061350614506155061650617506185061950620506215062250623506245062550626506275062850629506305063150632506335063450635506365063750638506395064050641506425064350644506455064650647506485064950650506515065250653506545065550656506575065850659506605066150662506635066450665506665066750668506695067050671506725067350674506755067650677506785067950680506815068250683506845068550686506875068850689506905069150692506935069450695506965069750698506995070050701507025070350704507055070650707507085070950710507115071250713507145071550716507175071850719507205072150722507235072450725507265072750728507295073050731507325073350734507355073650737507385073950740507415074250743507445074550746507475074850749507505075150752507535075450755507565075750758507595076050761507625076350764507655076650767507685076950770507715077250773507745077550776507775077850779507805078150782507835078450785507865078750788507895079050791507925079350794507955079650797507985079950800508015080250803508045080550806508075080850809508105081150812508135081450815508165081750818508195082050821508225082350824508255082650827508285082950830508315083250833508345083550836508375083850839508405084150842508435084450845508465084750848508495085050851508525085350854508555085650857508585085950860508615086250863508645086550866508675086850869508705087150872508735087450875508765087750878508795088050881508825088350884508855088650887508885088950890508915089250893508945089550896508975089850899509005090150902509035090450905509065090750908509095091050911509125091350914509155091650917509185091950920509215092250923509245092550926509275092850929509305093150932509335093450935509365093750938509395094050941509425094350944509455094650947509485094950950509515095250953509545095550956509575095850959509605096150962509635096450965509665096750968509695097050971509725097350974509755097650977509785097950980509815098250983509845098550986509875098850989509905099150992509935099450995509965099750998509995100051001510025100351004510055100651007510085100951010510115101251013510145101551016510175101851019510205102151022510235102451025510265102751028510295103051031510325103351034510355103651037510385103951040510415104251043510445104551046510475104851049510505105151052510535105451055510565105751058510595106051061510625106351064510655106651067510685106951070510715107251073510745107551076510775107851079510805108151082510835108451085510865108751088510895109051091510925109351094510955109651097510985109951100511015110251103511045110551106511075110851109511105111151112511135111451115511165111751118511195112051121511225112351124511255112651127511285112951130511315113251133511345113551136511375113851139511405114151142511435114451145511465114751148511495115051151511525115351154511555115651157511585115951160511615116251163511645116551166511675116851169511705117151172511735117451175511765117751178511795118051181511825118351184511855118651187511885118951190511915119251193511945119551196511975119851199512005120151202512035120451205512065120751208512095121051211512125121351214512155121651217512185121951220512215122251223512245122551226512275122851229512305123151232512335123451235512365123751238512395124051241512425124351244512455124651247512485124951250512515125251253512545125551256512575125851259512605126151262512635126451265512665126751268512695127051271512725127351274512755127651277512785127951280512815128251283512845128551286512875128851289512905129151292512935129451295512965129751298512995130051301513025130351304513055130651307513085130951310513115131251313513145131551316513175131851319513205132151322513235132451325513265132751328513295133051331513325133351334513355133651337513385133951340513415134251343513445134551346513475134851349513505135151352513535135451355513565135751358513595136051361513625136351364513655136651367513685136951370513715137251373513745137551376513775137851379513805138151382513835138451385513865138751388513895139051391513925139351394513955139651397513985139951400514015140251403514045140551406514075140851409514105141151412514135141451415514165141751418514195142051421514225142351424514255142651427514285142951430514315143251433514345143551436514375143851439514405144151442514435144451445514465144751448514495145051451514525145351454514555145651457514585145951460514615146251463514645146551466514675146851469514705147151472514735147451475514765147751478514795148051481514825148351484514855148651487514885148951490514915149251493514945149551496514975149851499515005150151502515035150451505515065150751508515095151051511515125151351514515155151651517515185151951520515215152251523515245152551526515275152851529515305153151532515335153451535515365153751538515395154051541515425154351544515455154651547515485154951550515515155251553515545155551556515575155851559515605156151562515635156451565515665156751568515695157051571515725157351574515755157651577515785157951580515815158251583515845158551586515875158851589515905159151592515935159451595515965159751598515995160051601516025160351604516055160651607516085160951610516115161251613516145161551616516175161851619516205162151622516235162451625516265162751628516295163051631516325163351634516355163651637516385163951640516415164251643516445164551646516475164851649516505165151652516535165451655516565165751658516595166051661516625166351664516655166651667516685166951670516715167251673516745167551676516775167851679516805168151682516835168451685516865168751688516895169051691516925169351694516955169651697516985169951700517015170251703517045170551706517075170851709517105171151712517135171451715517165171751718517195172051721517225172351724517255172651727517285172951730517315173251733517345173551736517375173851739517405174151742517435174451745517465174751748517495175051751517525175351754517555175651757517585175951760517615176251763517645176551766517675176851769517705177151772517735177451775517765177751778517795178051781517825178351784517855178651787517885178951790517915179251793517945179551796517975179851799518005180151802518035180451805518065180751808518095181051811518125181351814518155181651817518185181951820518215182251823518245182551826518275182851829518305183151832518335183451835518365183751838518395184051841518425184351844518455184651847518485184951850518515185251853518545185551856518575185851859518605186151862518635186451865518665186751868518695187051871518725187351874518755187651877518785187951880518815188251883518845188551886518875188851889518905189151892518935189451895518965189751898518995190051901519025190351904519055190651907519085190951910519115191251913519145191551916519175191851919519205192151922519235192451925519265192751928519295193051931519325193351934519355193651937519385193951940519415194251943519445194551946519475194851949519505195151952519535195451955519565195751958519595196051961519625196351964519655196651967519685196951970519715197251973519745197551976519775197851979519805198151982519835198451985519865198751988519895199051991519925199351994519955199651997519985199952000520015200252003520045200552006520075200852009520105201152012520135201452015520165201752018520195202052021520225202352024520255202652027520285202952030520315203252033520345203552036520375203852039520405204152042520435204452045520465204752048520495205052051520525205352054520555205652057520585205952060520615206252063520645206552066520675206852069520705207152072520735207452075520765207752078520795208052081520825208352084520855208652087520885208952090520915209252093520945209552096520975209852099521005210152102521035210452105521065210752108521095211052111521125211352114521155211652117521185211952120521215212252123521245212552126521275212852129521305213152132521335213452135521365213752138521395214052141521425214352144521455214652147521485214952150521515215252153521545215552156521575215852159521605216152162521635216452165521665216752168521695217052171521725217352174521755217652177521785217952180521815218252183521845218552186521875218852189521905219152192521935219452195521965219752198521995220052201522025220352204522055220652207522085220952210522115221252213522145221552216522175221852219522205222152222522235222452225522265222752228522295223052231522325223352234522355223652237522385223952240522415224252243522445224552246522475224852249522505225152252522535225452255522565225752258522595226052261522625226352264522655226652267522685226952270522715227252273522745227552276522775227852279522805228152282522835228452285522865228752288522895229052291522925229352294522955229652297522985229952300523015230252303523045230552306523075230852309523105231152312523135231452315523165231752318523195232052321523225232352324523255232652327523285232952330523315233252333523345233552336523375233852339523405234152342523435234452345523465234752348523495235052351523525235352354523555235652357523585235952360523615236252363523645236552366523675236852369523705237152372523735237452375523765237752378523795238052381523825238352384523855238652387523885238952390523915239252393523945239552396523975239852399524005240152402524035240452405524065240752408524095241052411524125241352414524155241652417524185241952420524215242252423524245242552426524275242852429524305243152432524335243452435524365243752438524395244052441524425244352444524455244652447524485244952450524515245252453524545245552456524575245852459524605246152462524635246452465524665246752468524695247052471524725247352474524755247652477524785247952480524815248252483524845248552486524875248852489524905249152492524935249452495524965249752498524995250052501525025250352504525055250652507525085250952510525115251252513525145251552516525175251852519525205252152522525235252452525525265252752528525295253052531525325253352534525355253652537525385253952540525415254252543525445254552546525475254852549525505255152552525535255452555525565255752558525595256052561525625256352564525655256652567525685256952570525715257252573525745257552576525775257852579525805258152582525835258452585525865258752588525895259052591525925259352594525955259652597525985259952600526015260252603526045260552606526075260852609526105261152612526135261452615526165261752618526195262052621526225262352624526255262652627526285262952630526315263252633526345263552636526375263852639526405264152642526435264452645526465264752648526495265052651526525265352654526555265652657526585265952660526615266252663526645266552666526675266852669526705267152672526735267452675526765267752678526795268052681526825268352684526855268652687526885268952690526915269252693526945269552696526975269852699527005270152702527035270452705527065270752708527095271052711527125271352714527155271652717527185271952720527215272252723527245272552726527275272852729527305273152732527335273452735527365273752738527395274052741527425274352744527455274652747527485274952750527515275252753527545275552756527575275852759527605276152762527635276452765527665276752768527695277052771527725277352774527755277652777527785277952780527815278252783527845278552786527875278852789527905279152792527935279452795527965279752798527995280052801528025280352804528055280652807528085280952810528115281252813528145281552816528175281852819528205282152822528235282452825528265282752828528295283052831528325283352834528355283652837528385283952840528415284252843528445284552846528475284852849528505285152852528535285452855528565285752858528595286052861528625286352864528655286652867528685286952870528715287252873528745287552876528775287852879528805288152882528835288452885528865288752888528895289052891528925289352894528955289652897528985289952900529015290252903529045290552906529075290852909529105291152912529135291452915529165291752918529195292052921529225292352924529255292652927529285292952930529315293252933529345293552936529375293852939529405294152942529435294452945529465294752948529495295052951529525295352954529555295652957529585295952960529615296252963529645296552966529675296852969529705297152972529735297452975529765297752978529795298052981529825298352984529855298652987529885298952990529915299252993529945299552996529975299852999530005300153002530035300453005530065300753008530095301053011530125301353014530155301653017530185301953020530215302253023530245302553026530275302853029530305303153032530335303453035530365303753038530395304053041530425304353044530455304653047530485304953050530515305253053530545305553056530575305853059530605306153062530635306453065530665306753068530695307053071530725307353074530755307653077530785307953080530815308253083530845308553086530875308853089530905309153092530935309453095530965309753098530995310053101531025310353104531055310653107531085310953110531115311253113531145311553116531175311853119531205312153122531235312453125531265312753128531295313053131531325313353134531355313653137531385313953140531415314253143531445314553146531475314853149531505315153152531535315453155531565315753158531595316053161531625316353164531655316653167531685316953170531715317253173531745317553176531775317853179531805318153182531835318453185531865318753188531895319053191531925319353194531955319653197531985319953200532015320253203532045320553206532075320853209532105321153212532135321453215532165321753218532195322053221532225322353224532255322653227532285322953230532315323253233532345323553236532375323853239532405324153242532435324453245532465324753248532495325053251532525325353254532555325653257532585325953260532615326253263532645326553266532675326853269532705327153272532735327453275532765327753278532795328053281532825328353284532855328653287532885328953290532915329253293532945329553296532975329853299533005330153302533035330453305533065330753308533095331053311533125331353314533155331653317533185331953320533215332253323533245332553326533275332853329533305333153332533335333453335533365333753338533395334053341533425334353344533455334653347533485334953350533515335253353533545335553356533575335853359533605336153362533635336453365533665336753368533695337053371533725337353374533755337653377533785337953380533815338253383533845338553386533875338853389533905339153392533935339453395533965339753398533995340053401534025340353404534055340653407534085340953410534115341253413534145341553416534175341853419534205342153422534235342453425534265342753428534295343053431534325343353434534355343653437534385343953440534415344253443534445344553446534475344853449534505345153452534535345453455534565345753458534595346053461534625346353464534655346653467534685346953470534715347253473534745347553476534775347853479534805348153482534835348453485534865348753488534895349053491534925349353494534955349653497534985349953500535015350253503535045350553506535075350853509535105351153512535135351453515535165351753518535195352053521535225352353524535255352653527535285352953530535315353253533535345353553536535375353853539535405354153542535435354453545535465354753548535495355053551535525355353554535555355653557535585355953560535615356253563535645356553566535675356853569535705357153572535735357453575535765357753578535795358053581535825358353584535855358653587535885358953590535915359253593535945359553596535975359853599536005360153602536035360453605536065360753608536095361053611536125361353614536155361653617536185361953620536215362253623536245362553626536275362853629536305363153632536335363453635536365363753638536395364053641536425364353644536455364653647536485364953650536515365253653536545365553656536575365853659536605366153662536635366453665536665366753668536695367053671536725367353674536755367653677536785367953680536815368253683536845368553686536875368853689536905369153692536935369453695536965369753698536995370053701537025370353704537055370653707537085370953710537115371253713537145371553716537175371853719537205372153722537235372453725537265372753728537295373053731537325373353734537355373653737537385373953740537415374253743537445374553746537475374853749537505375153752537535375453755537565375753758537595376053761537625376353764537655376653767537685376953770537715377253773537745377553776537775377853779537805378153782537835378453785537865378753788537895379053791537925379353794537955379653797537985379953800538015380253803538045380553806538075380853809538105381153812538135381453815538165381753818538195382053821538225382353824538255382653827538285382953830538315383253833538345383553836538375383853839538405384153842538435384453845538465384753848538495385053851538525385353854538555385653857538585385953860538615386253863538645386553866538675386853869538705387153872538735387453875538765387753878538795388053881538825388353884538855388653887538885388953890538915389253893538945389553896538975389853899539005390153902539035390453905539065390753908539095391053911539125391353914539155391653917539185391953920539215392253923539245392553926539275392853929539305393153932539335393453935539365393753938539395394053941539425394353944539455394653947539485394953950539515395253953539545395553956539575395853959539605396153962539635396453965539665396753968539695397053971539725397353974539755397653977539785397953980539815398253983539845398553986539875398853989539905399153992539935399453995539965399753998539995400054001540025400354004540055400654007540085400954010540115401254013540145401554016540175401854019540205402154022540235402454025540265402754028540295403054031540325403354034540355403654037540385403954040540415404254043540445404554046540475404854049540505405154052540535405454055540565405754058540595406054061540625406354064540655406654067540685406954070540715407254073540745407554076540775407854079540805408154082540835408454085540865408754088540895409054091540925409354094540955409654097540985409954100541015410254103541045410554106541075410854109541105411154112541135411454115541165411754118541195412054121541225412354124541255412654127541285412954130541315413254133541345413554136541375413854139541405414154142541435414454145541465414754148541495415054151541525415354154541555415654157541585415954160541615416254163541645416554166541675416854169541705417154172541735417454175541765417754178541795418054181541825418354184541855418654187541885418954190541915419254193541945419554196541975419854199542005420154202542035420454205542065420754208542095421054211542125421354214542155421654217542185421954220542215422254223542245422554226542275422854229542305423154232542335423454235542365423754238542395424054241542425424354244542455424654247542485424954250542515425254253542545425554256542575425854259542605426154262542635426454265542665426754268542695427054271542725427354274542755427654277542785427954280542815428254283542845428554286542875428854289542905429154292542935429454295542965429754298542995430054301543025430354304543055430654307543085430954310543115431254313543145431554316543175431854319543205432154322543235432454325543265432754328543295433054331543325433354334543355433654337543385433954340543415434254343543445434554346543475434854349543505435154352543535435454355543565435754358543595436054361543625436354364543655436654367543685436954370543715437254373543745437554376543775437854379543805438154382543835438454385543865438754388543895439054391543925439354394543955439654397543985439954400544015440254403544045440554406544075440854409544105441154412544135441454415544165441754418544195442054421544225442354424544255442654427544285442954430544315443254433544345443554436544375443854439544405444154442544435444454445544465444754448544495445054451544525445354454544555445654457544585445954460544615446254463544645446554466544675446854469544705447154472544735447454475544765447754478544795448054481544825448354484544855448654487544885448954490544915449254493544945449554496544975449854499545005450154502545035450454505545065450754508545095451054511545125451354514545155451654517545185451954520545215452254523545245452554526545275452854529545305453154532545335453454535545365453754538545395454054541545425454354544545455454654547545485454954550545515455254553545545455554556545575455854559545605456154562545635456454565545665456754568545695457054571545725457354574545755457654577545785457954580545815458254583545845458554586545875458854589545905459154592545935459454595545965459754598545995460054601546025460354604546055460654607546085460954610546115461254613546145461554616546175461854619546205462154622546235462454625546265462754628546295463054631546325463354634546355463654637546385463954640546415464254643546445464554646546475464854649546505465154652546535465454655546565465754658546595466054661546625466354664546655466654667546685466954670546715467254673546745467554676546775467854679546805468154682546835468454685546865468754688546895469054691546925469354694546955469654697546985469954700547015470254703547045470554706547075470854709547105471154712547135471454715547165471754718547195472054721547225472354724547255472654727547285472954730547315473254733547345473554736547375473854739547405474154742547435474454745547465474754748547495475054751547525475354754547555475654757547585475954760547615476254763547645476554766547675476854769547705477154772547735477454775547765477754778547795478054781547825478354784547855478654787547885478954790547915479254793547945479554796547975479854799548005480154802548035480454805548065480754808548095481054811548125481354814548155481654817548185481954820548215482254823548245482554826548275482854829548305483154832548335483454835548365483754838548395484054841548425484354844548455484654847548485484954850548515485254853548545485554856548575485854859548605486154862548635486454865548665486754868548695487054871548725487354874548755487654877548785487954880548815488254883548845488554886548875488854889548905489154892548935489454895548965489754898548995490054901549025490354904549055490654907549085490954910549115491254913549145491554916549175491854919549205492154922549235492454925549265492754928549295493054931549325493354934549355493654937549385493954940549415494254943549445494554946549475494854949549505495154952549535495454955549565495754958549595496054961549625496354964549655496654967549685496954970549715497254973549745497554976549775497854979549805498154982549835498454985549865498754988549895499054991549925499354994549955499654997549985499955000550015500255003550045500555006550075500855009550105501155012550135501455015550165501755018550195502055021550225502355024550255502655027550285502955030550315503255033550345503555036550375503855039550405504155042550435504455045550465504755048550495505055051550525505355054550555505655057550585505955060550615506255063550645506555066550675506855069550705507155072550735507455075550765507755078550795508055081550825508355084550855508655087550885508955090550915509255093550945509555096550975509855099551005510155102551035510455105551065510755108551095511055111551125511355114551155511655117551185511955120551215512255123551245512555126551275512855129551305513155132551335513455135551365513755138551395514055141551425514355144551455514655147551485514955150551515515255153551545515555156551575515855159551605516155162551635516455165551665516755168551695517055171551725517355174551755517655177551785517955180551815518255183551845518555186551875518855189551905519155192551935519455195551965519755198551995520055201552025520355204552055520655207552085520955210552115521255213552145521555216552175521855219552205522155222552235522455225552265522755228552295523055231552325523355234552355523655237552385523955240552415524255243552445524555246552475524855249552505525155252552535525455255552565525755258552595526055261552625526355264552655526655267552685526955270552715527255273552745527555276552775527855279552805528155282552835528455285552865528755288552895529055291552925529355294552955529655297552985529955300553015530255303553045530555306553075530855309553105531155312553135531455315553165531755318553195532055321553225532355324553255532655327553285532955330553315533255333553345533555336553375533855339553405534155342553435534455345553465534755348553495535055351553525535355354553555535655357553585535955360553615536255363553645536555366553675536855369553705537155372553735537455375553765537755378553795538055381553825538355384553855538655387553885538955390553915539255393553945539555396553975539855399554005540155402554035540455405554065540755408554095541055411554125541355414554155541655417554185541955420554215542255423554245542555426554275542855429554305543155432554335543455435554365543755438554395544055441554425544355444554455544655447554485544955450554515545255453554545545555456554575545855459554605546155462554635546455465554665546755468554695547055471554725547355474554755547655477554785547955480554815548255483554845548555486554875548855489554905549155492554935549455495554965549755498554995550055501555025550355504555055550655507555085550955510555115551255513555145551555516555175551855519555205552155522555235552455525555265552755528555295553055531555325553355534555355553655537555385553955540555415554255543555445554555546555475554855549555505555155552555535555455555555565555755558555595556055561555625556355564555655556655567555685556955570555715557255573555745557555576555775557855579555805558155582555835558455585555865558755588555895559055591555925559355594555955559655597555985559955600556015560255603556045560555606556075560855609556105561155612556135561455615556165561755618556195562055621556225562355624556255562655627556285562955630556315563255633556345563555636556375563855639556405564155642556435564455645556465564755648556495565055651556525565355654556555565655657556585565955660556615566255663556645566555666556675566855669556705567155672556735567455675556765567755678556795568055681556825568355684556855568655687556885568955690556915569255693556945569555696556975569855699557005570155702557035570455705557065570755708557095571055711557125571355714557155571655717557185571955720557215572255723557245572555726557275572855729557305573155732557335573455735557365573755738557395574055741557425574355744557455574655747557485574955750557515575255753557545575555756557575575855759557605576155762557635576455765557665576755768557695577055771557725577355774557755577655777557785577955780557815578255783557845578555786557875578855789557905579155792557935579455795557965579755798557995580055801558025580355804558055580655807558085580955810558115581255813558145581555816558175581855819558205582155822558235582455825558265582755828558295583055831558325583355834558355583655837558385583955840558415584255843558445584555846558475584855849558505585155852558535585455855558565585755858558595586055861558625586355864558655586655867558685586955870558715587255873558745587555876558775587855879558805588155882558835588455885558865588755888558895589055891558925589355894558955589655897558985589955900559015590255903559045590555906559075590855909559105591155912559135591455915559165591755918559195592055921559225592355924559255592655927559285592955930559315593255933559345593555936559375593855939559405594155942559435594455945559465594755948559495595055951559525595355954559555595655957559585595955960559615596255963559645596555966559675596855969559705597155972559735597455975559765597755978559795598055981559825598355984559855598655987559885598955990559915599255993559945599555996559975599855999560005600156002560035600456005560065600756008560095601056011560125601356014560155601656017560185601956020560215602256023560245602556026560275602856029560305603156032560335603456035560365603756038560395604056041560425604356044560455604656047560485604956050560515605256053560545605556056560575605856059560605606156062560635606456065560665606756068560695607056071560725607356074560755607656077560785607956080560815608256083560845608556086560875608856089560905609156092560935609456095560965609756098560995610056101561025610356104561055610656107561085610956110561115611256113561145611556116561175611856119561205612156122561235612456125561265612756128561295613056131561325613356134561355613656137561385613956140561415614256143561445614556146561475614856149561505615156152561535615456155561565615756158561595616056161561625616356164561655616656167561685616956170561715617256173561745617556176561775617856179561805618156182561835618456185561865618756188561895619056191561925619356194561955619656197561985619956200562015620256203562045620556206562075620856209562105621156212562135621456215562165621756218562195622056221562225622356224562255622656227562285622956230562315623256233562345623556236562375623856239562405624156242562435624456245562465624756248562495625056251562525625356254562555625656257562585625956260562615626256263562645626556266562675626856269562705627156272562735627456275562765627756278562795628056281562825628356284562855628656287562885628956290562915629256293562945629556296562975629856299563005630156302563035630456305563065630756308563095631056311563125631356314563155631656317563185631956320563215632256323563245632556326563275632856329563305633156332563335633456335563365633756338563395634056341563425634356344563455634656347563485634956350563515635256353563545635556356563575635856359563605636156362563635636456365563665636756368563695637056371563725637356374563755637656377563785637956380563815638256383563845638556386563875638856389563905639156392563935639456395563965639756398563995640056401564025640356404564055640656407564085640956410564115641256413564145641556416564175641856419564205642156422564235642456425564265642756428564295643056431564325643356434564355643656437564385643956440564415644256443564445644556446564475644856449564505645156452564535645456455564565645756458564595646056461564625646356464564655646656467564685646956470564715647256473564745647556476564775647856479564805648156482564835648456485564865648756488564895649056491564925649356494564955649656497564985649956500565015650256503565045650556506565075650856509565105651156512565135651456515565165651756518565195652056521565225652356524565255652656527565285652956530565315653256533565345653556536565375653856539565405654156542565435654456545565465654756548565495655056551565525655356554565555655656557565585655956560565615656256563565645656556566565675656856569565705657156572565735657456575565765657756578565795658056581565825658356584565855658656587565885658956590565915659256593565945659556596565975659856599566005660156602566035660456605566065660756608566095661056611566125661356614566155661656617566185661956620566215662256623566245662556626566275662856629566305663156632566335663456635566365663756638566395664056641566425664356644566455664656647566485664956650566515665256653566545665556656566575665856659566605666156662566635666456665566665666756668566695667056671566725667356674566755667656677566785667956680566815668256683566845668556686566875668856689566905669156692566935669456695566965669756698566995670056701567025670356704567055670656707567085670956710567115671256713567145671556716567175671856719567205672156722567235672456725567265672756728567295673056731567325673356734567355673656737567385673956740567415674256743567445674556746567475674856749567505675156752567535675456755567565675756758567595676056761567625676356764567655676656767567685676956770567715677256773567745677556776567775677856779567805678156782567835678456785567865678756788567895679056791567925679356794567955679656797567985679956800568015680256803568045680556806568075680856809568105681156812568135681456815568165681756818568195682056821568225682356824568255682656827568285682956830568315683256833568345683556836568375683856839568405684156842568435684456845568465684756848568495685056851568525685356854568555685656857568585685956860568615686256863568645686556866568675686856869568705687156872568735687456875568765687756878568795688056881568825688356884568855688656887568885688956890568915689256893568945689556896568975689856899569005690156902569035690456905569065690756908569095691056911569125691356914569155691656917569185691956920569215692256923569245692556926569275692856929569305693156932569335693456935569365693756938569395694056941569425694356944569455694656947569485694956950569515695256953569545695556956569575695856959569605696156962569635696456965569665696756968569695697056971569725697356974569755697656977569785697956980569815698256983569845698556986569875698856989569905699156992569935699456995569965699756998569995700057001570025700357004570055700657007570085700957010570115701257013570145701557016570175701857019570205702157022570235702457025570265702757028570295703057031570325703357034570355703657037570385703957040570415704257043570445704557046570475704857049570505705157052570535705457055570565705757058570595706057061570625706357064570655706657067570685706957070570715707257073570745707557076570775707857079570805708157082570835708457085570865708757088570895709057091570925709357094570955709657097570985709957100571015710257103571045710557106571075710857109571105711157112571135711457115571165711757118571195712057121571225712357124571255712657127571285712957130571315713257133571345713557136571375713857139571405714157142571435714457145571465714757148571495715057151571525715357154571555715657157571585715957160571615716257163571645716557166571675716857169571705717157172571735717457175571765717757178571795718057181571825718357184571855718657187571885718957190571915719257193571945719557196571975719857199572005720157202572035720457205572065720757208572095721057211572125721357214572155721657217572185721957220572215722257223572245722557226572275722857229572305723157232572335723457235572365723757238572395724057241572425724357244572455724657247572485724957250572515725257253572545725557256572575725857259572605726157262572635726457265572665726757268572695727057271572725727357274572755727657277572785727957280572815728257283572845728557286572875728857289572905729157292572935729457295572965729757298572995730057301573025730357304573055730657307573085730957310573115731257313573145731557316573175731857319573205732157322573235732457325573265732757328573295733057331573325733357334573355733657337573385733957340573415734257343573445734557346573475734857349573505735157352573535735457355573565735757358573595736057361573625736357364573655736657367573685736957370573715737257373573745737557376573775737857379573805738157382573835738457385573865738757388573895739057391573925739357394573955739657397573985739957400574015740257403574045740557406574075740857409574105741157412574135741457415574165741757418574195742057421574225742357424574255742657427574285742957430574315743257433574345743557436574375743857439574405744157442574435744457445574465744757448574495745057451574525745357454574555745657457574585745957460574615746257463574645746557466574675746857469574705747157472574735747457475574765747757478574795748057481574825748357484574855748657487574885748957490574915749257493574945749557496574975749857499575005750157502575035750457505575065750757508575095751057511575125751357514575155751657517575185751957520575215752257523575245752557526575275752857529575305753157532575335753457535575365753757538575395754057541575425754357544575455754657547575485754957550575515755257553575545755557556575575755857559575605756157562575635756457565575665756757568575695757057571575725757357574575755757657577575785757957580575815758257583575845758557586575875758857589575905759157592575935759457595575965759757598575995760057601576025760357604576055760657607576085760957610576115761257613576145761557616576175761857619576205762157622576235762457625576265762757628576295763057631576325763357634576355763657637576385763957640576415764257643576445764557646576475764857649576505765157652576535765457655576565765757658576595766057661576625766357664576655766657667576685766957670576715767257673576745767557676576775767857679576805768157682576835768457685576865768757688576895769057691576925769357694576955769657697576985769957700577015770257703577045770557706577075770857709577105771157712577135771457715577165771757718577195772057721577225772357724577255772657727577285772957730577315773257733577345773557736577375773857739577405774157742577435774457745577465774757748577495775057751577525775357754577555775657757577585775957760577615776257763577645776557766577675776857769577705777157772577735777457775577765777757778577795778057781577825778357784577855778657787577885778957790577915779257793577945779557796577975779857799578005780157802578035780457805578065780757808578095781057811578125781357814578155781657817578185781957820578215782257823578245782557826578275782857829578305783157832578335783457835578365783757838578395784057841578425784357844578455784657847578485784957850578515785257853578545785557856578575785857859578605786157862578635786457865578665786757868578695787057871578725787357874578755787657877578785787957880578815788257883578845788557886578875788857889578905789157892578935789457895578965789757898578995790057901579025790357904579055790657907579085790957910579115791257913579145791557916579175791857919579205792157922579235792457925579265792757928579295793057931579325793357934579355793657937579385793957940579415794257943579445794557946579475794857949579505795157952579535795457955579565795757958579595796057961579625796357964579655796657967579685796957970579715797257973579745797557976579775797857979579805798157982579835798457985579865798757988579895799057991579925799357994579955799657997579985799958000580015800258003580045800558006580075800858009580105801158012580135801458015580165801758018580195802058021580225802358024580255802658027580285802958030580315803258033580345803558036580375803858039580405804158042580435804458045580465804758048580495805058051580525805358054580555805658057580585805958060580615806258063580645806558066580675806858069580705807158072580735807458075580765807758078580795808058081580825808358084580855808658087580885808958090580915809258093580945809558096580975809858099581005810158102581035810458105581065810758108581095811058111581125811358114581155811658117581185811958120581215812258123581245812558126581275812858129581305813158132581335813458135581365813758138581395814058141581425814358144581455814658147581485814958150581515815258153581545815558156581575815858159581605816158162581635816458165581665816758168581695817058171581725817358174581755817658177581785817958180581815818258183581845818558186581875818858189581905819158192581935819458195581965819758198581995820058201582025820358204582055820658207582085820958210582115821258213582145821558216582175821858219582205822158222582235822458225582265822758228582295823058231582325823358234582355823658237582385823958240582415824258243582445824558246582475824858249582505825158252582535825458255582565825758258582595826058261582625826358264582655826658267582685826958270582715827258273582745827558276582775827858279582805828158282582835828458285582865828758288582895829058291582925829358294582955829658297582985829958300583015830258303583045830558306583075830858309583105831158312583135831458315583165831758318583195832058321583225832358324583255832658327583285832958330583315833258333583345833558336583375833858339583405834158342583435834458345583465834758348583495835058351583525835358354583555835658357583585835958360583615836258363583645836558366583675836858369583705837158372583735837458375583765837758378583795838058381583825838358384583855838658387583885838958390583915839258393583945839558396583975839858399584005840158402584035840458405584065840758408584095841058411584125841358414584155841658417584185841958420584215842258423584245842558426584275842858429584305843158432584335843458435584365843758438584395844058441584425844358444584455844658447584485844958450584515845258453584545845558456584575845858459584605846158462584635846458465584665846758468584695847058471584725847358474584755847658477584785847958480584815848258483584845848558486584875848858489584905849158492584935849458495584965849758498584995850058501585025850358504585055850658507585085850958510585115851258513585145851558516585175851858519585205852158522585235852458525585265852758528585295853058531585325853358534585355853658537585385853958540585415854258543585445854558546585475854858549585505855158552585535855458555585565855758558585595856058561585625856358564585655856658567585685856958570585715857258573585745857558576585775857858579585805858158582585835858458585585865858758588585895859058591585925859358594585955859658597585985859958600586015860258603586045860558606586075860858609586105861158612586135861458615586165861758618586195862058621586225862358624586255862658627586285862958630586315863258633586345863558636586375863858639586405864158642586435864458645586465864758648586495865058651586525865358654586555865658657586585865958660586615866258663586645866558666586675866858669586705867158672586735867458675586765867758678586795868058681586825868358684586855868658687586885868958690586915869258693586945869558696586975869858699587005870158702587035870458705587065870758708587095871058711587125871358714587155871658717587185871958720587215872258723587245872558726587275872858729587305873158732587335873458735587365873758738587395874058741587425874358744587455874658747587485874958750587515875258753587545875558756587575875858759587605876158762587635876458765587665876758768587695877058771587725877358774587755877658777587785877958780587815878258783587845878558786587875878858789587905879158792587935879458795587965879758798587995880058801588025880358804588055880658807588085880958810588115881258813588145881558816588175881858819588205882158822588235882458825588265882758828588295883058831588325883358834588355883658837588385883958840588415884258843588445884558846588475884858849588505885158852588535885458855588565885758858588595886058861588625886358864588655886658867588685886958870588715887258873588745887558876588775887858879588805888158882588835888458885588865888758888588895889058891588925889358894588955889658897588985889958900589015890258903589045890558906589075890858909589105891158912589135891458915589165891758918589195892058921589225892358924
  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' + new Date().getTime();
  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 = Date.now();
  1895. var throttled = function throttled() {
  1896. var now = Date.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 = new Date().getTime(); // 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 = new Date().getTime() - 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 device is touch-enabled.
  4245. *
  4246. * @static
  4247. * @const
  4248. * @type {Boolean}
  4249. */
  4250. var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch);
  4251. var browser = /*#__PURE__*/Object.freeze({
  4252. IS_IPAD: IS_IPAD,
  4253. IS_IPHONE: IS_IPHONE,
  4254. IS_IPOD: IS_IPOD,
  4255. IS_IOS: IS_IOS,
  4256. IOS_VERSION: IOS_VERSION,
  4257. IS_ANDROID: IS_ANDROID,
  4258. ANDROID_VERSION: ANDROID_VERSION,
  4259. IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
  4260. IS_FIREFOX: IS_FIREFOX,
  4261. IS_EDGE: IS_EDGE,
  4262. IS_CHROME: IS_CHROME,
  4263. CHROME_VERSION: CHROME_VERSION,
  4264. IE_VERSION: IE_VERSION,
  4265. IS_SAFARI: IS_SAFARI,
  4266. IS_ANY_SAFARI: IS_ANY_SAFARI,
  4267. TOUCH_ENABLED: TOUCH_ENABLED
  4268. });
  4269. /**
  4270. * @file time-ranges.js
  4271. * @module time-ranges
  4272. */
  4273. /**
  4274. * Returns the time for the specified index at the start or end
  4275. * of a TimeRange object.
  4276. *
  4277. * @typedef {Function} TimeRangeIndex
  4278. *
  4279. * @param {number} [index=0]
  4280. * The range number to return the time for.
  4281. *
  4282. * @return {number}
  4283. * The time offset at the specified index.
  4284. *
  4285. * @deprecated The index argument must be provided.
  4286. * In the future, leaving it out will throw an error.
  4287. */
  4288. /**
  4289. * An object that contains ranges of time.
  4290. *
  4291. * @typedef {Object} TimeRange
  4292. *
  4293. * @property {number} length
  4294. * The number of time ranges represented by this object.
  4295. *
  4296. * @property {module:time-ranges~TimeRangeIndex} start
  4297. * Returns the time offset at which a specified time range begins.
  4298. *
  4299. * @property {module:time-ranges~TimeRangeIndex} end
  4300. * Returns the time offset at which a specified time range ends.
  4301. *
  4302. * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
  4303. */
  4304. /**
  4305. * Check if any of the time ranges are over the maximum index.
  4306. *
  4307. * @private
  4308. * @param {string} fnName
  4309. * The function name to use for logging
  4310. *
  4311. * @param {number} index
  4312. * The index to check
  4313. *
  4314. * @param {number} maxIndex
  4315. * The maximum possible index
  4316. *
  4317. * @throws {Error} if the timeRanges provided are over the maxIndex
  4318. */
  4319. function rangeCheck(fnName, index, maxIndex) {
  4320. if (typeof index !== 'number' || index < 0 || index > maxIndex) {
  4321. throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ").");
  4322. }
  4323. }
  4324. /**
  4325. * Get the time for the specified index at the start or end
  4326. * of a TimeRange object.
  4327. *
  4328. * @private
  4329. * @param {string} fnName
  4330. * The function name to use for logging
  4331. *
  4332. * @param {string} valueIndex
  4333. * The property that should be used to get the time. should be
  4334. * 'start' or 'end'
  4335. *
  4336. * @param {Array} ranges
  4337. * An array of time ranges
  4338. *
  4339. * @param {Array} [rangeIndex=0]
  4340. * The index to start the search at
  4341. *
  4342. * @return {number}
  4343. * The time that offset at the specified index.
  4344. *
  4345. * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
  4346. * @throws {Error} if rangeIndex is more than the length of ranges
  4347. */
  4348. function getRange(fnName, valueIndex, ranges, rangeIndex) {
  4349. rangeCheck(fnName, rangeIndex, ranges.length - 1);
  4350. return ranges[rangeIndex][valueIndex];
  4351. }
  4352. /**
  4353. * Create a time range object given ranges of time.
  4354. *
  4355. * @private
  4356. * @param {Array} [ranges]
  4357. * An array of time ranges.
  4358. */
  4359. function createTimeRangesObj(ranges) {
  4360. if (ranges === undefined || ranges.length === 0) {
  4361. return {
  4362. length: 0,
  4363. start: function start() {
  4364. throw new Error('This TimeRanges object is empty');
  4365. },
  4366. end: function end() {
  4367. throw new Error('This TimeRanges object is empty');
  4368. }
  4369. };
  4370. }
  4371. return {
  4372. length: ranges.length,
  4373. start: getRange.bind(null, 'start', 0, ranges),
  4374. end: getRange.bind(null, 'end', 1, ranges)
  4375. };
  4376. }
  4377. /**
  4378. * Create a `TimeRange` object which mimics an
  4379. * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
  4380. *
  4381. * @param {number|Array[]} start
  4382. * The start of a single range (a number) or an array of ranges (an
  4383. * array of arrays of two numbers each).
  4384. *
  4385. * @param {number} end
  4386. * The end of a single range. Cannot be used with the array form of
  4387. * the `start` argument.
  4388. */
  4389. function createTimeRanges(start, end) {
  4390. if (Array.isArray(start)) {
  4391. return createTimeRangesObj(start);
  4392. } else if (start === undefined || end === undefined) {
  4393. return createTimeRangesObj();
  4394. }
  4395. return createTimeRangesObj([[start, end]]);
  4396. }
  4397. /**
  4398. * @file buffer.js
  4399. * @module buffer
  4400. */
  4401. /**
  4402. * Compute the percentage of the media that has been buffered.
  4403. *
  4404. * @param {TimeRange} buffered
  4405. * The current `TimeRange` object representing buffered time ranges
  4406. *
  4407. * @param {number} duration
  4408. * Total duration of the media
  4409. *
  4410. * @return {number}
  4411. * Percent buffered of the total duration in decimal form.
  4412. */
  4413. function bufferedPercent(buffered, duration) {
  4414. var bufferedDuration = 0;
  4415. var start;
  4416. var end;
  4417. if (!duration) {
  4418. return 0;
  4419. }
  4420. if (!buffered || !buffered.length) {
  4421. buffered = createTimeRanges(0, 0);
  4422. }
  4423. for (var i = 0; i < buffered.length; i++) {
  4424. start = buffered.start(i);
  4425. end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction
  4426. if (end > duration) {
  4427. end = duration;
  4428. }
  4429. bufferedDuration += end - start;
  4430. }
  4431. return bufferedDuration / duration;
  4432. }
  4433. /**
  4434. * @file fullscreen-api.js
  4435. * @module fullscreen-api
  4436. * @private
  4437. */
  4438. /**
  4439. * Store the browser-specific methods for the fullscreen API.
  4440. *
  4441. * @type {Object}
  4442. * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
  4443. * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
  4444. */
  4445. var FullscreenApi = {}; // browser API methods
  4446. var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit
  4447. ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla
  4448. ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft
  4449. ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']];
  4450. var specApi = apiMap[0];
  4451. var browserApi;
  4452. var prefixedAPI = false; // determine the supported set of functions
  4453. for (var i = 0; i < apiMap.length; i++) {
  4454. // check for exitFullscreen function
  4455. if (apiMap[i][1] in document) {
  4456. browserApi = apiMap[i];
  4457. break;
  4458. }
  4459. } // map the browser API names to the spec API names
  4460. if (browserApi) {
  4461. for (var _i = 0; _i < browserApi.length; _i++) {
  4462. FullscreenApi[specApi[_i]] = browserApi[_i];
  4463. }
  4464. prefixedAPI = browserApi[0] === specApi[0];
  4465. }
  4466. /**
  4467. * @file media-error.js
  4468. */
  4469. /**
  4470. * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
  4471. *
  4472. * @param {number|string|Object|MediaError} value
  4473. * This can be of multiple types:
  4474. * - number: should be a standard error code
  4475. * - string: an error message (the code will be 0)
  4476. * - Object: arbitrary properties
  4477. * - `MediaError` (native): used to populate a video.js `MediaError` object
  4478. * - `MediaError` (video.js): will return itself if it's already a
  4479. * video.js `MediaError` object.
  4480. *
  4481. * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
  4482. * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
  4483. *
  4484. * @class MediaError
  4485. */
  4486. function MediaError(value) {
  4487. // Allow redundant calls to this constructor to avoid having `instanceof`
  4488. // checks peppered around the code.
  4489. if (value instanceof MediaError) {
  4490. return value;
  4491. }
  4492. if (typeof value === 'number') {
  4493. this.code = value;
  4494. } else if (typeof value === 'string') {
  4495. // default code is zero, so this is a custom error
  4496. this.message = value;
  4497. } else if (isObject(value)) {
  4498. // We assign the `code` property manually because native `MediaError` objects
  4499. // do not expose it as an own/enumerable property of the object.
  4500. if (typeof value.code === 'number') {
  4501. this.code = value.code;
  4502. }
  4503. assign(this, value);
  4504. }
  4505. if (!this.message) {
  4506. this.message = MediaError.defaultMessages[this.code] || '';
  4507. }
  4508. }
  4509. /**
  4510. * The error code that refers two one of the defined `MediaError` types
  4511. *
  4512. * @type {Number}
  4513. */
  4514. MediaError.prototype.code = 0;
  4515. /**
  4516. * An optional message that to show with the error. Message is not part of the HTML5
  4517. * video spec but allows for more informative custom errors.
  4518. *
  4519. * @type {String}
  4520. */
  4521. MediaError.prototype.message = '';
  4522. /**
  4523. * An optional status code that can be set by plugins to allow even more detail about
  4524. * the error. For example a plugin might provide a specific HTTP status code and an
  4525. * error message for that code. Then when the plugin gets that error this class will
  4526. * know how to display an error message for it. This allows a custom message to show
  4527. * up on the `Player` error overlay.
  4528. *
  4529. * @type {Array}
  4530. */
  4531. MediaError.prototype.status = null;
  4532. /**
  4533. * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
  4534. * specification listed under {@link MediaError} for more information.
  4535. *
  4536. * @enum {array}
  4537. * @readonly
  4538. * @property {string} 0 - MEDIA_ERR_CUSTOM
  4539. * @property {string} 1 - MEDIA_ERR_ABORTED
  4540. * @property {string} 2 - MEDIA_ERR_NETWORK
  4541. * @property {string} 3 - MEDIA_ERR_DECODE
  4542. * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
  4543. * @property {string} 5 - MEDIA_ERR_ENCRYPTED
  4544. */
  4545. MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
  4546. /**
  4547. * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
  4548. *
  4549. * @type {Array}
  4550. * @constant
  4551. */
  4552. MediaError.defaultMessages = {
  4553. 1: 'You aborted the media playback',
  4554. 2: 'A network error caused the media download to fail part-way.',
  4555. 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
  4556. 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
  4557. 5: 'The media is encrypted and we do not have the keys to decrypt it.'
  4558. }; // Add types as properties on MediaError
  4559. // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
  4560. for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
  4561. MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance
  4562. MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
  4563. } // jsdocs for instance/static members added above
  4564. var tuple = SafeParseTuple;
  4565. function SafeParseTuple(obj, reviver) {
  4566. var json;
  4567. var error = null;
  4568. try {
  4569. json = JSON.parse(obj, reviver);
  4570. } catch (err) {
  4571. error = err;
  4572. }
  4573. return [error, json];
  4574. }
  4575. /**
  4576. * Returns whether an object is `Promise`-like (i.e. has a `then` method).
  4577. *
  4578. * @param {Object} value
  4579. * An object that may or may not be `Promise`-like.
  4580. *
  4581. * @return {boolean}
  4582. * Whether or not the object is `Promise`-like.
  4583. */
  4584. function isPromise(value) {
  4585. return value !== undefined && value !== null && typeof value.then === 'function';
  4586. }
  4587. /**
  4588. * Silence a Promise-like object.
  4589. *
  4590. * This is useful for avoiding non-harmful, but potentially confusing "uncaught
  4591. * play promise" rejection error messages.
  4592. *
  4593. * @param {Object} value
  4594. * An object that may or may not be `Promise`-like.
  4595. */
  4596. function silencePromise(value) {
  4597. if (isPromise(value)) {
  4598. value.then(null, function (e) {});
  4599. }
  4600. }
  4601. /**
  4602. * @file text-track-list-converter.js Utilities for capturing text track state and
  4603. * re-creating tracks based on a capture.
  4604. *
  4605. * @module text-track-list-converter
  4606. */
  4607. /**
  4608. * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
  4609. * represents the {@link TextTrack}'s state.
  4610. *
  4611. * @param {TextTrack} track
  4612. * The text track to query.
  4613. *
  4614. * @return {Object}
  4615. * A serializable javascript representation of the TextTrack.
  4616. * @private
  4617. */
  4618. var trackToJson_ = function trackToJson_(track) {
  4619. var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
  4620. if (track[prop]) {
  4621. acc[prop] = track[prop];
  4622. }
  4623. return acc;
  4624. }, {
  4625. cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
  4626. return {
  4627. startTime: cue.startTime,
  4628. endTime: cue.endTime,
  4629. text: cue.text,
  4630. id: cue.id
  4631. };
  4632. })
  4633. });
  4634. return ret;
  4635. };
  4636. /**
  4637. * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
  4638. * state of all {@link TextTrack}s currently configured. The return array is compatible with
  4639. * {@link text-track-list-converter:jsonToTextTracks}.
  4640. *
  4641. * @param {Tech} tech
  4642. * The tech object to query
  4643. *
  4644. * @return {Array}
  4645. * A serializable javascript representation of the {@link Tech}s
  4646. * {@link TextTrackList}.
  4647. */
  4648. var textTracksToJson = function textTracksToJson(tech) {
  4649. var trackEls = tech.$$('track');
  4650. var trackObjs = Array.prototype.map.call(trackEls, function (t) {
  4651. return t.track;
  4652. });
  4653. var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
  4654. var json = trackToJson_(trackEl.track);
  4655. if (trackEl.src) {
  4656. json.src = trackEl.src;
  4657. }
  4658. return json;
  4659. });
  4660. return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
  4661. return trackObjs.indexOf(track) === -1;
  4662. }).map(trackToJson_));
  4663. };
  4664. /**
  4665. * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
  4666. * object {@link TextTrack} representations.
  4667. *
  4668. * @param {Array} json
  4669. * An array of `TextTrack` representation objects, like those that would be
  4670. * produced by `textTracksToJson`.
  4671. *
  4672. * @param {Tech} tech
  4673. * The `Tech` to create the `TextTrack`s on.
  4674. */
  4675. var jsonToTextTracks = function jsonToTextTracks(json, tech) {
  4676. json.forEach(function (track) {
  4677. var addedTrack = tech.addRemoteTextTrack(track).track;
  4678. if (!track.src && track.cues) {
  4679. track.cues.forEach(function (cue) {
  4680. return addedTrack.addCue(cue);
  4681. });
  4682. }
  4683. });
  4684. return tech.textTracks();
  4685. };
  4686. var textTrackConverter = {
  4687. textTracksToJson: textTracksToJson,
  4688. jsonToTextTracks: jsonToTextTracks,
  4689. trackToJson_: trackToJson_
  4690. };
  4691. var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
  4692. function createCommonjsModule(fn, module) {
  4693. return module = { exports: {} }, fn(module, module.exports), module.exports;
  4694. }
  4695. var keycode = createCommonjsModule(function (module, exports) {
  4696. // Source: http://jsfiddle.net/vWx8V/
  4697. // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
  4698. /**
  4699. * Conenience method returns corresponding value for given keyName or keyCode.
  4700. *
  4701. * @param {Mixed} keyCode {Number} or keyName {String}
  4702. * @return {Mixed}
  4703. * @api public
  4704. */
  4705. function keyCode(searchInput) {
  4706. // Keyboard Events
  4707. if (searchInput && 'object' === typeof searchInput) {
  4708. var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
  4709. if (hasKeyCode) searchInput = hasKeyCode;
  4710. } // Numbers
  4711. if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string)
  4712. var search = String(searchInput); // check codes
  4713. var foundNamedKey = codes[search.toLowerCase()];
  4714. if (foundNamedKey) return foundNamedKey; // check aliases
  4715. var foundNamedKey = aliases[search.toLowerCase()];
  4716. if (foundNamedKey) return foundNamedKey; // weird character?
  4717. if (search.length === 1) return search.charCodeAt(0);
  4718. return undefined;
  4719. }
  4720. /**
  4721. * Compares a keyboard event with a given keyCode or keyName.
  4722. *
  4723. * @param {Event} event Keyboard event that should be tested
  4724. * @param {Mixed} keyCode {Number} or keyName {String}
  4725. * @return {Boolean}
  4726. * @api public
  4727. */
  4728. keyCode.isEventKey = function isEventKey(event, nameOrCode) {
  4729. if (event && 'object' === typeof event) {
  4730. var keyCode = event.which || event.keyCode || event.charCode;
  4731. if (keyCode === null || keyCode === undefined) {
  4732. return false;
  4733. }
  4734. if (typeof nameOrCode === 'string') {
  4735. // check codes
  4736. var foundNamedKey = codes[nameOrCode.toLowerCase()];
  4737. if (foundNamedKey) {
  4738. return foundNamedKey === keyCode;
  4739. } // check aliases
  4740. var foundNamedKey = aliases[nameOrCode.toLowerCase()];
  4741. if (foundNamedKey) {
  4742. return foundNamedKey === keyCode;
  4743. }
  4744. } else if (typeof nameOrCode === 'number') {
  4745. return nameOrCode === keyCode;
  4746. }
  4747. return false;
  4748. }
  4749. };
  4750. exports = module.exports = keyCode;
  4751. /**
  4752. * Get by name
  4753. *
  4754. * exports.code['enter'] // => 13
  4755. */
  4756. var codes = exports.code = exports.codes = {
  4757. 'backspace': 8,
  4758. 'tab': 9,
  4759. 'enter': 13,
  4760. 'shift': 16,
  4761. 'ctrl': 17,
  4762. 'alt': 18,
  4763. 'pause/break': 19,
  4764. 'caps lock': 20,
  4765. 'esc': 27,
  4766. 'space': 32,
  4767. 'page up': 33,
  4768. 'page down': 34,
  4769. 'end': 35,
  4770. 'home': 36,
  4771. 'left': 37,
  4772. 'up': 38,
  4773. 'right': 39,
  4774. 'down': 40,
  4775. 'insert': 45,
  4776. 'delete': 46,
  4777. 'command': 91,
  4778. 'left command': 91,
  4779. 'right command': 93,
  4780. 'numpad *': 106,
  4781. 'numpad +': 107,
  4782. 'numpad -': 109,
  4783. 'numpad .': 110,
  4784. 'numpad /': 111,
  4785. 'num lock': 144,
  4786. 'scroll lock': 145,
  4787. 'my computer': 182,
  4788. 'my calculator': 183,
  4789. ';': 186,
  4790. '=': 187,
  4791. ',': 188,
  4792. '-': 189,
  4793. '.': 190,
  4794. '/': 191,
  4795. '`': 192,
  4796. '[': 219,
  4797. '\\': 220,
  4798. ']': 221,
  4799. "'": 222 // Helper aliases
  4800. };
  4801. var aliases = exports.aliases = {
  4802. 'windows': 91,
  4803. '⇧': 16,
  4804. '⌥': 18,
  4805. '⌃': 17,
  4806. '⌘': 91,
  4807. 'ctl': 17,
  4808. 'control': 17,
  4809. 'option': 18,
  4810. 'pause': 19,
  4811. 'break': 19,
  4812. 'caps': 20,
  4813. 'return': 13,
  4814. 'escape': 27,
  4815. 'spc': 32,
  4816. 'spacebar': 32,
  4817. 'pgup': 33,
  4818. 'pgdn': 34,
  4819. 'ins': 45,
  4820. 'del': 46,
  4821. 'cmd': 91
  4822. /*!
  4823. * Programatically add the following
  4824. */
  4825. // lower case chars
  4826. };
  4827. for (i = 97; i < 123; i++) {
  4828. codes[String.fromCharCode(i)] = i - 32;
  4829. } // numbers
  4830. for (var i = 48; i < 58; i++) {
  4831. codes[i - 48] = i;
  4832. } // function keys
  4833. for (i = 1; i < 13; i++) {
  4834. codes['f' + i] = i + 111;
  4835. } // numpad keys
  4836. for (i = 0; i < 10; i++) {
  4837. codes['numpad ' + i] = i + 96;
  4838. }
  4839. /**
  4840. * Get by code
  4841. *
  4842. * exports.name[13] // => 'Enter'
  4843. */
  4844. var names = exports.names = exports.title = {}; // title for backward compat
  4845. // Create reverse mapping
  4846. for (i in codes) {
  4847. names[codes[i]] = i;
  4848. } // Add aliases
  4849. for (var alias in aliases) {
  4850. codes[alias] = aliases[alias];
  4851. }
  4852. });
  4853. var keycode_1 = keycode.code;
  4854. var keycode_2 = keycode.codes;
  4855. var keycode_3 = keycode.aliases;
  4856. var keycode_4 = keycode.names;
  4857. var keycode_5 = keycode.title;
  4858. var MODAL_CLASS_NAME = 'vjs-modal-dialog';
  4859. /**
  4860. * The `ModalDialog` displays over the video and its controls, which blocks
  4861. * interaction with the player until it is closed.
  4862. *
  4863. * Modal dialogs include a "Close" button and will close when that button
  4864. * is activated - or when ESC is pressed anywhere.
  4865. *
  4866. * @extends Component
  4867. */
  4868. var ModalDialog =
  4869. /*#__PURE__*/
  4870. function (_Component) {
  4871. _inheritsLoose(ModalDialog, _Component);
  4872. /**
  4873. * Create an instance of this class.
  4874. *
  4875. * @param {Player} player
  4876. * The `Player` that this class should be attached to.
  4877. *
  4878. * @param {Object} [options]
  4879. * The key/value store of player options.
  4880. *
  4881. * @param {Mixed} [options.content=undefined]
  4882. * Provide customized content for this modal.
  4883. *
  4884. * @param {string} [options.description]
  4885. * A text description for the modal, primarily for accessibility.
  4886. *
  4887. * @param {boolean} [options.fillAlways=false]
  4888. * Normally, modals are automatically filled only the first time
  4889. * they open. This tells the modal to refresh its content
  4890. * every time it opens.
  4891. *
  4892. * @param {string} [options.label]
  4893. * A text label for the modal, primarily for accessibility.
  4894. *
  4895. * @param {boolean} [options.pauseOnOpen=true]
  4896. * If `true`, playback will will be paused if playing when
  4897. * the modal opens, and resumed when it closes.
  4898. *
  4899. * @param {boolean} [options.temporary=true]
  4900. * If `true`, the modal can only be opened once; it will be
  4901. * disposed as soon as it's closed.
  4902. *
  4903. * @param {boolean} [options.uncloseable=false]
  4904. * If `true`, the user will not be able to close the modal
  4905. * through the UI in the normal ways. Programmatic closing is
  4906. * still possible.
  4907. */
  4908. function ModalDialog(player, options) {
  4909. var _this;
  4910. _this = _Component.call(this, player, options) || this;
  4911. _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
  4912. _this.closeable(!_this.options_.uncloseable);
  4913. _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized
  4914. // because we only want the contents of the modal in the contentEl
  4915. // (not the UI elements like the close button).
  4916. _this.contentEl_ = createEl('div', {
  4917. className: MODAL_CLASS_NAME + "-content"
  4918. }, {
  4919. role: 'document'
  4920. });
  4921. _this.descEl_ = createEl('p', {
  4922. className: MODAL_CLASS_NAME + "-description vjs-control-text",
  4923. id: _this.el().getAttribute('aria-describedby')
  4924. });
  4925. textContent(_this.descEl_, _this.description());
  4926. _this.el_.appendChild(_this.descEl_);
  4927. _this.el_.appendChild(_this.contentEl_);
  4928. return _this;
  4929. }
  4930. /**
  4931. * Create the `ModalDialog`'s DOM element
  4932. *
  4933. * @return {Element}
  4934. * The DOM element that gets created.
  4935. */
  4936. var _proto = ModalDialog.prototype;
  4937. _proto.createEl = function createEl$$1() {
  4938. return _Component.prototype.createEl.call(this, 'div', {
  4939. className: this.buildCSSClass(),
  4940. tabIndex: -1
  4941. }, {
  4942. 'aria-describedby': this.id() + "_description",
  4943. 'aria-hidden': 'true',
  4944. 'aria-label': this.label(),
  4945. 'role': 'dialog'
  4946. });
  4947. };
  4948. _proto.dispose = function dispose() {
  4949. this.contentEl_ = null;
  4950. this.descEl_ = null;
  4951. this.previouslyActiveEl_ = null;
  4952. _Component.prototype.dispose.call(this);
  4953. }
  4954. /**
  4955. * Builds the default DOM `className`.
  4956. *
  4957. * @return {string}
  4958. * The DOM `className` for this object.
  4959. */
  4960. ;
  4961. _proto.buildCSSClass = function buildCSSClass() {
  4962. return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this);
  4963. }
  4964. /**
  4965. * Handles `keydown` events on the document, looking for ESC, which closes
  4966. * the modal.
  4967. *
  4968. * @param {EventTarget~Event} event
  4969. * The keypress that triggered this event.
  4970. *
  4971. * @listens keydown
  4972. */
  4973. ;
  4974. _proto.handleKeyPress = function handleKeyPress(event) {
  4975. if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
  4976. this.close();
  4977. }
  4978. }
  4979. /**
  4980. * Returns the label string for this modal. Primarily used for accessibility.
  4981. *
  4982. * @return {string}
  4983. * the localized or raw label of this modal.
  4984. */
  4985. ;
  4986. _proto.label = function label() {
  4987. return this.localize(this.options_.label || 'Modal Window');
  4988. }
  4989. /**
  4990. * Returns the description string for this modal. Primarily used for
  4991. * accessibility.
  4992. *
  4993. * @return {string}
  4994. * The localized or raw description of this modal.
  4995. */
  4996. ;
  4997. _proto.description = function description() {
  4998. var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable.
  4999. if (this.closeable()) {
  5000. desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
  5001. }
  5002. return desc;
  5003. }
  5004. /**
  5005. * Opens the modal.
  5006. *
  5007. * @fires ModalDialog#beforemodalopen
  5008. * @fires ModalDialog#modalopen
  5009. */
  5010. ;
  5011. _proto.open = function open() {
  5012. if (!this.opened_) {
  5013. var player = this.player();
  5014. /**
  5015. * Fired just before a `ModalDialog` is opened.
  5016. *
  5017. * @event ModalDialog#beforemodalopen
  5018. * @type {EventTarget~Event}
  5019. */
  5020. this.trigger('beforemodalopen');
  5021. this.opened_ = true; // Fill content if the modal has never opened before and
  5022. // never been filled.
  5023. if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
  5024. this.fill();
  5025. } // If the player was playing, pause it and take note of its previously
  5026. // playing state.
  5027. this.wasPlaying_ = !player.paused();
  5028. if (this.options_.pauseOnOpen && this.wasPlaying_) {
  5029. player.pause();
  5030. }
  5031. if (this.closeable()) {
  5032. this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
  5033. } // Hide controls and note if they were enabled.
  5034. this.hadControls_ = player.controls();
  5035. player.controls(false);
  5036. this.show();
  5037. this.conditionalFocus_();
  5038. this.el().setAttribute('aria-hidden', 'false');
  5039. /**
  5040. * Fired just after a `ModalDialog` is opened.
  5041. *
  5042. * @event ModalDialog#modalopen
  5043. * @type {EventTarget~Event}
  5044. */
  5045. this.trigger('modalopen');
  5046. this.hasBeenOpened_ = true;
  5047. }
  5048. }
  5049. /**
  5050. * If the `ModalDialog` is currently open or closed.
  5051. *
  5052. * @param {boolean} [value]
  5053. * If given, it will open (`true`) or close (`false`) the modal.
  5054. *
  5055. * @return {boolean}
  5056. * the current open state of the modaldialog
  5057. */
  5058. ;
  5059. _proto.opened = function opened(value) {
  5060. if (typeof value === 'boolean') {
  5061. this[value ? 'open' : 'close']();
  5062. }
  5063. return this.opened_;
  5064. }
  5065. /**
  5066. * Closes the modal, does nothing if the `ModalDialog` is
  5067. * not open.
  5068. *
  5069. * @fires ModalDialog#beforemodalclose
  5070. * @fires ModalDialog#modalclose
  5071. */
  5072. ;
  5073. _proto.close = function close() {
  5074. if (!this.opened_) {
  5075. return;
  5076. }
  5077. var player = this.player();
  5078. /**
  5079. * Fired just before a `ModalDialog` is closed.
  5080. *
  5081. * @event ModalDialog#beforemodalclose
  5082. * @type {EventTarget~Event}
  5083. */
  5084. this.trigger('beforemodalclose');
  5085. this.opened_ = false;
  5086. if (this.wasPlaying_ && this.options_.pauseOnOpen) {
  5087. player.play();
  5088. }
  5089. if (this.closeable()) {
  5090. this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
  5091. }
  5092. if (this.hadControls_) {
  5093. player.controls(true);
  5094. }
  5095. this.hide();
  5096. this.el().setAttribute('aria-hidden', 'true');
  5097. /**
  5098. * Fired just after a `ModalDialog` is closed.
  5099. *
  5100. * @event ModalDialog#modalclose
  5101. * @type {EventTarget~Event}
  5102. */
  5103. this.trigger('modalclose');
  5104. this.conditionalBlur_();
  5105. if (this.options_.temporary) {
  5106. this.dispose();
  5107. }
  5108. }
  5109. /**
  5110. * Check to see if the `ModalDialog` is closeable via the UI.
  5111. *
  5112. * @param {boolean} [value]
  5113. * If given as a boolean, it will set the `closeable` option.
  5114. *
  5115. * @return {boolean}
  5116. * Returns the final value of the closable option.
  5117. */
  5118. ;
  5119. _proto.closeable = function closeable(value) {
  5120. if (typeof value === 'boolean') {
  5121. var closeable = this.closeable_ = !!value;
  5122. var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one.
  5123. if (closeable && !close) {
  5124. // The close button should be a child of the modal - not its
  5125. // content element, so temporarily change the content element.
  5126. var temp = this.contentEl_;
  5127. this.contentEl_ = this.el_;
  5128. close = this.addChild('closeButton', {
  5129. controlText: 'Close Modal Dialog'
  5130. });
  5131. this.contentEl_ = temp;
  5132. this.on(close, 'close', this.close);
  5133. } // If this is being made uncloseable and has a close button, remove it.
  5134. if (!closeable && close) {
  5135. this.off(close, 'close', this.close);
  5136. this.removeChild(close);
  5137. close.dispose();
  5138. }
  5139. }
  5140. return this.closeable_;
  5141. }
  5142. /**
  5143. * Fill the modal's content element with the modal's "content" option.
  5144. * The content element will be emptied before this change takes place.
  5145. */
  5146. ;
  5147. _proto.fill = function fill() {
  5148. this.fillWith(this.content());
  5149. }
  5150. /**
  5151. * Fill the modal's content element with arbitrary content.
  5152. * The content element will be emptied before this change takes place.
  5153. *
  5154. * @fires ModalDialog#beforemodalfill
  5155. * @fires ModalDialog#modalfill
  5156. *
  5157. * @param {Mixed} [content]
  5158. * The same rules apply to this as apply to the `content` option.
  5159. */
  5160. ;
  5161. _proto.fillWith = function fillWith(content) {
  5162. var contentEl = this.contentEl();
  5163. var parentEl = contentEl.parentNode;
  5164. var nextSiblingEl = contentEl.nextSibling;
  5165. /**
  5166. * Fired just before a `ModalDialog` is filled with content.
  5167. *
  5168. * @event ModalDialog#beforemodalfill
  5169. * @type {EventTarget~Event}
  5170. */
  5171. this.trigger('beforemodalfill');
  5172. this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing
  5173. // manipulation to avoid modifying the live DOM multiple times.
  5174. parentEl.removeChild(contentEl);
  5175. this.empty();
  5176. insertContent(contentEl, content);
  5177. /**
  5178. * Fired just after a `ModalDialog` is filled with content.
  5179. *
  5180. * @event ModalDialog#modalfill
  5181. * @type {EventTarget~Event}
  5182. */
  5183. this.trigger('modalfill'); // Re-inject the re-filled content element.
  5184. if (nextSiblingEl) {
  5185. parentEl.insertBefore(contentEl, nextSiblingEl);
  5186. } else {
  5187. parentEl.appendChild(contentEl);
  5188. } // make sure that the close button is last in the dialog DOM
  5189. var closeButton = this.getChild('closeButton');
  5190. if (closeButton) {
  5191. parentEl.appendChild(closeButton.el_);
  5192. }
  5193. }
  5194. /**
  5195. * Empties the content element. This happens anytime the modal is filled.
  5196. *
  5197. * @fires ModalDialog#beforemodalempty
  5198. * @fires ModalDialog#modalempty
  5199. */
  5200. ;
  5201. _proto.empty = function empty() {
  5202. /**
  5203. * Fired just before a `ModalDialog` is emptied.
  5204. *
  5205. * @event ModalDialog#beforemodalempty
  5206. * @type {EventTarget~Event}
  5207. */
  5208. this.trigger('beforemodalempty');
  5209. emptyEl(this.contentEl());
  5210. /**
  5211. * Fired just after a `ModalDialog` is emptied.
  5212. *
  5213. * @event ModalDialog#modalempty
  5214. * @type {EventTarget~Event}
  5215. */
  5216. this.trigger('modalempty');
  5217. }
  5218. /**
  5219. * Gets or sets the modal content, which gets normalized before being
  5220. * rendered into the DOM.
  5221. *
  5222. * This does not update the DOM or fill the modal, but it is called during
  5223. * that process.
  5224. *
  5225. * @param {Mixed} [value]
  5226. * If defined, sets the internal content value to be used on the
  5227. * next call(s) to `fill`. This value is normalized before being
  5228. * inserted. To "clear" the internal content value, pass `null`.
  5229. *
  5230. * @return {Mixed}
  5231. * The current content of the modal dialog
  5232. */
  5233. ;
  5234. _proto.content = function content(value) {
  5235. if (typeof value !== 'undefined') {
  5236. this.content_ = value;
  5237. }
  5238. return this.content_;
  5239. }
  5240. /**
  5241. * conditionally focus the modal dialog if focus was previously on the player.
  5242. *
  5243. * @private
  5244. */
  5245. ;
  5246. _proto.conditionalFocus_ = function conditionalFocus_() {
  5247. var activeEl = document.activeElement;
  5248. var playerEl = this.player_.el_;
  5249. this.previouslyActiveEl_ = null;
  5250. if (playerEl.contains(activeEl) || playerEl === activeEl) {
  5251. this.previouslyActiveEl_ = activeEl;
  5252. this.focus();
  5253. this.on(document, 'keydown', this.handleKeyDown);
  5254. }
  5255. }
  5256. /**
  5257. * conditionally blur the element and refocus the last focused element
  5258. *
  5259. * @private
  5260. */
  5261. ;
  5262. _proto.conditionalBlur_ = function conditionalBlur_() {
  5263. if (this.previouslyActiveEl_) {
  5264. this.previouslyActiveEl_.focus();
  5265. this.previouslyActiveEl_ = null;
  5266. }
  5267. this.off(document, 'keydown', this.handleKeyDown);
  5268. }
  5269. /**
  5270. * Keydown handler. Attached when modal is focused.
  5271. *
  5272. * @listens keydown
  5273. */
  5274. ;
  5275. _proto.handleKeyDown = function handleKeyDown(event) {
  5276. // exit early if it isn't a tab key
  5277. if (!keycode.isEventKey(event, 'Tab')) {
  5278. return;
  5279. }
  5280. var focusableEls = this.focusableEls_();
  5281. var activeEl = this.el_.querySelector(':focus');
  5282. var focusIndex;
  5283. for (var i = 0; i < focusableEls.length; i++) {
  5284. if (activeEl === focusableEls[i]) {
  5285. focusIndex = i;
  5286. break;
  5287. }
  5288. }
  5289. if (document.activeElement === this.el_) {
  5290. focusIndex = 0;
  5291. }
  5292. if (event.shiftKey && focusIndex === 0) {
  5293. focusableEls[focusableEls.length - 1].focus();
  5294. event.preventDefault();
  5295. } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
  5296. focusableEls[0].focus();
  5297. event.preventDefault();
  5298. }
  5299. }
  5300. /**
  5301. * get all focusable elements
  5302. *
  5303. * @private
  5304. */
  5305. ;
  5306. _proto.focusableEls_ = function focusableEls_() {
  5307. var allChildren = this.el_.querySelectorAll('*');
  5308. return Array.prototype.filter.call(allChildren, function (child) {
  5309. 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');
  5310. });
  5311. };
  5312. return ModalDialog;
  5313. }(Component);
  5314. /**
  5315. * Default options for `ModalDialog` default options.
  5316. *
  5317. * @type {Object}
  5318. * @private
  5319. */
  5320. ModalDialog.prototype.options_ = {
  5321. pauseOnOpen: true,
  5322. temporary: true
  5323. };
  5324. Component.registerComponent('ModalDialog', ModalDialog);
  5325. /**
  5326. * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
  5327. * {@link VideoTrackList}
  5328. *
  5329. * @extends EventTarget
  5330. */
  5331. var TrackList =
  5332. /*#__PURE__*/
  5333. function (_EventTarget) {
  5334. _inheritsLoose(TrackList, _EventTarget);
  5335. /**
  5336. * Create an instance of this class
  5337. *
  5338. * @param {Track[]} tracks
  5339. * A list of tracks to initialize the list with.
  5340. *
  5341. * @abstract
  5342. */
  5343. function TrackList(tracks) {
  5344. var _this;
  5345. if (tracks === void 0) {
  5346. tracks = [];
  5347. }
  5348. _this = _EventTarget.call(this) || this;
  5349. _this.tracks_ = [];
  5350. /**
  5351. * @memberof TrackList
  5352. * @member {number} length
  5353. * The current number of `Track`s in the this Trackist.
  5354. * @instance
  5355. */
  5356. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'length', {
  5357. get: function get() {
  5358. return this.tracks_.length;
  5359. }
  5360. });
  5361. for (var i = 0; i < tracks.length; i++) {
  5362. _this.addTrack(tracks[i]);
  5363. }
  5364. return _this;
  5365. }
  5366. /**
  5367. * Add a {@link Track} to the `TrackList`
  5368. *
  5369. * @param {Track} track
  5370. * The audio, video, or text track to add to the list.
  5371. *
  5372. * @fires TrackList#addtrack
  5373. */
  5374. var _proto = TrackList.prototype;
  5375. _proto.addTrack = function addTrack(track) {
  5376. var index = this.tracks_.length;
  5377. if (!('' + index in this)) {
  5378. Object.defineProperty(this, index, {
  5379. get: function get() {
  5380. return this.tracks_[index];
  5381. }
  5382. });
  5383. } // Do not add duplicate tracks
  5384. if (this.tracks_.indexOf(track) === -1) {
  5385. this.tracks_.push(track);
  5386. /**
  5387. * Triggered when a track is added to a track list.
  5388. *
  5389. * @event TrackList#addtrack
  5390. * @type {EventTarget~Event}
  5391. * @property {Track} track
  5392. * A reference to track that was added.
  5393. */
  5394. this.trigger({
  5395. track: track,
  5396. type: 'addtrack',
  5397. target: this
  5398. });
  5399. }
  5400. }
  5401. /**
  5402. * Remove a {@link Track} from the `TrackList`
  5403. *
  5404. * @param {Track} rtrack
  5405. * The audio, video, or text track to remove from the list.
  5406. *
  5407. * @fires TrackList#removetrack
  5408. */
  5409. ;
  5410. _proto.removeTrack = function removeTrack(rtrack) {
  5411. var track;
  5412. for (var i = 0, l = this.length; i < l; i++) {
  5413. if (this[i] === rtrack) {
  5414. track = this[i];
  5415. if (track.off) {
  5416. track.off();
  5417. }
  5418. this.tracks_.splice(i, 1);
  5419. break;
  5420. }
  5421. }
  5422. if (!track) {
  5423. return;
  5424. }
  5425. /**
  5426. * Triggered when a track is removed from track list.
  5427. *
  5428. * @event TrackList#removetrack
  5429. * @type {EventTarget~Event}
  5430. * @property {Track} track
  5431. * A reference to track that was removed.
  5432. */
  5433. this.trigger({
  5434. track: track,
  5435. type: 'removetrack',
  5436. target: this
  5437. });
  5438. }
  5439. /**
  5440. * Get a Track from the TrackList by a tracks id
  5441. *
  5442. * @param {string} id - the id of the track to get
  5443. * @method getTrackById
  5444. * @return {Track}
  5445. * @private
  5446. */
  5447. ;
  5448. _proto.getTrackById = function getTrackById(id) {
  5449. var result = null;
  5450. for (var i = 0, l = this.length; i < l; i++) {
  5451. var track = this[i];
  5452. if (track.id === id) {
  5453. result = track;
  5454. break;
  5455. }
  5456. }
  5457. return result;
  5458. };
  5459. return TrackList;
  5460. }(EventTarget);
  5461. /**
  5462. * Triggered when a different track is selected/enabled.
  5463. *
  5464. * @event TrackList#change
  5465. * @type {EventTarget~Event}
  5466. */
  5467. /**
  5468. * Events that can be called with on + eventName. See {@link EventHandler}.
  5469. *
  5470. * @property {Object} TrackList#allowedEvents_
  5471. * @private
  5472. */
  5473. TrackList.prototype.allowedEvents_ = {
  5474. change: 'change',
  5475. addtrack: 'addtrack',
  5476. removetrack: 'removetrack'
  5477. }; // emulate attribute EventHandler support to allow for feature detection
  5478. for (var event in TrackList.prototype.allowedEvents_) {
  5479. TrackList.prototype['on' + event] = null;
  5480. }
  5481. /**
  5482. * Anywhere we call this function we diverge from the spec
  5483. * as we only support one enabled audiotrack at a time
  5484. *
  5485. * @param {AudioTrackList} list
  5486. * list to work on
  5487. *
  5488. * @param {AudioTrack} track
  5489. * The track to skip
  5490. *
  5491. * @private
  5492. */
  5493. var disableOthers = function disableOthers(list, track) {
  5494. for (var i = 0; i < list.length; i++) {
  5495. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5496. continue;
  5497. } // another audio track is enabled, disable it
  5498. list[i].enabled = false;
  5499. }
  5500. };
  5501. /**
  5502. * The current list of {@link AudioTrack} for a media file.
  5503. *
  5504. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
  5505. * @extends TrackList
  5506. */
  5507. var AudioTrackList =
  5508. /*#__PURE__*/
  5509. function (_TrackList) {
  5510. _inheritsLoose(AudioTrackList, _TrackList);
  5511. /**
  5512. * Create an instance of this class.
  5513. *
  5514. * @param {AudioTrack[]} [tracks=[]]
  5515. * A list of `AudioTrack` to instantiate the list with.
  5516. */
  5517. function AudioTrackList(tracks) {
  5518. var _this;
  5519. if (tracks === void 0) {
  5520. tracks = [];
  5521. }
  5522. // make sure only 1 track is enabled
  5523. // sorted from last index to first index
  5524. for (var i = tracks.length - 1; i >= 0; i--) {
  5525. if (tracks[i].enabled) {
  5526. disableOthers(tracks, tracks[i]);
  5527. break;
  5528. }
  5529. }
  5530. _this = _TrackList.call(this, tracks) || this;
  5531. _this.changing_ = false;
  5532. return _this;
  5533. }
  5534. /**
  5535. * Add an {@link AudioTrack} to the `AudioTrackList`.
  5536. *
  5537. * @param {AudioTrack} track
  5538. * The AudioTrack to add to the list
  5539. *
  5540. * @fires TrackList#addtrack
  5541. */
  5542. var _proto = AudioTrackList.prototype;
  5543. _proto.addTrack = function addTrack(track) {
  5544. var _this2 = this;
  5545. if (track.enabled) {
  5546. disableOthers(this, track);
  5547. }
  5548. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5549. if (!track.addEventListener) {
  5550. return;
  5551. }
  5552. track.enabledChange_ = function () {
  5553. // when we are disabling other tracks (since we don't support
  5554. // more than one track at a time) we will set changing_
  5555. // to true so that we don't trigger additional change events
  5556. if (_this2.changing_) {
  5557. return;
  5558. }
  5559. _this2.changing_ = true;
  5560. disableOthers(_this2, track);
  5561. _this2.changing_ = false;
  5562. _this2.trigger('change');
  5563. };
  5564. /**
  5565. * @listens AudioTrack#enabledchange
  5566. * @fires TrackList#change
  5567. */
  5568. track.addEventListener('enabledchange', track.enabledChange_);
  5569. };
  5570. _proto.removeTrack = function removeTrack(rtrack) {
  5571. _TrackList.prototype.removeTrack.call(this, rtrack);
  5572. if (rtrack.removeEventListener && rtrack.enabledChange_) {
  5573. rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
  5574. rtrack.enabledChange_ = null;
  5575. }
  5576. };
  5577. return AudioTrackList;
  5578. }(TrackList);
  5579. /**
  5580. * Un-select all other {@link VideoTrack}s that are selected.
  5581. *
  5582. * @param {VideoTrackList} list
  5583. * list to work on
  5584. *
  5585. * @param {VideoTrack} track
  5586. * The track to skip
  5587. *
  5588. * @private
  5589. */
  5590. var disableOthers$1 = function disableOthers(list, track) {
  5591. for (var i = 0; i < list.length; i++) {
  5592. if (!Object.keys(list[i]).length || track.id === list[i].id) {
  5593. continue;
  5594. } // another video track is enabled, disable it
  5595. list[i].selected = false;
  5596. }
  5597. };
  5598. /**
  5599. * The current list of {@link VideoTrack} for a video.
  5600. *
  5601. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
  5602. * @extends TrackList
  5603. */
  5604. var VideoTrackList =
  5605. /*#__PURE__*/
  5606. function (_TrackList) {
  5607. _inheritsLoose(VideoTrackList, _TrackList);
  5608. /**
  5609. * Create an instance of this class.
  5610. *
  5611. * @param {VideoTrack[]} [tracks=[]]
  5612. * A list of `VideoTrack` to instantiate the list with.
  5613. */
  5614. function VideoTrackList(tracks) {
  5615. var _this;
  5616. if (tracks === void 0) {
  5617. tracks = [];
  5618. }
  5619. // make sure only 1 track is enabled
  5620. // sorted from last index to first index
  5621. for (var i = tracks.length - 1; i >= 0; i--) {
  5622. if (tracks[i].selected) {
  5623. disableOthers$1(tracks, tracks[i]);
  5624. break;
  5625. }
  5626. }
  5627. _this = _TrackList.call(this, tracks) || this;
  5628. _this.changing_ = false;
  5629. /**
  5630. * @member {number} VideoTrackList#selectedIndex
  5631. * The current index of the selected {@link VideoTrack`}.
  5632. */
  5633. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selectedIndex', {
  5634. get: function get() {
  5635. for (var _i = 0; _i < this.length; _i++) {
  5636. if (this[_i].selected) {
  5637. return _i;
  5638. }
  5639. }
  5640. return -1;
  5641. },
  5642. set: function set() {}
  5643. });
  5644. return _this;
  5645. }
  5646. /**
  5647. * Add a {@link VideoTrack} to the `VideoTrackList`.
  5648. *
  5649. * @param {VideoTrack} track
  5650. * The VideoTrack to add to the list
  5651. *
  5652. * @fires TrackList#addtrack
  5653. */
  5654. var _proto = VideoTrackList.prototype;
  5655. _proto.addTrack = function addTrack(track) {
  5656. var _this2 = this;
  5657. if (track.selected) {
  5658. disableOthers$1(this, track);
  5659. }
  5660. _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
  5661. if (!track.addEventListener) {
  5662. return;
  5663. }
  5664. track.selectedChange_ = function () {
  5665. if (_this2.changing_) {
  5666. return;
  5667. }
  5668. _this2.changing_ = true;
  5669. disableOthers$1(_this2, track);
  5670. _this2.changing_ = false;
  5671. _this2.trigger('change');
  5672. };
  5673. /**
  5674. * @listens VideoTrack#selectedchange
  5675. * @fires TrackList#change
  5676. */
  5677. track.addEventListener('selectedchange', track.selectedChange_);
  5678. };
  5679. _proto.removeTrack = function removeTrack(rtrack) {
  5680. _TrackList.prototype.removeTrack.call(this, rtrack);
  5681. if (rtrack.removeEventListener && rtrack.selectedChange_) {
  5682. rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
  5683. rtrack.selectedChange_ = null;
  5684. }
  5685. };
  5686. return VideoTrackList;
  5687. }(TrackList);
  5688. /**
  5689. * The current list of {@link TextTrack} for a media file.
  5690. *
  5691. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
  5692. * @extends TrackList
  5693. */
  5694. var TextTrackList =
  5695. /*#__PURE__*/
  5696. function (_TrackList) {
  5697. _inheritsLoose(TextTrackList, _TrackList);
  5698. function TextTrackList() {
  5699. return _TrackList.apply(this, arguments) || this;
  5700. }
  5701. var _proto = TextTrackList.prototype;
  5702. /**
  5703. * Add a {@link TextTrack} to the `TextTrackList`
  5704. *
  5705. * @param {TextTrack} track
  5706. * The text track to add to the list.
  5707. *
  5708. * @fires TrackList#addtrack
  5709. */
  5710. _proto.addTrack = function addTrack(track) {
  5711. var _this = this;
  5712. _TrackList.prototype.addTrack.call(this, track);
  5713. if (!this.queueChange_) {
  5714. this.queueChange_ = function () {
  5715. return _this.queueTrigger('change');
  5716. };
  5717. }
  5718. if (!this.triggerSelectedlanguagechange) {
  5719. this.triggerSelectedlanguagechange_ = function () {
  5720. return _this.trigger('selectedlanguagechange');
  5721. };
  5722. }
  5723. /**
  5724. * @listens TextTrack#modechange
  5725. * @fires TrackList#change
  5726. */
  5727. track.addEventListener('modechange', this.queueChange_);
  5728. var nonLanguageTextTrackKind = ['metadata', 'chapters'];
  5729. if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
  5730. track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
  5731. }
  5732. };
  5733. _proto.removeTrack = function removeTrack(rtrack) {
  5734. _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added
  5735. if (rtrack.removeEventListener) {
  5736. if (this.queueChange_) {
  5737. rtrack.removeEventListener('modechange', this.queueChange_);
  5738. }
  5739. if (this.selectedlanguagechange_) {
  5740. rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
  5741. }
  5742. }
  5743. };
  5744. return TextTrackList;
  5745. }(TrackList);
  5746. /**
  5747. * @file html-track-element-list.js
  5748. */
  5749. /**
  5750. * The current list of {@link HtmlTrackElement}s.
  5751. */
  5752. var HtmlTrackElementList =
  5753. /*#__PURE__*/
  5754. function () {
  5755. /**
  5756. * Create an instance of this class.
  5757. *
  5758. * @param {HtmlTrackElement[]} [tracks=[]]
  5759. * A list of `HtmlTrackElement` to instantiate the list with.
  5760. */
  5761. function HtmlTrackElementList(trackElements) {
  5762. if (trackElements === void 0) {
  5763. trackElements = [];
  5764. }
  5765. this.trackElements_ = [];
  5766. /**
  5767. * @memberof HtmlTrackElementList
  5768. * @member {number} length
  5769. * The current number of `Track`s in the this Trackist.
  5770. * @instance
  5771. */
  5772. Object.defineProperty(this, 'length', {
  5773. get: function get() {
  5774. return this.trackElements_.length;
  5775. }
  5776. });
  5777. for (var i = 0, length = trackElements.length; i < length; i++) {
  5778. this.addTrackElement_(trackElements[i]);
  5779. }
  5780. }
  5781. /**
  5782. * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
  5783. *
  5784. * @param {HtmlTrackElement} trackElement
  5785. * The track element to add to the list.
  5786. *
  5787. * @private
  5788. */
  5789. var _proto = HtmlTrackElementList.prototype;
  5790. _proto.addTrackElement_ = function addTrackElement_(trackElement) {
  5791. var index = this.trackElements_.length;
  5792. if (!('' + index in this)) {
  5793. Object.defineProperty(this, index, {
  5794. get: function get() {
  5795. return this.trackElements_[index];
  5796. }
  5797. });
  5798. } // Do not add duplicate elements
  5799. if (this.trackElements_.indexOf(trackElement) === -1) {
  5800. this.trackElements_.push(trackElement);
  5801. }
  5802. }
  5803. /**
  5804. * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
  5805. * {@link TextTrack}.
  5806. *
  5807. * @param {TextTrack} track
  5808. * The track associated with a track element.
  5809. *
  5810. * @return {HtmlTrackElement|undefined}
  5811. * The track element that was found or undefined.
  5812. *
  5813. * @private
  5814. */
  5815. ;
  5816. _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
  5817. var trackElement_;
  5818. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5819. if (track === this.trackElements_[i].track) {
  5820. trackElement_ = this.trackElements_[i];
  5821. break;
  5822. }
  5823. }
  5824. return trackElement_;
  5825. }
  5826. /**
  5827. * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
  5828. *
  5829. * @param {HtmlTrackElement} trackElement
  5830. * The track element to remove from the list.
  5831. *
  5832. * @private
  5833. */
  5834. ;
  5835. _proto.removeTrackElement_ = function removeTrackElement_(trackElement) {
  5836. for (var i = 0, length = this.trackElements_.length; i < length; i++) {
  5837. if (trackElement === this.trackElements_[i]) {
  5838. if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
  5839. this.trackElements_[i].track.off();
  5840. }
  5841. if (typeof this.trackElements_[i].off === 'function') {
  5842. this.trackElements_[i].off();
  5843. }
  5844. this.trackElements_.splice(i, 1);
  5845. break;
  5846. }
  5847. }
  5848. };
  5849. return HtmlTrackElementList;
  5850. }();
  5851. /**
  5852. * @file text-track-cue-list.js
  5853. */
  5854. /**
  5855. * @typedef {Object} TextTrackCueList~TextTrackCue
  5856. *
  5857. * @property {string} id
  5858. * The unique id for this text track cue
  5859. *
  5860. * @property {number} startTime
  5861. * The start time for this text track cue
  5862. *
  5863. * @property {number} endTime
  5864. * The end time for this text track cue
  5865. *
  5866. * @property {boolean} pauseOnExit
  5867. * Pause when the end time is reached if true.
  5868. *
  5869. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
  5870. */
  5871. /**
  5872. * A List of TextTrackCues.
  5873. *
  5874. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
  5875. */
  5876. var TextTrackCueList =
  5877. /*#__PURE__*/
  5878. function () {
  5879. /**
  5880. * Create an instance of this class..
  5881. *
  5882. * @param {Array} cues
  5883. * A list of cues to be initialized with
  5884. */
  5885. function TextTrackCueList(cues) {
  5886. TextTrackCueList.prototype.setCues_.call(this, cues);
  5887. /**
  5888. * @memberof TextTrackCueList
  5889. * @member {number} length
  5890. * The current number of `TextTrackCue`s in the TextTrackCueList.
  5891. * @instance
  5892. */
  5893. Object.defineProperty(this, 'length', {
  5894. get: function get() {
  5895. return this.length_;
  5896. }
  5897. });
  5898. }
  5899. /**
  5900. * A setter for cues in this list. Creates getters
  5901. * an an index for the cues.
  5902. *
  5903. * @param {Array} cues
  5904. * An array of cues to set
  5905. *
  5906. * @private
  5907. */
  5908. var _proto = TextTrackCueList.prototype;
  5909. _proto.setCues_ = function setCues_(cues) {
  5910. var oldLength = this.length || 0;
  5911. var i = 0;
  5912. var l = cues.length;
  5913. this.cues_ = cues;
  5914. this.length_ = cues.length;
  5915. var defineProp = function defineProp(index) {
  5916. if (!('' + index in this)) {
  5917. Object.defineProperty(this, '' + index, {
  5918. get: function get() {
  5919. return this.cues_[index];
  5920. }
  5921. });
  5922. }
  5923. };
  5924. if (oldLength < l) {
  5925. i = oldLength;
  5926. for (; i < l; i++) {
  5927. defineProp.call(this, i);
  5928. }
  5929. }
  5930. }
  5931. /**
  5932. * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
  5933. *
  5934. * @param {string} id
  5935. * The id of the cue that should be searched for.
  5936. *
  5937. * @return {TextTrackCueList~TextTrackCue|null}
  5938. * A single cue or null if none was found.
  5939. */
  5940. ;
  5941. _proto.getCueById = function getCueById(id) {
  5942. var result = null;
  5943. for (var i = 0, l = this.length; i < l; i++) {
  5944. var cue = this[i];
  5945. if (cue.id === id) {
  5946. result = cue;
  5947. break;
  5948. }
  5949. }
  5950. return result;
  5951. };
  5952. return TextTrackCueList;
  5953. }();
  5954. /**
  5955. * @file track-kinds.js
  5956. */
  5957. /**
  5958. * All possible `VideoTrackKind`s
  5959. *
  5960. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
  5961. * @typedef VideoTrack~Kind
  5962. * @enum
  5963. */
  5964. var VideoTrackKind = {
  5965. alternative: 'alternative',
  5966. captions: 'captions',
  5967. main: 'main',
  5968. sign: 'sign',
  5969. subtitles: 'subtitles',
  5970. commentary: 'commentary'
  5971. };
  5972. /**
  5973. * All possible `AudioTrackKind`s
  5974. *
  5975. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
  5976. * @typedef AudioTrack~Kind
  5977. * @enum
  5978. */
  5979. var AudioTrackKind = {
  5980. 'alternative': 'alternative',
  5981. 'descriptions': 'descriptions',
  5982. 'main': 'main',
  5983. 'main-desc': 'main-desc',
  5984. 'translation': 'translation',
  5985. 'commentary': 'commentary'
  5986. };
  5987. /**
  5988. * All possible `TextTrackKind`s
  5989. *
  5990. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
  5991. * @typedef TextTrack~Kind
  5992. * @enum
  5993. */
  5994. var TextTrackKind = {
  5995. subtitles: 'subtitles',
  5996. captions: 'captions',
  5997. descriptions: 'descriptions',
  5998. chapters: 'chapters',
  5999. metadata: 'metadata'
  6000. };
  6001. /**
  6002. * All possible `TextTrackMode`s
  6003. *
  6004. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
  6005. * @typedef TextTrack~Mode
  6006. * @enum
  6007. */
  6008. var TextTrackMode = {
  6009. disabled: 'disabled',
  6010. hidden: 'hidden',
  6011. showing: 'showing'
  6012. };
  6013. /**
  6014. * A Track class that contains all of the common functionality for {@link AudioTrack},
  6015. * {@link VideoTrack}, and {@link TextTrack}.
  6016. *
  6017. * > Note: This class should not be used directly
  6018. *
  6019. * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
  6020. * @extends EventTarget
  6021. * @abstract
  6022. */
  6023. var Track =
  6024. /*#__PURE__*/
  6025. function (_EventTarget) {
  6026. _inheritsLoose(Track, _EventTarget);
  6027. /**
  6028. * Create an instance of this class.
  6029. *
  6030. * @param {Object} [options={}]
  6031. * Object of option names and values
  6032. *
  6033. * @param {string} [options.kind='']
  6034. * A valid kind for the track type you are creating.
  6035. *
  6036. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6037. * A unique id for this AudioTrack.
  6038. *
  6039. * @param {string} [options.label='']
  6040. * The menu label for this track.
  6041. *
  6042. * @param {string} [options.language='']
  6043. * A valid two character language code.
  6044. *
  6045. * @abstract
  6046. */
  6047. function Track(options) {
  6048. var _this;
  6049. if (options === void 0) {
  6050. options = {};
  6051. }
  6052. _this = _EventTarget.call(this) || this;
  6053. var trackProps = {
  6054. id: options.id || 'vjs_track_' + newGUID(),
  6055. kind: options.kind || '',
  6056. label: options.label || '',
  6057. language: options.language || ''
  6058. };
  6059. /**
  6060. * @memberof Track
  6061. * @member {string} id
  6062. * The id of this track. Cannot be changed after creation.
  6063. * @instance
  6064. *
  6065. * @readonly
  6066. */
  6067. /**
  6068. * @memberof Track
  6069. * @member {string} kind
  6070. * The kind of track that this is. Cannot be changed after creation.
  6071. * @instance
  6072. *
  6073. * @readonly
  6074. */
  6075. /**
  6076. * @memberof Track
  6077. * @member {string} label
  6078. * The label of this track. Cannot be changed after creation.
  6079. * @instance
  6080. *
  6081. * @readonly
  6082. */
  6083. /**
  6084. * @memberof Track
  6085. * @member {string} language
  6086. * The two letter language code for this track. Cannot be changed after
  6087. * creation.
  6088. * @instance
  6089. *
  6090. * @readonly
  6091. */
  6092. var _loop = function _loop(key) {
  6093. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), key, {
  6094. get: function get() {
  6095. return trackProps[key];
  6096. },
  6097. set: function set() {}
  6098. });
  6099. };
  6100. for (var key in trackProps) {
  6101. _loop(key);
  6102. }
  6103. return _this;
  6104. }
  6105. return Track;
  6106. }(EventTarget);
  6107. /**
  6108. * @file url.js
  6109. * @module url
  6110. */
  6111. /**
  6112. * @typedef {Object} url:URLObject
  6113. *
  6114. * @property {string} protocol
  6115. * The protocol of the url that was parsed.
  6116. *
  6117. * @property {string} hostname
  6118. * The hostname of the url that was parsed.
  6119. *
  6120. * @property {string} port
  6121. * The port of the url that was parsed.
  6122. *
  6123. * @property {string} pathname
  6124. * The pathname of the url that was parsed.
  6125. *
  6126. * @property {string} search
  6127. * The search query of the url that was parsed.
  6128. *
  6129. * @property {string} hash
  6130. * The hash of the url that was parsed.
  6131. *
  6132. * @property {string} host
  6133. * The host of the url that was parsed.
  6134. */
  6135. /**
  6136. * Resolve and parse the elements of a URL.
  6137. *
  6138. * @function
  6139. * @param {String} url
  6140. * The url to parse
  6141. *
  6142. * @return {url:URLObject}
  6143. * An object of url details
  6144. */
  6145. var parseUrl = function parseUrl(url) {
  6146. var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL
  6147. var a = document.createElement('a');
  6148. a.href = url; // IE8 (and 9?) Fix
  6149. // ie8 doesn't parse the URL correctly until the anchor is actually
  6150. // added to the body, and an innerHTML is needed to trigger the parsing
  6151. var addToBody = a.host === '' && a.protocol !== 'file:';
  6152. var div;
  6153. if (addToBody) {
  6154. div = document.createElement('div');
  6155. div.innerHTML = "<a href=\"" + url + "\"></a>";
  6156. a = div.firstChild; // prevent the div from affecting layout
  6157. div.setAttribute('style', 'display:none; position:absolute;');
  6158. document.body.appendChild(div);
  6159. } // Copy the specific URL properties to a new object
  6160. // This is also needed for IE8 because the anchor loses its
  6161. // properties when it's removed from the dom
  6162. var details = {};
  6163. for (var i = 0; i < props.length; i++) {
  6164. details[props[i]] = a[props[i]];
  6165. } // IE9 adds the port to the host property unlike everyone else. If
  6166. // a port identifier is added for standard ports, strip it.
  6167. if (details.protocol === 'http:') {
  6168. details.host = details.host.replace(/:80$/, '');
  6169. }
  6170. if (details.protocol === 'https:') {
  6171. details.host = details.host.replace(/:443$/, '');
  6172. }
  6173. if (!details.protocol) {
  6174. details.protocol = window$1.location.protocol;
  6175. }
  6176. if (addToBody) {
  6177. document.body.removeChild(div);
  6178. }
  6179. return details;
  6180. };
  6181. /**
  6182. * Get absolute version of relative URL. Used to tell Flash the correct URL.
  6183. *
  6184. * @function
  6185. * @param {string} url
  6186. * URL to make absolute
  6187. *
  6188. * @return {string}
  6189. * Absolute URL
  6190. *
  6191. * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
  6192. */
  6193. var getAbsoluteURL = function getAbsoluteURL(url) {
  6194. // Check if absolute URL
  6195. if (!url.match(/^https?:\/\//)) {
  6196. // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
  6197. var div = document.createElement('div');
  6198. div.innerHTML = "<a href=\"" + url + "\">x</a>";
  6199. url = div.firstChild.href;
  6200. }
  6201. return url;
  6202. };
  6203. /**
  6204. * Returns the extension of the passed file name. It will return an empty string
  6205. * if passed an invalid path.
  6206. *
  6207. * @function
  6208. * @param {string} path
  6209. * The fileName path like '/path/to/file.mp4'
  6210. *
  6211. * @return {string}
  6212. * The extension in lower case or an empty string if no
  6213. * extension could be found.
  6214. */
  6215. var getFileExtension = function getFileExtension(path) {
  6216. if (typeof path === 'string') {
  6217. var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
  6218. var pathParts = splitPathRe.exec(path);
  6219. if (pathParts) {
  6220. return pathParts.pop().toLowerCase();
  6221. }
  6222. }
  6223. return '';
  6224. };
  6225. /**
  6226. * Returns whether the url passed is a cross domain request or not.
  6227. *
  6228. * @function
  6229. * @param {string} url
  6230. * The url to check.
  6231. *
  6232. * @return {boolean}
  6233. * Whether it is a cross domain request or not.
  6234. */
  6235. var isCrossOrigin = function isCrossOrigin(url) {
  6236. var winLoc = window$1.location;
  6237. var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol
  6238. var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin
  6239. // IE8 doesn't know location.origin, so we won't rely on it here
  6240. var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
  6241. return crossOrigin;
  6242. };
  6243. var Url = /*#__PURE__*/Object.freeze({
  6244. parseUrl: parseUrl,
  6245. getAbsoluteURL: getAbsoluteURL,
  6246. getFileExtension: getFileExtension,
  6247. isCrossOrigin: isCrossOrigin
  6248. });
  6249. var isFunction_1 = isFunction;
  6250. var toString$1 = Object.prototype.toString;
  6251. function isFunction(fn) {
  6252. var string = toString$1.call(fn);
  6253. return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below
  6254. fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
  6255. }
  6256. var trim_1 = createCommonjsModule(function (module, exports) {
  6257. exports = module.exports = trim;
  6258. function trim(str) {
  6259. return str.replace(/^\s*|\s*$/g, '');
  6260. }
  6261. exports.left = function (str) {
  6262. return str.replace(/^\s*/, '');
  6263. };
  6264. exports.right = function (str) {
  6265. return str.replace(/\s*$/, '');
  6266. };
  6267. });
  6268. var trim_2 = trim_1.left;
  6269. var trim_3 = trim_1.right;
  6270. var fnToStr = Function.prototype.toString;
  6271. var constructorRegex = /^\s*class\b/;
  6272. var isES6ClassFn = function isES6ClassFunction(value) {
  6273. try {
  6274. var fnStr = fnToStr.call(value);
  6275. return constructorRegex.test(fnStr);
  6276. } catch (e) {
  6277. return false; // not a function
  6278. }
  6279. };
  6280. var tryFunctionObject = function tryFunctionToStr(value) {
  6281. try {
  6282. if (isES6ClassFn(value)) {
  6283. return false;
  6284. }
  6285. fnToStr.call(value);
  6286. return true;
  6287. } catch (e) {
  6288. return false;
  6289. }
  6290. };
  6291. var toStr = Object.prototype.toString;
  6292. var fnClass = '[object Function]';
  6293. var genClass = '[object GeneratorFunction]';
  6294. var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
  6295. var isCallable = function isCallable(value) {
  6296. if (!value) {
  6297. return false;
  6298. }
  6299. if (typeof value !== 'function' && typeof value !== 'object') {
  6300. return false;
  6301. }
  6302. if (typeof value === 'function' && !value.prototype) {
  6303. return true;
  6304. }
  6305. if (hasToStringTag) {
  6306. return tryFunctionObject(value);
  6307. }
  6308. if (isES6ClassFn(value)) {
  6309. return false;
  6310. }
  6311. var strClass = toStr.call(value);
  6312. return strClass === fnClass || strClass === genClass;
  6313. };
  6314. var toStr$1 = Object.prototype.toString;
  6315. var hasOwnProperty = Object.prototype.hasOwnProperty;
  6316. var forEachArray = function forEachArray(array, iterator, receiver) {
  6317. for (var i = 0, len = array.length; i < len; i++) {
  6318. if (hasOwnProperty.call(array, i)) {
  6319. if (receiver == null) {
  6320. iterator(array[i], i, array);
  6321. } else {
  6322. iterator.call(receiver, array[i], i, array);
  6323. }
  6324. }
  6325. }
  6326. };
  6327. var forEachString = function forEachString(string, iterator, receiver) {
  6328. for (var i = 0, len = string.length; i < len; i++) {
  6329. // no such thing as a sparse string.
  6330. if (receiver == null) {
  6331. iterator(string.charAt(i), i, string);
  6332. } else {
  6333. iterator.call(receiver, string.charAt(i), i, string);
  6334. }
  6335. }
  6336. };
  6337. var forEachObject = function forEachObject(object, iterator, receiver) {
  6338. for (var k in object) {
  6339. if (hasOwnProperty.call(object, k)) {
  6340. if (receiver == null) {
  6341. iterator(object[k], k, object);
  6342. } else {
  6343. iterator.call(receiver, object[k], k, object);
  6344. }
  6345. }
  6346. }
  6347. };
  6348. var forEach = function forEach(list, iterator, thisArg) {
  6349. if (!isCallable(iterator)) {
  6350. throw new TypeError('iterator must be a function');
  6351. }
  6352. var receiver;
  6353. if (arguments.length >= 3) {
  6354. receiver = thisArg;
  6355. }
  6356. if (toStr$1.call(list) === '[object Array]') {
  6357. forEachArray(list, iterator, receiver);
  6358. } else if (typeof list === 'string') {
  6359. forEachString(list, iterator, receiver);
  6360. } else {
  6361. forEachObject(list, iterator, receiver);
  6362. }
  6363. };
  6364. var forEach_1 = forEach;
  6365. var isArray = function isArray(arg) {
  6366. return Object.prototype.toString.call(arg) === '[object Array]';
  6367. };
  6368. var parseHeaders = function parseHeaders(headers) {
  6369. if (!headers) return {};
  6370. var result = {};
  6371. forEach_1(trim_1(headers).split('\n'), function (row) {
  6372. var index = row.indexOf(':'),
  6373. key = trim_1(row.slice(0, index)).toLowerCase(),
  6374. value = trim_1(row.slice(index + 1));
  6375. if (typeof result[key] === 'undefined') {
  6376. result[key] = value;
  6377. } else if (isArray(result[key])) {
  6378. result[key].push(value);
  6379. } else {
  6380. result[key] = [result[key], value];
  6381. }
  6382. });
  6383. return result;
  6384. };
  6385. var immutable = extend;
  6386. var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
  6387. function extend() {
  6388. var target = {};
  6389. for (var i = 0; i < arguments.length; i++) {
  6390. var source = arguments[i];
  6391. for (var key in source) {
  6392. if (hasOwnProperty$1.call(source, key)) {
  6393. target[key] = source[key];
  6394. }
  6395. }
  6396. }
  6397. return target;
  6398. }
  6399. var xhr = createXHR;
  6400. createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop;
  6401. createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest;
  6402. forEachArray$1(["get", "put", "post", "patch", "head", "delete"], function (method) {
  6403. createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
  6404. options = initParams(uri, options, callback);
  6405. options.method = method.toUpperCase();
  6406. return _createXHR(options);
  6407. };
  6408. });
  6409. function forEachArray$1(array, iterator) {
  6410. for (var i = 0; i < array.length; i++) {
  6411. iterator(array[i]);
  6412. }
  6413. }
  6414. function isEmpty(obj) {
  6415. for (var i in obj) {
  6416. if (obj.hasOwnProperty(i)) return false;
  6417. }
  6418. return true;
  6419. }
  6420. function initParams(uri, options, callback) {
  6421. var params = uri;
  6422. if (isFunction_1(options)) {
  6423. callback = options;
  6424. if (typeof uri === "string") {
  6425. params = {
  6426. uri: uri
  6427. };
  6428. }
  6429. } else {
  6430. params = immutable(options, {
  6431. uri: uri
  6432. });
  6433. }
  6434. params.callback = callback;
  6435. return params;
  6436. }
  6437. function createXHR(uri, options, callback) {
  6438. options = initParams(uri, options, callback);
  6439. return _createXHR(options);
  6440. }
  6441. function _createXHR(options) {
  6442. if (typeof options.callback === "undefined") {
  6443. throw new Error("callback argument missing");
  6444. }
  6445. var called = false;
  6446. var callback = function cbOnce(err, response, body) {
  6447. if (!called) {
  6448. called = true;
  6449. options.callback(err, response, body);
  6450. }
  6451. };
  6452. function readystatechange() {
  6453. if (xhr.readyState === 4) {
  6454. setTimeout(loadFunc, 0);
  6455. }
  6456. }
  6457. function getBody() {
  6458. // Chrome with requestType=blob throws errors arround when even testing access to responseText
  6459. var body = undefined;
  6460. if (xhr.response) {
  6461. body = xhr.response;
  6462. } else {
  6463. body = xhr.responseText || getXml(xhr);
  6464. }
  6465. if (isJson) {
  6466. try {
  6467. body = JSON.parse(body);
  6468. } catch (e) {}
  6469. }
  6470. return body;
  6471. }
  6472. function errorFunc(evt) {
  6473. clearTimeout(timeoutTimer);
  6474. if (!(evt instanceof Error)) {
  6475. evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
  6476. }
  6477. evt.statusCode = 0;
  6478. return callback(evt, failureResponse);
  6479. } // will load the data & process the response in a special response object
  6480. function loadFunc() {
  6481. if (aborted) return;
  6482. var status;
  6483. clearTimeout(timeoutTimer);
  6484. if (options.useXDR && xhr.status === undefined) {
  6485. //IE8 CORS GET successful response doesn't have a status field, but body is fine
  6486. status = 200;
  6487. } else {
  6488. status = xhr.status === 1223 ? 204 : xhr.status;
  6489. }
  6490. var response = failureResponse;
  6491. var err = null;
  6492. if (status !== 0) {
  6493. response = {
  6494. body: getBody(),
  6495. statusCode: status,
  6496. method: method,
  6497. headers: {},
  6498. url: uri,
  6499. rawRequest: xhr
  6500. };
  6501. if (xhr.getAllResponseHeaders) {
  6502. //remember xhr can in fact be XDR for CORS in IE
  6503. response.headers = parseHeaders(xhr.getAllResponseHeaders());
  6504. }
  6505. } else {
  6506. err = new Error("Internal XMLHttpRequest Error");
  6507. }
  6508. return callback(err, response, response.body);
  6509. }
  6510. var xhr = options.xhr || null;
  6511. if (!xhr) {
  6512. if (options.cors || options.useXDR) {
  6513. xhr = new createXHR.XDomainRequest();
  6514. } else {
  6515. xhr = new createXHR.XMLHttpRequest();
  6516. }
  6517. }
  6518. var key;
  6519. var aborted;
  6520. var uri = xhr.url = options.uri || options.url;
  6521. var method = xhr.method = options.method || "GET";
  6522. var body = options.body || options.data;
  6523. var headers = xhr.headers = options.headers || {};
  6524. var sync = !!options.sync;
  6525. var isJson = false;
  6526. var timeoutTimer;
  6527. var failureResponse = {
  6528. body: undefined,
  6529. headers: {},
  6530. statusCode: 0,
  6531. method: method,
  6532. url: uri,
  6533. rawRequest: xhr
  6534. };
  6535. if ("json" in options && options.json !== false) {
  6536. isJson = true;
  6537. headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
  6538. if (method !== "GET" && method !== "HEAD") {
  6539. headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
  6540. body = JSON.stringify(options.json === true ? body : options.json);
  6541. }
  6542. }
  6543. xhr.onreadystatechange = readystatechange;
  6544. xhr.onload = loadFunc;
  6545. xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
  6546. xhr.onprogress = function () {// IE must die
  6547. };
  6548. xhr.onabort = function () {
  6549. aborted = true;
  6550. };
  6551. xhr.ontimeout = errorFunc;
  6552. xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
  6553. if (!sync) {
  6554. xhr.withCredentials = !!options.withCredentials;
  6555. } // Cannot set timeout with sync request
  6556. // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
  6557. // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
  6558. if (!sync && options.timeout > 0) {
  6559. timeoutTimer = setTimeout(function () {
  6560. if (aborted) return;
  6561. aborted = true; //IE9 may still call readystatechange
  6562. xhr.abort("timeout");
  6563. var e = new Error("XMLHttpRequest timeout");
  6564. e.code = "ETIMEDOUT";
  6565. errorFunc(e);
  6566. }, options.timeout);
  6567. }
  6568. if (xhr.setRequestHeader) {
  6569. for (key in headers) {
  6570. if (headers.hasOwnProperty(key)) {
  6571. xhr.setRequestHeader(key, headers[key]);
  6572. }
  6573. }
  6574. } else if (options.headers && !isEmpty(options.headers)) {
  6575. throw new Error("Headers cannot be set on an XDomainRequest object");
  6576. }
  6577. if ("responseType" in options) {
  6578. xhr.responseType = options.responseType;
  6579. }
  6580. if ("beforeSend" in options && typeof options.beforeSend === "function") {
  6581. options.beforeSend(xhr);
  6582. } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
  6583. // XMLHttpRequest spec says to pass null as body to indicate no body
  6584. // See https://github.com/naugtur/xhr/issues/100.
  6585. xhr.send(body || null);
  6586. return xhr;
  6587. }
  6588. function getXml(xhr) {
  6589. if (xhr.responseType === "document") {
  6590. return xhr.responseXML;
  6591. }
  6592. var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
  6593. if (xhr.responseType === "" && !firefoxBugTakenEffect) {
  6594. return xhr.responseXML;
  6595. }
  6596. return null;
  6597. }
  6598. function noop() {}
  6599. /**
  6600. * Takes a webvtt file contents and parses it into cues
  6601. *
  6602. * @param {string} srcContent
  6603. * webVTT file contents
  6604. *
  6605. * @param {TextTrack} track
  6606. * TextTrack to add cues to. Cues come from the srcContent.
  6607. *
  6608. * @private
  6609. */
  6610. var parseCues = function parseCues(srcContent, track) {
  6611. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder());
  6612. var errors = [];
  6613. parser.oncue = function (cue) {
  6614. track.addCue(cue);
  6615. };
  6616. parser.onparsingerror = function (error) {
  6617. errors.push(error);
  6618. };
  6619. parser.onflush = function () {
  6620. track.trigger({
  6621. type: 'loadeddata',
  6622. target: track
  6623. });
  6624. };
  6625. parser.parse(srcContent);
  6626. if (errors.length > 0) {
  6627. if (window$1.console && window$1.console.groupCollapsed) {
  6628. window$1.console.groupCollapsed("Text Track parsing errors for " + track.src);
  6629. }
  6630. errors.forEach(function (error) {
  6631. return log.error(error);
  6632. });
  6633. if (window$1.console && window$1.console.groupEnd) {
  6634. window$1.console.groupEnd();
  6635. }
  6636. }
  6637. parser.flush();
  6638. };
  6639. /**
  6640. * Load a `TextTrack` from a specified url.
  6641. *
  6642. * @param {string} src
  6643. * Url to load track from.
  6644. *
  6645. * @param {TextTrack} track
  6646. * Track to add cues to. Comes from the content at the end of `url`.
  6647. *
  6648. * @private
  6649. */
  6650. var loadTrack = function loadTrack(src, track) {
  6651. var opts = {
  6652. uri: src
  6653. };
  6654. var crossOrigin = isCrossOrigin(src);
  6655. if (crossOrigin) {
  6656. opts.cors = crossOrigin;
  6657. }
  6658. xhr(opts, bind(this, function (err, response, responseBody) {
  6659. if (err) {
  6660. return log.error(err, response);
  6661. }
  6662. track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  6663. // NOTE: this is only used for the alt/video.novtt.js build
  6664. if (typeof window$1.WebVTT !== 'function') {
  6665. if (track.tech_) {
  6666. // to prevent use before define eslint error, we define loadHandler
  6667. // as a let here
  6668. var loadHandler;
  6669. var errorHandler = function errorHandler() {
  6670. log.error("vttjs failed to load, stopping trying to process " + track.src);
  6671. track.tech_.off('vttjsloaded', loadHandler);
  6672. };
  6673. loadHandler = function loadHandler() {
  6674. track.tech_.off('vttjserror', errorHandler);
  6675. return parseCues(responseBody, track);
  6676. };
  6677. track.tech_.one('vttjsloaded', loadHandler);
  6678. track.tech_.one('vttjserror', errorHandler);
  6679. }
  6680. } else {
  6681. parseCues(responseBody, track);
  6682. }
  6683. }));
  6684. };
  6685. /**
  6686. * A representation of a single `TextTrack`.
  6687. *
  6688. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
  6689. * @extends Track
  6690. */
  6691. var TextTrack =
  6692. /*#__PURE__*/
  6693. function (_Track) {
  6694. _inheritsLoose(TextTrack, _Track);
  6695. /**
  6696. * Create an instance of this class.
  6697. *
  6698. * @param {Object} options={}
  6699. * Object of option names and values
  6700. *
  6701. * @param {Tech} options.tech
  6702. * A reference to the tech that owns this TextTrack.
  6703. *
  6704. * @param {TextTrack~Kind} [options.kind='subtitles']
  6705. * A valid text track kind.
  6706. *
  6707. * @param {TextTrack~Mode} [options.mode='disabled']
  6708. * A valid text track mode.
  6709. *
  6710. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6711. * A unique id for this TextTrack.
  6712. *
  6713. * @param {string} [options.label='']
  6714. * The menu label for this track.
  6715. *
  6716. * @param {string} [options.language='']
  6717. * A valid two character language code.
  6718. *
  6719. * @param {string} [options.srclang='']
  6720. * A valid two character language code. An alternative, but deprioritized
  6721. * version of `options.language`
  6722. *
  6723. * @param {string} [options.src]
  6724. * A url to TextTrack cues.
  6725. *
  6726. * @param {boolean} [options.default]
  6727. * If this track should default to on or off.
  6728. */
  6729. function TextTrack(options) {
  6730. var _this;
  6731. if (options === void 0) {
  6732. options = {};
  6733. }
  6734. if (!options.tech) {
  6735. throw new Error('A tech was not provided.');
  6736. }
  6737. var settings = mergeOptions(options, {
  6738. kind: TextTrackKind[options.kind] || 'subtitles',
  6739. language: options.language || options.srclang || ''
  6740. });
  6741. var mode = TextTrackMode[settings.mode] || 'disabled';
  6742. var default_ = settings.default;
  6743. if (settings.kind === 'metadata' || settings.kind === 'chapters') {
  6744. mode = 'hidden';
  6745. }
  6746. _this = _Track.call(this, settings) || this;
  6747. _this.tech_ = settings.tech;
  6748. _this.cues_ = [];
  6749. _this.activeCues_ = [];
  6750. var cues = new TextTrackCueList(_this.cues_);
  6751. var activeCues = new TextTrackCueList(_this.activeCues_);
  6752. var changed = false;
  6753. var timeupdateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), function () {
  6754. // Accessing this.activeCues for the side-effects of updating itself
  6755. // due to its nature as a getter function. Do not remove or cues will
  6756. // stop updating!
  6757. // Use the setter to prevent deletion from uglify (pure_getters rule)
  6758. this.activeCues = this.activeCues;
  6759. if (changed) {
  6760. this.trigger('cuechange');
  6761. changed = false;
  6762. }
  6763. });
  6764. if (mode !== 'disabled') {
  6765. _this.tech_.ready(function () {
  6766. _this.tech_.on('timeupdate', timeupdateHandler);
  6767. }, true);
  6768. }
  6769. Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), {
  6770. /**
  6771. * @memberof TextTrack
  6772. * @member {boolean} default
  6773. * If this track was set to be on or off by default. Cannot be changed after
  6774. * creation.
  6775. * @instance
  6776. *
  6777. * @readonly
  6778. */
  6779. default: {
  6780. get: function get() {
  6781. return default_;
  6782. },
  6783. set: function set() {}
  6784. },
  6785. /**
  6786. * @memberof TextTrack
  6787. * @member {string} mode
  6788. * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
  6789. * not be set if setting to an invalid mode.
  6790. * @instance
  6791. *
  6792. * @fires TextTrack#modechange
  6793. */
  6794. mode: {
  6795. get: function get() {
  6796. return mode;
  6797. },
  6798. set: function set(newMode) {
  6799. var _this2 = this;
  6800. if (!TextTrackMode[newMode]) {
  6801. return;
  6802. }
  6803. mode = newMode;
  6804. if (mode !== 'disabled') {
  6805. this.tech_.ready(function () {
  6806. _this2.tech_.on('timeupdate', timeupdateHandler);
  6807. }, true);
  6808. } else {
  6809. this.tech_.off('timeupdate', timeupdateHandler);
  6810. }
  6811. /**
  6812. * An event that fires when mode changes on this track. This allows
  6813. * the TextTrackList that holds this track to act accordingly.
  6814. *
  6815. * > Note: This is not part of the spec!
  6816. *
  6817. * @event TextTrack#modechange
  6818. * @type {EventTarget~Event}
  6819. */
  6820. this.trigger('modechange');
  6821. }
  6822. },
  6823. /**
  6824. * @memberof TextTrack
  6825. * @member {TextTrackCueList} cues
  6826. * The text track cue list for this TextTrack.
  6827. * @instance
  6828. */
  6829. cues: {
  6830. get: function get() {
  6831. if (!this.loaded_) {
  6832. return null;
  6833. }
  6834. return cues;
  6835. },
  6836. set: function set() {}
  6837. },
  6838. /**
  6839. * @memberof TextTrack
  6840. * @member {TextTrackCueList} activeCues
  6841. * The list text track cues that are currently active for this TextTrack.
  6842. * @instance
  6843. */
  6844. activeCues: {
  6845. get: function get() {
  6846. if (!this.loaded_) {
  6847. return null;
  6848. } // nothing to do
  6849. if (this.cues.length === 0) {
  6850. return activeCues;
  6851. }
  6852. var ct = this.tech_.currentTime();
  6853. var active = [];
  6854. for (var i = 0, l = this.cues.length; i < l; i++) {
  6855. var cue = this.cues[i];
  6856. if (cue.startTime <= ct && cue.endTime >= ct) {
  6857. active.push(cue);
  6858. } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
  6859. active.push(cue);
  6860. }
  6861. }
  6862. changed = false;
  6863. if (active.length !== this.activeCues_.length) {
  6864. changed = true;
  6865. } else {
  6866. for (var _i = 0; _i < active.length; _i++) {
  6867. if (this.activeCues_.indexOf(active[_i]) === -1) {
  6868. changed = true;
  6869. }
  6870. }
  6871. }
  6872. this.activeCues_ = active;
  6873. activeCues.setCues_(this.activeCues_);
  6874. return activeCues;
  6875. },
  6876. // /!\ Keep this setter empty (see the timeupdate handler above)
  6877. set: function set() {}
  6878. }
  6879. });
  6880. if (settings.src) {
  6881. _this.src = settings.src;
  6882. loadTrack(settings.src, _assertThisInitialized(_assertThisInitialized(_this)));
  6883. } else {
  6884. _this.loaded_ = true;
  6885. }
  6886. return _this;
  6887. }
  6888. /**
  6889. * Add a cue to the internal list of cues.
  6890. *
  6891. * @param {TextTrack~Cue} cue
  6892. * The cue to add to our internal list
  6893. */
  6894. var _proto = TextTrack.prototype;
  6895. _proto.addCue = function addCue(originalCue) {
  6896. var cue = originalCue;
  6897. if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) {
  6898. cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
  6899. for (var prop in originalCue) {
  6900. if (!(prop in cue)) {
  6901. cue[prop] = originalCue[prop];
  6902. }
  6903. } // make sure that `id` is copied over
  6904. cue.id = originalCue.id;
  6905. cue.originalCue_ = originalCue;
  6906. }
  6907. var tracks = this.tech_.textTracks();
  6908. for (var i = 0; i < tracks.length; i++) {
  6909. if (tracks[i] !== this) {
  6910. tracks[i].removeCue(cue);
  6911. }
  6912. }
  6913. this.cues_.push(cue);
  6914. this.cues.setCues_(this.cues_);
  6915. }
  6916. /**
  6917. * Remove a cue from our internal list
  6918. *
  6919. * @param {TextTrack~Cue} removeCue
  6920. * The cue to remove from our internal list
  6921. */
  6922. ;
  6923. _proto.removeCue = function removeCue(_removeCue) {
  6924. var i = this.cues_.length;
  6925. while (i--) {
  6926. var cue = this.cues_[i];
  6927. if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
  6928. this.cues_.splice(i, 1);
  6929. this.cues.setCues_(this.cues_);
  6930. break;
  6931. }
  6932. }
  6933. };
  6934. return TextTrack;
  6935. }(Track);
  6936. /**
  6937. * cuechange - One or more cues in the track have become active or stopped being active.
  6938. */
  6939. TextTrack.prototype.allowedEvents_ = {
  6940. cuechange: 'cuechange'
  6941. };
  6942. /**
  6943. * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
  6944. * only one `AudioTrack` in the list will be enabled at a time.
  6945. *
  6946. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
  6947. * @extends Track
  6948. */
  6949. var AudioTrack =
  6950. /*#__PURE__*/
  6951. function (_Track) {
  6952. _inheritsLoose(AudioTrack, _Track);
  6953. /**
  6954. * Create an instance of this class.
  6955. *
  6956. * @param {Object} [options={}]
  6957. * Object of option names and values
  6958. *
  6959. * @param {AudioTrack~Kind} [options.kind='']
  6960. * A valid audio track kind
  6961. *
  6962. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  6963. * A unique id for this AudioTrack.
  6964. *
  6965. * @param {string} [options.label='']
  6966. * The menu label for this track.
  6967. *
  6968. * @param {string} [options.language='']
  6969. * A valid two character language code.
  6970. *
  6971. * @param {boolean} [options.enabled]
  6972. * If this track is the one that is currently playing. If this track is part of
  6973. * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
  6974. */
  6975. function AudioTrack(options) {
  6976. var _this;
  6977. if (options === void 0) {
  6978. options = {};
  6979. }
  6980. var settings = mergeOptions(options, {
  6981. kind: AudioTrackKind[options.kind] || ''
  6982. });
  6983. _this = _Track.call(this, settings) || this;
  6984. var enabled = false;
  6985. /**
  6986. * @memberof AudioTrack
  6987. * @member {boolean} enabled
  6988. * If this `AudioTrack` is enabled or not. When setting this will
  6989. * fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
  6990. * @instance
  6991. *
  6992. * @fires VideoTrack#selectedchange
  6993. */
  6994. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'enabled', {
  6995. get: function get() {
  6996. return enabled;
  6997. },
  6998. set: function set(newEnabled) {
  6999. // an invalid or unchanged value
  7000. if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
  7001. return;
  7002. }
  7003. enabled = newEnabled;
  7004. /**
  7005. * An event that fires when enabled changes on this track. This allows
  7006. * the AudioTrackList that holds this track to act accordingly.
  7007. *
  7008. * > Note: This is not part of the spec! Native tracks will do
  7009. * this internally without an event.
  7010. *
  7011. * @event AudioTrack#enabledchange
  7012. * @type {EventTarget~Event}
  7013. */
  7014. this.trigger('enabledchange');
  7015. }
  7016. }); // if the user sets this track to selected then
  7017. // set selected to that true value otherwise
  7018. // we keep it false
  7019. if (settings.enabled) {
  7020. _this.enabled = settings.enabled;
  7021. }
  7022. _this.loaded_ = true;
  7023. return _this;
  7024. }
  7025. return AudioTrack;
  7026. }(Track);
  7027. /**
  7028. * A representation of a single `VideoTrack`.
  7029. *
  7030. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
  7031. * @extends Track
  7032. */
  7033. var VideoTrack =
  7034. /*#__PURE__*/
  7035. function (_Track) {
  7036. _inheritsLoose(VideoTrack, _Track);
  7037. /**
  7038. * Create an instance of this class.
  7039. *
  7040. * @param {Object} [options={}]
  7041. * Object of option names and values
  7042. *
  7043. * @param {string} [options.kind='']
  7044. * A valid {@link VideoTrack~Kind}
  7045. *
  7046. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  7047. * A unique id for this AudioTrack.
  7048. *
  7049. * @param {string} [options.label='']
  7050. * The menu label for this track.
  7051. *
  7052. * @param {string} [options.language='']
  7053. * A valid two character language code.
  7054. *
  7055. * @param {boolean} [options.selected]
  7056. * If this track is the one that is currently playing.
  7057. */
  7058. function VideoTrack(options) {
  7059. var _this;
  7060. if (options === void 0) {
  7061. options = {};
  7062. }
  7063. var settings = mergeOptions(options, {
  7064. kind: VideoTrackKind[options.kind] || ''
  7065. });
  7066. _this = _Track.call(this, settings) || this;
  7067. var selected = false;
  7068. /**
  7069. * @memberof VideoTrack
  7070. * @member {boolean} selected
  7071. * If this `VideoTrack` is selected or not. When setting this will
  7072. * fire {@link VideoTrack#selectedchange} if the state of selected changed.
  7073. * @instance
  7074. *
  7075. * @fires VideoTrack#selectedchange
  7076. */
  7077. Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selected', {
  7078. get: function get() {
  7079. return selected;
  7080. },
  7081. set: function set(newSelected) {
  7082. // an invalid or unchanged value
  7083. if (typeof newSelected !== 'boolean' || newSelected === selected) {
  7084. return;
  7085. }
  7086. selected = newSelected;
  7087. /**
  7088. * An event that fires when selected changes on this track. This allows
  7089. * the VideoTrackList that holds this track to act accordingly.
  7090. *
  7091. * > Note: This is not part of the spec! Native tracks will do
  7092. * this internally without an event.
  7093. *
  7094. * @event VideoTrack#selectedchange
  7095. * @type {EventTarget~Event}
  7096. */
  7097. this.trigger('selectedchange');
  7098. }
  7099. }); // if the user sets this track to selected then
  7100. // set selected to that true value otherwise
  7101. // we keep it false
  7102. if (settings.selected) {
  7103. _this.selected = settings.selected;
  7104. }
  7105. return _this;
  7106. }
  7107. return VideoTrack;
  7108. }(Track);
  7109. /**
  7110. * @memberof HTMLTrackElement
  7111. * @typedef {HTMLTrackElement~ReadyState}
  7112. * @enum {number}
  7113. */
  7114. var NONE = 0;
  7115. var LOADING = 1;
  7116. var LOADED = 2;
  7117. var ERROR = 3;
  7118. /**
  7119. * A single track represented in the DOM.
  7120. *
  7121. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
  7122. * @extends EventTarget
  7123. */
  7124. var HTMLTrackElement =
  7125. /*#__PURE__*/
  7126. function (_EventTarget) {
  7127. _inheritsLoose(HTMLTrackElement, _EventTarget);
  7128. /**
  7129. * Create an instance of this class.
  7130. *
  7131. * @param {Object} options={}
  7132. * Object of option names and values
  7133. *
  7134. * @param {Tech} options.tech
  7135. * A reference to the tech that owns this HTMLTrackElement.
  7136. *
  7137. * @param {TextTrack~Kind} [options.kind='subtitles']
  7138. * A valid text track kind.
  7139. *
  7140. * @param {TextTrack~Mode} [options.mode='disabled']
  7141. * A valid text track mode.
  7142. *
  7143. * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
  7144. * A unique id for this TextTrack.
  7145. *
  7146. * @param {string} [options.label='']
  7147. * The menu label for this track.
  7148. *
  7149. * @param {string} [options.language='']
  7150. * A valid two character language code.
  7151. *
  7152. * @param {string} [options.srclang='']
  7153. * A valid two character language code. An alternative, but deprioritized
  7154. * vesion of `options.language`
  7155. *
  7156. * @param {string} [options.src]
  7157. * A url to TextTrack cues.
  7158. *
  7159. * @param {boolean} [options.default]
  7160. * If this track should default to on or off.
  7161. */
  7162. function HTMLTrackElement(options) {
  7163. var _this;
  7164. if (options === void 0) {
  7165. options = {};
  7166. }
  7167. _this = _EventTarget.call(this) || this;
  7168. var readyState;
  7169. var track = new TextTrack(options);
  7170. _this.kind = track.kind;
  7171. _this.src = track.src;
  7172. _this.srclang = track.language;
  7173. _this.label = track.label;
  7174. _this.default = track.default;
  7175. Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), {
  7176. /**
  7177. * @memberof HTMLTrackElement
  7178. * @member {HTMLTrackElement~ReadyState} readyState
  7179. * The current ready state of the track element.
  7180. * @instance
  7181. */
  7182. readyState: {
  7183. get: function get() {
  7184. return readyState;
  7185. }
  7186. },
  7187. /**
  7188. * @memberof HTMLTrackElement
  7189. * @member {TextTrack} track
  7190. * The underlying TextTrack object.
  7191. * @instance
  7192. *
  7193. */
  7194. track: {
  7195. get: function get() {
  7196. return track;
  7197. }
  7198. }
  7199. });
  7200. readyState = NONE;
  7201. /**
  7202. * @listens TextTrack#loadeddata
  7203. * @fires HTMLTrackElement#load
  7204. */
  7205. track.addEventListener('loadeddata', function () {
  7206. readyState = LOADED;
  7207. _this.trigger({
  7208. type: 'load',
  7209. target: _assertThisInitialized(_assertThisInitialized(_this))
  7210. });
  7211. });
  7212. return _this;
  7213. }
  7214. return HTMLTrackElement;
  7215. }(EventTarget);
  7216. HTMLTrackElement.prototype.allowedEvents_ = {
  7217. load: 'load'
  7218. };
  7219. HTMLTrackElement.NONE = NONE;
  7220. HTMLTrackElement.LOADING = LOADING;
  7221. HTMLTrackElement.LOADED = LOADED;
  7222. HTMLTrackElement.ERROR = ERROR;
  7223. /*
  7224. * This file contains all track properties that are used in
  7225. * player.js, tech.js, html5.js and possibly other techs in the future.
  7226. */
  7227. var NORMAL = {
  7228. audio: {
  7229. ListClass: AudioTrackList,
  7230. TrackClass: AudioTrack,
  7231. capitalName: 'Audio'
  7232. },
  7233. video: {
  7234. ListClass: VideoTrackList,
  7235. TrackClass: VideoTrack,
  7236. capitalName: 'Video'
  7237. },
  7238. text: {
  7239. ListClass: TextTrackList,
  7240. TrackClass: TextTrack,
  7241. capitalName: 'Text'
  7242. }
  7243. };
  7244. Object.keys(NORMAL).forEach(function (type) {
  7245. NORMAL[type].getterName = type + "Tracks";
  7246. NORMAL[type].privateName = type + "Tracks_";
  7247. });
  7248. var REMOTE = {
  7249. remoteText: {
  7250. ListClass: TextTrackList,
  7251. TrackClass: TextTrack,
  7252. capitalName: 'RemoteText',
  7253. getterName: 'remoteTextTracks',
  7254. privateName: 'remoteTextTracks_'
  7255. },
  7256. remoteTextEl: {
  7257. ListClass: HtmlTrackElementList,
  7258. TrackClass: HTMLTrackElement,
  7259. capitalName: 'RemoteTextTrackEls',
  7260. getterName: 'remoteTextTrackEls',
  7261. privateName: 'remoteTextTrackEls_'
  7262. }
  7263. };
  7264. var ALL = mergeOptions(NORMAL, REMOTE);
  7265. REMOTE.names = Object.keys(REMOTE);
  7266. NORMAL.names = Object.keys(NORMAL);
  7267. ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
  7268. /**
  7269. * Copyright 2013 vtt.js Contributors
  7270. *
  7271. * Licensed under the Apache License, Version 2.0 (the "License");
  7272. * you may not use this file except in compliance with the License.
  7273. * You may obtain a copy of the License at
  7274. *
  7275. * http://www.apache.org/licenses/LICENSE-2.0
  7276. *
  7277. * Unless required by applicable law or agreed to in writing, software
  7278. * distributed under the License is distributed on an "AS IS" BASIS,
  7279. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7280. * See the License for the specific language governing permissions and
  7281. * limitations under the License.
  7282. */
  7283. /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  7284. /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
  7285. var _objCreate = Object.create || function () {
  7286. function F() {}
  7287. return function (o) {
  7288. if (arguments.length !== 1) {
  7289. throw new Error('Object.create shim only accepts one parameter.');
  7290. }
  7291. F.prototype = o;
  7292. return new F();
  7293. };
  7294. }(); // Creates a new ParserError object from an errorData object. The errorData
  7295. // object should have default code and message properties. The default message
  7296. // property can be overriden by passing in a message parameter.
  7297. // See ParsingError.Errors below for acceptable errors.
  7298. function ParsingError(errorData, message) {
  7299. this.name = "ParsingError";
  7300. this.code = errorData.code;
  7301. this.message = message || errorData.message;
  7302. }
  7303. ParsingError.prototype = _objCreate(Error.prototype);
  7304. ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors.
  7305. ParsingError.Errors = {
  7306. BadSignature: {
  7307. code: 0,
  7308. message: "Malformed WebVTT signature."
  7309. },
  7310. BadTimeStamp: {
  7311. code: 1,
  7312. message: "Malformed time stamp."
  7313. }
  7314. }; // Try to parse input as a time stamp.
  7315. function parseTimeStamp(input) {
  7316. function computeSeconds(h, m, s, f) {
  7317. return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
  7318. }
  7319. var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
  7320. if (!m) {
  7321. return null;
  7322. }
  7323. if (m[3]) {
  7324. // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
  7325. return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
  7326. } else if (m[1] > 59) {
  7327. // Timestamp takes the form of [hours]:[minutes].[milliseconds]
  7328. // First position is hours as it's over 59.
  7329. return computeSeconds(m[1], m[2], 0, m[4]);
  7330. } else {
  7331. // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
  7332. return computeSeconds(0, m[1], m[2], m[4]);
  7333. }
  7334. } // A settings object holds key/value pairs and will ignore anything but the first
  7335. // assignment to a specific key.
  7336. function Settings() {
  7337. this.values = _objCreate(null);
  7338. }
  7339. Settings.prototype = {
  7340. // Only accept the first assignment to any key.
  7341. set: function set(k, v) {
  7342. if (!this.get(k) && v !== "") {
  7343. this.values[k] = v;
  7344. }
  7345. },
  7346. // Return the value for a key, or a default value.
  7347. // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
  7348. // a number of possible default values as properties where 'defaultKey' is
  7349. // the key of the property that will be chosen; otherwise it's assumed to be
  7350. // a single value.
  7351. get: function get(k, dflt, defaultKey) {
  7352. if (defaultKey) {
  7353. return this.has(k) ? this.values[k] : dflt[defaultKey];
  7354. }
  7355. return this.has(k) ? this.values[k] : dflt;
  7356. },
  7357. // Check whether we have a value for a key.
  7358. has: function has(k) {
  7359. return k in this.values;
  7360. },
  7361. // Accept a setting if its one of the given alternatives.
  7362. alt: function alt(k, v, a) {
  7363. for (var n = 0; n < a.length; ++n) {
  7364. if (v === a[n]) {
  7365. this.set(k, v);
  7366. break;
  7367. }
  7368. }
  7369. },
  7370. // Accept a setting if its a valid (signed) integer.
  7371. integer: function integer(k, v) {
  7372. if (/^-?\d+$/.test(v)) {
  7373. // integer
  7374. this.set(k, parseInt(v, 10));
  7375. }
  7376. },
  7377. // Accept a setting if its a valid percentage.
  7378. percent: function percent(k, v) {
  7379. var m;
  7380. if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
  7381. v = parseFloat(v);
  7382. if (v >= 0 && v <= 100) {
  7383. this.set(k, v);
  7384. return true;
  7385. }
  7386. }
  7387. return false;
  7388. }
  7389. }; // Helper function to parse input into groups separated by 'groupDelim', and
  7390. // interprete each group as a key/value pair separated by 'keyValueDelim'.
  7391. function parseOptions(input, callback, keyValueDelim, groupDelim) {
  7392. var groups = groupDelim ? input.split(groupDelim) : [input];
  7393. for (var i in groups) {
  7394. if (typeof groups[i] !== "string") {
  7395. continue;
  7396. }
  7397. var kv = groups[i].split(keyValueDelim);
  7398. if (kv.length !== 2) {
  7399. continue;
  7400. }
  7401. var k = kv[0];
  7402. var v = kv[1];
  7403. callback(k, v);
  7404. }
  7405. }
  7406. function parseCue(input, cue, regionList) {
  7407. // Remember the original input if we need to throw an error.
  7408. var oInput = input; // 4.1 WebVTT timestamp
  7409. function consumeTimeStamp() {
  7410. var ts = parseTimeStamp(input);
  7411. if (ts === null) {
  7412. throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
  7413. } // Remove time stamp from input.
  7414. input = input.replace(/^[^\sa-zA-Z-]+/, "");
  7415. return ts;
  7416. } // 4.4.2 WebVTT cue settings
  7417. function consumeCueSettings(input, cue) {
  7418. var settings = new Settings();
  7419. parseOptions(input, function (k, v) {
  7420. switch (k) {
  7421. case "region":
  7422. // Find the last region we parsed with the same region id.
  7423. for (var i = regionList.length - 1; i >= 0; i--) {
  7424. if (regionList[i].id === v) {
  7425. settings.set(k, regionList[i].region);
  7426. break;
  7427. }
  7428. }
  7429. break;
  7430. case "vertical":
  7431. settings.alt(k, v, ["rl", "lr"]);
  7432. break;
  7433. case "line":
  7434. var vals = v.split(","),
  7435. vals0 = vals[0];
  7436. settings.integer(k, vals0);
  7437. settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
  7438. settings.alt(k, vals0, ["auto"]);
  7439. if (vals.length === 2) {
  7440. settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
  7441. }
  7442. break;
  7443. case "position":
  7444. vals = v.split(",");
  7445. settings.percent(k, vals[0]);
  7446. if (vals.length === 2) {
  7447. settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
  7448. }
  7449. break;
  7450. case "size":
  7451. settings.percent(k, v);
  7452. break;
  7453. case "align":
  7454. settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
  7455. break;
  7456. }
  7457. }, /:/, /\s/); // Apply default values for any missing fields.
  7458. cue.region = settings.get("region", null);
  7459. cue.vertical = settings.get("vertical", "");
  7460. cue.line = settings.get("line", "auto");
  7461. cue.lineAlign = settings.get("lineAlign", "start");
  7462. cue.snapToLines = settings.get("snapToLines", true);
  7463. cue.size = settings.get("size", 100);
  7464. cue.align = settings.get("align", "middle");
  7465. cue.position = settings.get("position", {
  7466. start: 0,
  7467. left: 0,
  7468. middle: 50,
  7469. end: 100,
  7470. right: 100
  7471. }, cue.align);
  7472. cue.positionAlign = settings.get("positionAlign", {
  7473. start: "start",
  7474. left: "start",
  7475. middle: "middle",
  7476. end: "end",
  7477. right: "end"
  7478. }, cue.align);
  7479. }
  7480. function skipWhitespace() {
  7481. input = input.replace(/^\s+/, "");
  7482. } // 4.1 WebVTT cue timings.
  7483. skipWhitespace();
  7484. cue.startTime = consumeTimeStamp(); // (1) collect cue start time
  7485. skipWhitespace();
  7486. if (input.substr(0, 3) !== "-->") {
  7487. // (3) next characters must match "-->"
  7488. throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
  7489. }
  7490. input = input.substr(3);
  7491. skipWhitespace();
  7492. cue.endTime = consumeTimeStamp(); // (5) collect cue end time
  7493. // 4.1 WebVTT cue settings list.
  7494. skipWhitespace();
  7495. consumeCueSettings(input, cue);
  7496. }
  7497. var ESCAPE = {
  7498. "&amp;": "&",
  7499. "&lt;": "<",
  7500. "&gt;": ">",
  7501. "&lrm;": "\u200E",
  7502. "&rlm;": "\u200F",
  7503. "&nbsp;": "\xA0"
  7504. };
  7505. var TAG_NAME = {
  7506. c: "span",
  7507. i: "i",
  7508. b: "b",
  7509. u: "u",
  7510. ruby: "ruby",
  7511. rt: "rt",
  7512. v: "span",
  7513. lang: "span"
  7514. };
  7515. var TAG_ANNOTATION = {
  7516. v: "title",
  7517. lang: "lang"
  7518. };
  7519. var NEEDS_PARENT = {
  7520. rt: "ruby"
  7521. }; // Parse content into a document fragment.
  7522. function parseContent(window, input) {
  7523. function nextToken() {
  7524. // Check for end-of-string.
  7525. if (!input) {
  7526. return null;
  7527. } // Consume 'n' characters from the input.
  7528. function consume(result) {
  7529. input = input.substr(result.length);
  7530. return result;
  7531. }
  7532. var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return
  7533. // the tag.
  7534. return consume(m[1] ? m[1] : m[2]);
  7535. } // Unescape a string 's'.
  7536. function unescape1(e) {
  7537. return ESCAPE[e];
  7538. }
  7539. function unescape(s) {
  7540. while (m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/)) {
  7541. s = s.replace(m[0], unescape1);
  7542. }
  7543. return s;
  7544. }
  7545. function shouldAdd(current, element) {
  7546. return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
  7547. } // Create an element for this tag.
  7548. function createElement(type, annotation) {
  7549. var tagName = TAG_NAME[type];
  7550. if (!tagName) {
  7551. return null;
  7552. }
  7553. var element = window.document.createElement(tagName);
  7554. element.localName = tagName;
  7555. var name = TAG_ANNOTATION[type];
  7556. if (name && annotation) {
  7557. element[name] = annotation.trim();
  7558. }
  7559. return element;
  7560. }
  7561. var rootDiv = window.document.createElement("div"),
  7562. current = rootDiv,
  7563. t,
  7564. tagStack = [];
  7565. while ((t = nextToken()) !== null) {
  7566. if (t[0] === '<') {
  7567. if (t[1] === "/") {
  7568. // If the closing tag matches, move back up to the parent node.
  7569. if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
  7570. tagStack.pop();
  7571. current = current.parentNode;
  7572. } // Otherwise just ignore the end tag.
  7573. continue;
  7574. }
  7575. var ts = parseTimeStamp(t.substr(1, t.length - 2));
  7576. var node;
  7577. if (ts) {
  7578. // Timestamps are lead nodes as well.
  7579. node = window.document.createProcessingInstruction("timestamp", ts);
  7580. current.appendChild(node);
  7581. continue;
  7582. }
  7583. var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag.
  7584. if (!m) {
  7585. continue;
  7586. } // Try to construct an element, and ignore the tag if we couldn't.
  7587. node = createElement(m[1], m[3]);
  7588. if (!node) {
  7589. continue;
  7590. } // Determine if the tag should be added based on the context of where it
  7591. // is placed in the cuetext.
  7592. if (!shouldAdd(current, node)) {
  7593. continue;
  7594. } // Set the class list (as a list of classes, separated by space).
  7595. if (m[2]) {
  7596. node.className = m[2].substr(1).replace('.', ' ');
  7597. } // Append the node to the current node, and enter the scope of the new
  7598. // node.
  7599. tagStack.push(m[1]);
  7600. current.appendChild(node);
  7601. current = node;
  7602. continue;
  7603. } // Text nodes are leaf nodes.
  7604. current.appendChild(window.document.createTextNode(unescape(t)));
  7605. }
  7606. return rootDiv;
  7607. } // This is a list of all the Unicode characters that have a strong
  7608. // right-to-left category. What this means is that these characters are
  7609. // written right-to-left for sure. It was generated by pulling all the strong
  7610. // right-to-left characters out of the Unicode data table. That table can
  7611. // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
  7612. 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]];
  7613. function isStrongRTLChar(charCode) {
  7614. for (var i = 0; i < strongRTLRanges.length; i++) {
  7615. var currentRange = strongRTLRanges[i];
  7616. if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
  7617. return true;
  7618. }
  7619. }
  7620. return false;
  7621. }
  7622. function determineBidi(cueDiv) {
  7623. var nodeStack = [],
  7624. text = "",
  7625. charCode;
  7626. if (!cueDiv || !cueDiv.childNodes) {
  7627. return "ltr";
  7628. }
  7629. function pushNodes(nodeStack, node) {
  7630. for (var i = node.childNodes.length - 1; i >= 0; i--) {
  7631. nodeStack.push(node.childNodes[i]);
  7632. }
  7633. }
  7634. function nextTextNode(nodeStack) {
  7635. if (!nodeStack || !nodeStack.length) {
  7636. return null;
  7637. }
  7638. var node = nodeStack.pop(),
  7639. text = node.textContent || node.innerText;
  7640. if (text) {
  7641. // TODO: This should match all unicode type B characters (paragraph
  7642. // separator characters). See issue #115.
  7643. var m = text.match(/^.*(\n|\r)/);
  7644. if (m) {
  7645. nodeStack.length = 0;
  7646. return m[0];
  7647. }
  7648. return text;
  7649. }
  7650. if (node.tagName === "ruby") {
  7651. return nextTextNode(nodeStack);
  7652. }
  7653. if (node.childNodes) {
  7654. pushNodes(nodeStack, node);
  7655. return nextTextNode(nodeStack);
  7656. }
  7657. }
  7658. pushNodes(nodeStack, cueDiv);
  7659. while (text = nextTextNode(nodeStack)) {
  7660. for (var i = 0; i < text.length; i++) {
  7661. charCode = text.charCodeAt(i);
  7662. if (isStrongRTLChar(charCode)) {
  7663. return "rtl";
  7664. }
  7665. }
  7666. }
  7667. return "ltr";
  7668. }
  7669. function computeLinePos(cue) {
  7670. if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
  7671. return cue.line;
  7672. }
  7673. if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
  7674. return -1;
  7675. }
  7676. var track = cue.track,
  7677. trackList = track.textTrackList,
  7678. count = 0;
  7679. for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
  7680. if (trackList[i].mode === "showing") {
  7681. count++;
  7682. }
  7683. }
  7684. return ++count * -1;
  7685. }
  7686. function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the
  7687. // div on 'this'.
  7688. StyleBox.prototype.applyStyles = function (styles, div) {
  7689. div = div || this.div;
  7690. for (var prop in styles) {
  7691. if (styles.hasOwnProperty(prop)) {
  7692. div.style[prop] = styles[prop];
  7693. }
  7694. }
  7695. };
  7696. StyleBox.prototype.formatStyle = function (val, unit) {
  7697. return val === 0 ? 0 : val + unit;
  7698. }; // Constructs the computed display state of the cue (a div). Places the div
  7699. // into the overlay which should be a block level element (usually a div).
  7700. function CueStyleBox(window, cue, styleOptions) {
  7701. StyleBox.call(this);
  7702. this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
  7703. // have inline positioning and will function as the cue background box.
  7704. this.cueDiv = parseContent(window, cue.text);
  7705. var styles = {
  7706. color: "rgba(255, 255, 255, 1)",
  7707. backgroundColor: "rgba(0, 0, 0, 0.8)",
  7708. position: "relative",
  7709. left: 0,
  7710. right: 0,
  7711. top: 0,
  7712. bottom: 0,
  7713. display: "inline",
  7714. writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
  7715. unicodeBidi: "plaintext"
  7716. };
  7717. this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue
  7718. // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
  7719. // mirrors of them except "middle" which is "center" in CSS.
  7720. this.div = window.document.createElement("div");
  7721. styles = {
  7722. direction: determineBidi(this.cueDiv),
  7723. writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
  7724. unicodeBidi: "plaintext",
  7725. textAlign: cue.align === "middle" ? "center" : cue.align,
  7726. font: styleOptions.font,
  7727. whiteSpace: "pre-line",
  7728. position: "absolute"
  7729. };
  7730. this.applyStyles(styles);
  7731. this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text
  7732. // position of the cue box. The reference edge will be resolved later when
  7733. // the box orientation styles are applied.
  7734. var textPos = 0;
  7735. switch (cue.positionAlign) {
  7736. case "start":
  7737. textPos = cue.position;
  7738. break;
  7739. case "middle":
  7740. textPos = cue.position - cue.size / 2;
  7741. break;
  7742. case "end":
  7743. textPos = cue.position - cue.size;
  7744. break;
  7745. } // Horizontal box orientation; textPos is the distance from the left edge of the
  7746. // area to the left edge of the box and cue.size is the distance extending to
  7747. // the right from there.
  7748. if (cue.vertical === "") {
  7749. this.applyStyles({
  7750. left: this.formatStyle(textPos, "%"),
  7751. width: this.formatStyle(cue.size, "%")
  7752. }); // Vertical box orientation; textPos is the distance from the top edge of the
  7753. // area to the top edge of the box and cue.size is the height extending
  7754. // downwards from there.
  7755. } else {
  7756. this.applyStyles({
  7757. top: this.formatStyle(textPos, "%"),
  7758. height: this.formatStyle(cue.size, "%")
  7759. });
  7760. }
  7761. this.move = function (box) {
  7762. this.applyStyles({
  7763. top: this.formatStyle(box.top, "px"),
  7764. bottom: this.formatStyle(box.bottom, "px"),
  7765. left: this.formatStyle(box.left, "px"),
  7766. right: this.formatStyle(box.right, "px"),
  7767. height: this.formatStyle(box.height, "px"),
  7768. width: this.formatStyle(box.width, "px")
  7769. });
  7770. };
  7771. }
  7772. CueStyleBox.prototype = _objCreate(StyleBox.prototype);
  7773. CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily
  7774. // compute things with such as if it overlaps or intersects with another Element.
  7775. // Can initialize it with either a StyleBox or another BoxPosition.
  7776. function BoxPosition(obj) {
  7777. // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
  7778. // was passed in and we need to copy the results of 'getBoundingClientRect'
  7779. // as the object returned is readonly. All co-ordinate values are in reference
  7780. // to the viewport origin (top left).
  7781. var lh, height, width, top;
  7782. if (obj.div) {
  7783. height = obj.div.offsetHeight;
  7784. width = obj.div.offsetWidth;
  7785. top = obj.div.offsetTop;
  7786. var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
  7787. obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of
  7788. // the inner div's lines. This could be due to bold text, etc, on some platforms.
  7789. // In this case we should get the average line height and use that. This will
  7790. // result in the desired behaviour.
  7791. lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
  7792. }
  7793. this.left = obj.left;
  7794. this.right = obj.right;
  7795. this.top = obj.top || top;
  7796. this.height = obj.height || height;
  7797. this.bottom = obj.bottom || top + (obj.height || height);
  7798. this.width = obj.width || width;
  7799. this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
  7800. } // Move the box along a particular axis. Optionally pass in an amount to move
  7801. // the box. If no amount is passed then the default is the line height of the
  7802. // box.
  7803. BoxPosition.prototype.move = function (axis, toMove) {
  7804. toMove = toMove !== undefined ? toMove : this.lineHeight;
  7805. switch (axis) {
  7806. case "+x":
  7807. this.left += toMove;
  7808. this.right += toMove;
  7809. break;
  7810. case "-x":
  7811. this.left -= toMove;
  7812. this.right -= toMove;
  7813. break;
  7814. case "+y":
  7815. this.top += toMove;
  7816. this.bottom += toMove;
  7817. break;
  7818. case "-y":
  7819. this.top -= toMove;
  7820. this.bottom -= toMove;
  7821. break;
  7822. }
  7823. }; // Check if this box overlaps another box, b2.
  7824. BoxPosition.prototype.overlaps = function (b2) {
  7825. return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
  7826. }; // Check if this box overlaps any other boxes in boxes.
  7827. BoxPosition.prototype.overlapsAny = function (boxes) {
  7828. for (var i = 0; i < boxes.length; i++) {
  7829. if (this.overlaps(boxes[i])) {
  7830. return true;
  7831. }
  7832. }
  7833. return false;
  7834. }; // Check if this box is within another box.
  7835. BoxPosition.prototype.within = function (container) {
  7836. return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
  7837. }; // Check if this box is entirely within the container or it is overlapping
  7838. // on the edge opposite of the axis direction passed. For example, if "+x" is
  7839. // passed and the box is overlapping on the left edge of the container, then
  7840. // return true.
  7841. BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
  7842. switch (axis) {
  7843. case "+x":
  7844. return this.left < container.left;
  7845. case "-x":
  7846. return this.right > container.right;
  7847. case "+y":
  7848. return this.top < container.top;
  7849. case "-y":
  7850. return this.bottom > container.bottom;
  7851. }
  7852. }; // Find the percentage of the area that this box is overlapping with another
  7853. // box.
  7854. BoxPosition.prototype.intersectPercentage = function (b2) {
  7855. var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
  7856. y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
  7857. intersectArea = x * y;
  7858. return intersectArea / (this.height * this.width);
  7859. }; // Convert the positions from this box to CSS compatible positions using
  7860. // the reference container's positions. This has to be done because this
  7861. // box's positions are in reference to the viewport origin, whereas, CSS
  7862. // values are in referecne to their respective edges.
  7863. BoxPosition.prototype.toCSSCompatValues = function (reference) {
  7864. return {
  7865. top: this.top - reference.top,
  7866. bottom: reference.bottom - this.bottom,
  7867. left: this.left - reference.left,
  7868. right: reference.right - this.right,
  7869. height: this.height,
  7870. width: this.width
  7871. };
  7872. }; // Get an object that represents the box's position without anything extra.
  7873. // Can pass a StyleBox, HTMLElement, or another BoxPositon.
  7874. BoxPosition.getSimpleBoxPosition = function (obj) {
  7875. var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
  7876. var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
  7877. var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
  7878. obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
  7879. var ret = {
  7880. left: obj.left,
  7881. right: obj.right,
  7882. top: obj.top || top,
  7883. height: obj.height || height,
  7884. bottom: obj.bottom || top + (obj.height || height),
  7885. width: obj.width || width
  7886. };
  7887. return ret;
  7888. }; // Move a StyleBox to its specified, or next best, position. The containerBox
  7889. // is the box that contains the StyleBox, such as a div. boxPositions are
  7890. // a list of other boxes that the styleBox can't overlap with.
  7891. function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
  7892. // Find the best position for a cue box, b, on the video. The axis parameter
  7893. // is a list of axis, the order of which, it will move the box along. For example:
  7894. // Passing ["+x", "-x"] will move the box first along the x axis in the positive
  7895. // direction. If it doesn't find a good position for it there it will then move
  7896. // it along the x axis in the negative direction.
  7897. function findBestPosition(b, axis) {
  7898. var bestPosition,
  7899. specifiedPosition = new BoxPosition(b),
  7900. percentage = 1; // Highest possible so the first thing we get is better.
  7901. for (var i = 0; i < axis.length; i++) {
  7902. while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
  7903. b.move(axis[i]);
  7904. } // We found a spot where we aren't overlapping anything. This is our
  7905. // best position.
  7906. if (b.within(containerBox)) {
  7907. return b;
  7908. }
  7909. var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try
  7910. // then remember this position as the best position.
  7911. if (percentage > p) {
  7912. bestPosition = new BoxPosition(b);
  7913. percentage = p;
  7914. } // Reset the box position to the specified position.
  7915. b = new BoxPosition(specifiedPosition);
  7916. }
  7917. return bestPosition || specifiedPosition;
  7918. }
  7919. var boxPosition = new BoxPosition(styleBox),
  7920. cue = styleBox.cue,
  7921. linePos = computeLinePos(cue),
  7922. axis = []; // If we have a line number to align the cue to.
  7923. if (cue.snapToLines) {
  7924. var size;
  7925. switch (cue.vertical) {
  7926. case "":
  7927. axis = ["+y", "-y"];
  7928. size = "height";
  7929. break;
  7930. case "rl":
  7931. axis = ["+x", "-x"];
  7932. size = "width";
  7933. break;
  7934. case "lr":
  7935. axis = ["-x", "+x"];
  7936. size = "width";
  7937. break;
  7938. }
  7939. var step = boxPosition.lineHeight,
  7940. position = step * Math.round(linePos),
  7941. maxPosition = containerBox[size] + step,
  7942. initialAxis = axis[0]; // If the specified intial position is greater then the max position then
  7943. // clamp the box to the amount of steps it would take for the box to
  7944. // reach the max position.
  7945. if (Math.abs(position) > maxPosition) {
  7946. position = position < 0 ? -1 : 1;
  7947. position *= Math.ceil(maxPosition / step) * step;
  7948. } // If computed line position returns negative then line numbers are
  7949. // relative to the bottom of the video instead of the top. Therefore, we
  7950. // need to increase our initial position by the length or width of the
  7951. // video, depending on the writing direction, and reverse our axis directions.
  7952. if (linePos < 0) {
  7953. position += cue.vertical === "" ? containerBox.height : containerBox.width;
  7954. axis = axis.reverse();
  7955. } // Move the box to the specified position. This may not be its best
  7956. // position.
  7957. boxPosition.move(initialAxis, position);
  7958. } else {
  7959. // If we have a percentage line value for the cue.
  7960. var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
  7961. switch (cue.lineAlign) {
  7962. case "middle":
  7963. linePos -= calculatedPercentage / 2;
  7964. break;
  7965. case "end":
  7966. linePos -= calculatedPercentage;
  7967. break;
  7968. } // Apply initial line position to the cue box.
  7969. switch (cue.vertical) {
  7970. case "":
  7971. styleBox.applyStyles({
  7972. top: styleBox.formatStyle(linePos, "%")
  7973. });
  7974. break;
  7975. case "rl":
  7976. styleBox.applyStyles({
  7977. left: styleBox.formatStyle(linePos, "%")
  7978. });
  7979. break;
  7980. case "lr":
  7981. styleBox.applyStyles({
  7982. right: styleBox.formatStyle(linePos, "%")
  7983. });
  7984. break;
  7985. }
  7986. axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning
  7987. // to it.
  7988. boxPosition = new BoxPosition(styleBox);
  7989. }
  7990. var bestPosition = findBestPosition(boxPosition, axis);
  7991. styleBox.move(bestPosition.toCSSCompatValues(containerBox));
  7992. }
  7993. function WebVTT$1() {} // Nothing
  7994. // Helper to allow strings to be decoded instead of the default binary utf8 data.
  7995. WebVTT$1.StringDecoder = function () {
  7996. return {
  7997. decode: function decode(data) {
  7998. if (!data) {
  7999. return "";
  8000. }
  8001. if (typeof data !== "string") {
  8002. throw new Error("Error - expected string data.");
  8003. }
  8004. return decodeURIComponent(encodeURIComponent(data));
  8005. }
  8006. };
  8007. };
  8008. WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
  8009. if (!window || !cuetext) {
  8010. return null;
  8011. }
  8012. return parseContent(window, cuetext);
  8013. };
  8014. var FONT_SIZE_PERCENT = 0.05;
  8015. var FONT_STYLE = "sans-serif";
  8016. var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it.
  8017. // @param overlay A block level element (usually a div) that the computed cues
  8018. // and regions will be placed into.
  8019. WebVTT$1.processCues = function (window, cues, overlay) {
  8020. if (!window || !cues || !overlay) {
  8021. return null;
  8022. } // Remove all previous children.
  8023. while (overlay.firstChild) {
  8024. overlay.removeChild(overlay.firstChild);
  8025. }
  8026. var paddedOverlay = window.document.createElement("div");
  8027. paddedOverlay.style.position = "absolute";
  8028. paddedOverlay.style.left = "0";
  8029. paddedOverlay.style.right = "0";
  8030. paddedOverlay.style.top = "0";
  8031. paddedOverlay.style.bottom = "0";
  8032. paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
  8033. overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could
  8034. // be the case if a cue's state has been changed since the last computation or
  8035. // if it has not been computed yet.
  8036. function shouldCompute(cues) {
  8037. for (var i = 0; i < cues.length; i++) {
  8038. if (cues[i].hasBeenReset || !cues[i].displayState) {
  8039. return true;
  8040. }
  8041. }
  8042. return false;
  8043. } // We don't need to recompute the cues' display states. Just reuse them.
  8044. if (!shouldCompute(cues)) {
  8045. for (var i = 0; i < cues.length; i++) {
  8046. paddedOverlay.appendChild(cues[i].displayState);
  8047. }
  8048. return;
  8049. }
  8050. var boxPositions = [],
  8051. containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
  8052. fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
  8053. var styleOptions = {
  8054. font: fontSize + "px " + FONT_STYLE
  8055. };
  8056. (function () {
  8057. var styleBox, cue;
  8058. for (var i = 0; i < cues.length; i++) {
  8059. cue = cues[i]; // Compute the intial position and styles of the cue div.
  8060. styleBox = new CueStyleBox(window, cue, styleOptions);
  8061. paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position.
  8062. moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later
  8063. // if we don't have too.
  8064. cue.displayState = styleBox.div;
  8065. boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
  8066. }
  8067. })();
  8068. };
  8069. WebVTT$1.Parser = function (window, vttjs, decoder) {
  8070. if (!decoder) {
  8071. decoder = vttjs;
  8072. vttjs = {};
  8073. }
  8074. if (!vttjs) {
  8075. vttjs = {};
  8076. }
  8077. this.window = window;
  8078. this.vttjs = vttjs;
  8079. this.state = "INITIAL";
  8080. this.buffer = "";
  8081. this.decoder = decoder || new TextDecoder("utf8");
  8082. this.regionList = [];
  8083. };
  8084. WebVTT$1.Parser.prototype = {
  8085. // If the error is a ParsingError then report it to the consumer if
  8086. // possible. If it's not a ParsingError then throw it like normal.
  8087. reportOrThrowError: function reportOrThrowError(e) {
  8088. if (e instanceof ParsingError) {
  8089. this.onparsingerror && this.onparsingerror(e);
  8090. } else {
  8091. throw e;
  8092. }
  8093. },
  8094. parse: function parse(data) {
  8095. var self = this; // If there is no data then we won't decode it, but will just try to parse
  8096. // whatever is in buffer already. This may occur in circumstances, for
  8097. // example when flush() is called.
  8098. if (data) {
  8099. // Try to decode the data that we received.
  8100. self.buffer += self.decoder.decode(data, {
  8101. stream: true
  8102. });
  8103. }
  8104. function collectNextLine() {
  8105. var buffer = self.buffer;
  8106. var pos = 0;
  8107. while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
  8108. ++pos;
  8109. }
  8110. var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below.
  8111. if (buffer[pos] === '\r') {
  8112. ++pos;
  8113. }
  8114. if (buffer[pos] === '\n') {
  8115. ++pos;
  8116. }
  8117. self.buffer = buffer.substr(pos);
  8118. return line;
  8119. } // 3.4 WebVTT region and WebVTT region settings syntax
  8120. function parseRegion(input) {
  8121. var settings = new Settings();
  8122. parseOptions(input, function (k, v) {
  8123. switch (k) {
  8124. case "id":
  8125. settings.set(k, v);
  8126. break;
  8127. case "width":
  8128. settings.percent(k, v);
  8129. break;
  8130. case "lines":
  8131. settings.integer(k, v);
  8132. break;
  8133. case "regionanchor":
  8134. case "viewportanchor":
  8135. var xy = v.split(',');
  8136. if (xy.length !== 2) {
  8137. break;
  8138. } // We have to make sure both x and y parse, so use a temporary
  8139. // settings object here.
  8140. var anchor = new Settings();
  8141. anchor.percent("x", xy[0]);
  8142. anchor.percent("y", xy[1]);
  8143. if (!anchor.has("x") || !anchor.has("y")) {
  8144. break;
  8145. }
  8146. settings.set(k + "X", anchor.get("x"));
  8147. settings.set(k + "Y", anchor.get("y"));
  8148. break;
  8149. case "scroll":
  8150. settings.alt(k, v, ["up"]);
  8151. break;
  8152. }
  8153. }, /=/, /\s/); // Create the region, using default values for any values that were not
  8154. // specified.
  8155. if (settings.has("id")) {
  8156. var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
  8157. region.width = settings.get("width", 100);
  8158. region.lines = settings.get("lines", 3);
  8159. region.regionAnchorX = settings.get("regionanchorX", 0);
  8160. region.regionAnchorY = settings.get("regionanchorY", 100);
  8161. region.viewportAnchorX = settings.get("viewportanchorX", 0);
  8162. region.viewportAnchorY = settings.get("viewportanchorY", 100);
  8163. region.scroll = settings.get("scroll", ""); // Register the region.
  8164. self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that
  8165. // reference it.
  8166. self.regionList.push({
  8167. id: settings.get("id"),
  8168. region: region
  8169. });
  8170. }
  8171. } // draft-pantos-http-live-streaming-20
  8172. // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
  8173. // 3.5 WebVTT
  8174. function parseTimestampMap(input) {
  8175. var settings = new Settings();
  8176. parseOptions(input, function (k, v) {
  8177. switch (k) {
  8178. case "MPEGT":
  8179. settings.integer(k + 'S', v);
  8180. break;
  8181. case "LOCA":
  8182. settings.set(k + 'L', parseTimeStamp(v));
  8183. break;
  8184. }
  8185. }, /[^\d]:/, /,/);
  8186. self.ontimestampmap && self.ontimestampmap({
  8187. "MPEGTS": settings.get("MPEGTS"),
  8188. "LOCAL": settings.get("LOCAL")
  8189. });
  8190. } // 3.2 WebVTT metadata header syntax
  8191. function parseHeader(input) {
  8192. if (input.match(/X-TIMESTAMP-MAP/)) {
  8193. // This line contains HLS X-TIMESTAMP-MAP metadata
  8194. parseOptions(input, function (k, v) {
  8195. switch (k) {
  8196. case "X-TIMESTAMP-MAP":
  8197. parseTimestampMap(v);
  8198. break;
  8199. }
  8200. }, /=/);
  8201. } else {
  8202. parseOptions(input, function (k, v) {
  8203. switch (k) {
  8204. case "Region":
  8205. // 3.3 WebVTT region metadata header syntax
  8206. parseRegion(v);
  8207. break;
  8208. }
  8209. }, /:/);
  8210. }
  8211. } // 5.1 WebVTT file parsing.
  8212. try {
  8213. var line;
  8214. if (self.state === "INITIAL") {
  8215. // We can't start parsing until we have the first line.
  8216. if (!/\r\n|\n/.test(self.buffer)) {
  8217. return this;
  8218. }
  8219. line = collectNextLine();
  8220. var m = line.match(/^WEBVTT([ \t].*)?$/);
  8221. if (!m || !m[0]) {
  8222. throw new ParsingError(ParsingError.Errors.BadSignature);
  8223. }
  8224. self.state = "HEADER";
  8225. }
  8226. var alreadyCollectedLine = false;
  8227. while (self.buffer) {
  8228. // We can't parse a line until we have the full line.
  8229. if (!/\r\n|\n/.test(self.buffer)) {
  8230. return this;
  8231. }
  8232. if (!alreadyCollectedLine) {
  8233. line = collectNextLine();
  8234. } else {
  8235. alreadyCollectedLine = false;
  8236. }
  8237. switch (self.state) {
  8238. case "HEADER":
  8239. // 13-18 - Allow a header (metadata) under the WEBVTT line.
  8240. if (/:/.test(line)) {
  8241. parseHeader(line);
  8242. } else if (!line) {
  8243. // An empty line terminates the header and starts the body (cues).
  8244. self.state = "ID";
  8245. }
  8246. continue;
  8247. case "NOTE":
  8248. // Ignore NOTE blocks.
  8249. if (!line) {
  8250. self.state = "ID";
  8251. }
  8252. continue;
  8253. case "ID":
  8254. // Check for the start of NOTE blocks.
  8255. if (/^NOTE($|[ \t])/.test(line)) {
  8256. self.state = "NOTE";
  8257. break;
  8258. } // 19-29 - Allow any number of line terminators, then initialize new cue values.
  8259. if (!line) {
  8260. continue;
  8261. }
  8262. self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
  8263. self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data.
  8264. if (line.indexOf("-->") === -1) {
  8265. self.cue.id = line;
  8266. continue;
  8267. }
  8268. // Process line as start of a cue.
  8269. /*falls through*/
  8270. case "CUE":
  8271. // 40 - Collect cue timings and settings.
  8272. try {
  8273. parseCue(line, self.cue, self.regionList);
  8274. } catch (e) {
  8275. self.reportOrThrowError(e); // In case of an error ignore rest of the cue.
  8276. self.cue = null;
  8277. self.state = "BADCUE";
  8278. continue;
  8279. }
  8280. self.state = "CUETEXT";
  8281. continue;
  8282. case "CUETEXT":
  8283. var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue.
  8284. // 35 - If we have the special substring '-->' then report the cue,
  8285. // but do not collect the line as we need to process the current
  8286. // one as a new cue.
  8287. if (!line || hasSubstring && (alreadyCollectedLine = true)) {
  8288. // We are done parsing self cue.
  8289. self.oncue && self.oncue(self.cue);
  8290. self.cue = null;
  8291. self.state = "ID";
  8292. continue;
  8293. }
  8294. if (self.cue.text) {
  8295. self.cue.text += "\n";
  8296. }
  8297. self.cue.text += line;
  8298. continue;
  8299. case "BADCUE":
  8300. // BADCUE
  8301. // 54-62 - Collect and discard the remaining cue.
  8302. if (!line) {
  8303. self.state = "ID";
  8304. }
  8305. continue;
  8306. }
  8307. }
  8308. } catch (e) {
  8309. self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have.
  8310. if (self.state === "CUETEXT" && self.cue && self.oncue) {
  8311. self.oncue(self.cue);
  8312. }
  8313. self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise
  8314. // another exception occurred so enter BADCUE state.
  8315. self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
  8316. }
  8317. return this;
  8318. },
  8319. flush: function flush() {
  8320. var self = this;
  8321. try {
  8322. // Finish decoding the stream.
  8323. self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region.
  8324. if (self.cue || self.state === "HEADER") {
  8325. self.buffer += "\n\n";
  8326. self.parse();
  8327. } // If we've flushed, parsed, and we're still on the INITIAL state then
  8328. // that means we don't have enough of the stream to parse the first
  8329. // line.
  8330. if (self.state === "INITIAL") {
  8331. throw new ParsingError(ParsingError.Errors.BadSignature);
  8332. }
  8333. } catch (e) {
  8334. self.reportOrThrowError(e);
  8335. }
  8336. self.onflush && self.onflush();
  8337. return this;
  8338. }
  8339. };
  8340. var vtt = WebVTT$1;
  8341. /**
  8342. * Copyright 2013 vtt.js Contributors
  8343. *
  8344. * Licensed under the Apache License, Version 2.0 (the "License");
  8345. * you may not use this file except in compliance with the License.
  8346. * You may obtain a copy of the License at
  8347. *
  8348. * http://www.apache.org/licenses/LICENSE-2.0
  8349. *
  8350. * Unless required by applicable law or agreed to in writing, software
  8351. * distributed under the License is distributed on an "AS IS" BASIS,
  8352. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8353. * See the License for the specific language governing permissions and
  8354. * limitations under the License.
  8355. */
  8356. var autoKeyword = "auto";
  8357. var directionSetting = {
  8358. "": 1,
  8359. "lr": 1,
  8360. "rl": 1
  8361. };
  8362. var alignSetting = {
  8363. "start": 1,
  8364. "middle": 1,
  8365. "end": 1,
  8366. "left": 1,
  8367. "right": 1
  8368. };
  8369. function findDirectionSetting(value) {
  8370. if (typeof value !== "string") {
  8371. return false;
  8372. }
  8373. var dir = directionSetting[value.toLowerCase()];
  8374. return dir ? value.toLowerCase() : false;
  8375. }
  8376. function findAlignSetting(value) {
  8377. if (typeof value !== "string") {
  8378. return false;
  8379. }
  8380. var align = alignSetting[value.toLowerCase()];
  8381. return align ? value.toLowerCase() : false;
  8382. }
  8383. function VTTCue(startTime, endTime, text) {
  8384. /**
  8385. * Shim implementation specific properties. These properties are not in
  8386. * the spec.
  8387. */
  8388. // Lets us know when the VTTCue's data has changed in such a way that we need
  8389. // to recompute its display state. This lets us compute its display state
  8390. // lazily.
  8391. this.hasBeenReset = false;
  8392. /**
  8393. * VTTCue and TextTrackCue properties
  8394. * http://dev.w3.org/html5/webvtt/#vttcue-interface
  8395. */
  8396. var _id = "";
  8397. var _pauseOnExit = false;
  8398. var _startTime = startTime;
  8399. var _endTime = endTime;
  8400. var _text = text;
  8401. var _region = null;
  8402. var _vertical = "";
  8403. var _snapToLines = true;
  8404. var _line = "auto";
  8405. var _lineAlign = "start";
  8406. var _position = 50;
  8407. var _positionAlign = "middle";
  8408. var _size = 50;
  8409. var _align = "middle";
  8410. Object.defineProperties(this, {
  8411. "id": {
  8412. enumerable: true,
  8413. get: function get() {
  8414. return _id;
  8415. },
  8416. set: function set(value) {
  8417. _id = "" + value;
  8418. }
  8419. },
  8420. "pauseOnExit": {
  8421. enumerable: true,
  8422. get: function get() {
  8423. return _pauseOnExit;
  8424. },
  8425. set: function set(value) {
  8426. _pauseOnExit = !!value;
  8427. }
  8428. },
  8429. "startTime": {
  8430. enumerable: true,
  8431. get: function get() {
  8432. return _startTime;
  8433. },
  8434. set: function set(value) {
  8435. if (typeof value !== "number") {
  8436. throw new TypeError("Start time must be set to a number.");
  8437. }
  8438. _startTime = value;
  8439. this.hasBeenReset = true;
  8440. }
  8441. },
  8442. "endTime": {
  8443. enumerable: true,
  8444. get: function get() {
  8445. return _endTime;
  8446. },
  8447. set: function set(value) {
  8448. if (typeof value !== "number") {
  8449. throw new TypeError("End time must be set to a number.");
  8450. }
  8451. _endTime = value;
  8452. this.hasBeenReset = true;
  8453. }
  8454. },
  8455. "text": {
  8456. enumerable: true,
  8457. get: function get() {
  8458. return _text;
  8459. },
  8460. set: function set(value) {
  8461. _text = "" + value;
  8462. this.hasBeenReset = true;
  8463. }
  8464. },
  8465. "region": {
  8466. enumerable: true,
  8467. get: function get() {
  8468. return _region;
  8469. },
  8470. set: function set(value) {
  8471. _region = value;
  8472. this.hasBeenReset = true;
  8473. }
  8474. },
  8475. "vertical": {
  8476. enumerable: true,
  8477. get: function get() {
  8478. return _vertical;
  8479. },
  8480. set: function set(value) {
  8481. var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string.
  8482. if (setting === false) {
  8483. throw new SyntaxError("An invalid or illegal string was specified.");
  8484. }
  8485. _vertical = setting;
  8486. this.hasBeenReset = true;
  8487. }
  8488. },
  8489. "snapToLines": {
  8490. enumerable: true,
  8491. get: function get() {
  8492. return _snapToLines;
  8493. },
  8494. set: function set(value) {
  8495. _snapToLines = !!value;
  8496. this.hasBeenReset = true;
  8497. }
  8498. },
  8499. "line": {
  8500. enumerable: true,
  8501. get: function get() {
  8502. return _line;
  8503. },
  8504. set: function set(value) {
  8505. if (typeof value !== "number" && value !== autoKeyword) {
  8506. throw new SyntaxError("An invalid number or illegal string was specified.");
  8507. }
  8508. _line = value;
  8509. this.hasBeenReset = true;
  8510. }
  8511. },
  8512. "lineAlign": {
  8513. enumerable: true,
  8514. get: function get() {
  8515. return _lineAlign;
  8516. },
  8517. set: function set(value) {
  8518. var setting = findAlignSetting(value);
  8519. if (!setting) {
  8520. throw new SyntaxError("An invalid or illegal string was specified.");
  8521. }
  8522. _lineAlign = setting;
  8523. this.hasBeenReset = true;
  8524. }
  8525. },
  8526. "position": {
  8527. enumerable: true,
  8528. get: function get() {
  8529. return _position;
  8530. },
  8531. set: function set(value) {
  8532. if (value < 0 || value > 100) {
  8533. throw new Error("Position must be between 0 and 100.");
  8534. }
  8535. _position = value;
  8536. this.hasBeenReset = true;
  8537. }
  8538. },
  8539. "positionAlign": {
  8540. enumerable: true,
  8541. get: function get() {
  8542. return _positionAlign;
  8543. },
  8544. set: function set(value) {
  8545. var setting = findAlignSetting(value);
  8546. if (!setting) {
  8547. throw new SyntaxError("An invalid or illegal string was specified.");
  8548. }
  8549. _positionAlign = setting;
  8550. this.hasBeenReset = true;
  8551. }
  8552. },
  8553. "size": {
  8554. enumerable: true,
  8555. get: function get() {
  8556. return _size;
  8557. },
  8558. set: function set(value) {
  8559. if (value < 0 || value > 100) {
  8560. throw new Error("Size must be between 0 and 100.");
  8561. }
  8562. _size = value;
  8563. this.hasBeenReset = true;
  8564. }
  8565. },
  8566. "align": {
  8567. enumerable: true,
  8568. get: function get() {
  8569. return _align;
  8570. },
  8571. set: function set(value) {
  8572. var setting = findAlignSetting(value);
  8573. if (!setting) {
  8574. throw new SyntaxError("An invalid or illegal string was specified.");
  8575. }
  8576. _align = setting;
  8577. this.hasBeenReset = true;
  8578. }
  8579. }
  8580. });
  8581. /**
  8582. * Other <track> spec defined properties
  8583. */
  8584. // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
  8585. this.displayState = undefined;
  8586. }
  8587. /**
  8588. * VTTCue methods
  8589. */
  8590. VTTCue.prototype.getCueAsHTML = function () {
  8591. // Assume WebVTT.convertCueToDOMTree is on the global.
  8592. return WebVTT.convertCueToDOMTree(window, this.text);
  8593. };
  8594. var vttcue = VTTCue;
  8595. /**
  8596. * Copyright 2013 vtt.js Contributors
  8597. *
  8598. * Licensed under the Apache License, Version 2.0 (the "License");
  8599. * you may not use this file except in compliance with the License.
  8600. * You may obtain a copy of the License at
  8601. *
  8602. * http://www.apache.org/licenses/LICENSE-2.0
  8603. *
  8604. * Unless required by applicable law or agreed to in writing, software
  8605. * distributed under the License is distributed on an "AS IS" BASIS,
  8606. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8607. * See the License for the specific language governing permissions and
  8608. * limitations under the License.
  8609. */
  8610. var scrollSetting = {
  8611. "": true,
  8612. "up": true
  8613. };
  8614. function findScrollSetting(value) {
  8615. if (typeof value !== "string") {
  8616. return false;
  8617. }
  8618. var scroll = scrollSetting[value.toLowerCase()];
  8619. return scroll ? value.toLowerCase() : false;
  8620. }
  8621. function isValidPercentValue(value) {
  8622. return typeof value === "number" && value >= 0 && value <= 100;
  8623. } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
  8624. function VTTRegion() {
  8625. var _width = 100;
  8626. var _lines = 3;
  8627. var _regionAnchorX = 0;
  8628. var _regionAnchorY = 100;
  8629. var _viewportAnchorX = 0;
  8630. var _viewportAnchorY = 100;
  8631. var _scroll = "";
  8632. Object.defineProperties(this, {
  8633. "width": {
  8634. enumerable: true,
  8635. get: function get() {
  8636. return _width;
  8637. },
  8638. set: function set(value) {
  8639. if (!isValidPercentValue(value)) {
  8640. throw new Error("Width must be between 0 and 100.");
  8641. }
  8642. _width = value;
  8643. }
  8644. },
  8645. "lines": {
  8646. enumerable: true,
  8647. get: function get() {
  8648. return _lines;
  8649. },
  8650. set: function set(value) {
  8651. if (typeof value !== "number") {
  8652. throw new TypeError("Lines must be set to a number.");
  8653. }
  8654. _lines = value;
  8655. }
  8656. },
  8657. "regionAnchorY": {
  8658. enumerable: true,
  8659. get: function get() {
  8660. return _regionAnchorY;
  8661. },
  8662. set: function set(value) {
  8663. if (!isValidPercentValue(value)) {
  8664. throw new Error("RegionAnchorX must be between 0 and 100.");
  8665. }
  8666. _regionAnchorY = value;
  8667. }
  8668. },
  8669. "regionAnchorX": {
  8670. enumerable: true,
  8671. get: function get() {
  8672. return _regionAnchorX;
  8673. },
  8674. set: function set(value) {
  8675. if (!isValidPercentValue(value)) {
  8676. throw new Error("RegionAnchorY must be between 0 and 100.");
  8677. }
  8678. _regionAnchorX = value;
  8679. }
  8680. },
  8681. "viewportAnchorY": {
  8682. enumerable: true,
  8683. get: function get() {
  8684. return _viewportAnchorY;
  8685. },
  8686. set: function set(value) {
  8687. if (!isValidPercentValue(value)) {
  8688. throw new Error("ViewportAnchorY must be between 0 and 100.");
  8689. }
  8690. _viewportAnchorY = value;
  8691. }
  8692. },
  8693. "viewportAnchorX": {
  8694. enumerable: true,
  8695. get: function get() {
  8696. return _viewportAnchorX;
  8697. },
  8698. set: function set(value) {
  8699. if (!isValidPercentValue(value)) {
  8700. throw new Error("ViewportAnchorX must be between 0 and 100.");
  8701. }
  8702. _viewportAnchorX = value;
  8703. }
  8704. },
  8705. "scroll": {
  8706. enumerable: true,
  8707. get: function get() {
  8708. return _scroll;
  8709. },
  8710. set: function set(value) {
  8711. var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value.
  8712. if (setting === false) {
  8713. throw new SyntaxError("An invalid or illegal string was specified.");
  8714. }
  8715. _scroll = setting;
  8716. }
  8717. }
  8718. });
  8719. }
  8720. var vttregion = VTTRegion;
  8721. var browserIndex = createCommonjsModule(function (module) {
  8722. /**
  8723. * Copyright 2013 vtt.js Contributors
  8724. *
  8725. * Licensed under the Apache License, Version 2.0 (the "License");
  8726. * you may not use this file except in compliance with the License.
  8727. * You may obtain a copy of the License at
  8728. *
  8729. * http://www.apache.org/licenses/LICENSE-2.0
  8730. *
  8731. * Unless required by applicable law or agreed to in writing, software
  8732. * distributed under the License is distributed on an "AS IS" BASIS,
  8733. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8734. * See the License for the specific language governing permissions and
  8735. * limitations under the License.
  8736. */
  8737. // Default exports for Node. Export the extended versions of VTTCue and
  8738. // VTTRegion in Node since we likely want the capability to convert back and
  8739. // forth between JSON. If we don't then it's not that big of a deal since we're
  8740. // off browser.
  8741. var vttjs = module.exports = {
  8742. WebVTT: vtt,
  8743. VTTCue: vttcue,
  8744. VTTRegion: vttregion
  8745. };
  8746. window$1.vttjs = vttjs;
  8747. window$1.WebVTT = vttjs.WebVTT;
  8748. var cueShim = vttjs.VTTCue;
  8749. var regionShim = vttjs.VTTRegion;
  8750. var nativeVTTCue = window$1.VTTCue;
  8751. var nativeVTTRegion = window$1.VTTRegion;
  8752. vttjs.shim = function () {
  8753. window$1.VTTCue = cueShim;
  8754. window$1.VTTRegion = regionShim;
  8755. };
  8756. vttjs.restore = function () {
  8757. window$1.VTTCue = nativeVTTCue;
  8758. window$1.VTTRegion = nativeVTTRegion;
  8759. };
  8760. if (!window$1.VTTCue) {
  8761. vttjs.shim();
  8762. }
  8763. });
  8764. var browserIndex_1 = browserIndex.WebVTT;
  8765. var browserIndex_2 = browserIndex.VTTCue;
  8766. var browserIndex_3 = browserIndex.VTTRegion;
  8767. /**
  8768. * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
  8769. * that just contains the src url alone.
  8770. * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
  8771. * `var SourceString = 'http://example.com/some-video.mp4';`
  8772. *
  8773. * @typedef {Object|string} Tech~SourceObject
  8774. *
  8775. * @property {string} src
  8776. * The url to the source
  8777. *
  8778. * @property {string} type
  8779. * The mime type of the source
  8780. */
  8781. /**
  8782. * A function used by {@link Tech} to create a new {@link TextTrack}.
  8783. *
  8784. * @private
  8785. *
  8786. * @param {Tech} self
  8787. * An instance of the Tech class.
  8788. *
  8789. * @param {string} kind
  8790. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  8791. *
  8792. * @param {string} [label]
  8793. * Label to identify the text track
  8794. *
  8795. * @param {string} [language]
  8796. * Two letter language abbreviation
  8797. *
  8798. * @param {Object} [options={}]
  8799. * An object with additional text track options
  8800. *
  8801. * @return {TextTrack}
  8802. * The text track that was created.
  8803. */
  8804. function createTrackHelper(self, kind, label, language, options) {
  8805. if (options === void 0) {
  8806. options = {};
  8807. }
  8808. var tracks = self.textTracks();
  8809. options.kind = kind;
  8810. if (label) {
  8811. options.label = label;
  8812. }
  8813. if (language) {
  8814. options.language = language;
  8815. }
  8816. options.tech = self;
  8817. var track = new ALL.text.TrackClass(options);
  8818. tracks.addTrack(track);
  8819. return track;
  8820. }
  8821. /**
  8822. * This is the base class for media playback technology controllers, such as
  8823. * {@link Flash} and {@link HTML5}
  8824. *
  8825. * @extends Component
  8826. */
  8827. var Tech =
  8828. /*#__PURE__*/
  8829. function (_Component) {
  8830. _inheritsLoose(Tech, _Component);
  8831. /**
  8832. * Create an instance of this Tech.
  8833. *
  8834. * @param {Object} [options]
  8835. * The key/value store of player options.
  8836. *
  8837. * @param {Component~ReadyCallback} ready
  8838. * Callback function to call when the `HTML5` Tech is ready.
  8839. */
  8840. function Tech(options, ready) {
  8841. var _this;
  8842. if (options === void 0) {
  8843. options = {};
  8844. }
  8845. if (ready === void 0) {
  8846. ready = function ready() {};
  8847. }
  8848. // we don't want the tech to report user activity automatically.
  8849. // This is done manually in addControlsListeners
  8850. options.reportTouchActivity = false;
  8851. _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to
  8852. // implement a very limited played()
  8853. _this.hasStarted_ = false;
  8854. _this.on('playing', function () {
  8855. this.hasStarted_ = true;
  8856. });
  8857. _this.on('loadstart', function () {
  8858. this.hasStarted_ = false;
  8859. });
  8860. ALL.names.forEach(function (name) {
  8861. var props = ALL[name];
  8862. if (options && options[props.getterName]) {
  8863. _this[props.privateName] = options[props.getterName];
  8864. }
  8865. }); // Manually track progress in cases where the browser/flash player doesn't report it.
  8866. if (!_this.featuresProgressEvents) {
  8867. _this.manualProgressOn();
  8868. } // Manually track timeupdates in cases where the browser/flash player doesn't report it.
  8869. if (!_this.featuresTimeupdateEvents) {
  8870. _this.manualTimeUpdatesOn();
  8871. }
  8872. ['Text', 'Audio', 'Video'].forEach(function (track) {
  8873. if (options["native" + track + "Tracks"] === false) {
  8874. _this["featuresNative" + track + "Tracks"] = false;
  8875. }
  8876. });
  8877. if (options.nativeCaptions === false || options.nativeTextTracks === false) {
  8878. _this.featuresNativeTextTracks = false;
  8879. } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
  8880. _this.featuresNativeTextTracks = true;
  8881. }
  8882. if (!_this.featuresNativeTextTracks) {
  8883. _this.emulateTextTracks();
  8884. }
  8885. _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
  8886. _this.initTrackListeners(); // Turn on component tap events only if not using native controls
  8887. if (!options.nativeControlsForTouch) {
  8888. _this.emitTapEvents();
  8889. }
  8890. if (_this.constructor) {
  8891. _this.name_ = _this.constructor.name || 'Unknown Tech';
  8892. }
  8893. return _this;
  8894. }
  8895. /**
  8896. * A special function to trigger source set in a way that will allow player
  8897. * to re-trigger if the player or tech are not ready yet.
  8898. *
  8899. * @fires Tech#sourceset
  8900. * @param {string} src The source string at the time of the source changing.
  8901. */
  8902. var _proto = Tech.prototype;
  8903. _proto.triggerSourceset = function triggerSourceset(src) {
  8904. var _this2 = this;
  8905. if (!this.isReady_) {
  8906. // on initial ready we have to trigger source set
  8907. // 1ms after ready so that player can watch for it.
  8908. this.one('ready', function () {
  8909. return _this2.setTimeout(function () {
  8910. return _this2.triggerSourceset(src);
  8911. }, 1);
  8912. });
  8913. }
  8914. /**
  8915. * Fired when the source is set on the tech causing the media element
  8916. * to reload.
  8917. *
  8918. * @see {@link Player#event:sourceset}
  8919. * @event Tech#sourceset
  8920. * @type {EventTarget~Event}
  8921. */
  8922. this.trigger({
  8923. src: src,
  8924. type: 'sourceset'
  8925. });
  8926. }
  8927. /* Fallbacks for unsupported event types
  8928. ================================================================================ */
  8929. /**
  8930. * Polyfill the `progress` event for browsers that don't support it natively.
  8931. *
  8932. * @see {@link Tech#trackProgress}
  8933. */
  8934. ;
  8935. _proto.manualProgressOn = function manualProgressOn() {
  8936. this.on('durationchange', this.onDurationChange);
  8937. this.manualProgress = true; // Trigger progress watching when a source begins loading
  8938. this.one('ready', this.trackProgress);
  8939. }
  8940. /**
  8941. * Turn off the polyfill for `progress` events that was created in
  8942. * {@link Tech#manualProgressOn}
  8943. */
  8944. ;
  8945. _proto.manualProgressOff = function manualProgressOff() {
  8946. this.manualProgress = false;
  8947. this.stopTrackingProgress();
  8948. this.off('durationchange', this.onDurationChange);
  8949. }
  8950. /**
  8951. * This is used to trigger a `progress` event when the buffered percent changes. It
  8952. * sets an interval function that will be called every 500 milliseconds to check if the
  8953. * buffer end percent has changed.
  8954. *
  8955. * > This function is called by {@link Tech#manualProgressOn}
  8956. *
  8957. * @param {EventTarget~Event} event
  8958. * The `ready` event that caused this to run.
  8959. *
  8960. * @listens Tech#ready
  8961. * @fires Tech#progress
  8962. */
  8963. ;
  8964. _proto.trackProgress = function trackProgress(event) {
  8965. this.stopTrackingProgress();
  8966. this.progressInterval = this.setInterval(bind(this, function () {
  8967. // Don't trigger unless buffered amount is greater than last time
  8968. var numBufferedPercent = this.bufferedPercent();
  8969. if (this.bufferedPercent_ !== numBufferedPercent) {
  8970. /**
  8971. * See {@link Player#progress}
  8972. *
  8973. * @event Tech#progress
  8974. * @type {EventTarget~Event}
  8975. */
  8976. this.trigger('progress');
  8977. }
  8978. this.bufferedPercent_ = numBufferedPercent;
  8979. if (numBufferedPercent === 1) {
  8980. this.stopTrackingProgress();
  8981. }
  8982. }), 500);
  8983. }
  8984. /**
  8985. * Update our internal duration on a `durationchange` event by calling
  8986. * {@link Tech#duration}.
  8987. *
  8988. * @param {EventTarget~Event} event
  8989. * The `durationchange` event that caused this to run.
  8990. *
  8991. * @listens Tech#durationchange
  8992. */
  8993. ;
  8994. _proto.onDurationChange = function onDurationChange(event) {
  8995. this.duration_ = this.duration();
  8996. }
  8997. /**
  8998. * Get and create a `TimeRange` object for buffering.
  8999. *
  9000. * @return {TimeRange}
  9001. * The time range object that was created.
  9002. */
  9003. ;
  9004. _proto.buffered = function buffered() {
  9005. return createTimeRanges(0, 0);
  9006. }
  9007. /**
  9008. * Get the percentage of the current video that is currently buffered.
  9009. *
  9010. * @return {number}
  9011. * A number from 0 to 1 that represents the decimal percentage of the
  9012. * video that is buffered.
  9013. *
  9014. */
  9015. ;
  9016. _proto.bufferedPercent = function bufferedPercent$$1() {
  9017. return bufferedPercent(this.buffered(), this.duration_);
  9018. }
  9019. /**
  9020. * Turn off the polyfill for `progress` events that was created in
  9021. * {@link Tech#manualProgressOn}
  9022. * Stop manually tracking progress events by clearing the interval that was set in
  9023. * {@link Tech#trackProgress}.
  9024. */
  9025. ;
  9026. _proto.stopTrackingProgress = function stopTrackingProgress() {
  9027. this.clearInterval(this.progressInterval);
  9028. }
  9029. /**
  9030. * Polyfill the `timeupdate` event for browsers that don't support it.
  9031. *
  9032. * @see {@link Tech#trackCurrentTime}
  9033. */
  9034. ;
  9035. _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
  9036. this.manualTimeUpdates = true;
  9037. this.on('play', this.trackCurrentTime);
  9038. this.on('pause', this.stopTrackingCurrentTime);
  9039. }
  9040. /**
  9041. * Turn off the polyfill for `timeupdate` events that was created in
  9042. * {@link Tech#manualTimeUpdatesOn}
  9043. */
  9044. ;
  9045. _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
  9046. this.manualTimeUpdates = false;
  9047. this.stopTrackingCurrentTime();
  9048. this.off('play', this.trackCurrentTime);
  9049. this.off('pause', this.stopTrackingCurrentTime);
  9050. }
  9051. /**
  9052. * Sets up an interval function to track current time and trigger `timeupdate` every
  9053. * 250 milliseconds.
  9054. *
  9055. * @listens Tech#play
  9056. * @triggers Tech#timeupdate
  9057. */
  9058. ;
  9059. _proto.trackCurrentTime = function trackCurrentTime() {
  9060. if (this.currentTimeInterval) {
  9061. this.stopTrackingCurrentTime();
  9062. }
  9063. this.currentTimeInterval = this.setInterval(function () {
  9064. /**
  9065. * Triggered at an interval of 250ms to indicated that time is passing in the video.
  9066. *
  9067. * @event Tech#timeupdate
  9068. * @type {EventTarget~Event}
  9069. */
  9070. this.trigger({
  9071. type: 'timeupdate',
  9072. target: this,
  9073. manuallyTriggered: true
  9074. }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  9075. }, 250);
  9076. }
  9077. /**
  9078. * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
  9079. * `timeupdate` event is no longer triggered.
  9080. *
  9081. * @listens {Tech#pause}
  9082. */
  9083. ;
  9084. _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
  9085. this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen,
  9086. // the progress bar won't make it all the way to the end
  9087. this.trigger({
  9088. type: 'timeupdate',
  9089. target: this,
  9090. manuallyTriggered: true
  9091. });
  9092. }
  9093. /**
  9094. * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
  9095. * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
  9096. *
  9097. * @fires Component#dispose
  9098. */
  9099. ;
  9100. _proto.dispose = function dispose() {
  9101. // clear out all tracks because we can't reuse them between techs
  9102. this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking
  9103. if (this.manualProgress) {
  9104. this.manualProgressOff();
  9105. }
  9106. if (this.manualTimeUpdates) {
  9107. this.manualTimeUpdatesOff();
  9108. }
  9109. _Component.prototype.dispose.call(this);
  9110. }
  9111. /**
  9112. * Clear out a single `TrackList` or an array of `TrackLists` given their names.
  9113. *
  9114. * > Note: Techs without source handlers should call this between sources for `video`
  9115. * & `audio` tracks. You don't want to use them between tracks!
  9116. *
  9117. * @param {string[]|string} types
  9118. * TrackList names to clear, valid names are `video`, `audio`, and
  9119. * `text`.
  9120. */
  9121. ;
  9122. _proto.clearTracks = function clearTracks(types) {
  9123. var _this3 = this;
  9124. types = [].concat(types); // clear out all tracks because we can't reuse them between techs
  9125. types.forEach(function (type) {
  9126. var list = _this3[type + "Tracks"]() || [];
  9127. var i = list.length;
  9128. while (i--) {
  9129. var track = list[i];
  9130. if (type === 'text') {
  9131. _this3.removeRemoteTextTrack(track);
  9132. }
  9133. list.removeTrack(track);
  9134. }
  9135. });
  9136. }
  9137. /**
  9138. * Remove any TextTracks added via addRemoteTextTrack that are
  9139. * flagged for automatic garbage collection
  9140. */
  9141. ;
  9142. _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
  9143. var list = this.autoRemoteTextTracks_ || [];
  9144. var i = list.length;
  9145. while (i--) {
  9146. var track = list[i];
  9147. this.removeRemoteTextTrack(track);
  9148. }
  9149. }
  9150. /**
  9151. * Reset the tech, which will removes all sources and reset the internal readyState.
  9152. *
  9153. * @abstract
  9154. */
  9155. ;
  9156. _proto.reset = function reset() {}
  9157. /**
  9158. * Get or set an error on the Tech.
  9159. *
  9160. * @param {MediaError} [err]
  9161. * Error to set on the Tech
  9162. *
  9163. * @return {MediaError|null}
  9164. * The current error object on the tech, or null if there isn't one.
  9165. */
  9166. ;
  9167. _proto.error = function error(err) {
  9168. if (err !== undefined) {
  9169. this.error_ = new MediaError(err);
  9170. this.trigger('error');
  9171. }
  9172. return this.error_;
  9173. }
  9174. /**
  9175. * Returns the `TimeRange`s that have been played through for the current source.
  9176. *
  9177. * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
  9178. * It only checks whether the source has played at all or not.
  9179. *
  9180. * @return {TimeRange}
  9181. * - A single time range if this video has played
  9182. * - An empty set of ranges if not.
  9183. */
  9184. ;
  9185. _proto.played = function played() {
  9186. if (this.hasStarted_) {
  9187. return createTimeRanges(0, 0);
  9188. }
  9189. return createTimeRanges();
  9190. }
  9191. /**
  9192. * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
  9193. * previously called.
  9194. *
  9195. * @fires Tech#timeupdate
  9196. */
  9197. ;
  9198. _proto.setCurrentTime = function setCurrentTime() {
  9199. // improve the accuracy of manual timeupdates
  9200. if (this.manualTimeUpdates) {
  9201. /**
  9202. * A manual `timeupdate` event.
  9203. *
  9204. * @event Tech#timeupdate
  9205. * @type {EventTarget~Event}
  9206. */
  9207. this.trigger({
  9208. type: 'timeupdate',
  9209. target: this,
  9210. manuallyTriggered: true
  9211. });
  9212. }
  9213. }
  9214. /**
  9215. * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
  9216. * {@link TextTrackList} events.
  9217. *
  9218. * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`.
  9219. *
  9220. * @fires Tech#audiotrackchange
  9221. * @fires Tech#videotrackchange
  9222. * @fires Tech#texttrackchange
  9223. */
  9224. ;
  9225. _proto.initTrackListeners = function initTrackListeners() {
  9226. var _this4 = this;
  9227. /**
  9228. * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
  9229. *
  9230. * @event Tech#audiotrackchange
  9231. * @type {EventTarget~Event}
  9232. */
  9233. /**
  9234. * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
  9235. *
  9236. * @event Tech#videotrackchange
  9237. * @type {EventTarget~Event}
  9238. */
  9239. /**
  9240. * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
  9241. *
  9242. * @event Tech#texttrackchange
  9243. * @type {EventTarget~Event}
  9244. */
  9245. NORMAL.names.forEach(function (name) {
  9246. var props = NORMAL[name];
  9247. var trackListChanges = function trackListChanges() {
  9248. _this4.trigger(name + "trackchange");
  9249. };
  9250. var tracks = _this4[props.getterName]();
  9251. tracks.addEventListener('removetrack', trackListChanges);
  9252. tracks.addEventListener('addtrack', trackListChanges);
  9253. _this4.on('dispose', function () {
  9254. tracks.removeEventListener('removetrack', trackListChanges);
  9255. tracks.removeEventListener('addtrack', trackListChanges);
  9256. });
  9257. });
  9258. }
  9259. /**
  9260. * Emulate TextTracks using vtt.js if necessary
  9261. *
  9262. * @fires Tech#vttjsloaded
  9263. * @fires Tech#vttjserror
  9264. */
  9265. ;
  9266. _proto.addWebVttScript_ = function addWebVttScript_() {
  9267. var _this5 = this;
  9268. if (window$1.WebVTT) {
  9269. return;
  9270. } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
  9271. // signals that the Tech is ready at which point Tech.el_ is part of the DOM
  9272. // before inserting the WebVTT script
  9273. if (document.body.contains(this.el())) {
  9274. // load via require if available and vtt.js script location was not passed in
  9275. // as an option. novtt builds will turn the above require call into an empty object
  9276. // which will cause this if check to always fail.
  9277. if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
  9278. this.trigger('vttjsloaded');
  9279. return;
  9280. } // load vtt.js via the script location option or the cdn of no location was
  9281. // passed in
  9282. var script = document.createElement('script');
  9283. script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
  9284. script.onload = function () {
  9285. /**
  9286. * Fired when vtt.js is loaded.
  9287. *
  9288. * @event Tech#vttjsloaded
  9289. * @type {EventTarget~Event}
  9290. */
  9291. _this5.trigger('vttjsloaded');
  9292. };
  9293. script.onerror = function () {
  9294. /**
  9295. * Fired when vtt.js was not loaded due to an error
  9296. *
  9297. * @event Tech#vttjsloaded
  9298. * @type {EventTarget~Event}
  9299. */
  9300. _this5.trigger('vttjserror');
  9301. };
  9302. this.on('dispose', function () {
  9303. script.onload = null;
  9304. script.onerror = null;
  9305. }); // but have not loaded yet and we set it to true before the inject so that
  9306. // we don't overwrite the injected window.WebVTT if it loads right away
  9307. window$1.WebVTT = true;
  9308. this.el().parentNode.appendChild(script);
  9309. } else {
  9310. this.ready(this.addWebVttScript_);
  9311. }
  9312. }
  9313. /**
  9314. * Emulate texttracks
  9315. *
  9316. */
  9317. ;
  9318. _proto.emulateTextTracks = function emulateTextTracks() {
  9319. var _this6 = this;
  9320. var tracks = this.textTracks();
  9321. var remoteTracks = this.remoteTextTracks();
  9322. var handleAddTrack = function handleAddTrack(e) {
  9323. return tracks.addTrack(e.track);
  9324. };
  9325. var handleRemoveTrack = function handleRemoveTrack(e) {
  9326. return tracks.removeTrack(e.track);
  9327. };
  9328. remoteTracks.on('addtrack', handleAddTrack);
  9329. remoteTracks.on('removetrack', handleRemoveTrack);
  9330. this.addWebVttScript_();
  9331. var updateDisplay = function updateDisplay() {
  9332. return _this6.trigger('texttrackchange');
  9333. };
  9334. var textTracksChanges = function textTracksChanges() {
  9335. updateDisplay();
  9336. for (var i = 0; i < tracks.length; i++) {
  9337. var track = tracks[i];
  9338. track.removeEventListener('cuechange', updateDisplay);
  9339. if (track.mode === 'showing') {
  9340. track.addEventListener('cuechange', updateDisplay);
  9341. }
  9342. }
  9343. };
  9344. textTracksChanges();
  9345. tracks.addEventListener('change', textTracksChanges);
  9346. tracks.addEventListener('addtrack', textTracksChanges);
  9347. tracks.addEventListener('removetrack', textTracksChanges);
  9348. this.on('dispose', function () {
  9349. remoteTracks.off('addtrack', handleAddTrack);
  9350. remoteTracks.off('removetrack', handleRemoveTrack);
  9351. tracks.removeEventListener('change', textTracksChanges);
  9352. tracks.removeEventListener('addtrack', textTracksChanges);
  9353. tracks.removeEventListener('removetrack', textTracksChanges);
  9354. for (var i = 0; i < tracks.length; i++) {
  9355. var track = tracks[i];
  9356. track.removeEventListener('cuechange', updateDisplay);
  9357. }
  9358. });
  9359. }
  9360. /**
  9361. * Create and returns a remote {@link TextTrack} object.
  9362. *
  9363. * @param {string} kind
  9364. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  9365. *
  9366. * @param {string} [label]
  9367. * Label to identify the text track
  9368. *
  9369. * @param {string} [language]
  9370. * Two letter language abbreviation
  9371. *
  9372. * @return {TextTrack}
  9373. * The TextTrack that gets created.
  9374. */
  9375. ;
  9376. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  9377. if (!kind) {
  9378. throw new Error('TextTrack kind is required but was not provided');
  9379. }
  9380. return createTrackHelper(this, kind, label, language);
  9381. }
  9382. /**
  9383. * Create an emulated TextTrack for use by addRemoteTextTrack
  9384. *
  9385. * This is intended to be overridden by classes that inherit from
  9386. * Tech in order to create native or custom TextTracks.
  9387. *
  9388. * @param {Object} options
  9389. * The object should contain the options to initialize the TextTrack with.
  9390. *
  9391. * @param {string} [options.kind]
  9392. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  9393. *
  9394. * @param {string} [options.label].
  9395. * Label to identify the text track
  9396. *
  9397. * @param {string} [options.language]
  9398. * Two letter language abbreviation.
  9399. *
  9400. * @return {HTMLTrackElement}
  9401. * The track element that gets created.
  9402. */
  9403. ;
  9404. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  9405. var track = mergeOptions(options, {
  9406. tech: this
  9407. });
  9408. return new REMOTE.remoteTextEl.TrackClass(track);
  9409. }
  9410. /**
  9411. * Creates a remote text track object and returns an html track element.
  9412. *
  9413. * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
  9414. *
  9415. * @param {Object} options
  9416. * See {@link Tech#createRemoteTextTrack} for more detailed properties.
  9417. *
  9418. * @param {boolean} [manualCleanup=true]
  9419. * - When false: the TextTrack will be automatically removed from the video
  9420. * element whenever the source changes
  9421. * - When True: The TextTrack will have to be cleaned up manually
  9422. *
  9423. * @return {HTMLTrackElement}
  9424. * An Html Track Element.
  9425. *
  9426. * @deprecated The default functionality for this function will be equivalent
  9427. * to "manualCleanup=false" in the future. The manualCleanup parameter will
  9428. * also be removed.
  9429. */
  9430. ;
  9431. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  9432. var _this7 = this;
  9433. if (options === void 0) {
  9434. options = {};
  9435. }
  9436. var htmlTrackElement = this.createRemoteTextTrack(options);
  9437. if (manualCleanup !== true && manualCleanup !== false) {
  9438. // deprecation warning
  9439. log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
  9440. manualCleanup = true;
  9441. } // store HTMLTrackElement and TextTrack to remote list
  9442. this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
  9443. this.remoteTextTracks().addTrack(htmlTrackElement.track);
  9444. if (manualCleanup !== true) {
  9445. // create the TextTrackList if it doesn't exist
  9446. this.ready(function () {
  9447. return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
  9448. });
  9449. }
  9450. return htmlTrackElement;
  9451. }
  9452. /**
  9453. * Remove a remote text track from the remote `TextTrackList`.
  9454. *
  9455. * @param {TextTrack} track
  9456. * `TextTrack` to remove from the `TextTrackList`
  9457. */
  9458. ;
  9459. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  9460. var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list
  9461. this.remoteTextTrackEls().removeTrackElement_(trackElement);
  9462. this.remoteTextTracks().removeTrack(track);
  9463. this.autoRemoteTextTracks_.removeTrack(track);
  9464. }
  9465. /**
  9466. * Gets available media playback quality metrics as specified by the W3C's Media
  9467. * Playback Quality API.
  9468. *
  9469. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  9470. *
  9471. * @return {Object}
  9472. * An object with supported media playback quality metrics
  9473. *
  9474. * @abstract
  9475. */
  9476. ;
  9477. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  9478. return {};
  9479. }
  9480. /**
  9481. * A method to set a poster from a `Tech`.
  9482. *
  9483. * @abstract
  9484. */
  9485. ;
  9486. _proto.setPoster = function setPoster() {}
  9487. /**
  9488. * A method to check for the presence of the 'playsinline' <video> attribute.
  9489. *
  9490. * @abstract
  9491. */
  9492. ;
  9493. _proto.playsinline = function playsinline() {}
  9494. /**
  9495. * A method to set or unset the 'playsinline' <video> attribute.
  9496. *
  9497. * @abstract
  9498. */
  9499. ;
  9500. _proto.setPlaysinline = function setPlaysinline() {}
  9501. /**
  9502. * Attempt to force override of native audio tracks.
  9503. *
  9504. * @param {boolean} override - If set to true native audio will be overridden,
  9505. * otherwise native audio will potentially be used.
  9506. *
  9507. * @abstract
  9508. */
  9509. ;
  9510. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {}
  9511. /**
  9512. * Attempt to force override of native video tracks.
  9513. *
  9514. * @param {boolean} override - If set to true native video will be overridden,
  9515. * otherwise native video will potentially be used.
  9516. *
  9517. * @abstract
  9518. */
  9519. ;
  9520. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {}
  9521. /*
  9522. * Check if the tech can support the given mime-type.
  9523. *
  9524. * The base tech does not support any type, but source handlers might
  9525. * overwrite this.
  9526. *
  9527. * @param {string} type
  9528. * The mimetype to check for support
  9529. *
  9530. * @return {string}
  9531. * 'probably', 'maybe', or empty string
  9532. *
  9533. * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
  9534. *
  9535. * @abstract
  9536. */
  9537. ;
  9538. _proto.canPlayType = function canPlayType() {
  9539. return '';
  9540. }
  9541. /**
  9542. * Check if the type is supported by this tech.
  9543. *
  9544. * The base tech does not support any type, but source handlers might
  9545. * overwrite this.
  9546. *
  9547. * @param {string} type
  9548. * The media type to check
  9549. * @return {string} Returns the native video element's response
  9550. */
  9551. ;
  9552. Tech.canPlayType = function canPlayType() {
  9553. return '';
  9554. }
  9555. /**
  9556. * Check if the tech can support the given source
  9557. *
  9558. * @param {Object} srcObj
  9559. * The source object
  9560. * @param {Object} options
  9561. * The options passed to the tech
  9562. * @return {string} 'probably', 'maybe', or '' (empty string)
  9563. */
  9564. ;
  9565. Tech.canPlaySource = function canPlaySource(srcObj, options) {
  9566. return Tech.canPlayType(srcObj.type);
  9567. }
  9568. /*
  9569. * Return whether the argument is a Tech or not.
  9570. * Can be passed either a Class like `Html5` or a instance like `player.tech_`
  9571. *
  9572. * @param {Object} component
  9573. * The item to check
  9574. *
  9575. * @return {boolean}
  9576. * Whether it is a tech or not
  9577. * - True if it is a tech
  9578. * - False if it is not
  9579. */
  9580. ;
  9581. Tech.isTech = function isTech(component) {
  9582. return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
  9583. }
  9584. /**
  9585. * Registers a `Tech` into a shared list for videojs.
  9586. *
  9587. * @param {string} name
  9588. * Name of the `Tech` to register.
  9589. *
  9590. * @param {Object} tech
  9591. * The `Tech` class to register.
  9592. */
  9593. ;
  9594. Tech.registerTech = function registerTech(name, tech) {
  9595. if (!Tech.techs_) {
  9596. Tech.techs_ = {};
  9597. }
  9598. if (!Tech.isTech(tech)) {
  9599. throw new Error("Tech " + name + " must be a Tech");
  9600. }
  9601. if (!Tech.canPlayType) {
  9602. throw new Error('Techs must have a static canPlayType method on them');
  9603. }
  9604. if (!Tech.canPlaySource) {
  9605. throw new Error('Techs must have a static canPlaySource method on them');
  9606. }
  9607. name = toTitleCase(name);
  9608. Tech.techs_[name] = tech;
  9609. if (name !== 'Tech') {
  9610. // camel case the techName for use in techOrder
  9611. Tech.defaultTechOrder_.push(name);
  9612. }
  9613. return tech;
  9614. }
  9615. /**
  9616. * Get a `Tech` from the shared list by name.
  9617. *
  9618. * @param {string} name
  9619. * `camelCase` or `TitleCase` name of the Tech to get
  9620. *
  9621. * @return {Tech|undefined}
  9622. * The `Tech` or undefined if there was no tech with the name requested.
  9623. */
  9624. ;
  9625. Tech.getTech = function getTech(name) {
  9626. if (!name) {
  9627. return;
  9628. }
  9629. name = toTitleCase(name);
  9630. if (Tech.techs_ && Tech.techs_[name]) {
  9631. return Tech.techs_[name];
  9632. }
  9633. if (window$1 && window$1.videojs && window$1.videojs[name]) {
  9634. log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)");
  9635. return window$1.videojs[name];
  9636. }
  9637. };
  9638. return Tech;
  9639. }(Component);
  9640. /**
  9641. * Get the {@link VideoTrackList}
  9642. *
  9643. * @returns {VideoTrackList}
  9644. * @method Tech.prototype.videoTracks
  9645. */
  9646. /**
  9647. * Get the {@link AudioTrackList}
  9648. *
  9649. * @returns {AudioTrackList}
  9650. * @method Tech.prototype.audioTracks
  9651. */
  9652. /**
  9653. * Get the {@link TextTrackList}
  9654. *
  9655. * @returns {TextTrackList}
  9656. * @method Tech.prototype.textTracks
  9657. */
  9658. /**
  9659. * Get the remote element {@link TextTrackList}
  9660. *
  9661. * @returns {TextTrackList}
  9662. * @method Tech.prototype.remoteTextTracks
  9663. */
  9664. /**
  9665. * Get the remote element {@link HtmlTrackElementList}
  9666. *
  9667. * @returns {HtmlTrackElementList}
  9668. * @method Tech.prototype.remoteTextTrackEls
  9669. */
  9670. ALL.names.forEach(function (name) {
  9671. var props = ALL[name];
  9672. Tech.prototype[props.getterName] = function () {
  9673. this[props.privateName] = this[props.privateName] || new props.ListClass();
  9674. return this[props.privateName];
  9675. };
  9676. });
  9677. /**
  9678. * List of associated text tracks
  9679. *
  9680. * @type {TextTrackList}
  9681. * @private
  9682. * @property Tech#textTracks_
  9683. */
  9684. /**
  9685. * List of associated audio tracks.
  9686. *
  9687. * @type {AudioTrackList}
  9688. * @private
  9689. * @property Tech#audioTracks_
  9690. */
  9691. /**
  9692. * List of associated video tracks.
  9693. *
  9694. * @type {VideoTrackList}
  9695. * @private
  9696. * @property Tech#videoTracks_
  9697. */
  9698. /**
  9699. * Boolean indicating whether the `Tech` supports volume control.
  9700. *
  9701. * @type {boolean}
  9702. * @default
  9703. */
  9704. Tech.prototype.featuresVolumeControl = true;
  9705. /**
  9706. * Boolean indicating whether the `Tech` supports muting volume.
  9707. *
  9708. * @type {bolean}
  9709. * @default
  9710. */
  9711. Tech.prototype.featuresMuteControl = true;
  9712. /**
  9713. * Boolean indicating whether the `Tech` supports fullscreen resize control.
  9714. * Resizing plugins using request fullscreen reloads the plugin
  9715. *
  9716. * @type {boolean}
  9717. * @default
  9718. */
  9719. Tech.prototype.featuresFullscreenResize = false;
  9720. /**
  9721. * Boolean indicating whether the `Tech` supports changing the speed at which the video
  9722. * plays. Examples:
  9723. * - Set player to play 2x (twice) as fast
  9724. * - Set player to play 0.5x (half) as fast
  9725. *
  9726. * @type {boolean}
  9727. * @default
  9728. */
  9729. Tech.prototype.featuresPlaybackRate = false;
  9730. /**
  9731. * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
  9732. * not triggered by video-js-swf. This will be used to determine if
  9733. * {@link Tech#manualProgressOn} should be called.
  9734. *
  9735. * @type {boolean}
  9736. * @default
  9737. */
  9738. Tech.prototype.featuresProgressEvents = false;
  9739. /**
  9740. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  9741. *
  9742. * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
  9743. * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
  9744. * a new source.
  9745. *
  9746. * @type {boolean}
  9747. * @default
  9748. */
  9749. Tech.prototype.featuresSourceset = false;
  9750. /**
  9751. * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
  9752. * not triggered by video-js-swf. This will be used to determine if
  9753. * {@link Tech#manualTimeUpdates} should be called.
  9754. *
  9755. * @type {boolean}
  9756. * @default
  9757. */
  9758. Tech.prototype.featuresTimeupdateEvents = false;
  9759. /**
  9760. * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
  9761. * This will help us integrate with native `TextTrack`s if the browser supports them.
  9762. *
  9763. * @type {boolean}
  9764. * @default
  9765. */
  9766. Tech.prototype.featuresNativeTextTracks = false;
  9767. /**
  9768. * A functional mixin for techs that want to use the Source Handler pattern.
  9769. * Source handlers are scripts for handling specific formats.
  9770. * The source handler pattern is used for adaptive formats (HLS, DASH) that
  9771. * manually load video data and feed it into a Source Buffer (Media Source Extensions)
  9772. * Example: `Tech.withSourceHandlers.call(MyTech);`
  9773. *
  9774. * @param {Tech} _Tech
  9775. * The tech to add source handler functions to.
  9776. *
  9777. * @mixes Tech~SourceHandlerAdditions
  9778. */
  9779. Tech.withSourceHandlers = function (_Tech) {
  9780. /**
  9781. * Register a source handler
  9782. *
  9783. * @param {Function} handler
  9784. * The source handler class
  9785. *
  9786. * @param {number} [index]
  9787. * Register it at the following index
  9788. */
  9789. _Tech.registerSourceHandler = function (handler, index) {
  9790. var handlers = _Tech.sourceHandlers;
  9791. if (!handlers) {
  9792. handlers = _Tech.sourceHandlers = [];
  9793. }
  9794. if (index === undefined) {
  9795. // add to the end of the list
  9796. index = handlers.length;
  9797. }
  9798. handlers.splice(index, 0, handler);
  9799. };
  9800. /**
  9801. * Check if the tech can support the given type. Also checks the
  9802. * Techs sourceHandlers.
  9803. *
  9804. * @param {string} type
  9805. * The mimetype to check.
  9806. *
  9807. * @return {string}
  9808. * 'probably', 'maybe', or '' (empty string)
  9809. */
  9810. _Tech.canPlayType = function (type) {
  9811. var handlers = _Tech.sourceHandlers || [];
  9812. var can;
  9813. for (var i = 0; i < handlers.length; i++) {
  9814. can = handlers[i].canPlayType(type);
  9815. if (can) {
  9816. return can;
  9817. }
  9818. }
  9819. return '';
  9820. };
  9821. /**
  9822. * Returns the first source handler that supports the source.
  9823. *
  9824. * TODO: Answer question: should 'probably' be prioritized over 'maybe'
  9825. *
  9826. * @param {Tech~SourceObject} source
  9827. * The source object
  9828. *
  9829. * @param {Object} options
  9830. * The options passed to the tech
  9831. *
  9832. * @return {SourceHandler|null}
  9833. * The first source handler that supports the source or null if
  9834. * no SourceHandler supports the source
  9835. */
  9836. _Tech.selectSourceHandler = function (source, options) {
  9837. var handlers = _Tech.sourceHandlers || [];
  9838. var can;
  9839. for (var i = 0; i < handlers.length; i++) {
  9840. can = handlers[i].canHandleSource(source, options);
  9841. if (can) {
  9842. return handlers[i];
  9843. }
  9844. }
  9845. return null;
  9846. };
  9847. /**
  9848. * Check if the tech can support the given source.
  9849. *
  9850. * @param {Tech~SourceObject} srcObj
  9851. * The source object
  9852. *
  9853. * @param {Object} options
  9854. * The options passed to the tech
  9855. *
  9856. * @return {string}
  9857. * 'probably', 'maybe', or '' (empty string)
  9858. */
  9859. _Tech.canPlaySource = function (srcObj, options) {
  9860. var sh = _Tech.selectSourceHandler(srcObj, options);
  9861. if (sh) {
  9862. return sh.canHandleSource(srcObj, options);
  9863. }
  9864. return '';
  9865. };
  9866. /**
  9867. * When using a source handler, prefer its implementation of
  9868. * any function normally provided by the tech.
  9869. */
  9870. var deferrable = ['seekable', 'seeking', 'duration'];
  9871. /**
  9872. * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
  9873. * function if it exists, with a fallback to the Techs seekable function.
  9874. *
  9875. * @method _Tech.seekable
  9876. */
  9877. /**
  9878. * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
  9879. * function if it exists, otherwise it will fallback to the techs duration function.
  9880. *
  9881. * @method _Tech.duration
  9882. */
  9883. deferrable.forEach(function (fnName) {
  9884. var originalFn = this[fnName];
  9885. if (typeof originalFn !== 'function') {
  9886. return;
  9887. }
  9888. this[fnName] = function () {
  9889. if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
  9890. return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
  9891. }
  9892. return originalFn.apply(this, arguments);
  9893. };
  9894. }, _Tech.prototype);
  9895. /**
  9896. * Create a function for setting the source using a source object
  9897. * and source handlers.
  9898. * Should never be called unless a source handler was found.
  9899. *
  9900. * @param {Tech~SourceObject} source
  9901. * A source object with src and type keys
  9902. */
  9903. _Tech.prototype.setSource = function (source) {
  9904. var sh = _Tech.selectSourceHandler(source, this.options_);
  9905. if (!sh) {
  9906. // Fall back to a native source hander when unsupported sources are
  9907. // deliberately set
  9908. if (_Tech.nativeSourceHandler) {
  9909. sh = _Tech.nativeSourceHandler;
  9910. } else {
  9911. log.error('No source handler found for the current source.');
  9912. }
  9913. } // Dispose any existing source handler
  9914. this.disposeSourceHandler();
  9915. this.off('dispose', this.disposeSourceHandler);
  9916. if (sh !== _Tech.nativeSourceHandler) {
  9917. this.currentSource_ = source;
  9918. }
  9919. this.sourceHandler_ = sh.handleSource(source, this, this.options_);
  9920. this.one('dispose', this.disposeSourceHandler);
  9921. };
  9922. /**
  9923. * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
  9924. *
  9925. * @listens Tech#dispose
  9926. */
  9927. _Tech.prototype.disposeSourceHandler = function () {
  9928. // if we have a source and get another one
  9929. // then we are loading something new
  9930. // than clear all of our current tracks
  9931. if (this.currentSource_) {
  9932. this.clearTracks(['audio', 'video']);
  9933. this.currentSource_ = null;
  9934. } // always clean up auto-text tracks
  9935. this.cleanupAutoTextTracks();
  9936. if (this.sourceHandler_) {
  9937. if (this.sourceHandler_.dispose) {
  9938. this.sourceHandler_.dispose();
  9939. }
  9940. this.sourceHandler_ = null;
  9941. }
  9942. };
  9943. }; // The base Tech class needs to be registered as a Component. It is the only
  9944. // Tech that can be registered as a Component.
  9945. Component.registerComponent('Tech', Tech);
  9946. Tech.registerTech('Tech', Tech);
  9947. /**
  9948. * A list of techs that should be added to techOrder on Players
  9949. *
  9950. * @private
  9951. */
  9952. Tech.defaultTechOrder_ = [];
  9953. /**
  9954. * @file middleware.js
  9955. * @module middleware
  9956. */
  9957. var middlewares = {};
  9958. var middlewareInstances = {};
  9959. var TERMINATOR = {};
  9960. /**
  9961. * A middleware object is a plain JavaScript object that has methods that
  9962. * match the {@link Tech} methods found in the lists of allowed
  9963. * {@link module:middleware.allowedGetters|getters},
  9964. * {@link module:middleware.allowedSetters|setters}, and
  9965. * {@link module:middleware.allowedMediators|mediators}.
  9966. *
  9967. * @typedef {Object} MiddlewareObject
  9968. */
  9969. /**
  9970. * A middleware factory function that should return a
  9971. * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
  9972. *
  9973. * This factory will be called for each player when needed, with the player
  9974. * passed in as an argument.
  9975. *
  9976. * @callback MiddlewareFactory
  9977. * @param {Player} player
  9978. * A Video.js player.
  9979. */
  9980. /**
  9981. * Define a middleware that the player should use by way of a factory function
  9982. * that returns a middleware object.
  9983. *
  9984. * @param {string} type
  9985. * The MIME type to match or `"*"` for all MIME types.
  9986. *
  9987. * @param {MiddlewareFactory} middleware
  9988. * A middleware factory function that will be executed for
  9989. * matching types.
  9990. */
  9991. function use(type, middleware) {
  9992. middlewares[type] = middlewares[type] || [];
  9993. middlewares[type].push(middleware);
  9994. }
  9995. /**
  9996. * Asynchronously sets a source using middleware by recursing through any
  9997. * matching middlewares and calling `setSource` on each, passing along the
  9998. * previous returned value each time.
  9999. *
  10000. * @param {Player} player
  10001. * A {@link Player} instance.
  10002. *
  10003. * @param {Tech~SourceObject} src
  10004. * A source object.
  10005. *
  10006. * @param {Function}
  10007. * The next middleware to run.
  10008. */
  10009. function setSource(player, src, next) {
  10010. player.setTimeout(function () {
  10011. return setSourceHelper(src, middlewares[src.type], next, player);
  10012. }, 1);
  10013. }
  10014. /**
  10015. * When the tech is set, passes the tech to each middleware's `setTech` method.
  10016. *
  10017. * @param {Object[]} middleware
  10018. * An array of middleware instances.
  10019. *
  10020. * @param {Tech} tech
  10021. * A Video.js tech.
  10022. */
  10023. function setTech(middleware, tech) {
  10024. middleware.forEach(function (mw) {
  10025. return mw.setTech && mw.setTech(tech);
  10026. });
  10027. }
  10028. /**
  10029. * Calls a getter on the tech first, through each middleware
  10030. * from right to left to the player.
  10031. *
  10032. * @param {Object[]} middleware
  10033. * An array of middleware instances.
  10034. *
  10035. * @param {Tech} tech
  10036. * The current tech.
  10037. *
  10038. * @param {string} method
  10039. * A method name.
  10040. *
  10041. * @return {Mixed}
  10042. * The final value from the tech after middleware has intercepted it.
  10043. */
  10044. function get(middleware, tech, method) {
  10045. return middleware.reduceRight(middlewareIterator(method), tech[method]());
  10046. }
  10047. /**
  10048. * Takes the argument given to the player and calls the setter method on each
  10049. * middleware from left to right to the tech.
  10050. *
  10051. * @param {Object[]} middleware
  10052. * An array of middleware instances.
  10053. *
  10054. * @param {Tech} tech
  10055. * The current tech.
  10056. *
  10057. * @param {string} method
  10058. * A method name.
  10059. *
  10060. * @param {Mixed} arg
  10061. * The value to set on the tech.
  10062. *
  10063. * @return {Mixed}
  10064. * The return value of the `method` of the `tech`.
  10065. */
  10066. function set$1(middleware, tech, method, arg) {
  10067. return tech[method](middleware.reduce(middlewareIterator(method), arg));
  10068. }
  10069. /**
  10070. * Takes the argument given to the player and calls the `call` version of the
  10071. * method on each middleware from left to right.
  10072. *
  10073. * Then, call the passed in method on the tech and return the result unchanged
  10074. * back to the player, through middleware, this time from right to left.
  10075. *
  10076. * @param {Object[]} middleware
  10077. * An array of middleware instances.
  10078. *
  10079. * @param {Tech} tech
  10080. * The current tech.
  10081. *
  10082. * @param {string} method
  10083. * A method name.
  10084. *
  10085. * @param {Mixed} arg
  10086. * The value to set on the tech.
  10087. *
  10088. * @return {Mixed}
  10089. * The return value of the `method` of the `tech`, regardless of the
  10090. * return values of middlewares.
  10091. */
  10092. function mediate(middleware, tech, method, arg) {
  10093. if (arg === void 0) {
  10094. arg = null;
  10095. }
  10096. var callMethod = 'call' + toTitleCase(method);
  10097. var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
  10098. var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to
  10099. // prevent confusion if a techs method actually returns null.
  10100. var returnValue = terminated ? null : tech[method](middlewareValue);
  10101. executeRight(middleware, method, returnValue, terminated);
  10102. return returnValue;
  10103. }
  10104. /**
  10105. * Enumeration of allowed getters where the keys are method names.
  10106. *
  10107. * @type {Object}
  10108. */
  10109. var allowedGetters = {
  10110. buffered: 1,
  10111. currentTime: 1,
  10112. duration: 1,
  10113. seekable: 1,
  10114. played: 1,
  10115. paused: 1
  10116. };
  10117. /**
  10118. * Enumeration of allowed setters where the keys are method names.
  10119. *
  10120. * @type {Object}
  10121. */
  10122. var allowedSetters = {
  10123. setCurrentTime: 1
  10124. };
  10125. /**
  10126. * Enumeration of allowed mediators where the keys are method names.
  10127. *
  10128. * @type {Object}
  10129. */
  10130. var allowedMediators = {
  10131. play: 1,
  10132. pause: 1
  10133. };
  10134. function middlewareIterator(method) {
  10135. return function (value, mw) {
  10136. // if the previous middleware terminated, pass along the termination
  10137. if (value === TERMINATOR) {
  10138. return TERMINATOR;
  10139. }
  10140. if (mw[method]) {
  10141. return mw[method](value);
  10142. }
  10143. return value;
  10144. };
  10145. }
  10146. function executeRight(mws, method, value, terminated) {
  10147. for (var i = mws.length - 1; i >= 0; i--) {
  10148. var mw = mws[i];
  10149. if (mw[method]) {
  10150. mw[method](terminated, value);
  10151. }
  10152. }
  10153. }
  10154. /**
  10155. * Clear the middleware cache for a player.
  10156. *
  10157. * @param {Player} player
  10158. * A {@link Player} instance.
  10159. */
  10160. function clearCacheForPlayer(player) {
  10161. middlewareInstances[player.id()] = null;
  10162. }
  10163. /**
  10164. * {
  10165. * [playerId]: [[mwFactory, mwInstance], ...]
  10166. * }
  10167. *
  10168. * @private
  10169. */
  10170. function getOrCreateFactory(player, mwFactory) {
  10171. var mws = middlewareInstances[player.id()];
  10172. var mw = null;
  10173. if (mws === undefined || mws === null) {
  10174. mw = mwFactory(player);
  10175. middlewareInstances[player.id()] = [[mwFactory, mw]];
  10176. return mw;
  10177. }
  10178. for (var i = 0; i < mws.length; i++) {
  10179. var _mws$i = mws[i],
  10180. mwf = _mws$i[0],
  10181. mwi = _mws$i[1];
  10182. if (mwf !== mwFactory) {
  10183. continue;
  10184. }
  10185. mw = mwi;
  10186. }
  10187. if (mw === null) {
  10188. mw = mwFactory(player);
  10189. mws.push([mwFactory, mw]);
  10190. }
  10191. return mw;
  10192. }
  10193. function setSourceHelper(src, middleware, next, player, acc, lastRun) {
  10194. if (src === void 0) {
  10195. src = {};
  10196. }
  10197. if (middleware === void 0) {
  10198. middleware = [];
  10199. }
  10200. if (acc === void 0) {
  10201. acc = [];
  10202. }
  10203. if (lastRun === void 0) {
  10204. lastRun = false;
  10205. }
  10206. var _middleware = middleware,
  10207. mwFactory = _middleware[0],
  10208. mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road
  10209. if (typeof mwFactory === 'string') {
  10210. setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw,
  10211. // then call the mw's setSource method
  10212. } else if (mwFactory) {
  10213. var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware
  10214. if (!mw.setSource) {
  10215. acc.push(mw);
  10216. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  10217. }
  10218. mw.setSource(assign({}, src), function (err, _src) {
  10219. // something happened, try the next middleware on the current level
  10220. // make sure to use the old src
  10221. if (err) {
  10222. return setSourceHelper(src, mwrest, next, player, acc, lastRun);
  10223. } // we've succeeded, now we need to go deeper
  10224. acc.push(mw); // if it's the same type, continue down the current chain
  10225. // otherwise, we want to go down the new chain
  10226. setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
  10227. });
  10228. } else if (mwrest.length) {
  10229. setSourceHelper(src, mwrest, next, player, acc, lastRun);
  10230. } else if (lastRun) {
  10231. next(src, acc);
  10232. } else {
  10233. setSourceHelper(src, middlewares['*'], next, player, acc, true);
  10234. }
  10235. }
  10236. /**
  10237. * Mimetypes
  10238. *
  10239. * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
  10240. * @typedef Mimetypes~Kind
  10241. * @enum
  10242. */
  10243. var MimetypesKind = {
  10244. opus: 'video/ogg',
  10245. ogv: 'video/ogg',
  10246. mp4: 'video/mp4',
  10247. mov: 'video/mp4',
  10248. m4v: 'video/mp4',
  10249. mkv: 'video/x-matroska',
  10250. mp3: 'audio/mpeg',
  10251. aac: 'audio/aac',
  10252. oga: 'audio/ogg',
  10253. m3u8: 'application/x-mpegURL',
  10254. jpg: 'image/jpeg',
  10255. jpeg: 'image/jpeg',
  10256. gif: 'image/gif',
  10257. png: 'image/png',
  10258. svg: 'image/svg+xml',
  10259. webp: 'image/webp'
  10260. };
  10261. /**
  10262. * Get the mimetype of a given src url if possible
  10263. *
  10264. * @param {string} src
  10265. * The url to the src
  10266. *
  10267. * @return {string}
  10268. * return the mimetype if it was known or empty string otherwise
  10269. */
  10270. var getMimetype = function getMimetype(src) {
  10271. if (src === void 0) {
  10272. src = '';
  10273. }
  10274. var ext = getFileExtension(src);
  10275. var mimetype = MimetypesKind[ext.toLowerCase()];
  10276. return mimetype || '';
  10277. };
  10278. /**
  10279. * Find the mime type of a given source string if possible. Uses the player
  10280. * source cache.
  10281. *
  10282. * @param {Player} player
  10283. * The player object
  10284. *
  10285. * @param {string} src
  10286. * The source string
  10287. *
  10288. * @return {string}
  10289. * The type that was found
  10290. */
  10291. var findMimetype = function findMimetype(player, src) {
  10292. if (!src) {
  10293. return '';
  10294. } // 1. check for the type in the `source` cache
  10295. if (player.cache_.source.src === src && player.cache_.source.type) {
  10296. return player.cache_.source.type;
  10297. } // 2. see if we have this source in our `currentSources` cache
  10298. var matchingSources = player.cache_.sources.filter(function (s) {
  10299. return s.src === src;
  10300. });
  10301. if (matchingSources.length) {
  10302. return matchingSources[0].type;
  10303. } // 3. look for the src url in source elements and use the type there
  10304. var sources = player.$$('source');
  10305. for (var i = 0; i < sources.length; i++) {
  10306. var s = sources[i];
  10307. if (s.type && s.src && s.src === src) {
  10308. return s.type;
  10309. }
  10310. } // 4. finally fallback to our list of mime types based on src url extension
  10311. return getMimetype(src);
  10312. };
  10313. /**
  10314. * @module filter-source
  10315. */
  10316. /**
  10317. * Filter out single bad source objects or multiple source objects in an
  10318. * array. Also flattens nested source object arrays into a 1 dimensional
  10319. * array of source objects.
  10320. *
  10321. * @param {Tech~SourceObject|Tech~SourceObject[]} src
  10322. * The src object to filter
  10323. *
  10324. * @return {Tech~SourceObject[]}
  10325. * An array of sourceobjects containing only valid sources
  10326. *
  10327. * @private
  10328. */
  10329. var filterSource = function filterSource(src) {
  10330. // traverse array
  10331. if (Array.isArray(src)) {
  10332. var newsrc = [];
  10333. src.forEach(function (srcobj) {
  10334. srcobj = filterSource(srcobj);
  10335. if (Array.isArray(srcobj)) {
  10336. newsrc = newsrc.concat(srcobj);
  10337. } else if (isObject(srcobj)) {
  10338. newsrc.push(srcobj);
  10339. }
  10340. });
  10341. src = newsrc;
  10342. } else if (typeof src === 'string' && src.trim()) {
  10343. // convert string into object
  10344. src = [fixSource({
  10345. src: src
  10346. })];
  10347. } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
  10348. // src is already valid
  10349. src = [fixSource(src)];
  10350. } else {
  10351. // invalid source, turn it into an empty array
  10352. src = [];
  10353. }
  10354. return src;
  10355. };
  10356. /**
  10357. * Checks src mimetype, adding it when possible
  10358. *
  10359. * @param {Tech~SourceObject} src
  10360. * The src object to check
  10361. * @return {Tech~SourceObject}
  10362. * src Object with known type
  10363. */
  10364. function fixSource(src) {
  10365. var mimetype = getMimetype(src.src);
  10366. if (!src.type && mimetype) {
  10367. src.type = mimetype;
  10368. }
  10369. return src;
  10370. }
  10371. /**
  10372. * The `MediaLoader` is the `Component` that decides which playback technology to load
  10373. * when a player is initialized.
  10374. *
  10375. * @extends Component
  10376. */
  10377. var MediaLoader =
  10378. /*#__PURE__*/
  10379. function (_Component) {
  10380. _inheritsLoose(MediaLoader, _Component);
  10381. /**
  10382. * Create an instance of this class.
  10383. *
  10384. * @param {Player} player
  10385. * The `Player` that this class should attach to.
  10386. *
  10387. * @param {Object} [options]
  10388. * The key/value store of player options.
  10389. *
  10390. * @param {Component~ReadyCallback} [ready]
  10391. * The function that is run when this component is ready.
  10392. */
  10393. function MediaLoader(player, options, ready) {
  10394. var _this;
  10395. // MediaLoader has no element
  10396. var options_ = mergeOptions({
  10397. createEl: false
  10398. }, options);
  10399. _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized,
  10400. // load the first supported playback technology.
  10401. if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
  10402. for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
  10403. var techName = toTitleCase(j[i]);
  10404. var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
  10405. // Remove once that deprecated behavior is removed.
  10406. if (!techName) {
  10407. tech = Component.getComponent(techName);
  10408. } // Check if the browser supports this technology
  10409. if (tech && tech.isSupported()) {
  10410. player.loadTech_(techName);
  10411. break;
  10412. }
  10413. }
  10414. } else {
  10415. // Loop through playback technologies (HTML5, Flash) and check for support.
  10416. // Then load the best source.
  10417. // A few assumptions here:
  10418. // All playback technologies respect preload false.
  10419. player.src(options.playerOptions.sources);
  10420. }
  10421. return _this;
  10422. }
  10423. return MediaLoader;
  10424. }(Component);
  10425. Component.registerComponent('MediaLoader', MediaLoader);
  10426. /**
  10427. * Clickable Component which is clickable or keyboard actionable,
  10428. * but is not a native HTML button.
  10429. *
  10430. * @extends Component
  10431. */
  10432. var ClickableComponent =
  10433. /*#__PURE__*/
  10434. function (_Component) {
  10435. _inheritsLoose(ClickableComponent, _Component);
  10436. /**
  10437. * Creates an instance of this class.
  10438. *
  10439. * @param {Player} player
  10440. * The `Player` that this class should be attached to.
  10441. *
  10442. * @param {Object} [options]
  10443. * The key/value store of player options.
  10444. */
  10445. function ClickableComponent(player, options) {
  10446. var _this;
  10447. _this = _Component.call(this, player, options) || this;
  10448. _this.emitTapEvents();
  10449. _this.enable();
  10450. return _this;
  10451. }
  10452. /**
  10453. * Create the `Component`s DOM element.
  10454. *
  10455. * @param {string} [tag=div]
  10456. * The element's node type.
  10457. *
  10458. * @param {Object} [props={}]
  10459. * An object of properties that should be set on the element.
  10460. *
  10461. * @param {Object} [attributes={}]
  10462. * An object of attributes that should be set on the element.
  10463. *
  10464. * @return {Element}
  10465. * The element that gets created.
  10466. */
  10467. var _proto = ClickableComponent.prototype;
  10468. _proto.createEl = function createEl$$1(tag, props, attributes) {
  10469. if (tag === void 0) {
  10470. tag = 'div';
  10471. }
  10472. if (props === void 0) {
  10473. props = {};
  10474. }
  10475. if (attributes === void 0) {
  10476. attributes = {};
  10477. }
  10478. props = assign({
  10479. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  10480. className: this.buildCSSClass(),
  10481. tabIndex: 0
  10482. }, props);
  10483. if (tag === 'button') {
  10484. log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead.");
  10485. } // Add ARIA attributes for clickable element which is not a native HTML button
  10486. attributes = assign({
  10487. role: 'button'
  10488. }, attributes);
  10489. this.tabIndex_ = props.tabIndex;
  10490. var el = _Component.prototype.createEl.call(this, tag, props, attributes);
  10491. this.createControlTextEl(el);
  10492. return el;
  10493. };
  10494. _proto.dispose = function dispose() {
  10495. // remove controlTextEl_ on dispose
  10496. this.controlTextEl_ = null;
  10497. _Component.prototype.dispose.call(this);
  10498. }
  10499. /**
  10500. * Create a control text element on this `Component`
  10501. *
  10502. * @param {Element} [el]
  10503. * Parent element for the control text.
  10504. *
  10505. * @return {Element}
  10506. * The control text element that gets created.
  10507. */
  10508. ;
  10509. _proto.createControlTextEl = function createControlTextEl(el) {
  10510. this.controlTextEl_ = createEl('span', {
  10511. className: 'vjs-control-text'
  10512. }, {
  10513. // let the screen reader user know that the text of the element may change
  10514. 'aria-live': 'polite'
  10515. });
  10516. if (el) {
  10517. el.appendChild(this.controlTextEl_);
  10518. }
  10519. this.controlText(this.controlText_, el);
  10520. return this.controlTextEl_;
  10521. }
  10522. /**
  10523. * Get or set the localize text to use for the controls on the `Component`.
  10524. *
  10525. * @param {string} [text]
  10526. * Control text for element.
  10527. *
  10528. * @param {Element} [el=this.el()]
  10529. * Element to set the title on.
  10530. *
  10531. * @return {string}
  10532. * - The control text when getting
  10533. */
  10534. ;
  10535. _proto.controlText = function controlText(text, el) {
  10536. if (el === void 0) {
  10537. el = this.el();
  10538. }
  10539. if (text === undefined) {
  10540. return this.controlText_ || 'Need Text';
  10541. }
  10542. var localizedText = this.localize(text);
  10543. this.controlText_ = text;
  10544. textContent(this.controlTextEl_, localizedText);
  10545. if (!this.nonIconControl) {
  10546. // Set title attribute if only an icon is shown
  10547. el.setAttribute('title', localizedText);
  10548. }
  10549. }
  10550. /**
  10551. * Builds the default DOM `className`.
  10552. *
  10553. * @return {string}
  10554. * The DOM `className` for this object.
  10555. */
  10556. ;
  10557. _proto.buildCSSClass = function buildCSSClass() {
  10558. return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this);
  10559. }
  10560. /**
  10561. * Enable this `Component`s element.
  10562. */
  10563. ;
  10564. _proto.enable = function enable() {
  10565. if (!this.enabled_) {
  10566. this.enabled_ = true;
  10567. this.removeClass('vjs-disabled');
  10568. this.el_.setAttribute('aria-disabled', 'false');
  10569. if (typeof this.tabIndex_ !== 'undefined') {
  10570. this.el_.setAttribute('tabIndex', this.tabIndex_);
  10571. }
  10572. this.on(['tap', 'click'], this.handleClick);
  10573. this.on('focus', this.handleFocus);
  10574. this.on('blur', this.handleBlur);
  10575. }
  10576. }
  10577. /**
  10578. * Disable this `Component`s element.
  10579. */
  10580. ;
  10581. _proto.disable = function disable() {
  10582. this.enabled_ = false;
  10583. this.addClass('vjs-disabled');
  10584. this.el_.setAttribute('aria-disabled', 'true');
  10585. if (typeof this.tabIndex_ !== 'undefined') {
  10586. this.el_.removeAttribute('tabIndex');
  10587. }
  10588. this.off(['tap', 'click'], this.handleClick);
  10589. this.off('focus', this.handleFocus);
  10590. this.off('blur', this.handleBlur);
  10591. }
  10592. /**
  10593. * This gets called when a `ClickableComponent` gets:
  10594. * - Clicked (via the `click` event, listening starts in the constructor)
  10595. * - Tapped (via the `tap` event, listening starts in the constructor)
  10596. * - The following things happen in order:
  10597. * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the
  10598. * `ClickableComponent`.
  10599. * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using
  10600. * {@link ClickableComponent#handleKeyPress}.
  10601. * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses
  10602. * the space or enter key.
  10603. * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown`
  10604. * event as a parameter.
  10605. *
  10606. * @param {EventTarget~Event} event
  10607. * The `keydown`, `tap`, or `click` event that caused this function to be
  10608. * called.
  10609. *
  10610. * @listens tap
  10611. * @listens click
  10612. * @abstract
  10613. */
  10614. ;
  10615. _proto.handleClick = function handleClick(event) {}
  10616. /**
  10617. * This gets called when a `ClickableComponent` gains focus via a `focus` event.
  10618. * Turns on listening for `keydown` events. When they happen it
  10619. * calls `this.handleKeyPress`.
  10620. *
  10621. * @param {EventTarget~Event} event
  10622. * The `focus` event that caused this function to be called.
  10623. *
  10624. * @listens focus
  10625. */
  10626. ;
  10627. _proto.handleFocus = function handleFocus(event) {
  10628. on(document, 'keydown', bind(this, this.handleKeyPress));
  10629. }
  10630. /**
  10631. * Called when this ClickableComponent has focus and a key gets pressed down. By
  10632. * default it will call `this.handleClick` when the key is space or enter.
  10633. *
  10634. * @param {EventTarget~Event} event
  10635. * The `keydown` event that caused this function to be called.
  10636. *
  10637. * @listens keydown
  10638. */
  10639. ;
  10640. _proto.handleKeyPress = function handleKeyPress(event) {
  10641. // Support Space or Enter key operation to fire a click event
  10642. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  10643. event.preventDefault();
  10644. this.trigger('click');
  10645. } else {
  10646. // Pass keypress handling up for unsupported keys
  10647. _Component.prototype.handleKeyPress.call(this, event);
  10648. }
  10649. }
  10650. /**
  10651. * Called when a `ClickableComponent` loses focus. Turns off the listener for
  10652. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  10653. *
  10654. * @param {EventTarget~Event} event
  10655. * The `blur` event that caused this function to be called.
  10656. *
  10657. * @listens blur
  10658. */
  10659. ;
  10660. _proto.handleBlur = function handleBlur(event) {
  10661. off(document, 'keydown', bind(this, this.handleKeyPress));
  10662. };
  10663. return ClickableComponent;
  10664. }(Component);
  10665. Component.registerComponent('ClickableComponent', ClickableComponent);
  10666. /**
  10667. * A `ClickableComponent` that handles showing the poster image for the player.
  10668. *
  10669. * @extends ClickableComponent
  10670. */
  10671. var PosterImage =
  10672. /*#__PURE__*/
  10673. function (_ClickableComponent) {
  10674. _inheritsLoose(PosterImage, _ClickableComponent);
  10675. /**
  10676. * Create an instance of this class.
  10677. *
  10678. * @param {Player} player
  10679. * The `Player` that this class should attach to.
  10680. *
  10681. * @param {Object} [options]
  10682. * The key/value store of player options.
  10683. */
  10684. function PosterImage(player, options) {
  10685. var _this;
  10686. _this = _ClickableComponent.call(this, player, options) || this;
  10687. _this.update();
  10688. player.on('posterchange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update));
  10689. return _this;
  10690. }
  10691. /**
  10692. * Clean up and dispose of the `PosterImage`.
  10693. */
  10694. var _proto = PosterImage.prototype;
  10695. _proto.dispose = function dispose() {
  10696. this.player().off('posterchange', this.update);
  10697. _ClickableComponent.prototype.dispose.call(this);
  10698. }
  10699. /**
  10700. * Create the `PosterImage`s DOM element.
  10701. *
  10702. * @return {Element}
  10703. * The element that gets created.
  10704. */
  10705. ;
  10706. _proto.createEl = function createEl$$1() {
  10707. var el = createEl('div', {
  10708. className: 'vjs-poster',
  10709. // Don't want poster to be tabbable.
  10710. tabIndex: -1
  10711. });
  10712. return el;
  10713. }
  10714. /**
  10715. * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
  10716. *
  10717. * @listens Player#posterchange
  10718. *
  10719. * @param {EventTarget~Event} [event]
  10720. * The `Player#posterchange` event that triggered this function.
  10721. */
  10722. ;
  10723. _proto.update = function update(event) {
  10724. var url = this.player().poster();
  10725. this.setSrc(url); // If there's no poster source we should display:none on this component
  10726. // so it's not still clickable or right-clickable
  10727. if (url) {
  10728. this.show();
  10729. } else {
  10730. this.hide();
  10731. }
  10732. }
  10733. /**
  10734. * Set the source of the `PosterImage` depending on the display method.
  10735. *
  10736. * @param {string} url
  10737. * The URL to the source for the `PosterImage`.
  10738. */
  10739. ;
  10740. _proto.setSrc = function setSrc(url) {
  10741. var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise
  10742. // this will throw an extra error
  10743. if (url) {
  10744. backgroundImage = "url(\"" + url + "\")";
  10745. }
  10746. this.el_.style.backgroundImage = backgroundImage;
  10747. }
  10748. /**
  10749. * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
  10750. * {@link ClickableComponent#handleClick} for instances where this will be triggered.
  10751. *
  10752. * @listens tap
  10753. * @listens click
  10754. * @listens keydown
  10755. *
  10756. * @param {EventTarget~Event} event
  10757. + The `click`, `tap` or `keydown` event that caused this function to be called.
  10758. */
  10759. ;
  10760. _proto.handleClick = function handleClick(event) {
  10761. // We don't want a click to trigger playback when controls are disabled
  10762. if (!this.player_.controls()) {
  10763. return;
  10764. }
  10765. if (this.player_.paused()) {
  10766. silencePromise(this.player_.play());
  10767. } else {
  10768. this.player_.pause();
  10769. } // call handleFocus manually to get hotkeys working
  10770. this.player_.handleFocus({});
  10771. };
  10772. return PosterImage;
  10773. }(ClickableComponent);
  10774. Component.registerComponent('PosterImage', PosterImage);
  10775. var darkGray = '#222';
  10776. var lightGray = '#ccc';
  10777. var fontMap = {
  10778. monospace: 'monospace',
  10779. sansSerif: 'sans-serif',
  10780. serif: 'serif',
  10781. monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
  10782. monospaceSerif: '"Courier New", monospace',
  10783. proportionalSansSerif: 'sans-serif',
  10784. proportionalSerif: 'serif',
  10785. casual: '"Comic Sans MS", Impact, fantasy',
  10786. script: '"Monotype Corsiva", cursive',
  10787. smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
  10788. };
  10789. /**
  10790. * Construct an rgba color from a given hex color code.
  10791. *
  10792. * @param {number} color
  10793. * Hex number for color, like #f0e or #f604e2.
  10794. *
  10795. * @param {number} opacity
  10796. * Value for opacity, 0.0 - 1.0.
  10797. *
  10798. * @return {string}
  10799. * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
  10800. */
  10801. function constructColor(color, opacity) {
  10802. var hex;
  10803. if (color.length === 4) {
  10804. // color looks like "#f0e"
  10805. hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
  10806. } else if (color.length === 7) {
  10807. // color looks like "#f604e2"
  10808. hex = color.slice(1);
  10809. } else {
  10810. throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
  10811. }
  10812. return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
  10813. }
  10814. /**
  10815. * Try to update the style of a DOM element. Some style changes will throw an error,
  10816. * particularly in IE8. Those should be noops.
  10817. *
  10818. * @param {Element} el
  10819. * The DOM element to be styled.
  10820. *
  10821. * @param {string} style
  10822. * The CSS property on the element that should be styled.
  10823. *
  10824. * @param {string} rule
  10825. * The style rule that should be applied to the property.
  10826. *
  10827. * @private
  10828. */
  10829. function tryUpdateStyle(el, style, rule) {
  10830. try {
  10831. el.style[style] = rule;
  10832. } catch (e) {
  10833. // Satisfies linter.
  10834. return;
  10835. }
  10836. }
  10837. /**
  10838. * The component for displaying text track cues.
  10839. *
  10840. * @extends Component
  10841. */
  10842. var TextTrackDisplay =
  10843. /*#__PURE__*/
  10844. function (_Component) {
  10845. _inheritsLoose(TextTrackDisplay, _Component);
  10846. /**
  10847. * Creates an instance of this class.
  10848. *
  10849. * @param {Player} player
  10850. * The `Player` that this class should be attached to.
  10851. *
  10852. * @param {Object} [options]
  10853. * The key/value store of player options.
  10854. *
  10855. * @param {Component~ReadyCallback} [ready]
  10856. * The function to call when `TextTrackDisplay` is ready.
  10857. */
  10858. function TextTrackDisplay(player, options, ready) {
  10859. var _this;
  10860. _this = _Component.call(this, player, options, ready) || this;
  10861. var updateDisplayHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay);
  10862. player.on('loadstart', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.toggleDisplay));
  10863. player.on('texttrackchange', updateDisplayHandler);
  10864. player.on('loadedmetadata', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.preselectTrack)); // This used to be called during player init, but was causing an error
  10865. // if a track should show by default and the display hadn't loaded yet.
  10866. // Should probably be moved to an external track loader when we support
  10867. // tracks that don't need a display.
  10868. player.ready(bind(_assertThisInitialized(_assertThisInitialized(_this)), function () {
  10869. if (player.tech_ && player.tech_.featuresNativeTextTracks) {
  10870. this.hide();
  10871. return;
  10872. }
  10873. player.on('fullscreenchange', updateDisplayHandler);
  10874. player.on('playerresize', updateDisplayHandler);
  10875. window$1.addEventListener('orientationchange', updateDisplayHandler);
  10876. player.on('dispose', function () {
  10877. return window$1.removeEventListener('orientationchange', updateDisplayHandler);
  10878. });
  10879. var tracks = this.options_.playerOptions.tracks || [];
  10880. for (var i = 0; i < tracks.length; i++) {
  10881. this.player_.addRemoteTextTrack(tracks[i], true);
  10882. }
  10883. this.preselectTrack();
  10884. }));
  10885. return _this;
  10886. }
  10887. /**
  10888. * Preselect a track following this precedence:
  10889. * - matches the previously selected {@link TextTrack}'s language and kind
  10890. * - matches the previously selected {@link TextTrack}'s language only
  10891. * - is the first default captions track
  10892. * - is the first default descriptions track
  10893. *
  10894. * @listens Player#loadstart
  10895. */
  10896. var _proto = TextTrackDisplay.prototype;
  10897. _proto.preselectTrack = function preselectTrack() {
  10898. var modes = {
  10899. captions: 1,
  10900. subtitles: 1
  10901. };
  10902. var trackList = this.player_.textTracks();
  10903. var userPref = this.player_.cache_.selectedLanguage;
  10904. var firstDesc;
  10905. var firstCaptions;
  10906. var preferredTrack;
  10907. for (var i = 0; i < trackList.length; i++) {
  10908. var track = trackList[i];
  10909. if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
  10910. // Always choose the track that matches both language and kind
  10911. if (track.kind === userPref.kind) {
  10912. preferredTrack = track; // or choose the first track that matches language
  10913. } else if (!preferredTrack) {
  10914. preferredTrack = track;
  10915. } // clear everything if offTextTrackMenuItem was clicked
  10916. } else if (userPref && !userPref.enabled) {
  10917. preferredTrack = null;
  10918. firstDesc = null;
  10919. firstCaptions = null;
  10920. } else if (track.default) {
  10921. if (track.kind === 'descriptions' && !firstDesc) {
  10922. firstDesc = track;
  10923. } else if (track.kind in modes && !firstCaptions) {
  10924. firstCaptions = track;
  10925. }
  10926. }
  10927. } // The preferredTrack matches the user preference and takes
  10928. // precedence over all the other tracks.
  10929. // So, display the preferredTrack before the first default track
  10930. // and the subtitles/captions track before the descriptions track
  10931. if (preferredTrack) {
  10932. preferredTrack.mode = 'showing';
  10933. } else if (firstCaptions) {
  10934. firstCaptions.mode = 'showing';
  10935. } else if (firstDesc) {
  10936. firstDesc.mode = 'showing';
  10937. }
  10938. }
  10939. /**
  10940. * Turn display of {@link TextTrack}'s from the current state into the other state.
  10941. * There are only two states:
  10942. * - 'shown'
  10943. * - 'hidden'
  10944. *
  10945. * @listens Player#loadstart
  10946. */
  10947. ;
  10948. _proto.toggleDisplay = function toggleDisplay() {
  10949. if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
  10950. this.hide();
  10951. } else {
  10952. this.show();
  10953. }
  10954. }
  10955. /**
  10956. * Create the {@link Component}'s DOM element.
  10957. *
  10958. * @return {Element}
  10959. * The element that was created.
  10960. */
  10961. ;
  10962. _proto.createEl = function createEl() {
  10963. return _Component.prototype.createEl.call(this, 'div', {
  10964. className: 'vjs-text-track-display'
  10965. }, {
  10966. 'aria-live': 'off',
  10967. 'aria-atomic': 'true'
  10968. });
  10969. }
  10970. /**
  10971. * Clear all displayed {@link TextTrack}s.
  10972. */
  10973. ;
  10974. _proto.clearDisplay = function clearDisplay() {
  10975. if (typeof window$1.WebVTT === 'function') {
  10976. window$1.WebVTT.processCues(window$1, [], this.el_);
  10977. }
  10978. }
  10979. /**
  10980. * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
  10981. * a {@link Player#fullscreenchange} is fired.
  10982. *
  10983. * @listens Player#texttrackchange
  10984. * @listens Player#fullscreenchange
  10985. */
  10986. ;
  10987. _proto.updateDisplay = function updateDisplay() {
  10988. var tracks = this.player_.textTracks();
  10989. this.clearDisplay(); // Track display prioritization model: if multiple tracks are 'showing',
  10990. // display the first 'subtitles' or 'captions' track which is 'showing',
  10991. // otherwise display the first 'descriptions' track which is 'showing'
  10992. var descriptionsTrack = null;
  10993. var captionsSubtitlesTrack = null;
  10994. var i = tracks.length;
  10995. while (i--) {
  10996. var track = tracks[i];
  10997. if (track.mode === 'showing') {
  10998. if (track.kind === 'descriptions') {
  10999. descriptionsTrack = track;
  11000. } else {
  11001. captionsSubtitlesTrack = track;
  11002. }
  11003. }
  11004. }
  11005. if (captionsSubtitlesTrack) {
  11006. if (this.getAttribute('aria-live') !== 'off') {
  11007. this.setAttribute('aria-live', 'off');
  11008. }
  11009. this.updateForTrack(captionsSubtitlesTrack);
  11010. } else if (descriptionsTrack) {
  11011. if (this.getAttribute('aria-live') !== 'assertive') {
  11012. this.setAttribute('aria-live', 'assertive');
  11013. }
  11014. this.updateForTrack(descriptionsTrack);
  11015. }
  11016. }
  11017. /**
  11018. * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
  11019. *
  11020. * @param {TextTrack} track
  11021. * Text track object to be added to the list.
  11022. */
  11023. ;
  11024. _proto.updateForTrack = function updateForTrack(track) {
  11025. if (typeof window$1.WebVTT !== 'function' || !track.activeCues) {
  11026. return;
  11027. }
  11028. var cues = [];
  11029. for (var _i = 0; _i < track.activeCues.length; _i++) {
  11030. cues.push(track.activeCues[_i]);
  11031. }
  11032. window$1.WebVTT.processCues(window$1, cues, this.el_);
  11033. if (!this.player_.textTrackSettings) {
  11034. return;
  11035. }
  11036. var overrides = this.player_.textTrackSettings.getValues();
  11037. var i = cues.length;
  11038. while (i--) {
  11039. var cue = cues[i];
  11040. if (!cue) {
  11041. continue;
  11042. }
  11043. var cueDiv = cue.displayState;
  11044. if (overrides.color) {
  11045. cueDiv.firstChild.style.color = overrides.color;
  11046. }
  11047. if (overrides.textOpacity) {
  11048. tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
  11049. }
  11050. if (overrides.backgroundColor) {
  11051. cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
  11052. }
  11053. if (overrides.backgroundOpacity) {
  11054. tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
  11055. }
  11056. if (overrides.windowColor) {
  11057. if (overrides.windowOpacity) {
  11058. tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
  11059. } else {
  11060. cueDiv.style.backgroundColor = overrides.windowColor;
  11061. }
  11062. }
  11063. if (overrides.edgeStyle) {
  11064. if (overrides.edgeStyle === 'dropshadow') {
  11065. cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray;
  11066. } else if (overrides.edgeStyle === 'raised') {
  11067. cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray;
  11068. } else if (overrides.edgeStyle === 'depressed') {
  11069. cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray;
  11070. } else if (overrides.edgeStyle === 'uniform') {
  11071. cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray;
  11072. }
  11073. }
  11074. if (overrides.fontPercent && overrides.fontPercent !== 1) {
  11075. var fontSize = window$1.parseFloat(cueDiv.style.fontSize);
  11076. cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
  11077. cueDiv.style.height = 'auto';
  11078. cueDiv.style.top = 'auto';
  11079. cueDiv.style.bottom = '2px';
  11080. }
  11081. if (overrides.fontFamily && overrides.fontFamily !== 'default') {
  11082. if (overrides.fontFamily === 'small-caps') {
  11083. cueDiv.firstChild.style.fontVariant = 'small-caps';
  11084. } else {
  11085. cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
  11086. }
  11087. }
  11088. }
  11089. };
  11090. return TextTrackDisplay;
  11091. }(Component);
  11092. Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
  11093. /**
  11094. * A loading spinner for use during waiting/loading events.
  11095. *
  11096. * @extends Component
  11097. */
  11098. var LoadingSpinner =
  11099. /*#__PURE__*/
  11100. function (_Component) {
  11101. _inheritsLoose(LoadingSpinner, _Component);
  11102. function LoadingSpinner() {
  11103. return _Component.apply(this, arguments) || this;
  11104. }
  11105. var _proto = LoadingSpinner.prototype;
  11106. /**
  11107. * Create the `LoadingSpinner`s DOM element.
  11108. *
  11109. * @return {Element}
  11110. * The dom element that gets created.
  11111. */
  11112. _proto.createEl = function createEl$$1() {
  11113. var isAudio = this.player_.isAudio();
  11114. var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
  11115. var controlText = createEl('span', {
  11116. className: 'vjs-control-text',
  11117. innerHTML: this.localize('{1} is loading.', [playerType])
  11118. });
  11119. var el = _Component.prototype.createEl.call(this, 'div', {
  11120. className: 'vjs-loading-spinner',
  11121. dir: 'ltr'
  11122. });
  11123. el.appendChild(controlText);
  11124. return el;
  11125. };
  11126. return LoadingSpinner;
  11127. }(Component);
  11128. Component.registerComponent('LoadingSpinner', LoadingSpinner);
  11129. /**
  11130. * Base class for all buttons.
  11131. *
  11132. * @extends ClickableComponent
  11133. */
  11134. var Button =
  11135. /*#__PURE__*/
  11136. function (_ClickableComponent) {
  11137. _inheritsLoose(Button, _ClickableComponent);
  11138. function Button() {
  11139. return _ClickableComponent.apply(this, arguments) || this;
  11140. }
  11141. var _proto = Button.prototype;
  11142. /**
  11143. * Create the `Button`s DOM element.
  11144. *
  11145. * @param {string} [tag="button"]
  11146. * The element's node type. This argument is IGNORED: no matter what
  11147. * is passed, it will always create a `button` element.
  11148. *
  11149. * @param {Object} [props={}]
  11150. * An object of properties that should be set on the element.
  11151. *
  11152. * @param {Object} [attributes={}]
  11153. * An object of attributes that should be set on the element.
  11154. *
  11155. * @return {Element}
  11156. * The element that gets created.
  11157. */
  11158. _proto.createEl = function createEl(tag, props, attributes) {
  11159. if (props === void 0) {
  11160. props = {};
  11161. }
  11162. if (attributes === void 0) {
  11163. attributes = {};
  11164. }
  11165. tag = 'button';
  11166. props = assign({
  11167. innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
  11168. className: this.buildCSSClass()
  11169. }, props); // Add attributes for button element
  11170. attributes = assign({
  11171. // Necessary since the default button type is "submit"
  11172. type: 'button'
  11173. }, attributes);
  11174. var el = Component.prototype.createEl.call(this, tag, props, attributes);
  11175. this.createControlTextEl(el);
  11176. return el;
  11177. }
  11178. /**
  11179. * Add a child `Component` inside of this `Button`.
  11180. *
  11181. * @param {string|Component} child
  11182. * The name or instance of a child to add.
  11183. *
  11184. * @param {Object} [options={}]
  11185. * The key/value store of options that will get passed to children of
  11186. * the child.
  11187. *
  11188. * @return {Component}
  11189. * The `Component` that gets added as a child. When using a string the
  11190. * `Component` will get created by this process.
  11191. *
  11192. * @deprecated since version 5
  11193. */
  11194. ;
  11195. _proto.addChild = function addChild(child, options) {
  11196. if (options === void 0) {
  11197. options = {};
  11198. }
  11199. var className = this.constructor.name;
  11200. 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
  11201. return Component.prototype.addChild.call(this, child, options);
  11202. }
  11203. /**
  11204. * Enable the `Button` element so that it can be activated or clicked. Use this with
  11205. * {@link Button#disable}.
  11206. */
  11207. ;
  11208. _proto.enable = function enable() {
  11209. _ClickableComponent.prototype.enable.call(this);
  11210. this.el_.removeAttribute('disabled');
  11211. }
  11212. /**
  11213. * Disable the `Button` element so that it cannot be activated or clicked. Use this with
  11214. * {@link Button#enable}.
  11215. */
  11216. ;
  11217. _proto.disable = function disable() {
  11218. _ClickableComponent.prototype.disable.call(this);
  11219. this.el_.setAttribute('disabled', 'disabled');
  11220. }
  11221. /**
  11222. * This gets called when a `Button` has focus and `keydown` is triggered via a key
  11223. * press.
  11224. *
  11225. * @param {EventTarget~Event} event
  11226. * The event that caused this function to get called.
  11227. *
  11228. * @listens keydown
  11229. */
  11230. ;
  11231. _proto.handleKeyPress = function handleKeyPress(event) {
  11232. // Ignore Space or Enter key operation, which is handled by the browser for a button.
  11233. if (!(keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter'))) {
  11234. // Pass keypress handling up for unsupported keys
  11235. _ClickableComponent.prototype.handleKeyPress.call(this, event);
  11236. }
  11237. };
  11238. return Button;
  11239. }(ClickableComponent);
  11240. Component.registerComponent('Button', Button);
  11241. /**
  11242. * The initial play button that shows before the video has played. The hiding of the
  11243. * `BigPlayButton` get done via CSS and `Player` states.
  11244. *
  11245. * @extends Button
  11246. */
  11247. var BigPlayButton =
  11248. /*#__PURE__*/
  11249. function (_Button) {
  11250. _inheritsLoose(BigPlayButton, _Button);
  11251. function BigPlayButton(player, options) {
  11252. var _this;
  11253. _this = _Button.call(this, player, options) || this;
  11254. _this.mouseused_ = false;
  11255. _this.on('mousedown', _this.handleMouseDown);
  11256. return _this;
  11257. }
  11258. /**
  11259. * Builds the default DOM `className`.
  11260. *
  11261. * @return {string}
  11262. * The DOM `className` for this object. Always returns 'vjs-big-play-button'.
  11263. */
  11264. var _proto = BigPlayButton.prototype;
  11265. _proto.buildCSSClass = function buildCSSClass() {
  11266. return 'vjs-big-play-button';
  11267. }
  11268. /**
  11269. * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
  11270. * for more detailed information on what a click can be.
  11271. *
  11272. * @param {EventTarget~Event} event
  11273. * The `keydown`, `tap`, or `click` event that caused this function to be
  11274. * called.
  11275. *
  11276. * @listens tap
  11277. * @listens click
  11278. */
  11279. ;
  11280. _proto.handleClick = function handleClick(event) {
  11281. var playPromise = this.player_.play(); // exit early if clicked via the mouse
  11282. if (this.mouseused_ && event.clientX && event.clientY) {
  11283. silencePromise(playPromise); // call handleFocus manually to get hotkeys working
  11284. this.player_.handleFocus({});
  11285. return;
  11286. }
  11287. var cb = this.player_.getChild('controlBar');
  11288. var playToggle = cb && cb.getChild('playToggle');
  11289. if (!playToggle) {
  11290. this.player_.focus();
  11291. return;
  11292. }
  11293. var playFocus = function playFocus() {
  11294. return playToggle.focus();
  11295. };
  11296. if (isPromise(playPromise)) {
  11297. playPromise.then(playFocus, function () {});
  11298. } else {
  11299. this.setTimeout(playFocus, 1);
  11300. }
  11301. };
  11302. _proto.handleKeyPress = function handleKeyPress(event) {
  11303. this.mouseused_ = false;
  11304. _Button.prototype.handleKeyPress.call(this, event);
  11305. };
  11306. _proto.handleMouseDown = function handleMouseDown(event) {
  11307. this.mouseused_ = true;
  11308. };
  11309. return BigPlayButton;
  11310. }(Button);
  11311. /**
  11312. * The text that should display over the `BigPlayButton`s controls. Added to for localization.
  11313. *
  11314. * @type {string}
  11315. * @private
  11316. */
  11317. BigPlayButton.prototype.controlText_ = 'Play Video';
  11318. Component.registerComponent('BigPlayButton', BigPlayButton);
  11319. /**
  11320. * The `CloseButton` is a `{@link Button}` that fires a `close` event when
  11321. * it gets clicked.
  11322. *
  11323. * @extends Button
  11324. */
  11325. var CloseButton =
  11326. /*#__PURE__*/
  11327. function (_Button) {
  11328. _inheritsLoose(CloseButton, _Button);
  11329. /**
  11330. * Creates an instance of the this class.
  11331. *
  11332. * @param {Player} player
  11333. * The `Player` that this class should be attached to.
  11334. *
  11335. * @param {Object} [options]
  11336. * The key/value store of player options.
  11337. */
  11338. function CloseButton(player, options) {
  11339. var _this;
  11340. _this = _Button.call(this, player, options) || this;
  11341. _this.controlText(options && options.controlText || _this.localize('Close'));
  11342. return _this;
  11343. }
  11344. /**
  11345. * Builds the default DOM `className`.
  11346. *
  11347. * @return {string}
  11348. * The DOM `className` for this object.
  11349. */
  11350. var _proto = CloseButton.prototype;
  11351. _proto.buildCSSClass = function buildCSSClass() {
  11352. return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this);
  11353. }
  11354. /**
  11355. * This gets called when a `CloseButton` has focus and `keydown` is triggered via a key
  11356. * press.
  11357. *
  11358. * @param {EventTarget~Event} event
  11359. * The event that caused this function to get called.
  11360. *
  11361. * @listens keydown
  11362. */
  11363. ;
  11364. _proto.handleKeyPress = function handleKeyPress(event) {} // Override the default `Button` behavior, and don't pass the keypress event
  11365. // up to the player because this button is part of a `ModalDialog`, which
  11366. // doesn't pass keypresses to the player either.
  11367. /**
  11368. * This gets called when a `CloseButton` gets clicked. See
  11369. * {@link ClickableComponent#handleClick} for more information on when this will be
  11370. * triggered
  11371. *
  11372. * @param {EventTarget~Event} event
  11373. * The `keydown`, `tap`, or `click` event that caused this function to be
  11374. * called.
  11375. *
  11376. * @listens tap
  11377. * @listens click
  11378. * @fires CloseButton#close
  11379. */
  11380. ;
  11381. _proto.handleClick = function handleClick(event) {
  11382. /**
  11383. * Triggered when the a `CloseButton` is clicked.
  11384. *
  11385. * @event CloseButton#close
  11386. * @type {EventTarget~Event}
  11387. *
  11388. * @property {boolean} [bubbles=false]
  11389. * set to false so that the close event does not
  11390. * bubble up to parents if there is no listener
  11391. */
  11392. this.trigger({
  11393. type: 'close',
  11394. bubbles: false
  11395. });
  11396. };
  11397. return CloseButton;
  11398. }(Button);
  11399. Component.registerComponent('CloseButton', CloseButton);
  11400. /**
  11401. * Button to toggle between play and pause.
  11402. *
  11403. * @extends Button
  11404. */
  11405. var PlayToggle =
  11406. /*#__PURE__*/
  11407. function (_Button) {
  11408. _inheritsLoose(PlayToggle, _Button);
  11409. /**
  11410. * Creates an instance of this class.
  11411. *
  11412. * @param {Player} player
  11413. * The `Player` that this class should be attached to.
  11414. *
  11415. * @param {Object} [options={}]
  11416. * The key/value store of player options.
  11417. */
  11418. function PlayToggle(player, options) {
  11419. var _this;
  11420. if (options === void 0) {
  11421. options = {};
  11422. }
  11423. _this = _Button.call(this, player, options) || this; // show or hide replay icon
  11424. options.replay = options.replay === undefined || options.replay;
  11425. _this.on(player, 'play', _this.handlePlay);
  11426. _this.on(player, 'pause', _this.handlePause);
  11427. if (options.replay) {
  11428. _this.on(player, 'ended', _this.handleEnded);
  11429. }
  11430. return _this;
  11431. }
  11432. /**
  11433. * Builds the default DOM `className`.
  11434. *
  11435. * @return {string}
  11436. * The DOM `className` for this object.
  11437. */
  11438. var _proto = PlayToggle.prototype;
  11439. _proto.buildCSSClass = function buildCSSClass() {
  11440. return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this);
  11441. }
  11442. /**
  11443. * This gets called when an `PlayToggle` is "clicked". See
  11444. * {@link ClickableComponent} for more detailed information on what a click can be.
  11445. *
  11446. * @param {EventTarget~Event} [event]
  11447. * The `keydown`, `tap`, or `click` event that caused this function to be
  11448. * called.
  11449. *
  11450. * @listens tap
  11451. * @listens click
  11452. */
  11453. ;
  11454. _proto.handleClick = function handleClick(event) {
  11455. if (this.player_.paused()) {
  11456. this.player_.play();
  11457. } else {
  11458. this.player_.pause();
  11459. }
  11460. }
  11461. /**
  11462. * This gets called once after the video has ended and the user seeks so that
  11463. * we can change the replay button back to a play button.
  11464. *
  11465. * @param {EventTarget~Event} [event]
  11466. * The event that caused this function to run.
  11467. *
  11468. * @listens Player#seeked
  11469. */
  11470. ;
  11471. _proto.handleSeeked = function handleSeeked(event) {
  11472. this.removeClass('vjs-ended');
  11473. if (this.player_.paused()) {
  11474. this.handlePause(event);
  11475. } else {
  11476. this.handlePlay(event);
  11477. }
  11478. }
  11479. /**
  11480. * Add the vjs-playing class to the element so it can change appearance.
  11481. *
  11482. * @param {EventTarget~Event} [event]
  11483. * The event that caused this function to run.
  11484. *
  11485. * @listens Player#play
  11486. */
  11487. ;
  11488. _proto.handlePlay = function handlePlay(event) {
  11489. this.removeClass('vjs-ended');
  11490. this.removeClass('vjs-paused');
  11491. this.addClass('vjs-playing'); // change the button text to "Pause"
  11492. this.controlText('Pause');
  11493. }
  11494. /**
  11495. * Add the vjs-paused class to the element so it can change appearance.
  11496. *
  11497. * @param {EventTarget~Event} [event]
  11498. * The event that caused this function to run.
  11499. *
  11500. * @listens Player#pause
  11501. */
  11502. ;
  11503. _proto.handlePause = function handlePause(event) {
  11504. this.removeClass('vjs-playing');
  11505. this.addClass('vjs-paused'); // change the button text to "Play"
  11506. this.controlText('Play');
  11507. }
  11508. /**
  11509. * Add the vjs-ended class to the element so it can change appearance
  11510. *
  11511. * @param {EventTarget~Event} [event]
  11512. * The event that caused this function to run.
  11513. *
  11514. * @listens Player#ended
  11515. */
  11516. ;
  11517. _proto.handleEnded = function handleEnded(event) {
  11518. this.removeClass('vjs-playing');
  11519. this.addClass('vjs-ended'); // change the button text to "Replay"
  11520. this.controlText('Replay'); // on the next seek remove the replay button
  11521. this.one(this.player_, 'seeked', this.handleSeeked);
  11522. };
  11523. return PlayToggle;
  11524. }(Button);
  11525. /**
  11526. * The text that should display over the `PlayToggle`s controls. Added for localization.
  11527. *
  11528. * @type {string}
  11529. * @private
  11530. */
  11531. PlayToggle.prototype.controlText_ = 'Play';
  11532. Component.registerComponent('PlayToggle', PlayToggle);
  11533. /**
  11534. * @file format-time.js
  11535. * @module format-time
  11536. */
  11537. /**
  11538. * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
  11539. * seconds) will force a number of leading zeros to cover the length of the
  11540. * guide.
  11541. *
  11542. * @private
  11543. * @param {number} seconds
  11544. * Number of seconds to be turned into a string
  11545. *
  11546. * @param {number} guide
  11547. * Number (in seconds) to model the string after
  11548. *
  11549. * @return {string}
  11550. * Time formatted as H:MM:SS or M:SS
  11551. */
  11552. var defaultImplementation = function defaultImplementation(seconds, guide) {
  11553. seconds = seconds < 0 ? 0 : seconds;
  11554. var s = Math.floor(seconds % 60);
  11555. var m = Math.floor(seconds / 60 % 60);
  11556. var h = Math.floor(seconds / 3600);
  11557. var gm = Math.floor(guide / 60 % 60);
  11558. var gh = Math.floor(guide / 3600); // handle invalid times
  11559. if (isNaN(seconds) || seconds === Infinity) {
  11560. // '-' is false for all relational operators (e.g. <, >=) so this setting
  11561. // will add the minimum number of fields specified by the guide
  11562. h = m = s = '-';
  11563. } // Check if we need to show hours
  11564. h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero.
  11565. // Always show at least one digit of minutes.
  11566. m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds
  11567. s = s < 10 ? '0' + s : s;
  11568. return h + m + s;
  11569. }; // Internal pointer to the current implementation.
  11570. var implementation = defaultImplementation;
  11571. /**
  11572. * Replaces the default formatTime implementation with a custom implementation.
  11573. *
  11574. * @param {Function} customImplementation
  11575. * A function which will be used in place of the default formatTime
  11576. * implementation. Will receive the current time in seconds and the
  11577. * guide (in seconds) as arguments.
  11578. */
  11579. function setFormatTime(customImplementation) {
  11580. implementation = customImplementation;
  11581. }
  11582. /**
  11583. * Resets formatTime to the default implementation.
  11584. */
  11585. function resetFormatTime() {
  11586. implementation = defaultImplementation;
  11587. }
  11588. /**
  11589. * Delegates to either the default time formatting function or a custom
  11590. * function supplied via `setFormatTime`.
  11591. *
  11592. * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
  11593. * guide (in seconds) will force a number of leading zeros to cover the
  11594. * length of the guide.
  11595. *
  11596. * @static
  11597. * @example formatTime(125, 600) === "02:05"
  11598. * @param {number} seconds
  11599. * Number of seconds to be turned into a string
  11600. *
  11601. * @param {number} guide
  11602. * Number (in seconds) to model the string after
  11603. *
  11604. * @return {string}
  11605. * Time formatted as H:MM:SS or M:SS
  11606. */
  11607. function formatTime(seconds, guide) {
  11608. if (guide === void 0) {
  11609. guide = seconds;
  11610. }
  11611. return implementation(seconds, guide);
  11612. }
  11613. /**
  11614. * Displays time information about the video
  11615. *
  11616. * @extends Component
  11617. */
  11618. var TimeDisplay =
  11619. /*#__PURE__*/
  11620. function (_Component) {
  11621. _inheritsLoose(TimeDisplay, _Component);
  11622. /**
  11623. * Creates an instance of this class.
  11624. *
  11625. * @param {Player} player
  11626. * The `Player` that this class should be attached to.
  11627. *
  11628. * @param {Object} [options]
  11629. * The key/value store of player options.
  11630. */
  11631. function TimeDisplay(player, options) {
  11632. var _this;
  11633. _this = _Component.call(this, player, options) || this;
  11634. _this.throttledUpdateContent = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateContent), 25);
  11635. _this.on(player, 'timeupdate', _this.throttledUpdateContent);
  11636. return _this;
  11637. }
  11638. /**
  11639. * Create the `Component`'s DOM element
  11640. *
  11641. * @return {Element}
  11642. * The element that was created.
  11643. */
  11644. var _proto = TimeDisplay.prototype;
  11645. _proto.createEl = function createEl$$1() {
  11646. var className = this.buildCSSClass();
  11647. var el = _Component.prototype.createEl.call(this, 'div', {
  11648. className: className + " vjs-time-control vjs-control",
  11649. innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>"
  11650. });
  11651. this.contentEl_ = createEl('span', {
  11652. className: className + "-display"
  11653. }, {
  11654. // tell screen readers not to automatically read the time as it changes
  11655. 'aria-live': 'off',
  11656. // span elements have no implicit role, but some screen readers (notably VoiceOver)
  11657. // treat them as a break between items in the DOM when using arrow keys
  11658. // (or left-to-right swipes on iOS) to read contents of a page. Using
  11659. // role='presentation' causes VoiceOver to NOT treat this span as a break.
  11660. 'role': 'presentation'
  11661. });
  11662. this.updateTextNode_();
  11663. el.appendChild(this.contentEl_);
  11664. return el;
  11665. };
  11666. _proto.dispose = function dispose() {
  11667. this.contentEl_ = null;
  11668. this.textNode_ = null;
  11669. _Component.prototype.dispose.call(this);
  11670. }
  11671. /**
  11672. * Updates the "remaining time" text node with new content using the
  11673. * contents of the `formattedTime_` property.
  11674. *
  11675. * @private
  11676. */
  11677. ;
  11678. _proto.updateTextNode_ = function updateTextNode_() {
  11679. if (!this.contentEl_) {
  11680. return;
  11681. }
  11682. while (this.contentEl_.firstChild) {
  11683. this.contentEl_.removeChild(this.contentEl_.firstChild);
  11684. }
  11685. this.textNode_ = document.createTextNode(this.formattedTime_ || this.formatTime_(0));
  11686. this.contentEl_.appendChild(this.textNode_);
  11687. }
  11688. /**
  11689. * Generates a formatted time for this component to use in display.
  11690. *
  11691. * @param {number} time
  11692. * A numeric time, in seconds.
  11693. *
  11694. * @return {string}
  11695. * A formatted time
  11696. *
  11697. * @private
  11698. */
  11699. ;
  11700. _proto.formatTime_ = function formatTime_(time) {
  11701. return formatTime(time);
  11702. }
  11703. /**
  11704. * Updates the time display text node if it has what was passed in changed
  11705. * the formatted time.
  11706. *
  11707. * @param {number} time
  11708. * The time to update to
  11709. *
  11710. * @private
  11711. */
  11712. ;
  11713. _proto.updateFormattedTime_ = function updateFormattedTime_(time) {
  11714. var formattedTime = this.formatTime_(time);
  11715. if (formattedTime === this.formattedTime_) {
  11716. return;
  11717. }
  11718. this.formattedTime_ = formattedTime;
  11719. this.requestAnimationFrame(this.updateTextNode_);
  11720. }
  11721. /**
  11722. * To be filled out in the child class, should update the displayed time
  11723. * in accordance with the fact that the current time has changed.
  11724. *
  11725. * @param {EventTarget~Event} [event]
  11726. * The `timeupdate` event that caused this to run.
  11727. *
  11728. * @listens Player#timeupdate
  11729. */
  11730. ;
  11731. _proto.updateContent = function updateContent(event) {};
  11732. return TimeDisplay;
  11733. }(Component);
  11734. /**
  11735. * The text that is added to the `TimeDisplay` for screen reader users.
  11736. *
  11737. * @type {string}
  11738. * @private
  11739. */
  11740. TimeDisplay.prototype.labelText_ = 'Time';
  11741. /**
  11742. * The text that should display over the `TimeDisplay`s controls. Added to for localization.
  11743. *
  11744. * @type {string}
  11745. * @private
  11746. *
  11747. * @deprecated in v7; controlText_ is not used in non-active display Components
  11748. */
  11749. TimeDisplay.prototype.controlText_ = 'Time';
  11750. Component.registerComponent('TimeDisplay', TimeDisplay);
  11751. /**
  11752. * Displays the current time
  11753. *
  11754. * @extends Component
  11755. */
  11756. var CurrentTimeDisplay =
  11757. /*#__PURE__*/
  11758. function (_TimeDisplay) {
  11759. _inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
  11760. /**
  11761. * Creates an instance of this class.
  11762. *
  11763. * @param {Player} player
  11764. * The `Player` that this class should be attached to.
  11765. *
  11766. * @param {Object} [options]
  11767. * The key/value store of player options.
  11768. */
  11769. function CurrentTimeDisplay(player, options) {
  11770. var _this;
  11771. _this = _TimeDisplay.call(this, player, options) || this;
  11772. _this.on(player, 'ended', _this.handleEnded);
  11773. return _this;
  11774. }
  11775. /**
  11776. * Builds the default DOM `className`.
  11777. *
  11778. * @return {string}
  11779. * The DOM `className` for this object.
  11780. */
  11781. var _proto = CurrentTimeDisplay.prototype;
  11782. _proto.buildCSSClass = function buildCSSClass() {
  11783. return 'vjs-current-time';
  11784. }
  11785. /**
  11786. * Update current time display
  11787. *
  11788. * @param {EventTarget~Event} [event]
  11789. * The `timeupdate` event that caused this function to run.
  11790. *
  11791. * @listens Player#timeupdate
  11792. */
  11793. ;
  11794. _proto.updateContent = function updateContent(event) {
  11795. // Allows for smooth scrubbing, when player can't keep up.
  11796. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  11797. this.updateFormattedTime_(time);
  11798. }
  11799. /**
  11800. * When the player fires ended there should be no time left. Sadly
  11801. * this is not always the case, lets make it seem like that is the case
  11802. * for users.
  11803. *
  11804. * @param {EventTarget~Event} [event]
  11805. * The `ended` event that caused this to run.
  11806. *
  11807. * @listens Player#ended
  11808. */
  11809. ;
  11810. _proto.handleEnded = function handleEnded(event) {
  11811. if (!this.player_.duration()) {
  11812. return;
  11813. }
  11814. this.updateFormattedTime_(this.player_.duration());
  11815. };
  11816. return CurrentTimeDisplay;
  11817. }(TimeDisplay);
  11818. /**
  11819. * The text that is added to the `CurrentTimeDisplay` for screen reader users.
  11820. *
  11821. * @type {string}
  11822. * @private
  11823. */
  11824. CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
  11825. /**
  11826. * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
  11827. *
  11828. * @type {string}
  11829. * @private
  11830. *
  11831. * @deprecated in v7; controlText_ is not used in non-active display Components
  11832. */
  11833. CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
  11834. Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
  11835. /**
  11836. * Displays the duration
  11837. *
  11838. * @extends Component
  11839. */
  11840. var DurationDisplay =
  11841. /*#__PURE__*/
  11842. function (_TimeDisplay) {
  11843. _inheritsLoose(DurationDisplay, _TimeDisplay);
  11844. /**
  11845. * Creates an instance of this class.
  11846. *
  11847. * @param {Player} player
  11848. * The `Player` that this class should be attached to.
  11849. *
  11850. * @param {Object} [options]
  11851. * The key/value store of player options.
  11852. */
  11853. function DurationDisplay(player, options) {
  11854. var _this;
  11855. _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes,
  11856. // as they should always display the changed duration as
  11857. // it has changed
  11858. _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded,
  11859. // but the durationchange on the user agent will not fire.
  11860. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  11861. _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
  11862. // listeners could have broken dependent applications/libraries. These
  11863. // can likely be removed for 7.0.
  11864. _this.on(player, 'loadedmetadata', _this.throttledUpdateContent);
  11865. return _this;
  11866. }
  11867. /**
  11868. * Builds the default DOM `className`.
  11869. *
  11870. * @return {string}
  11871. * The DOM `className` for this object.
  11872. */
  11873. var _proto = DurationDisplay.prototype;
  11874. _proto.buildCSSClass = function buildCSSClass() {
  11875. return 'vjs-duration';
  11876. }
  11877. /**
  11878. * Update duration time display.
  11879. *
  11880. * @param {EventTarget~Event} [event]
  11881. * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
  11882. * this function to be called.
  11883. *
  11884. * @listens Player#durationchange
  11885. * @listens Player#timeupdate
  11886. * @listens Player#loadedmetadata
  11887. */
  11888. ;
  11889. _proto.updateContent = function updateContent(event) {
  11890. var duration = this.player_.duration();
  11891. if (this.duration_ !== duration) {
  11892. this.duration_ = duration;
  11893. this.updateFormattedTime_(duration);
  11894. }
  11895. };
  11896. return DurationDisplay;
  11897. }(TimeDisplay);
  11898. /**
  11899. * The text that is added to the `DurationDisplay` for screen reader users.
  11900. *
  11901. * @type {string}
  11902. * @private
  11903. */
  11904. DurationDisplay.prototype.labelText_ = 'Duration';
  11905. /**
  11906. * The text that should display over the `DurationDisplay`s controls. Added to for localization.
  11907. *
  11908. * @type {string}
  11909. * @private
  11910. *
  11911. * @deprecated in v7; controlText_ is not used in non-active display Components
  11912. */
  11913. DurationDisplay.prototype.controlText_ = 'Duration';
  11914. Component.registerComponent('DurationDisplay', DurationDisplay);
  11915. /**
  11916. * The separator between the current time and duration.
  11917. * Can be hidden if it's not needed in the design.
  11918. *
  11919. * @extends Component
  11920. */
  11921. var TimeDivider =
  11922. /*#__PURE__*/
  11923. function (_Component) {
  11924. _inheritsLoose(TimeDivider, _Component);
  11925. function TimeDivider() {
  11926. return _Component.apply(this, arguments) || this;
  11927. }
  11928. var _proto = TimeDivider.prototype;
  11929. /**
  11930. * Create the component's DOM element
  11931. *
  11932. * @return {Element}
  11933. * The element that was created.
  11934. */
  11935. _proto.createEl = function createEl() {
  11936. return _Component.prototype.createEl.call(this, 'div', {
  11937. className: 'vjs-time-control vjs-time-divider',
  11938. innerHTML: '<div><span>/</span></div>'
  11939. }, {
  11940. // this element and its contents can be hidden from assistive techs since
  11941. // it is made extraneous by the announcement of the control text
  11942. // for the current time and duration displays
  11943. 'aria-hidden': true
  11944. });
  11945. };
  11946. return TimeDivider;
  11947. }(Component);
  11948. Component.registerComponent('TimeDivider', TimeDivider);
  11949. /**
  11950. * Displays the time left in the video
  11951. *
  11952. * @extends Component
  11953. */
  11954. var RemainingTimeDisplay =
  11955. /*#__PURE__*/
  11956. function (_TimeDisplay) {
  11957. _inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
  11958. /**
  11959. * Creates an instance of this class.
  11960. *
  11961. * @param {Player} player
  11962. * The `Player` that this class should be attached to.
  11963. *
  11964. * @param {Object} [options]
  11965. * The key/value store of player options.
  11966. */
  11967. function RemainingTimeDisplay(player, options) {
  11968. var _this;
  11969. _this = _TimeDisplay.call(this, player, options) || this;
  11970. _this.on(player, 'durationchange', _this.throttledUpdateContent);
  11971. _this.on(player, 'ended', _this.handleEnded);
  11972. return _this;
  11973. }
  11974. /**
  11975. * Builds the default DOM `className`.
  11976. *
  11977. * @return {string}
  11978. * The DOM `className` for this object.
  11979. */
  11980. var _proto = RemainingTimeDisplay.prototype;
  11981. _proto.buildCSSClass = function buildCSSClass() {
  11982. return 'vjs-remaining-time';
  11983. }
  11984. /**
  11985. * Create the `Component`'s DOM element with the "minus" characted prepend to the time
  11986. *
  11987. * @return {Element}
  11988. * The element that was created.
  11989. */
  11990. ;
  11991. _proto.createEl = function createEl$$1() {
  11992. var el = _TimeDisplay.prototype.createEl.call(this);
  11993. el.insertBefore(createEl('span', {}, {
  11994. 'aria-hidden': true
  11995. }, '-'), this.contentEl_);
  11996. return el;
  11997. }
  11998. /**
  11999. * Update remaining time display.
  12000. *
  12001. * @param {EventTarget~Event} [event]
  12002. * The `timeupdate` or `durationchange` event that caused this to run.
  12003. *
  12004. * @listens Player#timeupdate
  12005. * @listens Player#durationchange
  12006. */
  12007. ;
  12008. _proto.updateContent = function updateContent(event) {
  12009. if (typeof this.player_.duration() !== 'number') {
  12010. return;
  12011. } // @deprecated We should only use remainingTimeDisplay
  12012. // as of video.js 7
  12013. if (this.player_.remainingTimeDisplay) {
  12014. this.updateFormattedTime_(this.player_.remainingTimeDisplay());
  12015. } else {
  12016. this.updateFormattedTime_(this.player_.remainingTime());
  12017. }
  12018. }
  12019. /**
  12020. * When the player fires ended there should be no time left. Sadly
  12021. * this is not always the case, lets make it seem like that is the case
  12022. * for users.
  12023. *
  12024. * @param {EventTarget~Event} [event]
  12025. * The `ended` event that caused this to run.
  12026. *
  12027. * @listens Player#ended
  12028. */
  12029. ;
  12030. _proto.handleEnded = function handleEnded(event) {
  12031. if (!this.player_.duration()) {
  12032. return;
  12033. }
  12034. this.updateFormattedTime_(0);
  12035. };
  12036. return RemainingTimeDisplay;
  12037. }(TimeDisplay);
  12038. /**
  12039. * The text that is added to the `RemainingTimeDisplay` for screen reader users.
  12040. *
  12041. * @type {string}
  12042. * @private
  12043. */
  12044. RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
  12045. /**
  12046. * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
  12047. *
  12048. * @type {string}
  12049. * @private
  12050. *
  12051. * @deprecated in v7; controlText_ is not used in non-active display Components
  12052. */
  12053. RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
  12054. Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
  12055. /**
  12056. * Displays the live indicator when duration is Infinity.
  12057. *
  12058. * @extends Component
  12059. */
  12060. var LiveDisplay =
  12061. /*#__PURE__*/
  12062. function (_Component) {
  12063. _inheritsLoose(LiveDisplay, _Component);
  12064. /**
  12065. * Creates an instance of this class.
  12066. *
  12067. * @param {Player} player
  12068. * The `Player` that this class should be attached to.
  12069. *
  12070. * @param {Object} [options]
  12071. * The key/value store of player options.
  12072. */
  12073. function LiveDisplay(player, options) {
  12074. var _this;
  12075. _this = _Component.call(this, player, options) || this;
  12076. _this.updateShowing();
  12077. _this.on(_this.player(), 'durationchange', _this.updateShowing);
  12078. return _this;
  12079. }
  12080. /**
  12081. * Create the `Component`'s DOM element
  12082. *
  12083. * @return {Element}
  12084. * The element that was created.
  12085. */
  12086. var _proto = LiveDisplay.prototype;
  12087. _proto.createEl = function createEl$$1() {
  12088. var el = _Component.prototype.createEl.call(this, 'div', {
  12089. className: 'vjs-live-control vjs-control'
  12090. });
  12091. this.contentEl_ = createEl('div', {
  12092. className: 'vjs-live-display',
  12093. innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE')
  12094. }, {
  12095. 'aria-live': 'off'
  12096. });
  12097. el.appendChild(this.contentEl_);
  12098. return el;
  12099. };
  12100. _proto.dispose = function dispose() {
  12101. this.contentEl_ = null;
  12102. _Component.prototype.dispose.call(this);
  12103. }
  12104. /**
  12105. * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
  12106. * it accordingly
  12107. *
  12108. * @param {EventTarget~Event} [event]
  12109. * The {@link Player#durationchange} event that caused this function to run.
  12110. *
  12111. * @listens Player#durationchange
  12112. */
  12113. ;
  12114. _proto.updateShowing = function updateShowing(event) {
  12115. if (this.player().duration() === Infinity) {
  12116. this.show();
  12117. } else {
  12118. this.hide();
  12119. }
  12120. };
  12121. return LiveDisplay;
  12122. }(Component);
  12123. Component.registerComponent('LiveDisplay', LiveDisplay);
  12124. /**
  12125. * Displays the live indicator when duration is Infinity.
  12126. *
  12127. * @extends Component
  12128. */
  12129. var SeekToLive =
  12130. /*#__PURE__*/
  12131. function (_Button) {
  12132. _inheritsLoose(SeekToLive, _Button);
  12133. /**
  12134. * Creates an instance of this class.
  12135. *
  12136. * @param {Player} player
  12137. * The `Player` that this class should be attached to.
  12138. *
  12139. * @param {Object} [options]
  12140. * The key/value store of player options.
  12141. */
  12142. function SeekToLive(player, options) {
  12143. var _this;
  12144. _this = _Button.call(this, player, options) || this;
  12145. _this.updateLiveEdgeStatus();
  12146. if (_this.player_.liveTracker) {
  12147. _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus);
  12148. }
  12149. return _this;
  12150. }
  12151. /**
  12152. * Create the `Component`'s DOM element
  12153. *
  12154. * @return {Element}
  12155. * The element that was created.
  12156. */
  12157. var _proto = SeekToLive.prototype;
  12158. _proto.createEl = function createEl$$1() {
  12159. var el = _Button.prototype.createEl.call(this, 'button', {
  12160. className: 'vjs-seek-to-live-control vjs-control'
  12161. });
  12162. this.textEl_ = createEl('span', {
  12163. className: 'vjs-seek-to-live-text',
  12164. innerHTML: this.localize('LIVE')
  12165. }, {
  12166. 'aria-hidden': 'true'
  12167. });
  12168. el.appendChild(this.textEl_);
  12169. return el;
  12170. }
  12171. /**
  12172. * Update the state of this button if we are at the live edge
  12173. * or not
  12174. */
  12175. ;
  12176. _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus(e) {
  12177. // default to live edge
  12178. if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
  12179. this.setAttribute('aria-disabled', true);
  12180. this.addClass('vjs-at-live-edge');
  12181. this.controlText('Seek to live, currently playing live');
  12182. } else {
  12183. this.setAttribute('aria-disabled', false);
  12184. this.removeClass('vjs-at-live-edge');
  12185. this.controlText('Seek to live, currently behind live');
  12186. }
  12187. }
  12188. /**
  12189. * On click bring us as near to the live point as possible.
  12190. * This requires that we wait for the next `live-seekable-change`
  12191. * event which will happen every segment length seconds.
  12192. */
  12193. ;
  12194. _proto.handleClick = function handleClick() {
  12195. this.player_.liveTracker.seekToLiveEdge();
  12196. }
  12197. /**
  12198. * Dispose of the element and stop tracking
  12199. */
  12200. ;
  12201. _proto.dispose = function dispose() {
  12202. if (this.player_.liveTracker) {
  12203. this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus);
  12204. }
  12205. this.textEl_ = null;
  12206. _Button.prototype.dispose.call(this);
  12207. };
  12208. return SeekToLive;
  12209. }(Button);
  12210. SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
  12211. Component.registerComponent('SeekToLive', SeekToLive);
  12212. /**
  12213. * The base functionality for a slider. Can be vertical or horizontal.
  12214. * For instance the volume bar or the seek bar on a video is a slider.
  12215. *
  12216. * @extends Component
  12217. */
  12218. var Slider =
  12219. /*#__PURE__*/
  12220. function (_Component) {
  12221. _inheritsLoose(Slider, _Component);
  12222. /**
  12223. * Create an instance of this class
  12224. *
  12225. * @param {Player} player
  12226. * The `Player` that this class should be attached to.
  12227. *
  12228. * @param {Object} [options]
  12229. * The key/value store of player options.
  12230. */
  12231. function Slider(player, options) {
  12232. var _this;
  12233. _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for
  12234. _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type
  12235. _this.vertical(!!_this.options_.vertical);
  12236. _this.enable();
  12237. return _this;
  12238. }
  12239. /**
  12240. * Are controls are currently enabled for this slider or not.
  12241. *
  12242. * @return {boolean}
  12243. * true if controls are enabled, false otherwise
  12244. */
  12245. var _proto = Slider.prototype;
  12246. _proto.enabled = function enabled() {
  12247. return this.enabled_;
  12248. }
  12249. /**
  12250. * Enable controls for this slider if they are disabled
  12251. */
  12252. ;
  12253. _proto.enable = function enable() {
  12254. if (this.enabled()) {
  12255. return;
  12256. }
  12257. this.on('mousedown', this.handleMouseDown);
  12258. this.on('touchstart', this.handleMouseDown);
  12259. this.on('focus', this.handleFocus);
  12260. this.on('blur', this.handleBlur);
  12261. this.on('click', this.handleClick);
  12262. this.on(this.player_, 'controlsvisible', this.update);
  12263. if (this.playerEvent) {
  12264. this.on(this.player_, this.playerEvent, this.update);
  12265. }
  12266. this.removeClass('disabled');
  12267. this.setAttribute('tabindex', 0);
  12268. this.enabled_ = true;
  12269. }
  12270. /**
  12271. * Disable controls for this slider if they are enabled
  12272. */
  12273. ;
  12274. _proto.disable = function disable() {
  12275. if (!this.enabled()) {
  12276. return;
  12277. }
  12278. var doc = this.bar.el_.ownerDocument;
  12279. this.off('mousedown', this.handleMouseDown);
  12280. this.off('touchstart', this.handleMouseDown);
  12281. this.off('focus', this.handleFocus);
  12282. this.off('blur', this.handleBlur);
  12283. this.off('click', this.handleClick);
  12284. this.off(this.player_, 'controlsvisible', this.update);
  12285. this.off(doc, 'mousemove', this.handleMouseMove);
  12286. this.off(doc, 'mouseup', this.handleMouseUp);
  12287. this.off(doc, 'touchmove', this.handleMouseMove);
  12288. this.off(doc, 'touchend', this.handleMouseUp);
  12289. this.removeAttribute('tabindex');
  12290. this.addClass('disabled');
  12291. if (this.playerEvent) {
  12292. this.off(this.player_, this.playerEvent, this.update);
  12293. }
  12294. this.enabled_ = false;
  12295. }
  12296. /**
  12297. * Create the `Slider`s DOM element.
  12298. *
  12299. * @param {string} type
  12300. * Type of element to create.
  12301. *
  12302. * @param {Object} [props={}]
  12303. * List of properties in Object form.
  12304. *
  12305. * @param {Object} [attributes={}]
  12306. * list of attributes in Object form.
  12307. *
  12308. * @return {Element}
  12309. * The element that gets created.
  12310. */
  12311. ;
  12312. _proto.createEl = function createEl$$1(type, props, attributes) {
  12313. if (props === void 0) {
  12314. props = {};
  12315. }
  12316. if (attributes === void 0) {
  12317. attributes = {};
  12318. }
  12319. // Add the slider element class to all sub classes
  12320. props.className = props.className + ' vjs-slider';
  12321. props = assign({
  12322. tabIndex: 0
  12323. }, props);
  12324. attributes = assign({
  12325. 'role': 'slider',
  12326. 'aria-valuenow': 0,
  12327. 'aria-valuemin': 0,
  12328. 'aria-valuemax': 100,
  12329. 'tabIndex': 0
  12330. }, attributes);
  12331. return _Component.prototype.createEl.call(this, type, props, attributes);
  12332. }
  12333. /**
  12334. * Handle `mousedown` or `touchstart` events on the `Slider`.
  12335. *
  12336. * @param {EventTarget~Event} event
  12337. * `mousedown` or `touchstart` event that triggered this function
  12338. *
  12339. * @listens mousedown
  12340. * @listens touchstart
  12341. * @fires Slider#slideractive
  12342. */
  12343. ;
  12344. _proto.handleMouseDown = function handleMouseDown(event) {
  12345. var doc = this.bar.el_.ownerDocument;
  12346. if (event.type === 'mousedown') {
  12347. event.preventDefault();
  12348. } // Do not call preventDefault() on touchstart in Chrome
  12349. // to avoid console warnings. Use a 'touch-action: none' style
  12350. // instead to prevent unintented scrolling.
  12351. // https://developers.google.com/web/updates/2017/01/scrolling-intervention
  12352. if (event.type === 'touchstart' && !IS_CHROME) {
  12353. event.preventDefault();
  12354. }
  12355. blockTextSelection();
  12356. this.addClass('vjs-sliding');
  12357. /**
  12358. * Triggered when the slider is in an active state
  12359. *
  12360. * @event Slider#slideractive
  12361. * @type {EventTarget~Event}
  12362. */
  12363. this.trigger('slideractive');
  12364. this.on(doc, 'mousemove', this.handleMouseMove);
  12365. this.on(doc, 'mouseup', this.handleMouseUp);
  12366. this.on(doc, 'touchmove', this.handleMouseMove);
  12367. this.on(doc, 'touchend', this.handleMouseUp);
  12368. this.handleMouseMove(event);
  12369. }
  12370. /**
  12371. * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
  12372. * The `mousemove` and `touchmove` events will only only trigger this function during
  12373. * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
  12374. * {@link Slider#handleMouseUp}.
  12375. *
  12376. * @param {EventTarget~Event} event
  12377. * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
  12378. * this function
  12379. *
  12380. * @listens mousemove
  12381. * @listens touchmove
  12382. */
  12383. ;
  12384. _proto.handleMouseMove = function handleMouseMove(event) {}
  12385. /**
  12386. * Handle `mouseup` or `touchend` events on the `Slider`.
  12387. *
  12388. * @param {EventTarget~Event} event
  12389. * `mouseup` or `touchend` event that triggered this function.
  12390. *
  12391. * @listens touchend
  12392. * @listens mouseup
  12393. * @fires Slider#sliderinactive
  12394. */
  12395. ;
  12396. _proto.handleMouseUp = function handleMouseUp() {
  12397. var doc = this.bar.el_.ownerDocument;
  12398. unblockTextSelection();
  12399. this.removeClass('vjs-sliding');
  12400. /**
  12401. * Triggered when the slider is no longer in an active state.
  12402. *
  12403. * @event Slider#sliderinactive
  12404. * @type {EventTarget~Event}
  12405. */
  12406. this.trigger('sliderinactive');
  12407. this.off(doc, 'mousemove', this.handleMouseMove);
  12408. this.off(doc, 'mouseup', this.handleMouseUp);
  12409. this.off(doc, 'touchmove', this.handleMouseMove);
  12410. this.off(doc, 'touchend', this.handleMouseUp);
  12411. this.update();
  12412. }
  12413. /**
  12414. * Update the progress bar of the `Slider`.
  12415. *
  12416. * @return {number}
  12417. * The percentage of progress the progress bar represents as a
  12418. * number from 0 to 1.
  12419. */
  12420. ;
  12421. _proto.update = function update() {
  12422. // In VolumeBar init we have a setTimeout for update that pops and update
  12423. // to the end of the execution stack. The player is destroyed before then
  12424. // update will cause an error
  12425. if (!this.el_) {
  12426. return;
  12427. } // If scrubbing, we could use a cached value to make the handle keep up
  12428. // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
  12429. // some flash players are slow, so we might want to utilize this later.
  12430. // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
  12431. var progress = this.getPercent();
  12432. var bar = this.bar; // If there's no bar...
  12433. if (!bar) {
  12434. return;
  12435. } // Protect against no duration and other division issues
  12436. if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
  12437. progress = 0;
  12438. } // Convert to a percentage for setting
  12439. var percentage = (progress * 100).toFixed(2) + '%';
  12440. var style = bar.el().style; // Set the new bar width or height
  12441. if (this.vertical()) {
  12442. style.height = percentage;
  12443. } else {
  12444. style.width = percentage;
  12445. }
  12446. return progress;
  12447. }
  12448. /**
  12449. * Calculate distance for slider
  12450. *
  12451. * @param {EventTarget~Event} event
  12452. * The event that caused this function to run.
  12453. *
  12454. * @return {number}
  12455. * The current position of the Slider.
  12456. * - position.x for vertical `Slider`s
  12457. * - position.y for horizontal `Slider`s
  12458. */
  12459. ;
  12460. _proto.calculateDistance = function calculateDistance(event) {
  12461. var position = getPointerPosition(this.el_, event);
  12462. if (this.vertical()) {
  12463. return position.y;
  12464. }
  12465. return position.x;
  12466. }
  12467. /**
  12468. * Handle a `focus` event on this `Slider`.
  12469. *
  12470. * @param {EventTarget~Event} event
  12471. * The `focus` event that caused this function to run.
  12472. *
  12473. * @listens focus
  12474. */
  12475. ;
  12476. _proto.handleFocus = function handleFocus() {
  12477. this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  12478. }
  12479. /**
  12480. * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
  12481. * arrow keys. This function will only be called when the slider has focus. See
  12482. * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
  12483. *
  12484. * @param {EventTarget~Event} event
  12485. * the `keydown` event that caused this function to run.
  12486. *
  12487. * @listens keydown
  12488. */
  12489. ;
  12490. _proto.handleKeyPress = function handleKeyPress(event) {
  12491. // Left and Down Arrows
  12492. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  12493. event.preventDefault();
  12494. this.stepBack(); // Up and Right Arrows
  12495. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  12496. event.preventDefault();
  12497. this.stepForward();
  12498. } else {
  12499. // Pass keypress handling up for unsupported keys
  12500. _Component.prototype.handleKeyPress.call(this, event);
  12501. }
  12502. }
  12503. /**
  12504. * Handle a `blur` event on this `Slider`.
  12505. *
  12506. * @param {EventTarget~Event} event
  12507. * The `blur` event that caused this function to run.
  12508. *
  12509. * @listens blur
  12510. */
  12511. ;
  12512. _proto.handleBlur = function handleBlur() {
  12513. this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
  12514. }
  12515. /**
  12516. * Listener for click events on slider, used to prevent clicks
  12517. * from bubbling up to parent elements like button menus.
  12518. *
  12519. * @param {Object} event
  12520. * Event that caused this object to run
  12521. */
  12522. ;
  12523. _proto.handleClick = function handleClick(event) {
  12524. event.stopImmediatePropagation();
  12525. event.preventDefault();
  12526. }
  12527. /**
  12528. * Get/set if slider is horizontal for vertical
  12529. *
  12530. * @param {boolean} [bool]
  12531. * - true if slider is vertical,
  12532. * - false is horizontal
  12533. *
  12534. * @return {boolean}
  12535. * - true if slider is vertical, and getting
  12536. * - false if the slider is horizontal, and getting
  12537. */
  12538. ;
  12539. _proto.vertical = function vertical(bool) {
  12540. if (bool === undefined) {
  12541. return this.vertical_ || false;
  12542. }
  12543. this.vertical_ = !!bool;
  12544. if (this.vertical_) {
  12545. this.addClass('vjs-slider-vertical');
  12546. } else {
  12547. this.addClass('vjs-slider-horizontal');
  12548. }
  12549. };
  12550. return Slider;
  12551. }(Component);
  12552. Component.registerComponent('Slider', Slider);
  12553. /**
  12554. * Shows loading progress
  12555. *
  12556. * @extends Component
  12557. */
  12558. var LoadProgressBar =
  12559. /*#__PURE__*/
  12560. function (_Component) {
  12561. _inheritsLoose(LoadProgressBar, _Component);
  12562. /**
  12563. * Creates an instance of this class.
  12564. *
  12565. * @param {Player} player
  12566. * The `Player` that this class should be attached to.
  12567. *
  12568. * @param {Object} [options]
  12569. * The key/value store of player options.
  12570. */
  12571. function LoadProgressBar(player, options) {
  12572. var _this;
  12573. _this = _Component.call(this, player, options) || this;
  12574. _this.partEls_ = [];
  12575. _this.on(player, 'progress', _this.update);
  12576. return _this;
  12577. }
  12578. /**
  12579. * Create the `Component`'s DOM element
  12580. *
  12581. * @return {Element}
  12582. * The element that was created.
  12583. */
  12584. var _proto = LoadProgressBar.prototype;
  12585. _proto.createEl = function createEl$$1() {
  12586. return _Component.prototype.createEl.call(this, 'div', {
  12587. className: 'vjs-load-progress',
  12588. innerHTML: "<span class=\"vjs-control-text\"><span>" + this.localize('Loaded') + "</span>: <span class=\"vjs-control-text-loaded-percentage\">0%</span></span>"
  12589. });
  12590. };
  12591. _proto.dispose = function dispose() {
  12592. this.partEls_ = null;
  12593. _Component.prototype.dispose.call(this);
  12594. }
  12595. /**
  12596. * Update progress bar
  12597. *
  12598. * @param {EventTarget~Event} [event]
  12599. * The `progress` event that caused this function to run.
  12600. *
  12601. * @listens Player#progress
  12602. */
  12603. ;
  12604. _proto.update = function update(event) {
  12605. var liveTracker = this.player_.liveTracker;
  12606. var buffered = this.player_.buffered();
  12607. var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
  12608. var bufferedEnd = this.player_.bufferedEnd();
  12609. var children = this.partEls_;
  12610. var controlTextPercentage = this.$('.vjs-control-text-loaded-percentage'); // get the percent width of a time compared to the total end
  12611. var percentify = function percentify(time, end, rounded) {
  12612. // no NaN
  12613. var percent = time / end || 0;
  12614. percent = (percent >= 1 ? 1 : percent) * 100;
  12615. if (rounded) {
  12616. percent = percent.toFixed(2);
  12617. }
  12618. return percent + '%';
  12619. }; // update the width of the progress bar
  12620. this.el_.style.width = percentify(bufferedEnd, duration); // update the control-text
  12621. textContent(controlTextPercentage, percentify(bufferedEnd, duration, true)); // add child elements to represent the individual buffered time ranges
  12622. for (var i = 0; i < buffered.length; i++) {
  12623. var start = buffered.start(i);
  12624. var end = buffered.end(i);
  12625. var part = children[i];
  12626. if (!part) {
  12627. part = this.el_.appendChild(createEl());
  12628. children[i] = part;
  12629. } // set the percent based on the width of the progress bar (bufferedEnd)
  12630. part.style.left = percentify(start, bufferedEnd);
  12631. part.style.width = percentify(end - start, bufferedEnd);
  12632. } // remove unused buffered range elements
  12633. for (var _i = children.length; _i > buffered.length; _i--) {
  12634. this.el_.removeChild(children[_i - 1]);
  12635. }
  12636. children.length = buffered.length;
  12637. };
  12638. return LoadProgressBar;
  12639. }(Component);
  12640. Component.registerComponent('LoadProgressBar', LoadProgressBar);
  12641. /**
  12642. * Time tooltips display a time above the progress bar.
  12643. *
  12644. * @extends Component
  12645. */
  12646. var TimeTooltip =
  12647. /*#__PURE__*/
  12648. function (_Component) {
  12649. _inheritsLoose(TimeTooltip, _Component);
  12650. function TimeTooltip() {
  12651. return _Component.apply(this, arguments) || this;
  12652. }
  12653. var _proto = TimeTooltip.prototype;
  12654. /**
  12655. * Create the time tooltip DOM element
  12656. *
  12657. * @return {Element}
  12658. * The element that was created.
  12659. */
  12660. _proto.createEl = function createEl$$1() {
  12661. return _Component.prototype.createEl.call(this, 'div', {
  12662. className: 'vjs-time-tooltip'
  12663. }, {
  12664. 'aria-hidden': 'true'
  12665. });
  12666. }
  12667. /**
  12668. * Updates the position of the time tooltip relative to the `SeekBar`.
  12669. *
  12670. * @param {Object} seekBarRect
  12671. * The `ClientRect` for the {@link SeekBar} element.
  12672. *
  12673. * @param {number} seekBarPoint
  12674. * A number from 0 to 1, representing a horizontal reference point
  12675. * from the left edge of the {@link SeekBar}
  12676. */
  12677. ;
  12678. _proto.update = function update(seekBarRect, seekBarPoint, content) {
  12679. var tooltipRect = getBoundingClientRect(this.el_);
  12680. var playerRect = getBoundingClientRect(this.player_.el());
  12681. var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available
  12682. // for example, if the player isn't in the DOM for testing
  12683. if (!playerRect || !tooltipRect) {
  12684. return;
  12685. } // This is the space left of the `seekBarPoint` available within the bounds
  12686. // of the player. We calculate any gap between the left edge of the player
  12687. // and the left edge of the `SeekBar` and add the number of pixels in the
  12688. // `SeekBar` before hitting the `seekBarPoint`
  12689. var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds
  12690. // of the player. We calculate the number of pixels from the `seekBarPoint`
  12691. // to the right edge of the `SeekBar` and add to that any gap between the
  12692. // right edge of the `SeekBar` and the player.
  12693. var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled
  12694. // further to the right to center it over the `seekBarPoint`.
  12695. var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on
  12696. // the results of the space calculations above.
  12697. if (spaceLeftOfPoint < pullTooltipBy) {
  12698. pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
  12699. } else if (spaceRightOfPoint < pullTooltipBy) {
  12700. pullTooltipBy = spaceRightOfPoint;
  12701. } // Due to the imprecision of decimal/ratio based calculations and varying
  12702. // rounding behaviors, there are cases where the spacing adjustment is off
  12703. // by a pixel or two. This adds insurance to these calculations.
  12704. if (pullTooltipBy < 0) {
  12705. pullTooltipBy = 0;
  12706. } else if (pullTooltipBy > tooltipRect.width) {
  12707. pullTooltipBy = tooltipRect.width;
  12708. }
  12709. this.el_.style.right = "-" + pullTooltipBy + "px";
  12710. textContent(this.el_, content);
  12711. }
  12712. /**
  12713. * Updates the position of the time tooltip relative to the `SeekBar`.
  12714. *
  12715. * @param {Object} seekBarRect
  12716. * The `ClientRect` for the {@link SeekBar} element.
  12717. *
  12718. * @param {number} seekBarPoint
  12719. * A number from 0 to 1, representing a horizontal reference point
  12720. * from the left edge of the {@link SeekBar}
  12721. *
  12722. * @param {number} time
  12723. * The time to update the tooltip to, not used during live playback
  12724. *
  12725. * @param {Function} cb
  12726. * A function that will be called during the request animation frame
  12727. * for tooltips that need to do additional animations from the default
  12728. */
  12729. ;
  12730. _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) {
  12731. var _this = this;
  12732. // If there is an existing rAF ID, cancel it so we don't over-queue.
  12733. if (this.rafId_) {
  12734. this.cancelAnimationFrame(this.rafId_);
  12735. }
  12736. this.rafId_ = this.requestAnimationFrame(function () {
  12737. var content;
  12738. var duration = _this.player_.duration();
  12739. if (_this.player_.liveTracker && _this.player_.liveTracker.isLive()) {
  12740. var liveWindow = _this.player_.liveTracker.liveWindow();
  12741. var secondsBehind = liveWindow - seekBarPoint * liveWindow;
  12742. content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
  12743. } else {
  12744. content = formatTime(time, duration);
  12745. }
  12746. _this.update(seekBarRect, seekBarPoint, content);
  12747. if (cb) {
  12748. cb();
  12749. }
  12750. });
  12751. };
  12752. return TimeTooltip;
  12753. }(Component);
  12754. Component.registerComponent('TimeTooltip', TimeTooltip);
  12755. /**
  12756. * Used by {@link SeekBar} to display media playback progress as part of the
  12757. * {@link ProgressControl}.
  12758. *
  12759. * @extends Component
  12760. */
  12761. var PlayProgressBar =
  12762. /*#__PURE__*/
  12763. function (_Component) {
  12764. _inheritsLoose(PlayProgressBar, _Component);
  12765. function PlayProgressBar() {
  12766. return _Component.apply(this, arguments) || this;
  12767. }
  12768. var _proto = PlayProgressBar.prototype;
  12769. /**
  12770. * Create the the DOM element for this class.
  12771. *
  12772. * @return {Element}
  12773. * The element that was created.
  12774. */
  12775. _proto.createEl = function createEl() {
  12776. return _Component.prototype.createEl.call(this, 'div', {
  12777. className: 'vjs-play-progress vjs-slider-bar'
  12778. }, {
  12779. 'aria-hidden': 'true'
  12780. });
  12781. }
  12782. /**
  12783. * Enqueues updates to its own DOM as well as the DOM of its
  12784. * {@link TimeTooltip} child.
  12785. *
  12786. * @param {Object} seekBarRect
  12787. * The `ClientRect` for the {@link SeekBar} element.
  12788. *
  12789. * @param {number} seekBarPoint
  12790. * A number from 0 to 1, representing a horizontal reference point
  12791. * from the left edge of the {@link SeekBar}
  12792. */
  12793. ;
  12794. _proto.update = function update(seekBarRect, seekBarPoint) {
  12795. var timeTooltip = this.getChild('timeTooltip');
  12796. if (!timeTooltip) {
  12797. return;
  12798. }
  12799. var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  12800. timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
  12801. };
  12802. return PlayProgressBar;
  12803. }(Component);
  12804. /**
  12805. * Default options for {@link PlayProgressBar}.
  12806. *
  12807. * @type {Object}
  12808. * @private
  12809. */
  12810. PlayProgressBar.prototype.options_ = {
  12811. children: []
  12812. }; // Time tooltips should not be added to a player on mobile devices
  12813. if (!IS_IOS && !IS_ANDROID) {
  12814. PlayProgressBar.prototype.options_.children.push('timeTooltip');
  12815. }
  12816. Component.registerComponent('PlayProgressBar', PlayProgressBar);
  12817. /**
  12818. * The {@link MouseTimeDisplay} component tracks mouse movement over the
  12819. * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
  12820. * indicating the time which is represented by a given point in the
  12821. * {@link ProgressControl}.
  12822. *
  12823. * @extends Component
  12824. */
  12825. var MouseTimeDisplay =
  12826. /*#__PURE__*/
  12827. function (_Component) {
  12828. _inheritsLoose(MouseTimeDisplay, _Component);
  12829. /**
  12830. * Creates an instance of this class.
  12831. *
  12832. * @param {Player} player
  12833. * The {@link Player} that this class should be attached to.
  12834. *
  12835. * @param {Object} [options]
  12836. * The key/value store of player options.
  12837. */
  12838. function MouseTimeDisplay(player, options) {
  12839. var _this;
  12840. _this = _Component.call(this, player, options) || this;
  12841. _this.update = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update), 25);
  12842. return _this;
  12843. }
  12844. /**
  12845. * Create the DOM element for this class.
  12846. *
  12847. * @return {Element}
  12848. * The element that was created.
  12849. */
  12850. var _proto = MouseTimeDisplay.prototype;
  12851. _proto.createEl = function createEl() {
  12852. return _Component.prototype.createEl.call(this, 'div', {
  12853. className: 'vjs-mouse-display'
  12854. });
  12855. }
  12856. /**
  12857. * Enqueues updates to its own DOM as well as the DOM of its
  12858. * {@link TimeTooltip} child.
  12859. *
  12860. * @param {Object} seekBarRect
  12861. * The `ClientRect` for the {@link SeekBar} element.
  12862. *
  12863. * @param {number} seekBarPoint
  12864. * A number from 0 to 1, representing a horizontal reference point
  12865. * from the left edge of the {@link SeekBar}
  12866. */
  12867. ;
  12868. _proto.update = function update(seekBarRect, seekBarPoint) {
  12869. var _this2 = this;
  12870. var time = seekBarPoint * this.player_.duration();
  12871. this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () {
  12872. _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px";
  12873. });
  12874. };
  12875. return MouseTimeDisplay;
  12876. }(Component);
  12877. /**
  12878. * Default options for `MouseTimeDisplay`
  12879. *
  12880. * @type {Object}
  12881. * @private
  12882. */
  12883. MouseTimeDisplay.prototype.options_ = {
  12884. children: ['timeTooltip']
  12885. };
  12886. Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
  12887. var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
  12888. var PAGE_KEY_MULTIPLIER = 12; // The interval at which the bar should update as it progresses.
  12889. var UPDATE_REFRESH_INTERVAL = 30;
  12890. /**
  12891. * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
  12892. * as its `bar`.
  12893. *
  12894. * @extends Slider
  12895. */
  12896. var SeekBar =
  12897. /*#__PURE__*/
  12898. function (_Slider) {
  12899. _inheritsLoose(SeekBar, _Slider);
  12900. /**
  12901. * Creates an instance of this class.
  12902. *
  12903. * @param {Player} player
  12904. * The `Player` that this class should be attached to.
  12905. *
  12906. * @param {Object} [options]
  12907. * The key/value store of player options.
  12908. */
  12909. function SeekBar(player, options) {
  12910. var _this;
  12911. _this = _Slider.call(this, player, options) || this;
  12912. _this.setEventHandlers_();
  12913. return _this;
  12914. }
  12915. /**
  12916. * Sets the event handlers
  12917. *
  12918. * @private
  12919. */
  12920. var _proto = SeekBar.prototype;
  12921. _proto.setEventHandlers_ = function setEventHandlers_() {
  12922. this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL);
  12923. this.on(this.player_, 'timeupdate', this.update);
  12924. this.on(this.player_, 'ended', this.handleEnded);
  12925. this.on(this.player_, 'durationchange', this.update);
  12926. if (this.player_.liveTracker) {
  12927. this.on(this.player_.liveTracker, 'liveedgechange', this.update);
  12928. } // when playing, let's ensure we smoothly update the play progress bar
  12929. // via an interval
  12930. this.updateInterval = null;
  12931. this.on(this.player_, ['playing'], this.enableInterval_);
  12932. this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden,
  12933. // also, this causes the CPU to spike and eventually crash the page on IE11.
  12934. if ('hidden' in document && 'visibilityState' in document) {
  12935. this.on(document, 'visibilitychange', this.toggleVisibility_);
  12936. }
  12937. };
  12938. _proto.toggleVisibility_ = function toggleVisibility_(e) {
  12939. if (document.hidden) {
  12940. this.disableInterval_(e);
  12941. } else {
  12942. this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP
  12943. this.requestAnimationFrame(this.update);
  12944. }
  12945. };
  12946. _proto.enableInterval_ = function enableInterval_() {
  12947. var _this2 = this;
  12948. this.clearInterval(this.updateInterval);
  12949. this.updateInterval = this.setInterval(function () {
  12950. _this2.requestAnimationFrame(_this2.update);
  12951. }, UPDATE_REFRESH_INTERVAL);
  12952. };
  12953. _proto.disableInterval_ = function disableInterval_(e) {
  12954. if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e.type !== 'ended') {
  12955. return;
  12956. }
  12957. this.clearInterval(this.updateInterval);
  12958. }
  12959. /**
  12960. * Create the `Component`'s DOM element
  12961. *
  12962. * @return {Element}
  12963. * The element that was created.
  12964. */
  12965. ;
  12966. _proto.createEl = function createEl$$1() {
  12967. return _Slider.prototype.createEl.call(this, 'div', {
  12968. className: 'vjs-progress-holder'
  12969. }, {
  12970. 'aria-label': this.localize('Progress Bar')
  12971. });
  12972. }
  12973. /**
  12974. * This function updates the play progress bar and accessibility
  12975. * attributes to whatever is passed in.
  12976. *
  12977. * @param {number} currentTime
  12978. * The currentTime value that should be used for accessibility
  12979. *
  12980. * @param {number} percent
  12981. * The percentage as a decimal that the bar should be filled from 0-1.
  12982. *
  12983. * @private
  12984. */
  12985. ;
  12986. _proto.update_ = function update_(currentTime, percent) {
  12987. var liveTracker = this.player_.liveTracker;
  12988. var duration = this.player_.duration();
  12989. if (liveTracker && liveTracker.isLive()) {
  12990. duration = this.player_.liveTracker.liveCurrentTime();
  12991. } // machine readable value of progress bar (percentage complete)
  12992. this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); // human readable value of progress bar (time complete)
  12993. 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`.
  12994. if (this.bar) {
  12995. this.bar.update(getBoundingClientRect(this.el_), percent);
  12996. }
  12997. }
  12998. /**
  12999. * Update the seek bar's UI.
  13000. *
  13001. * @param {EventTarget~Event} [event]
  13002. * The `timeupdate` or `ended` event that caused this to run.
  13003. *
  13004. * @listens Player#timeupdate
  13005. *
  13006. * @return {number}
  13007. * The current percent at a number from 0-1
  13008. */
  13009. ;
  13010. _proto.update = function update(event) {
  13011. // if the offsetParent is null, then this element is hidden, in which case
  13012. // we don't need to update it.
  13013. if (this.el().offsetParent === null) {
  13014. return;
  13015. }
  13016. var percent = _Slider.prototype.update.call(this);
  13017. this.update_(this.getCurrentTime_(), percent);
  13018. return percent;
  13019. }
  13020. /**
  13021. * Get the value of current time but allows for smooth scrubbing,
  13022. * when player can't keep up.
  13023. *
  13024. * @return {number}
  13025. * The current time value to display
  13026. *
  13027. * @private
  13028. */
  13029. ;
  13030. _proto.getCurrentTime_ = function getCurrentTime_() {
  13031. return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
  13032. }
  13033. /**
  13034. * We want the seek bar to be full on ended
  13035. * no matter what the actual internal values are. so we force it.
  13036. *
  13037. * @param {EventTarget~Event} [event]
  13038. * The `timeupdate` or `ended` event that caused this to run.
  13039. *
  13040. * @listens Player#ended
  13041. */
  13042. ;
  13043. _proto.handleEnded = function handleEnded(event) {
  13044. this.update_(this.player_.duration(), 1);
  13045. }
  13046. /**
  13047. * Get the percentage of media played so far.
  13048. *
  13049. * @return {number}
  13050. * The percentage of media played so far (0 to 1).
  13051. */
  13052. ;
  13053. _proto.getPercent = function getPercent() {
  13054. var currentTime = this.getCurrentTime_();
  13055. var percent;
  13056. var liveTracker = this.player_.liveTracker;
  13057. if (liveTracker && liveTracker.isLive()) {
  13058. percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge
  13059. if (liveTracker.atLiveEdge()) {
  13060. percent = 1;
  13061. }
  13062. } else {
  13063. percent = currentTime / this.player_.duration();
  13064. }
  13065. return percent >= 1 ? 1 : percent || 0;
  13066. }
  13067. /**
  13068. * Handle mouse down on seek bar
  13069. *
  13070. * @param {EventTarget~Event} event
  13071. * The `mousedown` event that caused this to run.
  13072. *
  13073. * @listens mousedown
  13074. */
  13075. ;
  13076. _proto.handleMouseDown = function handleMouseDown(event) {
  13077. if (!isSingleLeftClick(event)) {
  13078. return;
  13079. } // Stop event propagation to prevent double fire in progress-control.js
  13080. event.stopPropagation();
  13081. this.player_.scrubbing(true);
  13082. this.videoWasPlaying = !this.player_.paused();
  13083. this.player_.pause();
  13084. _Slider.prototype.handleMouseDown.call(this, event);
  13085. }
  13086. /**
  13087. * Handle mouse move on seek bar
  13088. *
  13089. * @param {EventTarget~Event} event
  13090. * The `mousemove` event that caused this to run.
  13091. *
  13092. * @listens mousemove
  13093. */
  13094. ;
  13095. _proto.handleMouseMove = function handleMouseMove(event) {
  13096. if (!isSingleLeftClick(event)) {
  13097. return;
  13098. }
  13099. var newTime;
  13100. var distance = this.calculateDistance(event);
  13101. var liveTracker = this.player_.liveTracker;
  13102. if (!liveTracker || !liveTracker.isLive()) {
  13103. newTime = distance * this.player_.duration(); // Don't let video end while scrubbing.
  13104. if (newTime === this.player_.duration()) {
  13105. newTime = newTime - 0.1;
  13106. }
  13107. } else {
  13108. var seekableStart = liveTracker.seekableStart();
  13109. var seekableEnd = liveTracker.liveCurrentTime();
  13110. newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing.
  13111. if (newTime >= seekableEnd) {
  13112. newTime = seekableEnd;
  13113. } // Compensate for precision differences so that currentTime is not less
  13114. // than seekable start
  13115. if (newTime <= seekableStart) {
  13116. newTime = seekableStart + 0.1;
  13117. } // On android seekableEnd can be Infinity sometimes,
  13118. // this will cause newTime to be Infinity, which is
  13119. // not a valid currentTime.
  13120. if (newTime === Infinity) {
  13121. return;
  13122. }
  13123. } // Set new time (tell player to seek to new time)
  13124. this.player_.currentTime(newTime);
  13125. };
  13126. _proto.enable = function enable() {
  13127. _Slider.prototype.enable.call(this);
  13128. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  13129. if (!mouseTimeDisplay) {
  13130. return;
  13131. }
  13132. mouseTimeDisplay.show();
  13133. };
  13134. _proto.disable = function disable() {
  13135. _Slider.prototype.disable.call(this);
  13136. var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
  13137. if (!mouseTimeDisplay) {
  13138. return;
  13139. }
  13140. mouseTimeDisplay.hide();
  13141. }
  13142. /**
  13143. * Handle mouse up on seek bar
  13144. *
  13145. * @param {EventTarget~Event} event
  13146. * The `mouseup` event that caused this to run.
  13147. *
  13148. * @listens mouseup
  13149. */
  13150. ;
  13151. _proto.handleMouseUp = function handleMouseUp(event) {
  13152. _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js
  13153. if (event) {
  13154. event.stopPropagation();
  13155. }
  13156. this.player_.scrubbing(false);
  13157. /**
  13158. * Trigger timeupdate because we're done seeking and the time has changed.
  13159. * This is particularly useful for if the player is paused to time the time displays.
  13160. *
  13161. * @event Tech#timeupdate
  13162. * @type {EventTarget~Event}
  13163. */
  13164. this.player_.trigger({
  13165. type: 'timeupdate',
  13166. target: this,
  13167. manuallyTriggered: true
  13168. });
  13169. if (this.videoWasPlaying) {
  13170. silencePromise(this.player_.play());
  13171. }
  13172. }
  13173. /**
  13174. * Move more quickly fast forward for keyboard-only users
  13175. */
  13176. ;
  13177. _proto.stepForward = function stepForward() {
  13178. this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
  13179. }
  13180. /**
  13181. * Move more quickly rewind for keyboard-only users
  13182. */
  13183. ;
  13184. _proto.stepBack = function stepBack() {
  13185. this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
  13186. }
  13187. /**
  13188. * Toggles the playback state of the player
  13189. * This gets called when enter or space is used on the seekbar
  13190. *
  13191. * @param {EventTarget~Event} event
  13192. * The `keydown` event that caused this function to be called
  13193. *
  13194. */
  13195. ;
  13196. _proto.handleAction = function handleAction(event) {
  13197. if (this.player_.paused()) {
  13198. this.player_.play();
  13199. } else {
  13200. this.player_.pause();
  13201. }
  13202. }
  13203. /**
  13204. * Called when this SeekBar has focus and a key gets pressed down.
  13205. * Supports the following keys:
  13206. *
  13207. * Space or Enter key fire a click event
  13208. * Home key moves to start of the timeline
  13209. * End key moves to end of the timeline
  13210. * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
  13211. * PageDown key moves back a larger step than ArrowDown
  13212. * PageUp key moves forward a large step
  13213. *
  13214. * @param {EventTarget~Event} event
  13215. * The `keydown` event that caused this function to be called.
  13216. *
  13217. * @listens keydown
  13218. */
  13219. ;
  13220. _proto.handleKeyPress = function handleKeyPress(event) {
  13221. if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
  13222. event.preventDefault();
  13223. this.handleAction(event);
  13224. } else if (keycode.isEventKey(event, 'Home')) {
  13225. event.preventDefault();
  13226. this.player_.currentTime(0);
  13227. } else if (keycode.isEventKey(event, 'End')) {
  13228. event.preventDefault();
  13229. this.player_.currentTime(this.player_.duration());
  13230. } else if (/^[0-9]$/.test(keycode(event))) {
  13231. event.preventDefault();
  13232. var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
  13233. this.player_.currentTime(this.player_.duration() * gotoFraction);
  13234. } else if (keycode.isEventKey(event, 'PgDn')) {
  13235. event.preventDefault();
  13236. this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  13237. } else if (keycode.isEventKey(event, 'PgUp')) {
  13238. event.preventDefault();
  13239. this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
  13240. } else {
  13241. // Pass keypress handling up for unsupported keys
  13242. _Slider.prototype.handleKeyPress.call(this, event);
  13243. }
  13244. };
  13245. return SeekBar;
  13246. }(Slider);
  13247. /**
  13248. * Default options for the `SeekBar`
  13249. *
  13250. * @type {Object}
  13251. * @private
  13252. */
  13253. SeekBar.prototype.options_ = {
  13254. children: ['loadProgressBar', 'playProgressBar'],
  13255. barName: 'playProgressBar'
  13256. }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices
  13257. if (!IS_IOS && !IS_ANDROID) {
  13258. SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
  13259. }
  13260. Component.registerComponent('SeekBar', SeekBar);
  13261. /**
  13262. * The Progress Control component contains the seek bar, load progress,
  13263. * and play progress.
  13264. *
  13265. * @extends Component
  13266. */
  13267. var ProgressControl =
  13268. /*#__PURE__*/
  13269. function (_Component) {
  13270. _inheritsLoose(ProgressControl, _Component);
  13271. /**
  13272. * Creates an instance of this class.
  13273. *
  13274. * @param {Player} player
  13275. * The `Player` that this class should be attached to.
  13276. *
  13277. * @param {Object} [options]
  13278. * The key/value store of player options.
  13279. */
  13280. function ProgressControl(player, options) {
  13281. var _this;
  13282. _this = _Component.call(this, player, options) || this;
  13283. _this.handleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25);
  13284. _this.throttledHandleMouseSeek = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseSeek), 25);
  13285. _this.enable();
  13286. return _this;
  13287. }
  13288. /**
  13289. * Create the `Component`'s DOM element
  13290. *
  13291. * @return {Element}
  13292. * The element that was created.
  13293. */
  13294. var _proto = ProgressControl.prototype;
  13295. _proto.createEl = function createEl$$1() {
  13296. return _Component.prototype.createEl.call(this, 'div', {
  13297. className: 'vjs-progress-control vjs-control'
  13298. });
  13299. }
  13300. /**
  13301. * When the mouse moves over the `ProgressControl`, the pointer position
  13302. * gets passed down to the `MouseTimeDisplay` component.
  13303. *
  13304. * @param {EventTarget~Event} event
  13305. * The `mousemove` event that caused this function to run.
  13306. *
  13307. * @listen mousemove
  13308. */
  13309. ;
  13310. _proto.handleMouseMove = function handleMouseMove(event) {
  13311. var seekBar = this.getChild('seekBar');
  13312. if (seekBar) {
  13313. var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
  13314. var seekBarEl = seekBar.el();
  13315. var seekBarRect = getBoundingClientRect(seekBarEl);
  13316. var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
  13317. // that it's possible to trigger this behavior outside the boundaries of
  13318. // the `SeekBar`. This ensures we stay within it at all times.
  13319. if (seekBarPoint > 1) {
  13320. seekBarPoint = 1;
  13321. } else if (seekBarPoint < 0) {
  13322. seekBarPoint = 0;
  13323. }
  13324. if (mouseTimeDisplay) {
  13325. mouseTimeDisplay.update(seekBarRect, seekBarPoint);
  13326. }
  13327. }
  13328. }
  13329. /**
  13330. * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
  13331. *
  13332. * @method ProgressControl#throttledHandleMouseSeek
  13333. * @param {EventTarget~Event} event
  13334. * The `mousemove` event that caused this function to run.
  13335. *
  13336. * @listen mousemove
  13337. * @listen touchmove
  13338. */
  13339. /**
  13340. * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
  13341. *
  13342. * @param {EventTarget~Event} event
  13343. * `mousedown` or `touchstart` event that triggered this function
  13344. *
  13345. * @listens mousemove
  13346. * @listens touchmove
  13347. */
  13348. ;
  13349. _proto.handleMouseSeek = function handleMouseSeek(event) {
  13350. var seekBar = this.getChild('seekBar');
  13351. if (seekBar) {
  13352. seekBar.handleMouseMove(event);
  13353. }
  13354. }
  13355. /**
  13356. * Are controls are currently enabled for this progress control.
  13357. *
  13358. * @return {boolean}
  13359. * true if controls are enabled, false otherwise
  13360. */
  13361. ;
  13362. _proto.enabled = function enabled() {
  13363. return this.enabled_;
  13364. }
  13365. /**
  13366. * Disable all controls on the progress control and its children
  13367. */
  13368. ;
  13369. _proto.disable = function disable() {
  13370. this.children().forEach(function (child) {
  13371. return child.disable && child.disable();
  13372. });
  13373. if (!this.enabled()) {
  13374. return;
  13375. }
  13376. this.off(['mousedown', 'touchstart'], this.handleMouseDown);
  13377. this.off(this.el_, 'mousemove', this.handleMouseMove);
  13378. this.handleMouseUp();
  13379. this.addClass('disabled');
  13380. this.enabled_ = false;
  13381. }
  13382. /**
  13383. * Enable all controls on the progress control and its children
  13384. */
  13385. ;
  13386. _proto.enable = function enable() {
  13387. this.children().forEach(function (child) {
  13388. return child.enable && child.enable();
  13389. });
  13390. if (this.enabled()) {
  13391. return;
  13392. }
  13393. this.on(['mousedown', 'touchstart'], this.handleMouseDown);
  13394. this.on(this.el_, 'mousemove', this.handleMouseMove);
  13395. this.removeClass('disabled');
  13396. this.enabled_ = true;
  13397. }
  13398. /**
  13399. * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
  13400. *
  13401. * @param {EventTarget~Event} event
  13402. * `mousedown` or `touchstart` event that triggered this function
  13403. *
  13404. * @listens mousedown
  13405. * @listens touchstart
  13406. */
  13407. ;
  13408. _proto.handleMouseDown = function handleMouseDown(event) {
  13409. var doc = this.el_.ownerDocument;
  13410. var seekBar = this.getChild('seekBar');
  13411. if (seekBar) {
  13412. seekBar.handleMouseDown(event);
  13413. }
  13414. this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
  13415. this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
  13416. this.on(doc, 'mouseup', this.handleMouseUp);
  13417. this.on(doc, 'touchend', this.handleMouseUp);
  13418. }
  13419. /**
  13420. * Handle `mouseup` or `touchend` events on the `ProgressControl`.
  13421. *
  13422. * @param {EventTarget~Event} event
  13423. * `mouseup` or `touchend` event that triggered this function.
  13424. *
  13425. * @listens touchend
  13426. * @listens mouseup
  13427. */
  13428. ;
  13429. _proto.handleMouseUp = function handleMouseUp(event) {
  13430. var doc = this.el_.ownerDocument;
  13431. var seekBar = this.getChild('seekBar');
  13432. if (seekBar) {
  13433. seekBar.handleMouseUp(event);
  13434. }
  13435. this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
  13436. this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
  13437. this.off(doc, 'mouseup', this.handleMouseUp);
  13438. this.off(doc, 'touchend', this.handleMouseUp);
  13439. };
  13440. return ProgressControl;
  13441. }(Component);
  13442. /**
  13443. * Default options for `ProgressControl`
  13444. *
  13445. * @type {Object}
  13446. * @private
  13447. */
  13448. ProgressControl.prototype.options_ = {
  13449. children: ['seekBar']
  13450. };
  13451. Component.registerComponent('ProgressControl', ProgressControl);
  13452. /**
  13453. * Toggle fullscreen video
  13454. *
  13455. * @extends Button
  13456. */
  13457. var FullscreenToggle =
  13458. /*#__PURE__*/
  13459. function (_Button) {
  13460. _inheritsLoose(FullscreenToggle, _Button);
  13461. /**
  13462. * Creates an instance of this class.
  13463. *
  13464. * @param {Player} player
  13465. * The `Player` that this class should be attached to.
  13466. *
  13467. * @param {Object} [options]
  13468. * The key/value store of player options.
  13469. */
  13470. function FullscreenToggle(player, options) {
  13471. var _this;
  13472. _this = _Button.call(this, player, options) || this;
  13473. _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
  13474. if (document[FullscreenApi.fullscreenEnabled] === false) {
  13475. _this.disable();
  13476. }
  13477. return _this;
  13478. }
  13479. /**
  13480. * Builds the default DOM `className`.
  13481. *
  13482. * @return {string}
  13483. * The DOM `className` for this object.
  13484. */
  13485. var _proto = FullscreenToggle.prototype;
  13486. _proto.buildCSSClass = function buildCSSClass() {
  13487. return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this);
  13488. }
  13489. /**
  13490. * Handles fullscreenchange on the player and change control text accordingly.
  13491. *
  13492. * @param {EventTarget~Event} [event]
  13493. * The {@link Player#fullscreenchange} event that caused this function to be
  13494. * called.
  13495. *
  13496. * @listens Player#fullscreenchange
  13497. */
  13498. ;
  13499. _proto.handleFullscreenChange = function handleFullscreenChange(event) {
  13500. if (this.player_.isFullscreen()) {
  13501. this.controlText('Non-Fullscreen');
  13502. } else {
  13503. this.controlText('Fullscreen');
  13504. }
  13505. }
  13506. /**
  13507. * This gets called when an `FullscreenToggle` is "clicked". See
  13508. * {@link ClickableComponent} for more detailed information on what a click can be.
  13509. *
  13510. * @param {EventTarget~Event} [event]
  13511. * The `keydown`, `tap`, or `click` event that caused this function to be
  13512. * called.
  13513. *
  13514. * @listens tap
  13515. * @listens click
  13516. */
  13517. ;
  13518. _proto.handleClick = function handleClick(event) {
  13519. if (!this.player_.isFullscreen()) {
  13520. this.player_.requestFullscreen();
  13521. } else {
  13522. this.player_.exitFullscreen();
  13523. }
  13524. };
  13525. return FullscreenToggle;
  13526. }(Button);
  13527. /**
  13528. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  13529. *
  13530. * @type {string}
  13531. * @private
  13532. */
  13533. FullscreenToggle.prototype.controlText_ = 'Fullscreen';
  13534. Component.registerComponent('FullscreenToggle', FullscreenToggle);
  13535. /**
  13536. * Check if volume control is supported and if it isn't hide the
  13537. * `Component` that was passed using the `vjs-hidden` class.
  13538. *
  13539. * @param {Component} self
  13540. * The component that should be hidden if volume is unsupported
  13541. *
  13542. * @param {Player} player
  13543. * A reference to the player
  13544. *
  13545. * @private
  13546. */
  13547. var checkVolumeSupport = function checkVolumeSupport(self, player) {
  13548. // hide volume controls when they're not supported by the current tech
  13549. if (player.tech_ && !player.tech_.featuresVolumeControl) {
  13550. self.addClass('vjs-hidden');
  13551. }
  13552. self.on(player, 'loadstart', function () {
  13553. if (!player.tech_.featuresVolumeControl) {
  13554. self.addClass('vjs-hidden');
  13555. } else {
  13556. self.removeClass('vjs-hidden');
  13557. }
  13558. });
  13559. };
  13560. /**
  13561. * Shows volume level
  13562. *
  13563. * @extends Component
  13564. */
  13565. var VolumeLevel =
  13566. /*#__PURE__*/
  13567. function (_Component) {
  13568. _inheritsLoose(VolumeLevel, _Component);
  13569. function VolumeLevel() {
  13570. return _Component.apply(this, arguments) || this;
  13571. }
  13572. var _proto = VolumeLevel.prototype;
  13573. /**
  13574. * Create the `Component`'s DOM element
  13575. *
  13576. * @return {Element}
  13577. * The element that was created.
  13578. */
  13579. _proto.createEl = function createEl() {
  13580. return _Component.prototype.createEl.call(this, 'div', {
  13581. className: 'vjs-volume-level',
  13582. innerHTML: '<span class="vjs-control-text"></span>'
  13583. });
  13584. };
  13585. return VolumeLevel;
  13586. }(Component);
  13587. Component.registerComponent('VolumeLevel', VolumeLevel);
  13588. /**
  13589. * The bar that contains the volume level and can be clicked on to adjust the level
  13590. *
  13591. * @extends Slider
  13592. */
  13593. var VolumeBar =
  13594. /*#__PURE__*/
  13595. function (_Slider) {
  13596. _inheritsLoose(VolumeBar, _Slider);
  13597. /**
  13598. * Creates an instance of this class.
  13599. *
  13600. * @param {Player} player
  13601. * The `Player` that this class should be attached to.
  13602. *
  13603. * @param {Object} [options]
  13604. * The key/value store of player options.
  13605. */
  13606. function VolumeBar(player, options) {
  13607. var _this;
  13608. _this = _Slider.call(this, player, options) || this;
  13609. _this.on('slideractive', _this.updateLastVolume_);
  13610. _this.on(player, 'volumechange', _this.updateARIAAttributes);
  13611. player.ready(function () {
  13612. return _this.updateARIAAttributes();
  13613. });
  13614. return _this;
  13615. }
  13616. /**
  13617. * Create the `Component`'s DOM element
  13618. *
  13619. * @return {Element}
  13620. * The element that was created.
  13621. */
  13622. var _proto = VolumeBar.prototype;
  13623. _proto.createEl = function createEl$$1() {
  13624. return _Slider.prototype.createEl.call(this, 'div', {
  13625. className: 'vjs-volume-bar vjs-slider-bar'
  13626. }, {
  13627. 'aria-label': this.localize('Volume Level'),
  13628. 'aria-live': 'polite'
  13629. });
  13630. }
  13631. /**
  13632. * Handle mouse down on volume bar
  13633. *
  13634. * @param {EventTarget~Event} event
  13635. * The `mousedown` event that caused this to run.
  13636. *
  13637. * @listens mousedown
  13638. */
  13639. ;
  13640. _proto.handleMouseDown = function handleMouseDown(event) {
  13641. if (!isSingleLeftClick(event)) {
  13642. return;
  13643. }
  13644. _Slider.prototype.handleMouseDown.call(this, event);
  13645. }
  13646. /**
  13647. * Handle movement events on the {@link VolumeMenuButton}.
  13648. *
  13649. * @param {EventTarget~Event} event
  13650. * The event that caused this function to run.
  13651. *
  13652. * @listens mousemove
  13653. */
  13654. ;
  13655. _proto.handleMouseMove = function handleMouseMove(event) {
  13656. if (!isSingleLeftClick(event)) {
  13657. return;
  13658. }
  13659. this.checkMuted();
  13660. this.player_.volume(this.calculateDistance(event));
  13661. }
  13662. /**
  13663. * If the player is muted unmute it.
  13664. */
  13665. ;
  13666. _proto.checkMuted = function checkMuted() {
  13667. if (this.player_.muted()) {
  13668. this.player_.muted(false);
  13669. }
  13670. }
  13671. /**
  13672. * Get percent of volume level
  13673. *
  13674. * @return {number}
  13675. * Volume level percent as a decimal number.
  13676. */
  13677. ;
  13678. _proto.getPercent = function getPercent() {
  13679. if (this.player_.muted()) {
  13680. return 0;
  13681. }
  13682. return this.player_.volume();
  13683. }
  13684. /**
  13685. * Increase volume level for keyboard users
  13686. */
  13687. ;
  13688. _proto.stepForward = function stepForward() {
  13689. this.checkMuted();
  13690. this.player_.volume(this.player_.volume() + 0.1);
  13691. }
  13692. /**
  13693. * Decrease volume level for keyboard users
  13694. */
  13695. ;
  13696. _proto.stepBack = function stepBack() {
  13697. this.checkMuted();
  13698. this.player_.volume(this.player_.volume() - 0.1);
  13699. }
  13700. /**
  13701. * Update ARIA accessibility attributes
  13702. *
  13703. * @param {EventTarget~Event} [event]
  13704. * The `volumechange` event that caused this function to run.
  13705. *
  13706. * @listens Player#volumechange
  13707. */
  13708. ;
  13709. _proto.updateARIAAttributes = function updateARIAAttributes(event) {
  13710. var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
  13711. this.el_.setAttribute('aria-valuenow', ariaValue);
  13712. this.el_.setAttribute('aria-valuetext', ariaValue + '%');
  13713. }
  13714. /**
  13715. * Returns the current value of the player volume as a percentage
  13716. *
  13717. * @private
  13718. */
  13719. ;
  13720. _proto.volumeAsPercentage_ = function volumeAsPercentage_() {
  13721. return Math.round(this.player_.volume() * 100);
  13722. }
  13723. /**
  13724. * When user starts dragging the VolumeBar, store the volume and listen for
  13725. * the end of the drag. When the drag ends, if the volume was set to zero,
  13726. * set lastVolume to the stored volume.
  13727. *
  13728. * @listens slideractive
  13729. * @private
  13730. */
  13731. ;
  13732. _proto.updateLastVolume_ = function updateLastVolume_() {
  13733. var _this2 = this;
  13734. var volumeBeforeDrag = this.player_.volume();
  13735. this.one('sliderinactive', function () {
  13736. if (_this2.player_.volume() === 0) {
  13737. _this2.player_.lastVolume_(volumeBeforeDrag);
  13738. }
  13739. });
  13740. };
  13741. return VolumeBar;
  13742. }(Slider);
  13743. /**
  13744. * Default options for the `VolumeBar`
  13745. *
  13746. * @type {Object}
  13747. * @private
  13748. */
  13749. VolumeBar.prototype.options_ = {
  13750. children: ['volumeLevel'],
  13751. barName: 'volumeLevel'
  13752. };
  13753. /**
  13754. * Call the update event for this Slider when this event happens on the player.
  13755. *
  13756. * @type {string}
  13757. */
  13758. VolumeBar.prototype.playerEvent = 'volumechange';
  13759. Component.registerComponent('VolumeBar', VolumeBar);
  13760. /**
  13761. * The component for controlling the volume level
  13762. *
  13763. * @extends Component
  13764. */
  13765. var VolumeControl =
  13766. /*#__PURE__*/
  13767. function (_Component) {
  13768. _inheritsLoose(VolumeControl, _Component);
  13769. /**
  13770. * Creates an instance of this class.
  13771. *
  13772. * @param {Player} player
  13773. * The `Player` that this class should be attached to.
  13774. *
  13775. * @param {Object} [options={}]
  13776. * The key/value store of player options.
  13777. */
  13778. function VolumeControl(player, options) {
  13779. var _this;
  13780. if (options === void 0) {
  13781. options = {};
  13782. }
  13783. options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if
  13784. // the VolumeBar is turned on.
  13785. if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
  13786. options.volumeBar = options.volumeBar || {};
  13787. options.volumeBar.vertical = options.vertical;
  13788. }
  13789. _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing
  13790. checkVolumeSupport(_assertThisInitialized(_assertThisInitialized(_this)), player);
  13791. _this.throttledHandleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25);
  13792. _this.on('mousedown', _this.handleMouseDown);
  13793. _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and
  13794. // is dragging) or in focus we do not want to hide the VolumeBar
  13795. _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
  13796. _this.volumeBar.addClass('vjs-slider-active');
  13797. _this.addClass('vjs-slider-active');
  13798. _this.trigger('slideractive');
  13799. });
  13800. _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
  13801. _this.volumeBar.removeClass('vjs-slider-active');
  13802. _this.removeClass('vjs-slider-active');
  13803. _this.trigger('sliderinactive');
  13804. });
  13805. return _this;
  13806. }
  13807. /**
  13808. * Create the `Component`'s DOM element
  13809. *
  13810. * @return {Element}
  13811. * The element that was created.
  13812. */
  13813. var _proto = VolumeControl.prototype;
  13814. _proto.createEl = function createEl() {
  13815. var orientationClass = 'vjs-volume-horizontal';
  13816. if (this.options_.vertical) {
  13817. orientationClass = 'vjs-volume-vertical';
  13818. }
  13819. return _Component.prototype.createEl.call(this, 'div', {
  13820. className: "vjs-volume-control vjs-control " + orientationClass
  13821. });
  13822. }
  13823. /**
  13824. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  13825. *
  13826. * @param {EventTarget~Event} event
  13827. * `mousedown` or `touchstart` event that triggered this function
  13828. *
  13829. * @listens mousedown
  13830. * @listens touchstart
  13831. */
  13832. ;
  13833. _proto.handleMouseDown = function handleMouseDown(event) {
  13834. var doc = this.el_.ownerDocument;
  13835. this.on(doc, 'mousemove', this.throttledHandleMouseMove);
  13836. this.on(doc, 'touchmove', this.throttledHandleMouseMove);
  13837. this.on(doc, 'mouseup', this.handleMouseUp);
  13838. this.on(doc, 'touchend', this.handleMouseUp);
  13839. }
  13840. /**
  13841. * Handle `mouseup` or `touchend` events on the `VolumeControl`.
  13842. *
  13843. * @param {EventTarget~Event} event
  13844. * `mouseup` or `touchend` event that triggered this function.
  13845. *
  13846. * @listens touchend
  13847. * @listens mouseup
  13848. */
  13849. ;
  13850. _proto.handleMouseUp = function handleMouseUp(event) {
  13851. var doc = this.el_.ownerDocument;
  13852. this.off(doc, 'mousemove', this.throttledHandleMouseMove);
  13853. this.off(doc, 'touchmove', this.throttledHandleMouseMove);
  13854. this.off(doc, 'mouseup', this.handleMouseUp);
  13855. this.off(doc, 'touchend', this.handleMouseUp);
  13856. }
  13857. /**
  13858. * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
  13859. *
  13860. * @param {EventTarget~Event} event
  13861. * `mousedown` or `touchstart` event that triggered this function
  13862. *
  13863. * @listens mousedown
  13864. * @listens touchstart
  13865. */
  13866. ;
  13867. _proto.handleMouseMove = function handleMouseMove(event) {
  13868. this.volumeBar.handleMouseMove(event);
  13869. };
  13870. return VolumeControl;
  13871. }(Component);
  13872. /**
  13873. * Default options for the `VolumeControl`
  13874. *
  13875. * @type {Object}
  13876. * @private
  13877. */
  13878. VolumeControl.prototype.options_ = {
  13879. children: ['volumeBar']
  13880. };
  13881. Component.registerComponent('VolumeControl', VolumeControl);
  13882. /**
  13883. * Check if muting volume is supported and if it isn't hide the mute toggle
  13884. * button.
  13885. *
  13886. * @param {Component} self
  13887. * A reference to the mute toggle button
  13888. *
  13889. * @param {Player} player
  13890. * A reference to the player
  13891. *
  13892. * @private
  13893. */
  13894. var checkMuteSupport = function checkMuteSupport(self, player) {
  13895. // hide mute toggle button if it's not supported by the current tech
  13896. if (player.tech_ && !player.tech_.featuresMuteControl) {
  13897. self.addClass('vjs-hidden');
  13898. }
  13899. self.on(player, 'loadstart', function () {
  13900. if (!player.tech_.featuresMuteControl) {
  13901. self.addClass('vjs-hidden');
  13902. } else {
  13903. self.removeClass('vjs-hidden');
  13904. }
  13905. });
  13906. };
  13907. /**
  13908. * A button component for muting the audio.
  13909. *
  13910. * @extends Button
  13911. */
  13912. var MuteToggle =
  13913. /*#__PURE__*/
  13914. function (_Button) {
  13915. _inheritsLoose(MuteToggle, _Button);
  13916. /**
  13917. * Creates an instance of this class.
  13918. *
  13919. * @param {Player} player
  13920. * The `Player` that this class should be attached to.
  13921. *
  13922. * @param {Object} [options]
  13923. * The key/value store of player options.
  13924. */
  13925. function MuteToggle(player, options) {
  13926. var _this;
  13927. _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing
  13928. checkMuteSupport(_assertThisInitialized(_assertThisInitialized(_this)), player);
  13929. _this.on(player, ['loadstart', 'volumechange'], _this.update);
  13930. return _this;
  13931. }
  13932. /**
  13933. * Builds the default DOM `className`.
  13934. *
  13935. * @return {string}
  13936. * The DOM `className` for this object.
  13937. */
  13938. var _proto = MuteToggle.prototype;
  13939. _proto.buildCSSClass = function buildCSSClass() {
  13940. return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this);
  13941. }
  13942. /**
  13943. * This gets called when an `MuteToggle` is "clicked". See
  13944. * {@link ClickableComponent} for more detailed information on what a click can be.
  13945. *
  13946. * @param {EventTarget~Event} [event]
  13947. * The `keydown`, `tap`, or `click` event that caused this function to be
  13948. * called.
  13949. *
  13950. * @listens tap
  13951. * @listens click
  13952. */
  13953. ;
  13954. _proto.handleClick = function handleClick(event) {
  13955. var vol = this.player_.volume();
  13956. var lastVolume = this.player_.lastVolume_();
  13957. if (vol === 0) {
  13958. var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
  13959. this.player_.volume(volumeToSet);
  13960. this.player_.muted(false);
  13961. } else {
  13962. this.player_.muted(this.player_.muted() ? false : true);
  13963. }
  13964. }
  13965. /**
  13966. * Update the `MuteToggle` button based on the state of `volume` and `muted`
  13967. * on the player.
  13968. *
  13969. * @param {EventTarget~Event} [event]
  13970. * The {@link Player#loadstart} event if this function was called
  13971. * through an event.
  13972. *
  13973. * @listens Player#loadstart
  13974. * @listens Player#volumechange
  13975. */
  13976. ;
  13977. _proto.update = function update(event) {
  13978. this.updateIcon_();
  13979. this.updateControlText_();
  13980. }
  13981. /**
  13982. * Update the appearance of the `MuteToggle` icon.
  13983. *
  13984. * Possible states (given `level` variable below):
  13985. * - 0: crossed out
  13986. * - 1: zero bars of volume
  13987. * - 2: one bar of volume
  13988. * - 3: two bars of volume
  13989. *
  13990. * @private
  13991. */
  13992. ;
  13993. _proto.updateIcon_ = function updateIcon_() {
  13994. var vol = this.player_.volume();
  13995. var level = 3; // in iOS when a player is loaded with muted attribute
  13996. // and volume is changed with a native mute button
  13997. // we want to make sure muted state is updated
  13998. if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
  13999. this.player_.muted(this.player_.tech_.el_.muted);
  14000. }
  14001. if (vol === 0 || this.player_.muted()) {
  14002. level = 0;
  14003. } else if (vol < 0.33) {
  14004. level = 1;
  14005. } else if (vol < 0.67) {
  14006. level = 2;
  14007. } // TODO improve muted icon classes
  14008. for (var i = 0; i < 4; i++) {
  14009. removeClass(this.el_, "vjs-vol-" + i);
  14010. }
  14011. addClass(this.el_, "vjs-vol-" + level);
  14012. }
  14013. /**
  14014. * If `muted` has changed on the player, update the control text
  14015. * (`title` attribute on `vjs-mute-control` element and content of
  14016. * `vjs-control-text` element).
  14017. *
  14018. * @private
  14019. */
  14020. ;
  14021. _proto.updateControlText_ = function updateControlText_() {
  14022. var soundOff = this.player_.muted() || this.player_.volume() === 0;
  14023. var text = soundOff ? 'Unmute' : 'Mute';
  14024. if (this.controlText() !== text) {
  14025. this.controlText(text);
  14026. }
  14027. };
  14028. return MuteToggle;
  14029. }(Button);
  14030. /**
  14031. * The text that should display over the `MuteToggle`s controls. Added for localization.
  14032. *
  14033. * @type {string}
  14034. * @private
  14035. */
  14036. MuteToggle.prototype.controlText_ = 'Mute';
  14037. Component.registerComponent('MuteToggle', MuteToggle);
  14038. /**
  14039. * A Component to contain the MuteToggle and VolumeControl so that
  14040. * they can work together.
  14041. *
  14042. * @extends Component
  14043. */
  14044. var VolumePanel =
  14045. /*#__PURE__*/
  14046. function (_Component) {
  14047. _inheritsLoose(VolumePanel, _Component);
  14048. /**
  14049. * Creates an instance of this class.
  14050. *
  14051. * @param {Player} player
  14052. * The `Player` that this class should be attached to.
  14053. *
  14054. * @param {Object} [options={}]
  14055. * The key/value store of player options.
  14056. */
  14057. function VolumePanel(player, options) {
  14058. var _this;
  14059. if (options === void 0) {
  14060. options = {};
  14061. }
  14062. if (typeof options.inline !== 'undefined') {
  14063. options.inline = options.inline;
  14064. } else {
  14065. options.inline = true;
  14066. } // pass the inline option down to the VolumeControl as vertical if
  14067. // the VolumeControl is on.
  14068. if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
  14069. options.volumeControl = options.volumeControl || {};
  14070. options.volumeControl.vertical = !options.inline;
  14071. }
  14072. _this = _Component.call(this, player, options) || this;
  14073. _this.on(player, ['loadstart'], _this.volumePanelState_); // while the slider is active (the mouse has been pressed down and
  14074. // is dragging) we do not want to hide the VolumeBar
  14075. _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
  14076. _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
  14077. return _this;
  14078. }
  14079. /**
  14080. * Add vjs-slider-active class to the VolumePanel
  14081. *
  14082. * @listens VolumeControl#slideractive
  14083. * @private
  14084. */
  14085. var _proto = VolumePanel.prototype;
  14086. _proto.sliderActive_ = function sliderActive_() {
  14087. this.addClass('vjs-slider-active');
  14088. }
  14089. /**
  14090. * Removes vjs-slider-active class to the VolumePanel
  14091. *
  14092. * @listens VolumeControl#sliderinactive
  14093. * @private
  14094. */
  14095. ;
  14096. _proto.sliderInactive_ = function sliderInactive_() {
  14097. this.removeClass('vjs-slider-active');
  14098. }
  14099. /**
  14100. * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
  14101. * depending on MuteToggle and VolumeControl state
  14102. *
  14103. * @listens Player#loadstart
  14104. * @private
  14105. */
  14106. ;
  14107. _proto.volumePanelState_ = function volumePanelState_() {
  14108. // hide volume panel if neither volume control or mute toggle
  14109. // are displayed
  14110. if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
  14111. this.addClass('vjs-hidden');
  14112. } // if only mute toggle is visible we don't want
  14113. // volume panel expanding when hovered or active
  14114. if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
  14115. this.addClass('vjs-mute-toggle-only');
  14116. }
  14117. }
  14118. /**
  14119. * Create the `Component`'s DOM element
  14120. *
  14121. * @return {Element}
  14122. * The element that was created.
  14123. */
  14124. ;
  14125. _proto.createEl = function createEl() {
  14126. var orientationClass = 'vjs-volume-panel-horizontal';
  14127. if (!this.options_.inline) {
  14128. orientationClass = 'vjs-volume-panel-vertical';
  14129. }
  14130. return _Component.prototype.createEl.call(this, 'div', {
  14131. className: "vjs-volume-panel vjs-control " + orientationClass
  14132. });
  14133. };
  14134. return VolumePanel;
  14135. }(Component);
  14136. /**
  14137. * Default options for the `VolumeControl`
  14138. *
  14139. * @type {Object}
  14140. * @private
  14141. */
  14142. VolumePanel.prototype.options_ = {
  14143. children: ['muteToggle', 'volumeControl']
  14144. };
  14145. Component.registerComponent('VolumePanel', VolumePanel);
  14146. /**
  14147. * The Menu component is used to build popup menus, including subtitle and
  14148. * captions selection menus.
  14149. *
  14150. * @extends Component
  14151. */
  14152. var Menu =
  14153. /*#__PURE__*/
  14154. function (_Component) {
  14155. _inheritsLoose(Menu, _Component);
  14156. /**
  14157. * Create an instance of this class.
  14158. *
  14159. * @param {Player} player
  14160. * the player that this component should attach to
  14161. *
  14162. * @param {Object} [options]
  14163. * Object of option names and values
  14164. *
  14165. */
  14166. function Menu(player, options) {
  14167. var _this;
  14168. _this = _Component.call(this, player, options) || this;
  14169. if (options) {
  14170. _this.menuButton_ = options.menuButton;
  14171. }
  14172. _this.focusedChild_ = -1;
  14173. _this.on('keydown', _this.handleKeyPress); // All the menu item instances share the same blur handler provided by the menu container.
  14174. _this.boundHandleBlur_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleBlur);
  14175. _this.boundHandleTapClick_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTapClick);
  14176. return _this;
  14177. }
  14178. /**
  14179. * Add event listeners to the {@link MenuItem}.
  14180. *
  14181. * @param {Object} component
  14182. * The instance of the `MenuItem` to add listeners to.
  14183. *
  14184. */
  14185. var _proto = Menu.prototype;
  14186. _proto.addEventListenerForItem = function addEventListenerForItem(component) {
  14187. if (!(component instanceof Component)) {
  14188. return;
  14189. }
  14190. this.on(component, 'blur', this.boundHandleBlur_);
  14191. this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
  14192. }
  14193. /**
  14194. * Remove event listeners from the {@link MenuItem}.
  14195. *
  14196. * @param {Object} component
  14197. * The instance of the `MenuItem` to remove listeners.
  14198. *
  14199. */
  14200. ;
  14201. _proto.removeEventListenerForItem = function removeEventListenerForItem(component) {
  14202. if (!(component instanceof Component)) {
  14203. return;
  14204. }
  14205. this.off(component, 'blur', this.boundHandleBlur_);
  14206. this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
  14207. }
  14208. /**
  14209. * This method will be called indirectly when the component has been added
  14210. * before the component adds to the new menu instance by `addItem`.
  14211. * In this case, the original menu instance will remove the component
  14212. * by calling `removeChild`.
  14213. *
  14214. * @param {Object} component
  14215. * The instance of the `MenuItem`
  14216. */
  14217. ;
  14218. _proto.removeChild = function removeChild(component) {
  14219. if (typeof component === 'string') {
  14220. component = this.getChild(component);
  14221. }
  14222. this.removeEventListenerForItem(component);
  14223. _Component.prototype.removeChild.call(this, component);
  14224. }
  14225. /**
  14226. * Add a {@link MenuItem} to the menu.
  14227. *
  14228. * @param {Object|string} component
  14229. * The name or instance of the `MenuItem` to add.
  14230. *
  14231. */
  14232. ;
  14233. _proto.addItem = function addItem(component) {
  14234. var childComponent = this.addChild(component);
  14235. if (childComponent) {
  14236. this.addEventListenerForItem(childComponent);
  14237. }
  14238. }
  14239. /**
  14240. * Create the `Menu`s DOM element.
  14241. *
  14242. * @return {Element}
  14243. * the element that was created
  14244. */
  14245. ;
  14246. _proto.createEl = function createEl$$1() {
  14247. var contentElType = this.options_.contentElType || 'ul';
  14248. this.contentEl_ = createEl(contentElType, {
  14249. className: 'vjs-menu-content'
  14250. });
  14251. this.contentEl_.setAttribute('role', 'menu');
  14252. var el = _Component.prototype.createEl.call(this, 'div', {
  14253. append: this.contentEl_,
  14254. className: 'vjs-menu'
  14255. });
  14256. el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons,
  14257. // where a click on the parent is significant
  14258. on(el, 'click', function (event) {
  14259. event.preventDefault();
  14260. event.stopImmediatePropagation();
  14261. });
  14262. return el;
  14263. };
  14264. _proto.dispose = function dispose() {
  14265. this.contentEl_ = null;
  14266. this.boundHandleBlur_ = null;
  14267. this.boundHandleTapClick_ = null;
  14268. _Component.prototype.dispose.call(this);
  14269. }
  14270. /**
  14271. * Called when a `MenuItem` loses focus.
  14272. *
  14273. * @param {EventTarget~Event} event
  14274. * The `blur` event that caused this function to be called.
  14275. *
  14276. * @listens blur
  14277. */
  14278. ;
  14279. _proto.handleBlur = function handleBlur(event) {
  14280. var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu
  14281. if (!this.children().some(function (element) {
  14282. return element.el() === relatedTarget;
  14283. })) {
  14284. var btn = this.menuButton_;
  14285. if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
  14286. btn.unpressButton();
  14287. }
  14288. }
  14289. }
  14290. /**
  14291. * Called when a `MenuItem` gets clicked or tapped.
  14292. *
  14293. * @param {EventTarget~Event} event
  14294. * The `click` or `tap` event that caused this function to be called.
  14295. *
  14296. * @listens click,tap
  14297. */
  14298. ;
  14299. _proto.handleTapClick = function handleTapClick(event) {
  14300. // Unpress the associated MenuButton, and move focus back to it
  14301. if (this.menuButton_) {
  14302. this.menuButton_.unpressButton();
  14303. var childComponents = this.children();
  14304. if (!Array.isArray(childComponents)) {
  14305. return;
  14306. }
  14307. var foundComponent = childComponents.filter(function (component) {
  14308. return component.el() === event.target;
  14309. })[0];
  14310. if (!foundComponent) {
  14311. return;
  14312. } // don't focus menu button if item is a caption settings item
  14313. // because focus will move elsewhere
  14314. if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
  14315. this.menuButton_.focus();
  14316. }
  14317. }
  14318. }
  14319. /**
  14320. * Handle a `keydown` event on this menu. This listener is added in the constructor.
  14321. *
  14322. * @param {EventTarget~Event} event
  14323. * A `keydown` event that happened on the menu.
  14324. *
  14325. * @listens keydown
  14326. */
  14327. ;
  14328. _proto.handleKeyPress = function handleKeyPress(event) {
  14329. // Left and Down Arrows
  14330. if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
  14331. event.preventDefault();
  14332. this.stepForward(); // Up and Right Arrows
  14333. } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
  14334. event.preventDefault();
  14335. this.stepBack();
  14336. }
  14337. }
  14338. /**
  14339. * Move to next (lower) menu item for keyboard users.
  14340. */
  14341. ;
  14342. _proto.stepForward = function stepForward() {
  14343. var stepChild = 0;
  14344. if (this.focusedChild_ !== undefined) {
  14345. stepChild = this.focusedChild_ + 1;
  14346. }
  14347. this.focus(stepChild);
  14348. }
  14349. /**
  14350. * Move to previous (higher) menu item for keyboard users.
  14351. */
  14352. ;
  14353. _proto.stepBack = function stepBack() {
  14354. var stepChild = 0;
  14355. if (this.focusedChild_ !== undefined) {
  14356. stepChild = this.focusedChild_ - 1;
  14357. }
  14358. this.focus(stepChild);
  14359. }
  14360. /**
  14361. * Set focus on a {@link MenuItem} in the `Menu`.
  14362. *
  14363. * @param {Object|string} [item=0]
  14364. * Index of child item set focus on.
  14365. */
  14366. ;
  14367. _proto.focus = function focus(item) {
  14368. if (item === void 0) {
  14369. item = 0;
  14370. }
  14371. var children = this.children().slice();
  14372. var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
  14373. if (haveTitle) {
  14374. children.shift();
  14375. }
  14376. if (children.length > 0) {
  14377. if (item < 0) {
  14378. item = 0;
  14379. } else if (item >= children.length) {
  14380. item = children.length - 1;
  14381. }
  14382. this.focusedChild_ = item;
  14383. children[item].el_.focus();
  14384. }
  14385. };
  14386. return Menu;
  14387. }(Component);
  14388. Component.registerComponent('Menu', Menu);
  14389. /**
  14390. * A `MenuButton` class for any popup {@link Menu}.
  14391. *
  14392. * @extends Component
  14393. */
  14394. var MenuButton =
  14395. /*#__PURE__*/
  14396. function (_Component) {
  14397. _inheritsLoose(MenuButton, _Component);
  14398. /**
  14399. * Creates an instance of this class.
  14400. *
  14401. * @param {Player} player
  14402. * The `Player` that this class should be attached to.
  14403. *
  14404. * @param {Object} [options={}]
  14405. * The key/value store of player options.
  14406. */
  14407. function MenuButton(player, options) {
  14408. var _this;
  14409. if (options === void 0) {
  14410. options = {};
  14411. }
  14412. _this = _Component.call(this, player, options) || this;
  14413. _this.menuButton_ = new Button(player, options);
  14414. _this.menuButton_.controlText(_this.controlText_);
  14415. _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper
  14416. var buttonClass = Button.prototype.buildCSSClass();
  14417. _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
  14418. _this.menuButton_.removeClass('vjs-control');
  14419. _this.addChild(_this.menuButton_);
  14420. _this.update();
  14421. _this.enabled_ = true;
  14422. _this.on(_this.menuButton_, 'tap', _this.handleClick);
  14423. _this.on(_this.menuButton_, 'click', _this.handleClick);
  14424. _this.on(_this.menuButton_, 'focus', _this.handleFocus);
  14425. _this.on(_this.menuButton_, 'blur', _this.handleBlur);
  14426. _this.on(_this.menuButton_, 'mouseenter', function () {
  14427. _this.menu.show();
  14428. });
  14429. _this.on('keydown', _this.handleSubmenuKeyPress);
  14430. return _this;
  14431. }
  14432. /**
  14433. * Update the menu based on the current state of its items.
  14434. */
  14435. var _proto = MenuButton.prototype;
  14436. _proto.update = function update() {
  14437. var menu = this.createMenu();
  14438. if (this.menu) {
  14439. this.menu.dispose();
  14440. this.removeChild(this.menu);
  14441. }
  14442. this.menu = menu;
  14443. this.addChild(menu);
  14444. /**
  14445. * Track the state of the menu button
  14446. *
  14447. * @type {Boolean}
  14448. * @private
  14449. */
  14450. this.buttonPressed_ = false;
  14451. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  14452. if (this.items && this.items.length <= this.hideThreshold_) {
  14453. this.hide();
  14454. } else {
  14455. this.show();
  14456. }
  14457. }
  14458. /**
  14459. * Create the menu and add all items to it.
  14460. *
  14461. * @return {Menu}
  14462. * The constructed menu
  14463. */
  14464. ;
  14465. _proto.createMenu = function createMenu() {
  14466. var menu = new Menu(this.player_, {
  14467. menuButton: this
  14468. });
  14469. /**
  14470. * Hide the menu if the number of items is less than or equal to this threshold. This defaults
  14471. * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
  14472. * it here because every time we run `createMenu` we need to reset the value.
  14473. *
  14474. * @protected
  14475. * @type {Number}
  14476. */
  14477. this.hideThreshold_ = 0; // Add a title list item to the top
  14478. if (this.options_.title) {
  14479. var titleEl = createEl('li', {
  14480. className: 'vjs-menu-title',
  14481. innerHTML: toTitleCase(this.options_.title),
  14482. tabIndex: -1
  14483. });
  14484. this.hideThreshold_ += 1;
  14485. var titleComponent = new Component(this.player_, {
  14486. el: titleEl
  14487. });
  14488. menu.addItem(titleComponent);
  14489. }
  14490. this.items = this.createItems();
  14491. if (this.items) {
  14492. // Add menu items to the menu
  14493. for (var i = 0; i < this.items.length; i++) {
  14494. menu.addItem(this.items[i]);
  14495. }
  14496. }
  14497. return menu;
  14498. }
  14499. /**
  14500. * Create the list of menu items. Specific to each subclass.
  14501. *
  14502. * @abstract
  14503. */
  14504. ;
  14505. _proto.createItems = function createItems() {}
  14506. /**
  14507. * Create the `MenuButtons`s DOM element.
  14508. *
  14509. * @return {Element}
  14510. * The element that gets created.
  14511. */
  14512. ;
  14513. _proto.createEl = function createEl$$1() {
  14514. return _Component.prototype.createEl.call(this, 'div', {
  14515. className: this.buildWrapperCSSClass()
  14516. }, {});
  14517. }
  14518. /**
  14519. * Allow sub components to stack CSS class names for the wrapper element
  14520. *
  14521. * @return {string}
  14522. * The constructed wrapper DOM `className`
  14523. */
  14524. ;
  14525. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  14526. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  14527. if (this.options_.inline === true) {
  14528. menuButtonClass += '-inline';
  14529. } else {
  14530. menuButtonClass += '-popup';
  14531. } // TODO: Fix the CSS so that this isn't necessary
  14532. var buttonClass = Button.prototype.buildCSSClass();
  14533. return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this);
  14534. }
  14535. /**
  14536. * Builds the default DOM `className`.
  14537. *
  14538. * @return {string}
  14539. * The DOM `className` for this object.
  14540. */
  14541. ;
  14542. _proto.buildCSSClass = function buildCSSClass() {
  14543. var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
  14544. if (this.options_.inline === true) {
  14545. menuButtonClass += '-inline';
  14546. } else {
  14547. menuButtonClass += '-popup';
  14548. }
  14549. return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this);
  14550. }
  14551. /**
  14552. * Get or set the localized control text that will be used for accessibility.
  14553. *
  14554. * > NOTE: This will come from the internal `menuButton_` element.
  14555. *
  14556. * @param {string} [text]
  14557. * Control text for element.
  14558. *
  14559. * @param {Element} [el=this.menuButton_.el()]
  14560. * Element to set the title on.
  14561. *
  14562. * @return {string}
  14563. * - The control text when getting
  14564. */
  14565. ;
  14566. _proto.controlText = function controlText(text, el) {
  14567. if (el === void 0) {
  14568. el = this.menuButton_.el();
  14569. }
  14570. return this.menuButton_.controlText(text, el);
  14571. }
  14572. /**
  14573. * Handle a click on a `MenuButton`.
  14574. * See {@link ClickableComponent#handleClick} for instances where this is called.
  14575. *
  14576. * @param {EventTarget~Event} event
  14577. * The `keydown`, `tap`, or `click` event that caused this function to be
  14578. * called.
  14579. *
  14580. * @listens tap
  14581. * @listens click
  14582. */
  14583. ;
  14584. _proto.handleClick = function handleClick(event) {
  14585. if (this.buttonPressed_) {
  14586. this.unpressButton();
  14587. } else {
  14588. this.pressButton();
  14589. }
  14590. }
  14591. /**
  14592. * Set the focus to the actual button, not to this element
  14593. */
  14594. ;
  14595. _proto.focus = function focus() {
  14596. this.menuButton_.focus();
  14597. }
  14598. /**
  14599. * Remove the focus from the actual button, not this element
  14600. */
  14601. ;
  14602. _proto.blur = function blur() {
  14603. this.menuButton_.blur();
  14604. }
  14605. /**
  14606. * This gets called when a `MenuButton` gains focus via a `focus` event.
  14607. * Turns on listening for `keydown` events. When they happen it
  14608. * calls `this.handleKeyPress`.
  14609. *
  14610. * @param {EventTarget~Event} event
  14611. * The `focus` event that caused this function to be called.
  14612. *
  14613. * @listens focus
  14614. */
  14615. ;
  14616. _proto.handleFocus = function handleFocus() {
  14617. on(document, 'keydown', bind(this, this.handleKeyPress));
  14618. }
  14619. /**
  14620. * Called when a `MenuButton` loses focus. Turns off the listener for
  14621. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  14622. *
  14623. * @param {EventTarget~Event} event
  14624. * The `blur` event that caused this function to be called.
  14625. *
  14626. * @listens blur
  14627. */
  14628. ;
  14629. _proto.handleBlur = function handleBlur() {
  14630. off(document, 'keydown', bind(this, this.handleKeyPress));
  14631. }
  14632. /**
  14633. * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
  14634. * {@link ClickableComponent#handleKeyPress} for instances where this is called.
  14635. *
  14636. * @param {EventTarget~Event} event
  14637. * The `keydown` event that caused this function to be called.
  14638. *
  14639. * @listens keydown
  14640. */
  14641. ;
  14642. _proto.handleKeyPress = function handleKeyPress(event) {
  14643. // Escape or Tab unpress the 'button'
  14644. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  14645. if (this.buttonPressed_) {
  14646. this.unpressButton();
  14647. } // Don't preventDefault for Tab key - we still want to lose focus
  14648. if (!keycode.isEventKey(event, 'Tab')) {
  14649. event.preventDefault(); // Set focus back to the menu button's button
  14650. this.menuButton_.focus();
  14651. } // Up Arrow or Down Arrow also 'press' the button to open the menu
  14652. } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
  14653. if (!this.buttonPressed_) {
  14654. event.preventDefault();
  14655. this.pressButton();
  14656. }
  14657. }
  14658. }
  14659. /**
  14660. * Handle a `keydown` event on a sub-menu. The listener for this is added in
  14661. * the constructor.
  14662. *
  14663. * @param {EventTarget~Event} event
  14664. * Key press event
  14665. *
  14666. * @listens keydown
  14667. */
  14668. ;
  14669. _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
  14670. // Escape or Tab unpress the 'button'
  14671. if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
  14672. if (this.buttonPressed_) {
  14673. this.unpressButton();
  14674. } // Don't preventDefault for Tab key - we still want to lose focus
  14675. if (!keycode.isEventKey(event, 'Tab')) {
  14676. event.preventDefault(); // Set focus back to the menu button's button
  14677. this.menuButton_.focus();
  14678. }
  14679. }
  14680. }
  14681. /**
  14682. * Put the current `MenuButton` into a pressed state.
  14683. */
  14684. ;
  14685. _proto.pressButton = function pressButton() {
  14686. if (this.enabled_) {
  14687. this.buttonPressed_ = true;
  14688. this.menu.show();
  14689. this.menu.lockShowing();
  14690. this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in
  14691. // undesired scrolling behavior when the player is in an iframe
  14692. if (IS_IOS && isInFrame()) {
  14693. // Return early so that the menu isn't focused
  14694. return;
  14695. }
  14696. this.menu.focus();
  14697. }
  14698. }
  14699. /**
  14700. * Take the current `MenuButton` out of a pressed state.
  14701. */
  14702. ;
  14703. _proto.unpressButton = function unpressButton() {
  14704. if (this.enabled_) {
  14705. this.buttonPressed_ = false;
  14706. this.menu.unlockShowing();
  14707. this.menu.hide();
  14708. this.menuButton_.el_.setAttribute('aria-expanded', 'false');
  14709. }
  14710. }
  14711. /**
  14712. * Disable the `MenuButton`. Don't allow it to be clicked.
  14713. */
  14714. ;
  14715. _proto.disable = function disable() {
  14716. this.unpressButton();
  14717. this.enabled_ = false;
  14718. this.addClass('vjs-disabled');
  14719. this.menuButton_.disable();
  14720. }
  14721. /**
  14722. * Enable the `MenuButton`. Allow it to be clicked.
  14723. */
  14724. ;
  14725. _proto.enable = function enable() {
  14726. this.enabled_ = true;
  14727. this.removeClass('vjs-disabled');
  14728. this.menuButton_.enable();
  14729. };
  14730. return MenuButton;
  14731. }(Component);
  14732. Component.registerComponent('MenuButton', MenuButton);
  14733. /**
  14734. * The base class for buttons that toggle specific track types (e.g. subtitles).
  14735. *
  14736. * @extends MenuButton
  14737. */
  14738. var TrackButton =
  14739. /*#__PURE__*/
  14740. function (_MenuButton) {
  14741. _inheritsLoose(TrackButton, _MenuButton);
  14742. /**
  14743. * Creates an instance of this class.
  14744. *
  14745. * @param {Player} player
  14746. * The `Player` that this class should be attached to.
  14747. *
  14748. * @param {Object} [options]
  14749. * The key/value store of player options.
  14750. */
  14751. function TrackButton(player, options) {
  14752. var _this;
  14753. var tracks = options.tracks;
  14754. _this = _MenuButton.call(this, player, options) || this;
  14755. if (_this.items.length <= 1) {
  14756. _this.hide();
  14757. }
  14758. if (!tracks) {
  14759. return _assertThisInitialized(_this);
  14760. }
  14761. var updateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update);
  14762. tracks.addEventListener('removetrack', updateHandler);
  14763. tracks.addEventListener('addtrack', updateHandler);
  14764. _this.player_.on('ready', updateHandler);
  14765. _this.player_.on('dispose', function () {
  14766. tracks.removeEventListener('removetrack', updateHandler);
  14767. tracks.removeEventListener('addtrack', updateHandler);
  14768. });
  14769. return _this;
  14770. }
  14771. return TrackButton;
  14772. }(MenuButton);
  14773. Component.registerComponent('TrackButton', TrackButton);
  14774. /**
  14775. * @file menu-keys.js
  14776. */
  14777. /**
  14778. * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
  14779. * Note that 'Enter' and 'Space' are not included here (otherwise they would
  14780. * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
  14781. * @typedef MenuKeys
  14782. * @array
  14783. */
  14784. var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
  14785. /**
  14786. * The component for a menu item. `<li>`
  14787. *
  14788. * @extends ClickableComponent
  14789. */
  14790. var MenuItem =
  14791. /*#__PURE__*/
  14792. function (_ClickableComponent) {
  14793. _inheritsLoose(MenuItem, _ClickableComponent);
  14794. /**
  14795. * Creates an instance of the this class.
  14796. *
  14797. * @param {Player} player
  14798. * The `Player` that this class should be attached to.
  14799. *
  14800. * @param {Object} [options={}]
  14801. * The key/value store of player options.
  14802. *
  14803. */
  14804. function MenuItem(player, options) {
  14805. var _this;
  14806. _this = _ClickableComponent.call(this, player, options) || this;
  14807. _this.selectable = options.selectable;
  14808. _this.isSelected_ = options.selected || false;
  14809. _this.multiSelectable = options.multiSelectable;
  14810. _this.selected(_this.isSelected_);
  14811. if (_this.selectable) {
  14812. if (_this.multiSelectable) {
  14813. _this.el_.setAttribute('role', 'menuitemcheckbox');
  14814. } else {
  14815. _this.el_.setAttribute('role', 'menuitemradio');
  14816. }
  14817. } else {
  14818. _this.el_.setAttribute('role', 'menuitem');
  14819. }
  14820. return _this;
  14821. }
  14822. /**
  14823. * Create the `MenuItem's DOM element
  14824. *
  14825. * @param {string} [type=li]
  14826. * Element's node type, not actually used, always set to `li`.
  14827. *
  14828. * @param {Object} [props={}]
  14829. * An object of properties that should be set on the element
  14830. *
  14831. * @param {Object} [attrs={}]
  14832. * An object of attributes that should be set on the element
  14833. *
  14834. * @return {Element}
  14835. * The element that gets created.
  14836. */
  14837. var _proto = MenuItem.prototype;
  14838. _proto.createEl = function createEl(type, props, attrs) {
  14839. // The control is textual, not just an icon
  14840. this.nonIconControl = true;
  14841. return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
  14842. className: 'vjs-menu-item',
  14843. innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>",
  14844. tabIndex: -1
  14845. }, props), attrs);
  14846. }
  14847. /**
  14848. * Ignore keys which are used by the menu, but pass any other ones up. See
  14849. * {@link ClickableComponent#handleKeyPress} for instances where this is called.
  14850. *
  14851. * @param {EventTarget~Event} event
  14852. * The `keydown` event that caused this function to be called.
  14853. *
  14854. * @listens keydown
  14855. */
  14856. ;
  14857. _proto.handleKeyPress = function handleKeyPress(event) {
  14858. if (!MenuKeys.some(function (key) {
  14859. return keycode.isEventKey(event, key);
  14860. })) {
  14861. // Pass keypress handling up for unused keys
  14862. _ClickableComponent.prototype.handleKeyPress.call(this, event);
  14863. }
  14864. }
  14865. /**
  14866. * Any click on a `MenuItem` puts it into the selected state.
  14867. * See {@link ClickableComponent#handleClick} for instances where this is called.
  14868. *
  14869. * @param {EventTarget~Event} event
  14870. * The `keydown`, `tap`, or `click` event that caused this function to be
  14871. * called.
  14872. *
  14873. * @listens tap
  14874. * @listens click
  14875. */
  14876. ;
  14877. _proto.handleClick = function handleClick(event) {
  14878. this.selected(true);
  14879. }
  14880. /**
  14881. * Set the state for this menu item as selected or not.
  14882. *
  14883. * @param {boolean} selected
  14884. * if the menu item is selected or not
  14885. */
  14886. ;
  14887. _proto.selected = function selected(_selected) {
  14888. if (this.selectable) {
  14889. if (_selected) {
  14890. this.addClass('vjs-selected');
  14891. this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers,
  14892. // so indicate selected state to screen reader in the control text.
  14893. this.controlText(', selected');
  14894. this.isSelected_ = true;
  14895. } else {
  14896. this.removeClass('vjs-selected');
  14897. this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader
  14898. this.controlText('');
  14899. this.isSelected_ = false;
  14900. }
  14901. }
  14902. };
  14903. return MenuItem;
  14904. }(ClickableComponent);
  14905. Component.registerComponent('MenuItem', MenuItem);
  14906. /**
  14907. * The specific menu item type for selecting a language within a text track kind
  14908. *
  14909. * @extends MenuItem
  14910. */
  14911. var TextTrackMenuItem =
  14912. /*#__PURE__*/
  14913. function (_MenuItem) {
  14914. _inheritsLoose(TextTrackMenuItem, _MenuItem);
  14915. /**
  14916. * Creates an instance of this class.
  14917. *
  14918. * @param {Player} player
  14919. * The `Player` that this class should be attached to.
  14920. *
  14921. * @param {Object} [options]
  14922. * The key/value store of player options.
  14923. */
  14924. function TextTrackMenuItem(player, options) {
  14925. var _this;
  14926. var track = options.track;
  14927. var tracks = player.textTracks(); // Modify options for parent MenuItem class's init.
  14928. options.label = track.label || track.language || 'Unknown';
  14929. options.selected = track.mode === 'showing';
  14930. _this = _MenuItem.call(this, player, options) || this;
  14931. _this.track = track;
  14932. var changeHandler = function changeHandler() {
  14933. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  14934. args[_key] = arguments[_key];
  14935. }
  14936. _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  14937. };
  14938. var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
  14939. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  14940. args[_key2] = arguments[_key2];
  14941. }
  14942. _this.handleSelectedLanguageChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  14943. };
  14944. player.on(['loadstart', 'texttrackchange'], changeHandler);
  14945. tracks.addEventListener('change', changeHandler);
  14946. tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  14947. _this.on('dispose', function () {
  14948. player.off(['loadstart', 'texttrackchange'], changeHandler);
  14949. tracks.removeEventListener('change', changeHandler);
  14950. tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
  14951. }); // iOS7 doesn't dispatch change events to TextTrackLists when an
  14952. // associated track's mode changes. Without something like
  14953. // Object.observe() (also not present on iOS7), it's not
  14954. // possible to detect changes to the mode attribute and polyfill
  14955. // the change event. As a poor substitute, we manually dispatch
  14956. // change events whenever the controls modify the mode.
  14957. if (tracks.onchange === undefined) {
  14958. var event;
  14959. _this.on(['tap', 'click'], function () {
  14960. if (typeof window$1.Event !== 'object') {
  14961. // Android 2.3 throws an Illegal Constructor error for window.Event
  14962. try {
  14963. event = new window$1.Event('change');
  14964. } catch (err) {// continue regardless of error
  14965. }
  14966. }
  14967. if (!event) {
  14968. event = document.createEvent('Event');
  14969. event.initEvent('change', true, true);
  14970. }
  14971. tracks.dispatchEvent(event);
  14972. });
  14973. } // set the default state based on current tracks
  14974. _this.handleTracksChange();
  14975. return _this;
  14976. }
  14977. /**
  14978. * This gets called when an `TextTrackMenuItem` is "clicked". See
  14979. * {@link ClickableComponent} for more detailed information on what a click can be.
  14980. *
  14981. * @param {EventTarget~Event} event
  14982. * The `keydown`, `tap`, or `click` event that caused this function to be
  14983. * called.
  14984. *
  14985. * @listens tap
  14986. * @listens click
  14987. */
  14988. var _proto = TextTrackMenuItem.prototype;
  14989. _proto.handleClick = function handleClick(event) {
  14990. var referenceTrack = this.track;
  14991. var tracks = this.player_.textTracks();
  14992. _MenuItem.prototype.handleClick.call(this, event);
  14993. if (!tracks) {
  14994. return;
  14995. } // Determine the relevant kind(s) of tracks for this component and filter
  14996. // out empty kinds.
  14997. var kinds = (referenceTrack.kinds || [referenceTrack.kind]).filter(Boolean);
  14998. for (var i = 0; i < tracks.length; i++) {
  14999. var track = tracks[i]; // If the track from the text tracks list is not of the right kind,
  15000. // skip it. We do not want to affect tracks of incompatible kind(s).
  15001. if (kinds.indexOf(track.kind) === -1) {
  15002. continue;
  15003. } // If this text track is the component's track and it is not showing,
  15004. // set it to showing.
  15005. if (track === referenceTrack) {
  15006. if (track.mode !== 'showing') {
  15007. track.mode = 'showing';
  15008. } // If this text track is not the component's track and it is not
  15009. // disabled, set it to disabled.
  15010. } else if (track.mode !== 'disabled') {
  15011. track.mode = 'disabled';
  15012. }
  15013. }
  15014. }
  15015. /**
  15016. * Handle text track list change
  15017. *
  15018. * @param {EventTarget~Event} event
  15019. * The `change` event that caused this function to be called.
  15020. *
  15021. * @listens TextTrackList#change
  15022. */
  15023. ;
  15024. _proto.handleTracksChange = function handleTracksChange(event) {
  15025. var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause
  15026. // screen readers to read the appended control text unnecessarily
  15027. if (shouldBeSelected !== this.isSelected_) {
  15028. this.selected(shouldBeSelected);
  15029. }
  15030. };
  15031. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  15032. if (this.track.mode === 'showing') {
  15033. var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language
  15034. if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
  15035. return;
  15036. }
  15037. this.player_.cache_.selectedLanguage = {
  15038. enabled: true,
  15039. language: this.track.language,
  15040. kind: this.track.kind
  15041. };
  15042. }
  15043. };
  15044. _proto.dispose = function dispose() {
  15045. // remove reference to track object on dispose
  15046. this.track = null;
  15047. _MenuItem.prototype.dispose.call(this);
  15048. };
  15049. return TextTrackMenuItem;
  15050. }(MenuItem);
  15051. Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
  15052. /**
  15053. * A special menu item for turning of a specific type of text track
  15054. *
  15055. * @extends TextTrackMenuItem
  15056. */
  15057. var OffTextTrackMenuItem =
  15058. /*#__PURE__*/
  15059. function (_TextTrackMenuItem) {
  15060. _inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
  15061. /**
  15062. * Creates an instance of this class.
  15063. *
  15064. * @param {Player} player
  15065. * The `Player` that this class should be attached to.
  15066. *
  15067. * @param {Object} [options]
  15068. * The key/value store of player options.
  15069. */
  15070. function OffTextTrackMenuItem(player, options) {
  15071. // Create pseudo track info
  15072. // Requires options['kind']
  15073. options.track = {
  15074. player: player,
  15075. kind: options.kind,
  15076. kinds: options.kinds,
  15077. default: false,
  15078. mode: 'disabled'
  15079. };
  15080. if (!options.kinds) {
  15081. options.kinds = [options.kind];
  15082. }
  15083. if (options.label) {
  15084. options.track.label = options.label;
  15085. } else {
  15086. options.track.label = options.kinds.join(' and ') + ' off';
  15087. } // MenuItem is selectable
  15088. options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  15089. options.multiSelectable = false;
  15090. return _TextTrackMenuItem.call(this, player, options) || this;
  15091. }
  15092. /**
  15093. * Handle text track change
  15094. *
  15095. * @param {EventTarget~Event} event
  15096. * The event that caused this function to run
  15097. */
  15098. var _proto = OffTextTrackMenuItem.prototype;
  15099. _proto.handleTracksChange = function handleTracksChange(event) {
  15100. var tracks = this.player().textTracks();
  15101. var shouldBeSelected = true;
  15102. for (var i = 0, l = tracks.length; i < l; i++) {
  15103. var track = tracks[i];
  15104. if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
  15105. shouldBeSelected = false;
  15106. break;
  15107. }
  15108. } // Prevent redundant selected() calls because they may cause
  15109. // screen readers to read the appended control text unnecessarily
  15110. if (shouldBeSelected !== this.isSelected_) {
  15111. this.selected(shouldBeSelected);
  15112. }
  15113. };
  15114. _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
  15115. var tracks = this.player().textTracks();
  15116. var allHidden = true;
  15117. for (var i = 0, l = tracks.length; i < l; i++) {
  15118. var track = tracks[i];
  15119. if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
  15120. allHidden = false;
  15121. break;
  15122. }
  15123. }
  15124. if (allHidden) {
  15125. this.player_.cache_.selectedLanguage = {
  15126. enabled: false
  15127. };
  15128. }
  15129. };
  15130. return OffTextTrackMenuItem;
  15131. }(TextTrackMenuItem);
  15132. Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
  15133. /**
  15134. * The base class for buttons that toggle specific text track types (e.g. subtitles)
  15135. *
  15136. * @extends MenuButton
  15137. */
  15138. var TextTrackButton =
  15139. /*#__PURE__*/
  15140. function (_TrackButton) {
  15141. _inheritsLoose(TextTrackButton, _TrackButton);
  15142. /**
  15143. * Creates an instance of this class.
  15144. *
  15145. * @param {Player} player
  15146. * The `Player` that this class should be attached to.
  15147. *
  15148. * @param {Object} [options={}]
  15149. * The key/value store of player options.
  15150. */
  15151. function TextTrackButton(player, options) {
  15152. if (options === void 0) {
  15153. options = {};
  15154. }
  15155. options.tracks = player.textTracks();
  15156. return _TrackButton.call(this, player, options) || this;
  15157. }
  15158. /**
  15159. * Create a menu item for each text track
  15160. *
  15161. * @param {TextTrackMenuItem[]} [items=[]]
  15162. * Existing array of items to use during creation
  15163. *
  15164. * @return {TextTrackMenuItem[]}
  15165. * Array of menu items that were created
  15166. */
  15167. var _proto = TextTrackButton.prototype;
  15168. _proto.createItems = function createItems(items, TrackMenuItem) {
  15169. if (items === void 0) {
  15170. items = [];
  15171. }
  15172. if (TrackMenuItem === void 0) {
  15173. TrackMenuItem = TextTrackMenuItem;
  15174. }
  15175. // Label is an override for the [track] off label
  15176. // USed to localise captions/subtitles
  15177. var label;
  15178. if (this.label_) {
  15179. label = this.label_ + " off";
  15180. } // Add an OFF menu item to turn all tracks off
  15181. items.push(new OffTextTrackMenuItem(this.player_, {
  15182. kinds: this.kinds_,
  15183. kind: this.kind_,
  15184. label: label
  15185. }));
  15186. this.hideThreshold_ += 1;
  15187. var tracks = this.player_.textTracks();
  15188. if (!Array.isArray(this.kinds_)) {
  15189. this.kinds_ = [this.kind_];
  15190. }
  15191. for (var i = 0; i < tracks.length; i++) {
  15192. var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label
  15193. if (this.kinds_.indexOf(track.kind) > -1) {
  15194. var item = new TrackMenuItem(this.player_, {
  15195. track: track,
  15196. // MenuItem is selectable
  15197. selectable: true,
  15198. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  15199. multiSelectable: false
  15200. });
  15201. item.addClass("vjs-" + track.kind + "-menu-item");
  15202. items.push(item);
  15203. }
  15204. }
  15205. return items;
  15206. };
  15207. return TextTrackButton;
  15208. }(TrackButton);
  15209. Component.registerComponent('TextTrackButton', TextTrackButton);
  15210. /**
  15211. * The chapter track menu item
  15212. *
  15213. * @extends MenuItem
  15214. */
  15215. var ChaptersTrackMenuItem =
  15216. /*#__PURE__*/
  15217. function (_MenuItem) {
  15218. _inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
  15219. /**
  15220. * Creates an instance of this class.
  15221. *
  15222. * @param {Player} player
  15223. * The `Player` that this class should be attached to.
  15224. *
  15225. * @param {Object} [options]
  15226. * The key/value store of player options.
  15227. */
  15228. function ChaptersTrackMenuItem(player, options) {
  15229. var _this;
  15230. var track = options.track;
  15231. var cue = options.cue;
  15232. var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init.
  15233. options.selectable = true;
  15234. options.multiSelectable = false;
  15235. options.label = cue.text;
  15236. options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
  15237. _this = _MenuItem.call(this, player, options) || this;
  15238. _this.track = track;
  15239. _this.cue = cue;
  15240. track.addEventListener('cuechange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update));
  15241. return _this;
  15242. }
  15243. /**
  15244. * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
  15245. * {@link ClickableComponent} for more detailed information on what a click can be.
  15246. *
  15247. * @param {EventTarget~Event} [event]
  15248. * The `keydown`, `tap`, or `click` event that caused this function to be
  15249. * called.
  15250. *
  15251. * @listens tap
  15252. * @listens click
  15253. */
  15254. var _proto = ChaptersTrackMenuItem.prototype;
  15255. _proto.handleClick = function handleClick(event) {
  15256. _MenuItem.prototype.handleClick.call(this);
  15257. this.player_.currentTime(this.cue.startTime);
  15258. this.update(this.cue.startTime);
  15259. }
  15260. /**
  15261. * Update chapter menu item
  15262. *
  15263. * @param {EventTarget~Event} [event]
  15264. * The `cuechange` event that caused this function to run.
  15265. *
  15266. * @listens TextTrack#cuechange
  15267. */
  15268. ;
  15269. _proto.update = function update(event) {
  15270. var cue = this.cue;
  15271. var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime);
  15272. this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
  15273. };
  15274. return ChaptersTrackMenuItem;
  15275. }(MenuItem);
  15276. Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
  15277. /**
  15278. * The button component for toggling and selecting chapters
  15279. * Chapters act much differently than other text tracks
  15280. * Cues are navigation vs. other tracks of alternative languages
  15281. *
  15282. * @extends TextTrackButton
  15283. */
  15284. var ChaptersButton =
  15285. /*#__PURE__*/
  15286. function (_TextTrackButton) {
  15287. _inheritsLoose(ChaptersButton, _TextTrackButton);
  15288. /**
  15289. * Creates an instance of this class.
  15290. *
  15291. * @param {Player} player
  15292. * The `Player` that this class should be attached to.
  15293. *
  15294. * @param {Object} [options]
  15295. * The key/value store of player options.
  15296. *
  15297. * @param {Component~ReadyCallback} [ready]
  15298. * The function to call when this function is ready.
  15299. */
  15300. function ChaptersButton(player, options, ready) {
  15301. return _TextTrackButton.call(this, player, options, ready) || this;
  15302. }
  15303. /**
  15304. * Builds the default DOM `className`.
  15305. *
  15306. * @return {string}
  15307. * The DOM `className` for this object.
  15308. */
  15309. var _proto = ChaptersButton.prototype;
  15310. _proto.buildCSSClass = function buildCSSClass() {
  15311. return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  15312. };
  15313. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15314. return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  15315. }
  15316. /**
  15317. * Update the menu based on the current state of its items.
  15318. *
  15319. * @param {EventTarget~Event} [event]
  15320. * An event that triggered this function to run.
  15321. *
  15322. * @listens TextTrackList#addtrack
  15323. * @listens TextTrackList#removetrack
  15324. * @listens TextTrackList#change
  15325. */
  15326. ;
  15327. _proto.update = function update(event) {
  15328. if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
  15329. this.setTrack(this.findChaptersTrack());
  15330. }
  15331. _TextTrackButton.prototype.update.call(this);
  15332. }
  15333. /**
  15334. * Set the currently selected track for the chapters button.
  15335. *
  15336. * @param {TextTrack} track
  15337. * The new track to select. Nothing will change if this is the currently selected
  15338. * track.
  15339. */
  15340. ;
  15341. _proto.setTrack = function setTrack(track) {
  15342. if (this.track_ === track) {
  15343. return;
  15344. }
  15345. if (!this.updateHandler_) {
  15346. this.updateHandler_ = this.update.bind(this);
  15347. } // here this.track_ refers to the old track instance
  15348. if (this.track_) {
  15349. var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  15350. if (remoteTextTrackEl) {
  15351. remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
  15352. }
  15353. this.track_ = null;
  15354. }
  15355. this.track_ = track; // here this.track_ refers to the new track instance
  15356. if (this.track_) {
  15357. this.track_.mode = 'hidden';
  15358. var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
  15359. if (_remoteTextTrackEl) {
  15360. _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
  15361. }
  15362. }
  15363. }
  15364. /**
  15365. * Find the track object that is currently in use by this ChaptersButton
  15366. *
  15367. * @return {TextTrack|undefined}
  15368. * The current track or undefined if none was found.
  15369. */
  15370. ;
  15371. _proto.findChaptersTrack = function findChaptersTrack() {
  15372. var tracks = this.player_.textTracks() || [];
  15373. for (var i = tracks.length - 1; i >= 0; i--) {
  15374. // We will always choose the last track as our chaptersTrack
  15375. var track = tracks[i];
  15376. if (track.kind === this.kind_) {
  15377. return track;
  15378. }
  15379. }
  15380. }
  15381. /**
  15382. * Get the caption for the ChaptersButton based on the track label. This will also
  15383. * use the current tracks localized kind as a fallback if a label does not exist.
  15384. *
  15385. * @return {string}
  15386. * The tracks current label or the localized track kind.
  15387. */
  15388. ;
  15389. _proto.getMenuCaption = function getMenuCaption() {
  15390. if (this.track_ && this.track_.label) {
  15391. return this.track_.label;
  15392. }
  15393. return this.localize(toTitleCase(this.kind_));
  15394. }
  15395. /**
  15396. * Create menu from chapter track
  15397. *
  15398. * @return {Menu}
  15399. * New menu for the chapter buttons
  15400. */
  15401. ;
  15402. _proto.createMenu = function createMenu() {
  15403. this.options_.title = this.getMenuCaption();
  15404. return _TextTrackButton.prototype.createMenu.call(this);
  15405. }
  15406. /**
  15407. * Create a menu item for each text track
  15408. *
  15409. * @return {TextTrackMenuItem[]}
  15410. * Array of menu items
  15411. */
  15412. ;
  15413. _proto.createItems = function createItems() {
  15414. var items = [];
  15415. if (!this.track_) {
  15416. return items;
  15417. }
  15418. var cues = this.track_.cues;
  15419. if (!cues) {
  15420. return items;
  15421. }
  15422. for (var i = 0, l = cues.length; i < l; i++) {
  15423. var cue = cues[i];
  15424. var mi = new ChaptersTrackMenuItem(this.player_, {
  15425. track: this.track_,
  15426. cue: cue
  15427. });
  15428. items.push(mi);
  15429. }
  15430. return items;
  15431. };
  15432. return ChaptersButton;
  15433. }(TextTrackButton);
  15434. /**
  15435. * `kind` of TextTrack to look for to associate it with this menu.
  15436. *
  15437. * @type {string}
  15438. * @private
  15439. */
  15440. ChaptersButton.prototype.kind_ = 'chapters';
  15441. /**
  15442. * The text that should display over the `ChaptersButton`s controls. Added for localization.
  15443. *
  15444. * @type {string}
  15445. * @private
  15446. */
  15447. ChaptersButton.prototype.controlText_ = 'Chapters';
  15448. Component.registerComponent('ChaptersButton', ChaptersButton);
  15449. /**
  15450. * The button component for toggling and selecting descriptions
  15451. *
  15452. * @extends TextTrackButton
  15453. */
  15454. var DescriptionsButton =
  15455. /*#__PURE__*/
  15456. function (_TextTrackButton) {
  15457. _inheritsLoose(DescriptionsButton, _TextTrackButton);
  15458. /**
  15459. * Creates an instance of this class.
  15460. *
  15461. * @param {Player} player
  15462. * The `Player` that this class should be attached to.
  15463. *
  15464. * @param {Object} [options]
  15465. * The key/value store of player options.
  15466. *
  15467. * @param {Component~ReadyCallback} [ready]
  15468. * The function to call when this component is ready.
  15469. */
  15470. function DescriptionsButton(player, options, ready) {
  15471. var _this;
  15472. _this = _TextTrackButton.call(this, player, options, ready) || this;
  15473. var tracks = player.textTracks();
  15474. var changeHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTracksChange);
  15475. tracks.addEventListener('change', changeHandler);
  15476. _this.on('dispose', function () {
  15477. tracks.removeEventListener('change', changeHandler);
  15478. });
  15479. return _this;
  15480. }
  15481. /**
  15482. * Handle text track change
  15483. *
  15484. * @param {EventTarget~Event} event
  15485. * The event that caused this function to run
  15486. *
  15487. * @listens TextTrackList#change
  15488. */
  15489. var _proto = DescriptionsButton.prototype;
  15490. _proto.handleTracksChange = function handleTracksChange(event) {
  15491. var tracks = this.player().textTracks();
  15492. var disabled = false; // Check whether a track of a different kind is showing
  15493. for (var i = 0, l = tracks.length; i < l; i++) {
  15494. var track = tracks[i];
  15495. if (track.kind !== this.kind_ && track.mode === 'showing') {
  15496. disabled = true;
  15497. break;
  15498. }
  15499. } // If another track is showing, disable this menu button
  15500. if (disabled) {
  15501. this.disable();
  15502. } else {
  15503. this.enable();
  15504. }
  15505. }
  15506. /**
  15507. * Builds the default DOM `className`.
  15508. *
  15509. * @return {string}
  15510. * The DOM `className` for this object.
  15511. */
  15512. ;
  15513. _proto.buildCSSClass = function buildCSSClass() {
  15514. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  15515. };
  15516. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15517. return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  15518. };
  15519. return DescriptionsButton;
  15520. }(TextTrackButton);
  15521. /**
  15522. * `kind` of TextTrack to look for to associate it with this menu.
  15523. *
  15524. * @type {string}
  15525. * @private
  15526. */
  15527. DescriptionsButton.prototype.kind_ = 'descriptions';
  15528. /**
  15529. * The text that should display over the `DescriptionsButton`s controls. Added for localization.
  15530. *
  15531. * @type {string}
  15532. * @private
  15533. */
  15534. DescriptionsButton.prototype.controlText_ = 'Descriptions';
  15535. Component.registerComponent('DescriptionsButton', DescriptionsButton);
  15536. /**
  15537. * The button component for toggling and selecting subtitles
  15538. *
  15539. * @extends TextTrackButton
  15540. */
  15541. var SubtitlesButton =
  15542. /*#__PURE__*/
  15543. function (_TextTrackButton) {
  15544. _inheritsLoose(SubtitlesButton, _TextTrackButton);
  15545. /**
  15546. * Creates an instance of this class.
  15547. *
  15548. * @param {Player} player
  15549. * The `Player` that this class should be attached to.
  15550. *
  15551. * @param {Object} [options]
  15552. * The key/value store of player options.
  15553. *
  15554. * @param {Component~ReadyCallback} [ready]
  15555. * The function to call when this component is ready.
  15556. */
  15557. function SubtitlesButton(player, options, ready) {
  15558. return _TextTrackButton.call(this, player, options, ready) || this;
  15559. }
  15560. /**
  15561. * Builds the default DOM `className`.
  15562. *
  15563. * @return {string}
  15564. * The DOM `className` for this object.
  15565. */
  15566. var _proto = SubtitlesButton.prototype;
  15567. _proto.buildCSSClass = function buildCSSClass() {
  15568. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  15569. };
  15570. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15571. return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  15572. };
  15573. return SubtitlesButton;
  15574. }(TextTrackButton);
  15575. /**
  15576. * `kind` of TextTrack to look for to associate it with this menu.
  15577. *
  15578. * @type {string}
  15579. * @private
  15580. */
  15581. SubtitlesButton.prototype.kind_ = 'subtitles';
  15582. /**
  15583. * The text that should display over the `SubtitlesButton`s controls. Added for localization.
  15584. *
  15585. * @type {string}
  15586. * @private
  15587. */
  15588. SubtitlesButton.prototype.controlText_ = 'Subtitles';
  15589. Component.registerComponent('SubtitlesButton', SubtitlesButton);
  15590. /**
  15591. * The menu item for caption track settings menu
  15592. *
  15593. * @extends TextTrackMenuItem
  15594. */
  15595. var CaptionSettingsMenuItem =
  15596. /*#__PURE__*/
  15597. function (_TextTrackMenuItem) {
  15598. _inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
  15599. /**
  15600. * Creates an instance of this class.
  15601. *
  15602. * @param {Player} player
  15603. * The `Player` that this class should be attached to.
  15604. *
  15605. * @param {Object} [options]
  15606. * The key/value store of player options.
  15607. */
  15608. function CaptionSettingsMenuItem(player, options) {
  15609. var _this;
  15610. options.track = {
  15611. player: player,
  15612. kind: options.kind,
  15613. label: options.kind + ' settings',
  15614. selectable: false,
  15615. default: false,
  15616. mode: 'disabled'
  15617. }; // CaptionSettingsMenuItem has no concept of 'selected'
  15618. options.selectable = false;
  15619. options.name = 'CaptionSettingsMenuItem';
  15620. _this = _TextTrackMenuItem.call(this, player, options) || this;
  15621. _this.addClass('vjs-texttrack-settings');
  15622. _this.controlText(', opens ' + options.kind + ' settings dialog');
  15623. return _this;
  15624. }
  15625. /**
  15626. * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
  15627. * {@link ClickableComponent} for more detailed information on what a click can be.
  15628. *
  15629. * @param {EventTarget~Event} [event]
  15630. * The `keydown`, `tap`, or `click` event that caused this function to be
  15631. * called.
  15632. *
  15633. * @listens tap
  15634. * @listens click
  15635. */
  15636. var _proto = CaptionSettingsMenuItem.prototype;
  15637. _proto.handleClick = function handleClick(event) {
  15638. this.player().getChild('textTrackSettings').open();
  15639. };
  15640. return CaptionSettingsMenuItem;
  15641. }(TextTrackMenuItem);
  15642. Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
  15643. /**
  15644. * The button component for toggling and selecting captions
  15645. *
  15646. * @extends TextTrackButton
  15647. */
  15648. var CaptionsButton =
  15649. /*#__PURE__*/
  15650. function (_TextTrackButton) {
  15651. _inheritsLoose(CaptionsButton, _TextTrackButton);
  15652. /**
  15653. * Creates an instance of this class.
  15654. *
  15655. * @param {Player} player
  15656. * The `Player` that this class should be attached to.
  15657. *
  15658. * @param {Object} [options]
  15659. * The key/value store of player options.
  15660. *
  15661. * @param {Component~ReadyCallback} [ready]
  15662. * The function to call when this component is ready.
  15663. */
  15664. function CaptionsButton(player, options, ready) {
  15665. return _TextTrackButton.call(this, player, options, ready) || this;
  15666. }
  15667. /**
  15668. * Builds the default DOM `className`.
  15669. *
  15670. * @return {string}
  15671. * The DOM `className` for this object.
  15672. */
  15673. var _proto = CaptionsButton.prototype;
  15674. _proto.buildCSSClass = function buildCSSClass() {
  15675. return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  15676. };
  15677. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15678. return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  15679. }
  15680. /**
  15681. * Create caption menu items
  15682. *
  15683. * @return {CaptionSettingsMenuItem[]}
  15684. * The array of current menu items.
  15685. */
  15686. ;
  15687. _proto.createItems = function createItems() {
  15688. var items = [];
  15689. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  15690. items.push(new CaptionSettingsMenuItem(this.player_, {
  15691. kind: this.kind_
  15692. }));
  15693. this.hideThreshold_ += 1;
  15694. }
  15695. return _TextTrackButton.prototype.createItems.call(this, items);
  15696. };
  15697. return CaptionsButton;
  15698. }(TextTrackButton);
  15699. /**
  15700. * `kind` of TextTrack to look for to associate it with this menu.
  15701. *
  15702. * @type {string}
  15703. * @private
  15704. */
  15705. CaptionsButton.prototype.kind_ = 'captions';
  15706. /**
  15707. * The text that should display over the `CaptionsButton`s controls. Added for localization.
  15708. *
  15709. * @type {string}
  15710. * @private
  15711. */
  15712. CaptionsButton.prototype.controlText_ = 'Captions';
  15713. Component.registerComponent('CaptionsButton', CaptionsButton);
  15714. /**
  15715. * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
  15716. * in the SubsCapsMenu.
  15717. *
  15718. * @extends TextTrackMenuItem
  15719. */
  15720. var SubsCapsMenuItem =
  15721. /*#__PURE__*/
  15722. function (_TextTrackMenuItem) {
  15723. _inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
  15724. function SubsCapsMenuItem() {
  15725. return _TextTrackMenuItem.apply(this, arguments) || this;
  15726. }
  15727. var _proto = SubsCapsMenuItem.prototype;
  15728. _proto.createEl = function createEl(type, props, attrs) {
  15729. var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
  15730. if (this.options_.track.kind === 'captions') {
  15731. innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n ";
  15732. }
  15733. innerHTML += '</span>';
  15734. var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
  15735. innerHTML: innerHTML
  15736. }, props), attrs);
  15737. return el;
  15738. };
  15739. return SubsCapsMenuItem;
  15740. }(TextTrackMenuItem);
  15741. Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
  15742. /**
  15743. * The button component for toggling and selecting captions and/or subtitles
  15744. *
  15745. * @extends TextTrackButton
  15746. */
  15747. var SubsCapsButton =
  15748. /*#__PURE__*/
  15749. function (_TextTrackButton) {
  15750. _inheritsLoose(SubsCapsButton, _TextTrackButton);
  15751. function SubsCapsButton(player, options) {
  15752. var _this;
  15753. if (options === void 0) {
  15754. options = {};
  15755. }
  15756. _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for
  15757. // "captions and subtitles" other locales use "subtitles"
  15758. _this.label_ = 'subtitles';
  15759. if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
  15760. _this.label_ = 'captions';
  15761. }
  15762. _this.menuButton_.controlText(toTitleCase(_this.label_));
  15763. return _this;
  15764. }
  15765. /**
  15766. * Builds the default DOM `className`.
  15767. *
  15768. * @return {string}
  15769. * The DOM `className` for this object.
  15770. */
  15771. var _proto = SubsCapsButton.prototype;
  15772. _proto.buildCSSClass = function buildCSSClass() {
  15773. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
  15774. };
  15775. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15776. return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
  15777. }
  15778. /**
  15779. * Create caption/subtitles menu items
  15780. *
  15781. * @return {CaptionSettingsMenuItem[]}
  15782. * The array of current menu items.
  15783. */
  15784. ;
  15785. _proto.createItems = function createItems() {
  15786. var items = [];
  15787. if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
  15788. items.push(new CaptionSettingsMenuItem(this.player_, {
  15789. kind: this.label_
  15790. }));
  15791. this.hideThreshold_ += 1;
  15792. }
  15793. items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
  15794. return items;
  15795. };
  15796. return SubsCapsButton;
  15797. }(TextTrackButton);
  15798. /**
  15799. * `kind`s of TextTrack to look for to associate it with this menu.
  15800. *
  15801. * @type {array}
  15802. * @private
  15803. */
  15804. SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
  15805. /**
  15806. * The text that should display over the `SubsCapsButton`s controls.
  15807. *
  15808. *
  15809. * @type {string}
  15810. * @private
  15811. */
  15812. SubsCapsButton.prototype.controlText_ = 'Subtitles';
  15813. Component.registerComponent('SubsCapsButton', SubsCapsButton);
  15814. /**
  15815. * An {@link AudioTrack} {@link MenuItem}
  15816. *
  15817. * @extends MenuItem
  15818. */
  15819. var AudioTrackMenuItem =
  15820. /*#__PURE__*/
  15821. function (_MenuItem) {
  15822. _inheritsLoose(AudioTrackMenuItem, _MenuItem);
  15823. /**
  15824. * Creates an instance of this class.
  15825. *
  15826. * @param {Player} player
  15827. * The `Player` that this class should be attached to.
  15828. *
  15829. * @param {Object} [options]
  15830. * The key/value store of player options.
  15831. */
  15832. function AudioTrackMenuItem(player, options) {
  15833. var _this;
  15834. var track = options.track;
  15835. var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init.
  15836. options.label = track.label || track.language || 'Unknown';
  15837. options.selected = track.enabled;
  15838. _this = _MenuItem.call(this, player, options) || this;
  15839. _this.track = track;
  15840. _this.addClass("vjs-" + track.kind + "-menu-item");
  15841. var changeHandler = function changeHandler() {
  15842. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  15843. args[_key] = arguments[_key];
  15844. }
  15845. _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
  15846. };
  15847. tracks.addEventListener('change', changeHandler);
  15848. _this.on('dispose', function () {
  15849. tracks.removeEventListener('change', changeHandler);
  15850. });
  15851. return _this;
  15852. }
  15853. var _proto = AudioTrackMenuItem.prototype;
  15854. _proto.createEl = function createEl(type, props, attrs) {
  15855. var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
  15856. if (this.options_.track.kind === 'main-desc') {
  15857. innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n ";
  15858. }
  15859. innerHTML += '</span>';
  15860. var el = _MenuItem.prototype.createEl.call(this, type, assign({
  15861. innerHTML: innerHTML
  15862. }, props), attrs);
  15863. return el;
  15864. }
  15865. /**
  15866. * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
  15867. * for more detailed information on what a click can be.
  15868. *
  15869. * @param {EventTarget~Event} [event]
  15870. * The `keydown`, `tap`, or `click` event that caused this function to be
  15871. * called.
  15872. *
  15873. * @listens tap
  15874. * @listens click
  15875. */
  15876. ;
  15877. _proto.handleClick = function handleClick(event) {
  15878. var tracks = this.player_.audioTracks();
  15879. _MenuItem.prototype.handleClick.call(this, event);
  15880. for (var i = 0; i < tracks.length; i++) {
  15881. var track = tracks[i];
  15882. track.enabled = track === this.track;
  15883. }
  15884. }
  15885. /**
  15886. * Handle any {@link AudioTrack} change.
  15887. *
  15888. * @param {EventTarget~Event} [event]
  15889. * The {@link AudioTrackList#change} event that caused this to run.
  15890. *
  15891. * @listens AudioTrackList#change
  15892. */
  15893. ;
  15894. _proto.handleTracksChange = function handleTracksChange(event) {
  15895. this.selected(this.track.enabled);
  15896. };
  15897. return AudioTrackMenuItem;
  15898. }(MenuItem);
  15899. Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
  15900. /**
  15901. * The base class for buttons that toggle specific {@link AudioTrack} types.
  15902. *
  15903. * @extends TrackButton
  15904. */
  15905. var AudioTrackButton =
  15906. /*#__PURE__*/
  15907. function (_TrackButton) {
  15908. _inheritsLoose(AudioTrackButton, _TrackButton);
  15909. /**
  15910. * Creates an instance of this class.
  15911. *
  15912. * @param {Player} player
  15913. * The `Player` that this class should be attached to.
  15914. *
  15915. * @param {Object} [options={}]
  15916. * The key/value store of player options.
  15917. */
  15918. function AudioTrackButton(player, options) {
  15919. if (options === void 0) {
  15920. options = {};
  15921. }
  15922. options.tracks = player.audioTracks();
  15923. return _TrackButton.call(this, player, options) || this;
  15924. }
  15925. /**
  15926. * Builds the default DOM `className`.
  15927. *
  15928. * @return {string}
  15929. * The DOM `className` for this object.
  15930. */
  15931. var _proto = AudioTrackButton.prototype;
  15932. _proto.buildCSSClass = function buildCSSClass() {
  15933. return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this);
  15934. };
  15935. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  15936. return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this);
  15937. }
  15938. /**
  15939. * Create a menu item for each audio track
  15940. *
  15941. * @param {AudioTrackMenuItem[]} [items=[]]
  15942. * An array of existing menu items to use.
  15943. *
  15944. * @return {AudioTrackMenuItem[]}
  15945. * An array of menu items
  15946. */
  15947. ;
  15948. _proto.createItems = function createItems(items) {
  15949. if (items === void 0) {
  15950. items = [];
  15951. }
  15952. // if there's only one audio track, there no point in showing it
  15953. this.hideThreshold_ = 1;
  15954. var tracks = this.player_.audioTracks();
  15955. for (var i = 0; i < tracks.length; i++) {
  15956. var track = tracks[i];
  15957. items.push(new AudioTrackMenuItem(this.player_, {
  15958. track: track,
  15959. // MenuItem is selectable
  15960. selectable: true,
  15961. // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
  15962. multiSelectable: false
  15963. }));
  15964. }
  15965. return items;
  15966. };
  15967. return AudioTrackButton;
  15968. }(TrackButton);
  15969. /**
  15970. * The text that should display over the `AudioTrackButton`s controls. Added for localization.
  15971. *
  15972. * @type {string}
  15973. * @private
  15974. */
  15975. AudioTrackButton.prototype.controlText_ = 'Audio Track';
  15976. Component.registerComponent('AudioTrackButton', AudioTrackButton);
  15977. /**
  15978. * The specific menu item type for selecting a playback rate.
  15979. *
  15980. * @extends MenuItem
  15981. */
  15982. var PlaybackRateMenuItem =
  15983. /*#__PURE__*/
  15984. function (_MenuItem) {
  15985. _inheritsLoose(PlaybackRateMenuItem, _MenuItem);
  15986. /**
  15987. * Creates an instance of this class.
  15988. *
  15989. * @param {Player} player
  15990. * The `Player` that this class should be attached to.
  15991. *
  15992. * @param {Object} [options]
  15993. * The key/value store of player options.
  15994. */
  15995. function PlaybackRateMenuItem(player, options) {
  15996. var _this;
  15997. var label = options.rate;
  15998. var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init.
  15999. options.label = label;
  16000. options.selected = rate === 1;
  16001. options.selectable = true;
  16002. options.multiSelectable = false;
  16003. _this = _MenuItem.call(this, player, options) || this;
  16004. _this.label = label;
  16005. _this.rate = rate;
  16006. _this.on(player, 'ratechange', _this.update);
  16007. return _this;
  16008. }
  16009. /**
  16010. * This gets called when an `PlaybackRateMenuItem` is "clicked". See
  16011. * {@link ClickableComponent} for more detailed information on what a click can be.
  16012. *
  16013. * @param {EventTarget~Event} [event]
  16014. * The `keydown`, `tap`, or `click` event that caused this function to be
  16015. * called.
  16016. *
  16017. * @listens tap
  16018. * @listens click
  16019. */
  16020. var _proto = PlaybackRateMenuItem.prototype;
  16021. _proto.handleClick = function handleClick(event) {
  16022. _MenuItem.prototype.handleClick.call(this);
  16023. this.player().playbackRate(this.rate);
  16024. }
  16025. /**
  16026. * Update the PlaybackRateMenuItem when the playbackrate changes.
  16027. *
  16028. * @param {EventTarget~Event} [event]
  16029. * The `ratechange` event that caused this function to run.
  16030. *
  16031. * @listens Player#ratechange
  16032. */
  16033. ;
  16034. _proto.update = function update(event) {
  16035. this.selected(this.player().playbackRate() === this.rate);
  16036. };
  16037. return PlaybackRateMenuItem;
  16038. }(MenuItem);
  16039. /**
  16040. * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
  16041. *
  16042. * @type {string}
  16043. * @private
  16044. */
  16045. PlaybackRateMenuItem.prototype.contentElType = 'button';
  16046. Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
  16047. /**
  16048. * The component for controlling the playback rate.
  16049. *
  16050. * @extends MenuButton
  16051. */
  16052. var PlaybackRateMenuButton =
  16053. /*#__PURE__*/
  16054. function (_MenuButton) {
  16055. _inheritsLoose(PlaybackRateMenuButton, _MenuButton);
  16056. /**
  16057. * Creates an instance of this class.
  16058. *
  16059. * @param {Player} player
  16060. * The `Player` that this class should be attached to.
  16061. *
  16062. * @param {Object} [options]
  16063. * The key/value store of player options.
  16064. */
  16065. function PlaybackRateMenuButton(player, options) {
  16066. var _this;
  16067. _this = _MenuButton.call(this, player, options) || this;
  16068. _this.updateVisibility();
  16069. _this.updateLabel();
  16070. _this.on(player, 'loadstart', _this.updateVisibility);
  16071. _this.on(player, 'ratechange', _this.updateLabel);
  16072. return _this;
  16073. }
  16074. /**
  16075. * Create the `Component`'s DOM element
  16076. *
  16077. * @return {Element}
  16078. * The element that was created.
  16079. */
  16080. var _proto = PlaybackRateMenuButton.prototype;
  16081. _proto.createEl = function createEl$$1() {
  16082. var el = _MenuButton.prototype.createEl.call(this);
  16083. this.labelEl_ = createEl('div', {
  16084. className: 'vjs-playback-rate-value',
  16085. innerHTML: '1x'
  16086. });
  16087. el.appendChild(this.labelEl_);
  16088. return el;
  16089. };
  16090. _proto.dispose = function dispose() {
  16091. this.labelEl_ = null;
  16092. _MenuButton.prototype.dispose.call(this);
  16093. }
  16094. /**
  16095. * Builds the default DOM `className`.
  16096. *
  16097. * @return {string}
  16098. * The DOM `className` for this object.
  16099. */
  16100. ;
  16101. _proto.buildCSSClass = function buildCSSClass() {
  16102. return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this);
  16103. };
  16104. _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
  16105. return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this);
  16106. }
  16107. /**
  16108. * Create the playback rate menu
  16109. *
  16110. * @return {Menu}
  16111. * Menu object populated with {@link PlaybackRateMenuItem}s
  16112. */
  16113. ;
  16114. _proto.createMenu = function createMenu() {
  16115. var menu = new Menu(this.player());
  16116. var rates = this.playbackRates();
  16117. if (rates) {
  16118. for (var i = rates.length - 1; i >= 0; i--) {
  16119. menu.addChild(new PlaybackRateMenuItem(this.player(), {
  16120. rate: rates[i] + 'x'
  16121. }));
  16122. }
  16123. }
  16124. return menu;
  16125. }
  16126. /**
  16127. * Updates ARIA accessibility attributes
  16128. */
  16129. ;
  16130. _proto.updateARIAAttributes = function updateARIAAttributes() {
  16131. // Current playback rate
  16132. this.el().setAttribute('aria-valuenow', this.player().playbackRate());
  16133. }
  16134. /**
  16135. * This gets called when an `PlaybackRateMenuButton` is "clicked". See
  16136. * {@link ClickableComponent} for more detailed information on what a click can be.
  16137. *
  16138. * @param {EventTarget~Event} [event]
  16139. * The `keydown`, `tap`, or `click` event that caused this function to be
  16140. * called.
  16141. *
  16142. * @listens tap
  16143. * @listens click
  16144. */
  16145. ;
  16146. _proto.handleClick = function handleClick(event) {
  16147. // select next rate option
  16148. var currentRate = this.player().playbackRate();
  16149. var rates = this.playbackRates(); // this will select first one if the last one currently selected
  16150. var newRate = rates[0];
  16151. for (var i = 0; i < rates.length; i++) {
  16152. if (rates[i] > currentRate) {
  16153. newRate = rates[i];
  16154. break;
  16155. }
  16156. }
  16157. this.player().playbackRate(newRate);
  16158. }
  16159. /**
  16160. * Get possible playback rates
  16161. *
  16162. * @return {Array}
  16163. * All possible playback rates
  16164. */
  16165. ;
  16166. _proto.playbackRates = function playbackRates() {
  16167. return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
  16168. }
  16169. /**
  16170. * Get whether playback rates is supported by the tech
  16171. * and an array of playback rates exists
  16172. *
  16173. * @return {boolean}
  16174. * Whether changing playback rate is supported
  16175. */
  16176. ;
  16177. _proto.playbackRateSupported = function playbackRateSupported() {
  16178. return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
  16179. }
  16180. /**
  16181. * Hide playback rate controls when they're no playback rate options to select
  16182. *
  16183. * @param {EventTarget~Event} [event]
  16184. * The event that caused this function to run.
  16185. *
  16186. * @listens Player#loadstart
  16187. */
  16188. ;
  16189. _proto.updateVisibility = function updateVisibility(event) {
  16190. if (this.playbackRateSupported()) {
  16191. this.removeClass('vjs-hidden');
  16192. } else {
  16193. this.addClass('vjs-hidden');
  16194. }
  16195. }
  16196. /**
  16197. * Update button label when rate changed
  16198. *
  16199. * @param {EventTarget~Event} [event]
  16200. * The event that caused this function to run.
  16201. *
  16202. * @listens Player#ratechange
  16203. */
  16204. ;
  16205. _proto.updateLabel = function updateLabel(event) {
  16206. if (this.playbackRateSupported()) {
  16207. this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
  16208. }
  16209. };
  16210. return PlaybackRateMenuButton;
  16211. }(MenuButton);
  16212. /**
  16213. * The text that should display over the `FullscreenToggle`s controls. Added for localization.
  16214. *
  16215. * @type {string}
  16216. * @private
  16217. */
  16218. PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
  16219. Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
  16220. /**
  16221. * Just an empty spacer element that can be used as an append point for plugins, etc.
  16222. * Also can be used to create space between elements when necessary.
  16223. *
  16224. * @extends Component
  16225. */
  16226. var Spacer =
  16227. /*#__PURE__*/
  16228. function (_Component) {
  16229. _inheritsLoose(Spacer, _Component);
  16230. function Spacer() {
  16231. return _Component.apply(this, arguments) || this;
  16232. }
  16233. var _proto = Spacer.prototype;
  16234. /**
  16235. * Builds the default DOM `className`.
  16236. *
  16237. * @return {string}
  16238. * The DOM `className` for this object.
  16239. */
  16240. _proto.buildCSSClass = function buildCSSClass() {
  16241. return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this);
  16242. }
  16243. /**
  16244. * Create the `Component`'s DOM element
  16245. *
  16246. * @return {Element}
  16247. * The element that was created.
  16248. */
  16249. ;
  16250. _proto.createEl = function createEl() {
  16251. return _Component.prototype.createEl.call(this, 'div', {
  16252. className: this.buildCSSClass()
  16253. });
  16254. };
  16255. return Spacer;
  16256. }(Component);
  16257. Component.registerComponent('Spacer', Spacer);
  16258. /**
  16259. * Spacer specifically meant to be used as an insertion point for new plugins, etc.
  16260. *
  16261. * @extends Spacer
  16262. */
  16263. var CustomControlSpacer =
  16264. /*#__PURE__*/
  16265. function (_Spacer) {
  16266. _inheritsLoose(CustomControlSpacer, _Spacer);
  16267. function CustomControlSpacer() {
  16268. return _Spacer.apply(this, arguments) || this;
  16269. }
  16270. var _proto = CustomControlSpacer.prototype;
  16271. /**
  16272. * Builds the default DOM `className`.
  16273. *
  16274. * @return {string}
  16275. * The DOM `className` for this object.
  16276. */
  16277. _proto.buildCSSClass = function buildCSSClass() {
  16278. return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this);
  16279. }
  16280. /**
  16281. * Create the `Component`'s DOM element
  16282. *
  16283. * @return {Element}
  16284. * The element that was created.
  16285. */
  16286. ;
  16287. _proto.createEl = function createEl() {
  16288. var el = _Spacer.prototype.createEl.call(this, {
  16289. className: this.buildCSSClass()
  16290. }); // No-flex/table-cell mode requires there be some content
  16291. // in the cell to fill the remaining space of the table.
  16292. el.innerHTML = "\xA0";
  16293. return el;
  16294. };
  16295. return CustomControlSpacer;
  16296. }(Spacer);
  16297. Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
  16298. /**
  16299. * Container of main controls.
  16300. *
  16301. * @extends Component
  16302. */
  16303. var ControlBar =
  16304. /*#__PURE__*/
  16305. function (_Component) {
  16306. _inheritsLoose(ControlBar, _Component);
  16307. function ControlBar() {
  16308. return _Component.apply(this, arguments) || this;
  16309. }
  16310. var _proto = ControlBar.prototype;
  16311. /**
  16312. * Create the `Component`'s DOM element
  16313. *
  16314. * @return {Element}
  16315. * The element that was created.
  16316. */
  16317. _proto.createEl = function createEl() {
  16318. return _Component.prototype.createEl.call(this, 'div', {
  16319. className: 'vjs-control-bar',
  16320. dir: 'ltr'
  16321. });
  16322. };
  16323. return ControlBar;
  16324. }(Component);
  16325. /**
  16326. * Default options for `ControlBar`
  16327. *
  16328. * @type {Object}
  16329. * @private
  16330. */
  16331. ControlBar.prototype.options_ = {
  16332. children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
  16333. };
  16334. Component.registerComponent('ControlBar', ControlBar);
  16335. /**
  16336. * A display that indicates an error has occurred. This means that the video
  16337. * is unplayable.
  16338. *
  16339. * @extends ModalDialog
  16340. */
  16341. var ErrorDisplay =
  16342. /*#__PURE__*/
  16343. function (_ModalDialog) {
  16344. _inheritsLoose(ErrorDisplay, _ModalDialog);
  16345. /**
  16346. * Creates an instance of this class.
  16347. *
  16348. * @param {Player} player
  16349. * The `Player` that this class should be attached to.
  16350. *
  16351. * @param {Object} [options]
  16352. * The key/value store of player options.
  16353. */
  16354. function ErrorDisplay(player, options) {
  16355. var _this;
  16356. _this = _ModalDialog.call(this, player, options) || this;
  16357. _this.on(player, 'error', _this.open);
  16358. return _this;
  16359. }
  16360. /**
  16361. * Builds the default DOM `className`.
  16362. *
  16363. * @return {string}
  16364. * The DOM `className` for this object.
  16365. *
  16366. * @deprecated Since version 5.
  16367. */
  16368. var _proto = ErrorDisplay.prototype;
  16369. _proto.buildCSSClass = function buildCSSClass() {
  16370. return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this);
  16371. }
  16372. /**
  16373. * Gets the localized error message based on the `Player`s error.
  16374. *
  16375. * @return {string}
  16376. * The `Player`s error message localized or an empty string.
  16377. */
  16378. ;
  16379. _proto.content = function content() {
  16380. var error = this.player().error();
  16381. return error ? this.localize(error.message) : '';
  16382. };
  16383. return ErrorDisplay;
  16384. }(ModalDialog);
  16385. /**
  16386. * The default options for an `ErrorDisplay`.
  16387. *
  16388. * @private
  16389. */
  16390. ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
  16391. pauseOnOpen: false,
  16392. fillAlways: true,
  16393. temporary: false,
  16394. uncloseable: true
  16395. });
  16396. Component.registerComponent('ErrorDisplay', ErrorDisplay);
  16397. var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
  16398. var COLOR_BLACK = ['#000', 'Black'];
  16399. var COLOR_BLUE = ['#00F', 'Blue'];
  16400. var COLOR_CYAN = ['#0FF', 'Cyan'];
  16401. var COLOR_GREEN = ['#0F0', 'Green'];
  16402. var COLOR_MAGENTA = ['#F0F', 'Magenta'];
  16403. var COLOR_RED = ['#F00', 'Red'];
  16404. var COLOR_WHITE = ['#FFF', 'White'];
  16405. var COLOR_YELLOW = ['#FF0', 'Yellow'];
  16406. var OPACITY_OPAQUE = ['1', 'Opaque'];
  16407. var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
  16408. var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component.
  16409. //
  16410. // Possible keys include:
  16411. //
  16412. // `default`:
  16413. // The default option index. Only needs to be provided if not zero.
  16414. // `parser`:
  16415. // A function which is used to parse the value from the selected option in
  16416. // a customized way.
  16417. // `selector`:
  16418. // The selector used to find the associated <select> element.
  16419. var selectConfigs = {
  16420. backgroundColor: {
  16421. selector: '.vjs-bg-color > select',
  16422. id: 'captions-background-color-%s',
  16423. label: 'Color',
  16424. options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  16425. },
  16426. backgroundOpacity: {
  16427. selector: '.vjs-bg-opacity > select',
  16428. id: 'captions-background-opacity-%s',
  16429. label: 'Transparency',
  16430. options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
  16431. },
  16432. color: {
  16433. selector: '.vjs-fg-color > select',
  16434. id: 'captions-foreground-color-%s',
  16435. label: 'Color',
  16436. options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
  16437. },
  16438. edgeStyle: {
  16439. selector: '.vjs-edge-style > select',
  16440. id: '%s',
  16441. label: 'Text Edge Style',
  16442. options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
  16443. },
  16444. fontFamily: {
  16445. selector: '.vjs-font-family > select',
  16446. id: 'captions-font-family-%s',
  16447. label: 'Font Family',
  16448. options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
  16449. },
  16450. fontPercent: {
  16451. selector: '.vjs-font-percent > select',
  16452. id: 'captions-font-size-%s',
  16453. label: 'Font Size',
  16454. 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%']],
  16455. default: 2,
  16456. parser: function parser(v) {
  16457. return v === '1.00' ? null : Number(v);
  16458. }
  16459. },
  16460. textOpacity: {
  16461. selector: '.vjs-text-opacity > select',
  16462. id: 'captions-foreground-opacity-%s',
  16463. label: 'Transparency',
  16464. options: [OPACITY_OPAQUE, OPACITY_SEMI]
  16465. },
  16466. // Options for this object are defined below.
  16467. windowColor: {
  16468. selector: '.vjs-window-color > select',
  16469. id: 'captions-window-color-%s',
  16470. label: 'Color'
  16471. },
  16472. // Options for this object are defined below.
  16473. windowOpacity: {
  16474. selector: '.vjs-window-opacity > select',
  16475. id: 'captions-window-opacity-%s',
  16476. label: 'Transparency',
  16477. options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
  16478. }
  16479. };
  16480. selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
  16481. /**
  16482. * Get the actual value of an option.
  16483. *
  16484. * @param {string} value
  16485. * The value to get
  16486. *
  16487. * @param {Function} [parser]
  16488. * Optional function to adjust the value.
  16489. *
  16490. * @return {Mixed}
  16491. * - Will be `undefined` if no value exists
  16492. * - Will be `undefined` if the given value is "none".
  16493. * - Will be the actual value otherwise.
  16494. *
  16495. * @private
  16496. */
  16497. function parseOptionValue(value, parser) {
  16498. if (parser) {
  16499. value = parser(value);
  16500. }
  16501. if (value && value !== 'none') {
  16502. return value;
  16503. }
  16504. }
  16505. /**
  16506. * Gets the value of the selected <option> element within a <select> element.
  16507. *
  16508. * @param {Element} el
  16509. * the element to look in
  16510. *
  16511. * @param {Function} [parser]
  16512. * Optional function to adjust the value.
  16513. *
  16514. * @return {Mixed}
  16515. * - Will be `undefined` if no value exists
  16516. * - Will be `undefined` if the given value is "none".
  16517. * - Will be the actual value otherwise.
  16518. *
  16519. * @private
  16520. */
  16521. function getSelectedOptionValue(el, parser) {
  16522. var value = el.options[el.options.selectedIndex].value;
  16523. return parseOptionValue(value, parser);
  16524. }
  16525. /**
  16526. * Sets the selected <option> element within a <select> element based on a
  16527. * given value.
  16528. *
  16529. * @param {Element} el
  16530. * The element to look in.
  16531. *
  16532. * @param {string} value
  16533. * the property to look on.
  16534. *
  16535. * @param {Function} [parser]
  16536. * Optional function to adjust the value before comparing.
  16537. *
  16538. * @private
  16539. */
  16540. function setSelectedOption(el, value, parser) {
  16541. if (!value) {
  16542. return;
  16543. }
  16544. for (var i = 0; i < el.options.length; i++) {
  16545. if (parseOptionValue(el.options[i].value, parser) === value) {
  16546. el.selectedIndex = i;
  16547. break;
  16548. }
  16549. }
  16550. }
  16551. /**
  16552. * Manipulate Text Tracks settings.
  16553. *
  16554. * @extends ModalDialog
  16555. */
  16556. var TextTrackSettings =
  16557. /*#__PURE__*/
  16558. function (_ModalDialog) {
  16559. _inheritsLoose(TextTrackSettings, _ModalDialog);
  16560. /**
  16561. * Creates an instance of this class.
  16562. *
  16563. * @param {Player} player
  16564. * The `Player` that this class should be attached to.
  16565. *
  16566. * @param {Object} [options]
  16567. * The key/value store of player options.
  16568. */
  16569. function TextTrackSettings(player, options) {
  16570. var _this;
  16571. options.temporary = false;
  16572. _this = _ModalDialog.call(this, player, options) || this;
  16573. _this.updateDisplay = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay); // fill the modal and pretend we have opened it
  16574. _this.fill();
  16575. _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
  16576. _this.endDialog = createEl('p', {
  16577. className: 'vjs-control-text',
  16578. textContent: _this.localize('End of dialog window.')
  16579. });
  16580. _this.el().appendChild(_this.endDialog);
  16581. _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options
  16582. if (options.persistTextTrackSettings === undefined) {
  16583. _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
  16584. }
  16585. _this.on(_this.$('.vjs-done-button'), 'click', function () {
  16586. _this.saveSettings();
  16587. _this.close();
  16588. });
  16589. _this.on(_this.$('.vjs-default-button'), 'click', function () {
  16590. _this.setDefaults();
  16591. _this.updateDisplay();
  16592. });
  16593. each(selectConfigs, function (config) {
  16594. _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
  16595. });
  16596. if (_this.options_.persistTextTrackSettings) {
  16597. _this.restoreSettings();
  16598. }
  16599. return _this;
  16600. }
  16601. var _proto = TextTrackSettings.prototype;
  16602. _proto.dispose = function dispose() {
  16603. this.endDialog = null;
  16604. _ModalDialog.prototype.dispose.call(this);
  16605. }
  16606. /**
  16607. * Create a <select> element with configured options.
  16608. *
  16609. * @param {string} key
  16610. * Configuration key to use during creation.
  16611. *
  16612. * @return {string}
  16613. * An HTML string.
  16614. *
  16615. * @private
  16616. */
  16617. ;
  16618. _proto.createElSelect_ = function createElSelect_(key, legendId, type) {
  16619. var _this2 = this;
  16620. if (legendId === void 0) {
  16621. legendId = '';
  16622. }
  16623. if (type === void 0) {
  16624. type = 'label';
  16625. }
  16626. var config = selectConfigs[key];
  16627. var id = config.id.replace('%s', this.id_);
  16628. var selectLabelledbyIds = [legendId, id].join(' ').trim();
  16629. return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) {
  16630. var optionId = id + '-' + o[1].replace(/\W+/g, '');
  16631. return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join('');
  16632. })).concat('</select>').join('');
  16633. }
  16634. /**
  16635. * Create foreground color element for the component
  16636. *
  16637. * @return {string}
  16638. * An HTML string.
  16639. *
  16640. * @private
  16641. */
  16642. ;
  16643. _proto.createElFgColor_ = function createElFgColor_() {
  16644. var legendId = "captions-text-legend-" + this.id_;
  16645. 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('');
  16646. }
  16647. /**
  16648. * Create background color element for the component
  16649. *
  16650. * @return {string}
  16651. * An HTML string.
  16652. *
  16653. * @private
  16654. */
  16655. ;
  16656. _proto.createElBgColor_ = function createElBgColor_() {
  16657. var legendId = "captions-background-" + this.id_;
  16658. 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('');
  16659. }
  16660. /**
  16661. * Create window color element for the component
  16662. *
  16663. * @return {string}
  16664. * An HTML string.
  16665. *
  16666. * @private
  16667. */
  16668. ;
  16669. _proto.createElWinColor_ = function createElWinColor_() {
  16670. var legendId = "captions-window-" + this.id_;
  16671. 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('');
  16672. }
  16673. /**
  16674. * Create color elements for the component
  16675. *
  16676. * @return {Element}
  16677. * The element that was created
  16678. *
  16679. * @private
  16680. */
  16681. ;
  16682. _proto.createElColors_ = function createElColors_() {
  16683. return createEl('div', {
  16684. className: 'vjs-track-settings-colors',
  16685. innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
  16686. });
  16687. }
  16688. /**
  16689. * Create font elements for the component
  16690. *
  16691. * @return {Element}
  16692. * The element that was created.
  16693. *
  16694. * @private
  16695. */
  16696. ;
  16697. _proto.createElFont_ = function createElFont_() {
  16698. return createEl('div', {
  16699. className: 'vjs-track-settings-font',
  16700. 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('')
  16701. });
  16702. }
  16703. /**
  16704. * Create controls for the component
  16705. *
  16706. * @return {Element}
  16707. * The element that was created.
  16708. *
  16709. * @private
  16710. */
  16711. ;
  16712. _proto.createElControls_ = function createElControls_() {
  16713. var defaultsDescription = this.localize('restore all settings to the default values');
  16714. return createEl('div', {
  16715. className: 'vjs-track-settings-controls',
  16716. 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('')
  16717. });
  16718. };
  16719. _proto.content = function content() {
  16720. return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
  16721. };
  16722. _proto.label = function label() {
  16723. return this.localize('Caption Settings Dialog');
  16724. };
  16725. _proto.description = function description() {
  16726. return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
  16727. };
  16728. _proto.buildCSSClass = function buildCSSClass() {
  16729. return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
  16730. }
  16731. /**
  16732. * Gets an object of text track settings (or null).
  16733. *
  16734. * @return {Object}
  16735. * An object with config values parsed from the DOM or localStorage.
  16736. */
  16737. ;
  16738. _proto.getValues = function getValues() {
  16739. var _this3 = this;
  16740. return reduce(selectConfigs, function (accum, config, key) {
  16741. var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
  16742. if (value !== undefined) {
  16743. accum[key] = value;
  16744. }
  16745. return accum;
  16746. }, {});
  16747. }
  16748. /**
  16749. * Sets text track settings from an object of values.
  16750. *
  16751. * @param {Object} values
  16752. * An object with config values parsed from the DOM or localStorage.
  16753. */
  16754. ;
  16755. _proto.setValues = function setValues(values) {
  16756. var _this4 = this;
  16757. each(selectConfigs, function (config, key) {
  16758. setSelectedOption(_this4.$(config.selector), values[key], config.parser);
  16759. });
  16760. }
  16761. /**
  16762. * Sets all `<select>` elements to their default values.
  16763. */
  16764. ;
  16765. _proto.setDefaults = function setDefaults() {
  16766. var _this5 = this;
  16767. each(selectConfigs, function (config) {
  16768. var index = config.hasOwnProperty('default') ? config.default : 0;
  16769. _this5.$(config.selector).selectedIndex = index;
  16770. });
  16771. }
  16772. /**
  16773. * Restore texttrack settings from localStorage
  16774. */
  16775. ;
  16776. _proto.restoreSettings = function restoreSettings() {
  16777. var values;
  16778. try {
  16779. values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY));
  16780. } catch (err) {
  16781. log.warn(err);
  16782. }
  16783. if (values) {
  16784. this.setValues(values);
  16785. }
  16786. }
  16787. /**
  16788. * Save text track settings to localStorage
  16789. */
  16790. ;
  16791. _proto.saveSettings = function saveSettings() {
  16792. if (!this.options_.persistTextTrackSettings) {
  16793. return;
  16794. }
  16795. var values = this.getValues();
  16796. try {
  16797. if (Object.keys(values).length) {
  16798. window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
  16799. } else {
  16800. window$1.localStorage.removeItem(LOCAL_STORAGE_KEY);
  16801. }
  16802. } catch (err) {
  16803. log.warn(err);
  16804. }
  16805. }
  16806. /**
  16807. * Update display of text track settings
  16808. */
  16809. ;
  16810. _proto.updateDisplay = function updateDisplay() {
  16811. var ttDisplay = this.player_.getChild('textTrackDisplay');
  16812. if (ttDisplay) {
  16813. ttDisplay.updateDisplay();
  16814. }
  16815. }
  16816. /**
  16817. * conditionally blur the element and refocus the captions button
  16818. *
  16819. * @private
  16820. */
  16821. ;
  16822. _proto.conditionalBlur_ = function conditionalBlur_() {
  16823. this.previouslyActiveEl_ = null;
  16824. this.off(document, 'keydown', this.handleKeyDown);
  16825. var cb = this.player_.controlBar;
  16826. var subsCapsBtn = cb && cb.subsCapsButton;
  16827. var ccBtn = cb && cb.captionsButton;
  16828. if (subsCapsBtn) {
  16829. subsCapsBtn.focus();
  16830. } else if (ccBtn) {
  16831. ccBtn.focus();
  16832. }
  16833. };
  16834. return TextTrackSettings;
  16835. }(ModalDialog);
  16836. Component.registerComponent('TextTrackSettings', TextTrackSettings);
  16837. /**
  16838. * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
  16839. *
  16840. * 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}.
  16841. *
  16842. * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
  16843. * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
  16844. * @example <caption>How to disable the resize manager</caption>
  16845. * const player = videojs('#vid', {
  16846. * resizeManager: false
  16847. * });
  16848. *
  16849. * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
  16850. *
  16851. * @extends Component
  16852. */
  16853. var ResizeManager =
  16854. /*#__PURE__*/
  16855. function (_Component) {
  16856. _inheritsLoose(ResizeManager, _Component);
  16857. /**
  16858. * Create the ResizeManager.
  16859. *
  16860. * @param {Object} player
  16861. * The `Player` that this class should be attached to.
  16862. *
  16863. * @param {Object} [options]
  16864. * The key/value store of ResizeManager options.
  16865. *
  16866. * @param {Object} [options.ResizeObserver]
  16867. * A polyfill for ResizeObserver can be passed in here.
  16868. * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
  16869. */
  16870. function ResizeManager(player, options) {
  16871. var _this;
  16872. var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
  16873. if (options.ResizeObserver === null) {
  16874. RESIZE_OBSERVER_AVAILABLE = false;
  16875. } // Only create an element when ResizeObserver isn't available
  16876. var options_ = mergeOptions({
  16877. createEl: !RESIZE_OBSERVER_AVAILABLE,
  16878. reportTouchActivity: false
  16879. }, options);
  16880. _this = _Component.call(this, player, options_) || this;
  16881. _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver;
  16882. _this.loadListener_ = null;
  16883. _this.resizeObserver_ = null;
  16884. _this.debouncedHandler_ = debounce(function () {
  16885. _this.resizeHandler();
  16886. }, 100, false, _assertThisInitialized(_assertThisInitialized(_this)));
  16887. if (RESIZE_OBSERVER_AVAILABLE) {
  16888. _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
  16889. _this.resizeObserver_.observe(player.el());
  16890. } else {
  16891. _this.loadListener_ = function () {
  16892. if (!_this.el_ || !_this.el_.contentWindow) {
  16893. return;
  16894. }
  16895. var debouncedHandler_ = _this.debouncedHandler_;
  16896. var unloadListener_ = _this.unloadListener_ = function () {
  16897. off(this, 'resize', debouncedHandler_);
  16898. off(this, 'unload', unloadListener_);
  16899. unloadListener_ = null;
  16900. }; // safari and edge can unload the iframe before resizemanager dispose
  16901. // we have to dispose of event handlers correctly before that happens
  16902. on(_this.el_.contentWindow, 'unload', unloadListener_);
  16903. on(_this.el_.contentWindow, 'resize', debouncedHandler_);
  16904. };
  16905. _this.one('load', _this.loadListener_);
  16906. }
  16907. return _this;
  16908. }
  16909. var _proto = ResizeManager.prototype;
  16910. _proto.createEl = function createEl() {
  16911. return _Component.prototype.createEl.call(this, 'iframe', {
  16912. className: 'vjs-resize-manager',
  16913. tabIndex: -1
  16914. }, {
  16915. 'aria-hidden': 'true'
  16916. });
  16917. }
  16918. /**
  16919. * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
  16920. *
  16921. * @fires Player#playerresize
  16922. */
  16923. ;
  16924. _proto.resizeHandler = function resizeHandler() {
  16925. /**
  16926. * Called when the player size has changed
  16927. *
  16928. * @event Player#playerresize
  16929. * @type {EventTarget~Event}
  16930. */
  16931. // make sure player is still around to trigger
  16932. // prevents this from causing an error after dispose
  16933. if (!this.player_ || !this.player_.trigger) {
  16934. return;
  16935. }
  16936. this.player_.trigger('playerresize');
  16937. };
  16938. _proto.dispose = function dispose() {
  16939. if (this.debouncedHandler_) {
  16940. this.debouncedHandler_.cancel();
  16941. }
  16942. if (this.resizeObserver_) {
  16943. if (this.player_.el()) {
  16944. this.resizeObserver_.unobserve(this.player_.el());
  16945. }
  16946. this.resizeObserver_.disconnect();
  16947. }
  16948. if (this.loadListener_) {
  16949. this.off('load', this.loadListener_);
  16950. }
  16951. if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
  16952. this.unloadListener_.call(this.el_.contentWindow);
  16953. }
  16954. this.ResizeObserver = null;
  16955. this.resizeObserver = null;
  16956. this.debouncedHandler_ = null;
  16957. this.loadListener_ = null;
  16958. _Component.prototype.dispose.call(this);
  16959. };
  16960. return ResizeManager;
  16961. }(Component);
  16962. Component.registerComponent('ResizeManager', ResizeManager);
  16963. /* track when we are at the live edge, and other helpers for live playback */
  16964. var LiveTracker =
  16965. /*#__PURE__*/
  16966. function (_Component) {
  16967. _inheritsLoose(LiveTracker, _Component);
  16968. function LiveTracker(player, options) {
  16969. var _this;
  16970. // LiveTracker does not need an element
  16971. var options_ = mergeOptions({
  16972. createEl: false
  16973. }, options);
  16974. _this = _Component.call(this, player, options_) || this;
  16975. _this.reset_();
  16976. _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden,
  16977. // also, tracking when the document is hidden can
  16978. // cause the CPU to spike and eventually crash the page on IE11.
  16979. if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
  16980. _this.on(document, 'visibilitychange', _this.handleVisibilityChange);
  16981. }
  16982. return _this;
  16983. }
  16984. var _proto = LiveTracker.prototype;
  16985. _proto.handleVisibilityChange = function handleVisibilityChange() {
  16986. if (this.player_.duration() !== Infinity) {
  16987. return;
  16988. }
  16989. if (document.hidden) {
  16990. this.stopTracking();
  16991. } else {
  16992. this.startTracking();
  16993. }
  16994. };
  16995. _proto.isBehind_ = function isBehind_() {
  16996. // don't report that we are behind until a timeupdate has been seen
  16997. if (!this.timeupdateSeen_) {
  16998. return false;
  16999. }
  17000. var liveCurrentTime = this.liveCurrentTime();
  17001. var currentTime = this.player_.currentTime();
  17002. var seekableIncrement = this.seekableIncrement_; // the live edge window is the amount of seconds away from live
  17003. // that a player can be, but still be considered live.
  17004. // we add 0.07 because the live tracking happens every 30ms
  17005. // and we want some wiggle room for short segment live playback
  17006. var liveEdgeWindow = seekableIncrement * 2 + 0.07; // on Android liveCurrentTime can bee Infinity, because seekableEnd
  17007. // can be Infinity, so we handle that case.
  17008. return liveCurrentTime !== Infinity && liveCurrentTime - liveEdgeWindow >= currentTime;
  17009. } // all the functionality for tracking when seek end changes
  17010. // and for tracking how far past seek end we should be
  17011. ;
  17012. _proto.trackLive_ = function trackLive_() {
  17013. this.pastSeekEnd_ = this.pastSeekEnd_;
  17014. var seekable = this.player_.seekable(); // skip undefined seekable
  17015. if (!seekable || !seekable.length) {
  17016. return;
  17017. }
  17018. var newSeekEnd = this.seekableEnd(); // we can only tell if we are behind live, when seekable changes
  17019. // once we detect that seekable has changed we check the new seek
  17020. // end against current time, with a fudge value of half a second.
  17021. if (newSeekEnd !== this.lastSeekEnd_) {
  17022. if (this.lastSeekEnd_) {
  17023. this.seekableIncrement_ = Math.abs(newSeekEnd - this.lastSeekEnd_);
  17024. }
  17025. this.pastSeekEnd_ = 0;
  17026. this.lastSeekEnd_ = newSeekEnd;
  17027. this.trigger('seekableendchange');
  17028. }
  17029. this.pastSeekEnd_ = this.pastSeekEnd() + 0.03;
  17030. if (this.isBehind_() !== this.behindLiveEdge()) {
  17031. this.behindLiveEdge_ = this.isBehind_();
  17032. this.trigger('liveedgechange');
  17033. }
  17034. }
  17035. /**
  17036. * handle a durationchange event on the player
  17037. * and start/stop tracking accordingly.
  17038. */
  17039. ;
  17040. _proto.handleDurationchange = function handleDurationchange() {
  17041. if (this.player_.duration() === Infinity) {
  17042. this.startTracking();
  17043. } else {
  17044. this.stopTracking();
  17045. }
  17046. }
  17047. /**
  17048. * start tracking live playback
  17049. */
  17050. ;
  17051. _proto.startTracking = function startTracking() {
  17052. var _this2 = this;
  17053. if (this.isTracking()) {
  17054. return;
  17055. }
  17056. this.trackingInterval_ = this.setInterval(this.trackLive_, 30);
  17057. this.trackLive_();
  17058. this.on(this.player_, 'play', this.trackLive_);
  17059. this.on(this.player_, 'pause', this.trackLive_);
  17060. this.one(this.player_, 'play', this.handlePlay); // this is to prevent showing that we are not live
  17061. // before a video starts to play
  17062. if (!this.timeupdateSeen_) {
  17063. this.handleTimeupdate = function () {
  17064. _this2.timeupdateSeen_ = true;
  17065. _this2.handleTimeupdate = null;
  17066. };
  17067. this.one(this.player_, 'timeupdate', this.handleTimeupdate);
  17068. }
  17069. };
  17070. _proto.handlePlay = function handlePlay() {
  17071. this.one(this.player_, 'timeupdate', this.seekToLiveEdge);
  17072. }
  17073. /**
  17074. * Stop tracking, and set all internal variables to
  17075. * their initial value.
  17076. */
  17077. ;
  17078. _proto.reset_ = function reset_() {
  17079. this.pastSeekEnd_ = 0;
  17080. this.lastSeekEnd_ = null;
  17081. this.behindLiveEdge_ = null;
  17082. this.timeupdateSeen_ = false;
  17083. this.clearInterval(this.trackingInterval_);
  17084. this.trackingInterval_ = null;
  17085. this.seekableIncrement_ = 12;
  17086. this.off(this.player_, 'play', this.trackLive_);
  17087. this.off(this.player_, 'pause', this.trackLive_);
  17088. this.off(this.player_, 'play', this.handlePlay);
  17089. this.off(this.player_, 'timeupdate', this.seekToLiveEdge);
  17090. if (this.handleTimeupdate) {
  17091. this.off(this.player_, 'timeupdate', this.handleTimeupdate);
  17092. this.handleTimeupdate = null;
  17093. }
  17094. }
  17095. /**
  17096. * stop tracking live playback
  17097. */
  17098. ;
  17099. _proto.stopTracking = function stopTracking() {
  17100. if (!this.isTracking()) {
  17101. return;
  17102. }
  17103. this.reset_();
  17104. }
  17105. /**
  17106. * A helper to get the player seekable end
  17107. * so that we don't have to null check everywhere
  17108. */
  17109. ;
  17110. _proto.seekableEnd = function seekableEnd() {
  17111. var seekable = this.player_.seekable();
  17112. var seekableEnds = [];
  17113. var i = seekable ? seekable.length : 0;
  17114. while (i--) {
  17115. seekableEnds.push(seekable.end(i));
  17116. } // grab the furthest seekable end after sorting, or if there are none
  17117. // default to Infinity
  17118. return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
  17119. }
  17120. /**
  17121. * A helper to get the player seekable start
  17122. * so that we don't have to null check everywhere
  17123. */
  17124. ;
  17125. _proto.seekableStart = function seekableStart() {
  17126. var seekable = this.player_.seekable();
  17127. var seekableStarts = [];
  17128. var i = seekable ? seekable.length : 0;
  17129. while (i--) {
  17130. seekableStarts.push(seekable.start(i));
  17131. } // grab the first seekable start after sorting, or if there are none
  17132. // default to 0
  17133. return seekableStarts.length ? seekableStarts.sort()[0] : 0;
  17134. }
  17135. /**
  17136. * Get the live time window
  17137. */
  17138. ;
  17139. _proto.liveWindow = function liveWindow() {
  17140. var liveCurrentTime = this.liveCurrentTime();
  17141. if (liveCurrentTime === Infinity) {
  17142. return Infinity;
  17143. }
  17144. return liveCurrentTime - this.seekableStart();
  17145. }
  17146. /**
  17147. * Determines if the player is live, only checks if this component
  17148. * is tracking live playback or not
  17149. */
  17150. ;
  17151. _proto.isLive = function isLive() {
  17152. return this.isTracking();
  17153. }
  17154. /**
  17155. * Determines if currentTime is at the live edge and won't fall behind
  17156. * on each seekableendchange
  17157. */
  17158. ;
  17159. _proto.atLiveEdge = function atLiveEdge() {
  17160. return !this.behindLiveEdge();
  17161. }
  17162. /**
  17163. * get what we expect the live current time to be
  17164. */
  17165. ;
  17166. _proto.liveCurrentTime = function liveCurrentTime() {
  17167. return this.pastSeekEnd() + this.seekableEnd();
  17168. }
  17169. /**
  17170. * Returns how far past seek end we expect current time to be
  17171. */
  17172. ;
  17173. _proto.pastSeekEnd = function pastSeekEnd() {
  17174. return this.pastSeekEnd_;
  17175. }
  17176. /**
  17177. * If we are currently behind the live edge, aka currentTime will be
  17178. * behind on a seekableendchange
  17179. */
  17180. ;
  17181. _proto.behindLiveEdge = function behindLiveEdge() {
  17182. return this.behindLiveEdge_;
  17183. };
  17184. _proto.isTracking = function isTracking() {
  17185. return typeof this.trackingInterval_ === 'number';
  17186. }
  17187. /**
  17188. * Seek to the live edge if we are behind the live edge
  17189. */
  17190. ;
  17191. _proto.seekToLiveEdge = function seekToLiveEdge() {
  17192. if (this.atLiveEdge()) {
  17193. return;
  17194. }
  17195. this.player_.currentTime(this.liveCurrentTime());
  17196. if (this.player_.paused()) {
  17197. this.player_.play();
  17198. }
  17199. };
  17200. _proto.dispose = function dispose() {
  17201. this.stopTracking();
  17202. _Component.prototype.dispose.call(this);
  17203. };
  17204. return LiveTracker;
  17205. }(Component);
  17206. Component.registerComponent('LiveTracker', LiveTracker);
  17207. /**
  17208. * This function is used to fire a sourceset when there is something
  17209. * similar to `mediaEl.load()` being called. It will try to find the source via
  17210. * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
  17211. * with the source that was found or empty string if we cannot know. If it cannot
  17212. * find a source then `sourceset` will not be fired.
  17213. *
  17214. * @param {Html5} tech
  17215. * The tech object that sourceset was setup on
  17216. *
  17217. * @return {boolean}
  17218. * returns false if the sourceset was not fired and true otherwise.
  17219. */
  17220. var sourcesetLoad = function sourcesetLoad(tech) {
  17221. var el = tech.el(); // if `el.src` is set, that source will be loaded.
  17222. if (el.hasAttribute('src')) {
  17223. tech.triggerSourceset(el.src);
  17224. return true;
  17225. }
  17226. /**
  17227. * Since there isn't a src property on the media element, source elements will be used for
  17228. * implementing the source selection algorithm. This happens asynchronously and
  17229. * for most cases were there is more than one source we cannot tell what source will
  17230. * be loaded, without re-implementing the source selection algorithm. At this time we are not
  17231. * going to do that. There are three special cases that we do handle here though:
  17232. *
  17233. * 1. If there are no sources, do not fire `sourceset`.
  17234. * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
  17235. * 3. If there is more than one `<source>` but all of them have the same `src` url.
  17236. * That will be our src.
  17237. */
  17238. var sources = tech.$$('source');
  17239. var srcUrls = [];
  17240. var src = ''; // if there are no sources, do not fire sourceset
  17241. if (!sources.length) {
  17242. return false;
  17243. } // only count valid/non-duplicate source elements
  17244. for (var i = 0; i < sources.length; i++) {
  17245. var url = sources[i].src;
  17246. if (url && srcUrls.indexOf(url) === -1) {
  17247. srcUrls.push(url);
  17248. }
  17249. } // there were no valid sources
  17250. if (!srcUrls.length) {
  17251. return false;
  17252. } // there is only one valid source element url
  17253. // use that
  17254. if (srcUrls.length === 1) {
  17255. src = srcUrls[0];
  17256. }
  17257. tech.triggerSourceset(src);
  17258. return true;
  17259. };
  17260. /**
  17261. * our implementation of an `innerHTML` descriptor for browsers
  17262. * that do not have one.
  17263. */
  17264. var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
  17265. get: function get() {
  17266. return this.cloneNode(true).innerHTML;
  17267. },
  17268. set: function set(v) {
  17269. // make a dummy node to use innerHTML on
  17270. var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided
  17271. dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy
  17272. var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy
  17273. // to the document fragment
  17274. while (dummy.childNodes.length) {
  17275. docFrag.appendChild(dummy.childNodes[0]);
  17276. } // remove content
  17277. this.innerText = ''; // now we add all of that html in one by appending the
  17278. // document fragment. This is how innerHTML does it.
  17279. window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would
  17280. return this.innerHTML;
  17281. }
  17282. });
  17283. /**
  17284. * Get a property descriptor given a list of priorities and the
  17285. * property to get.
  17286. */
  17287. var getDescriptor = function getDescriptor(priority, prop) {
  17288. var descriptor = {};
  17289. for (var i = 0; i < priority.length; i++) {
  17290. descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
  17291. if (descriptor && descriptor.set && descriptor.get) {
  17292. break;
  17293. }
  17294. }
  17295. descriptor.enumerable = true;
  17296. descriptor.configurable = true;
  17297. return descriptor;
  17298. };
  17299. var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
  17300. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
  17301. };
  17302. /**
  17303. * Patches browser internal functions so that we can tell synchronously
  17304. * if a `<source>` was appended to the media element. For some reason this
  17305. * causes a `sourceset` if the the media element is ready and has no source.
  17306. * This happens when:
  17307. * - The page has just loaded and the media element does not have a source.
  17308. * - The media element was emptied of all sources, then `load()` was called.
  17309. *
  17310. * It does this by patching the following functions/properties when they are supported:
  17311. *
  17312. * - `append()` - can be used to add a `<source>` element to the media element
  17313. * - `appendChild()` - can be used to add a `<source>` element to the media element
  17314. * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element
  17315. * - `innerHTML` - can be used to add a `<source>` element to the media element
  17316. *
  17317. * @param {Html5} tech
  17318. * The tech object that sourceset is being setup on.
  17319. */
  17320. var firstSourceWatch = function firstSourceWatch(tech) {
  17321. var el = tech.el(); // make sure firstSourceWatch isn't setup twice.
  17322. if (el.resetSourceWatch_) {
  17323. return;
  17324. }
  17325. var old = {};
  17326. var innerDescriptor = getInnerHTMLDescriptor(tech);
  17327. var appendWrapper = function appendWrapper(appendFn) {
  17328. return function () {
  17329. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  17330. args[_key] = arguments[_key];
  17331. }
  17332. var retval = appendFn.apply(el, args);
  17333. sourcesetLoad(tech);
  17334. return retval;
  17335. };
  17336. };
  17337. ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
  17338. if (!el[k]) {
  17339. return;
  17340. } // store the old function
  17341. old[k] = el[k]; // call the old function with a sourceset if a source
  17342. // was loaded
  17343. el[k] = appendWrapper(old[k]);
  17344. });
  17345. Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
  17346. set: appendWrapper(innerDescriptor.set)
  17347. }));
  17348. el.resetSourceWatch_ = function () {
  17349. el.resetSourceWatch_ = null;
  17350. Object.keys(old).forEach(function (k) {
  17351. el[k] = old[k];
  17352. });
  17353. Object.defineProperty(el, 'innerHTML', innerDescriptor);
  17354. }; // on the first sourceset, we need to revert our changes
  17355. tech.one('sourceset', el.resetSourceWatch_);
  17356. };
  17357. /**
  17358. * our implementation of a `src` descriptor for browsers
  17359. * that do not have one.
  17360. */
  17361. var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
  17362. get: function get() {
  17363. if (this.hasAttribute('src')) {
  17364. return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src'));
  17365. }
  17366. return '';
  17367. },
  17368. set: function set(v) {
  17369. window$1.Element.prototype.setAttribute.call(this, 'src', v);
  17370. return v;
  17371. }
  17372. });
  17373. var getSrcDescriptor = function getSrcDescriptor(tech) {
  17374. return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
  17375. };
  17376. /**
  17377. * setup `sourceset` handling on the `Html5` tech. This function
  17378. * patches the following element properties/functions:
  17379. *
  17380. * - `src` - to determine when `src` is set
  17381. * - `setAttribute()` - to determine when `src` is set
  17382. * - `load()` - this re-triggers the source selection algorithm, and can
  17383. * cause a sourceset.
  17384. *
  17385. * If there is no source when we are adding `sourceset` support or during a `load()`
  17386. * we also patch the functions listed in `firstSourceWatch`.
  17387. *
  17388. * @param {Html5} tech
  17389. * The tech to patch
  17390. */
  17391. var setupSourceset = function setupSourceset(tech) {
  17392. if (!tech.featuresSourceset) {
  17393. return;
  17394. }
  17395. var el = tech.el(); // make sure sourceset isn't setup twice.
  17396. if (el.resetSourceset_) {
  17397. return;
  17398. }
  17399. var srcDescriptor = getSrcDescriptor(tech);
  17400. var oldSetAttribute = el.setAttribute;
  17401. var oldLoad = el.load;
  17402. Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
  17403. set: function set(v) {
  17404. var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src
  17405. tech.triggerSourceset(el.src);
  17406. return retval;
  17407. }
  17408. }));
  17409. el.setAttribute = function (n, v) {
  17410. var retval = oldSetAttribute.call(el, n, v);
  17411. if (/src/i.test(n)) {
  17412. tech.triggerSourceset(el.src);
  17413. }
  17414. return retval;
  17415. };
  17416. el.load = function () {
  17417. var retval = oldLoad.call(el); // if load was called, but there was no source to fire
  17418. // sourceset on. We have to watch for a source append
  17419. // as that can trigger a `sourceset` when the media element
  17420. // has no source
  17421. if (!sourcesetLoad(tech)) {
  17422. tech.triggerSourceset('');
  17423. firstSourceWatch(tech);
  17424. }
  17425. return retval;
  17426. };
  17427. if (el.currentSrc) {
  17428. tech.triggerSourceset(el.currentSrc);
  17429. } else if (!sourcesetLoad(tech)) {
  17430. firstSourceWatch(tech);
  17431. }
  17432. el.resetSourceset_ = function () {
  17433. el.resetSourceset_ = null;
  17434. el.load = oldLoad;
  17435. el.setAttribute = oldSetAttribute;
  17436. Object.defineProperty(el, 'src', srcDescriptor);
  17437. if (el.resetSourceWatch_) {
  17438. el.resetSourceWatch_();
  17439. }
  17440. };
  17441. };
  17442. function _templateObject$1() {
  17443. 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."]);
  17444. _templateObject$1 = function _templateObject() {
  17445. return data;
  17446. };
  17447. return data;
  17448. }
  17449. /**
  17450. * HTML5 Media Controller - Wrapper for HTML5 Media API
  17451. *
  17452. * @mixes Tech~SourceHandlerAdditions
  17453. * @extends Tech
  17454. */
  17455. var Html5 =
  17456. /*#__PURE__*/
  17457. function (_Tech) {
  17458. _inheritsLoose(Html5, _Tech);
  17459. /**
  17460. * Create an instance of this Tech.
  17461. *
  17462. * @param {Object} [options]
  17463. * The key/value store of player options.
  17464. *
  17465. * @param {Component~ReadyCallback} ready
  17466. * Callback function to call when the `HTML5` Tech is ready.
  17467. */
  17468. function Html5(options, ready) {
  17469. var _this;
  17470. _this = _Tech.call(this, options, ready) || this;
  17471. var source = options.source;
  17472. var crossoriginTracks = false; // Set the source if one is provided
  17473. // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
  17474. // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
  17475. // anyway so the error gets fired.
  17476. if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
  17477. _this.setSource(source);
  17478. } else {
  17479. _this.handleLateInit_(_this.el_);
  17480. } // setup sourceset after late sourceset/init
  17481. if (options.enableSourceset) {
  17482. _this.setupSourcesetHandling_();
  17483. }
  17484. if (_this.el_.hasChildNodes()) {
  17485. var nodes = _this.el_.childNodes;
  17486. var nodesLength = nodes.length;
  17487. var removeNodes = [];
  17488. while (nodesLength--) {
  17489. var node = nodes[nodesLength];
  17490. var nodeName = node.nodeName.toLowerCase();
  17491. if (nodeName === 'track') {
  17492. if (!_this.featuresNativeTextTracks) {
  17493. // Empty video tag tracks so the built-in player doesn't use them also.
  17494. // This may not be fast enough to stop HTML5 browsers from reading the tags
  17495. // so we'll need to turn off any default tracks if we're manually doing
  17496. // captions and subtitles. videoElement.textTracks
  17497. removeNodes.push(node);
  17498. } else {
  17499. // store HTMLTrackElement and TextTrack to remote list
  17500. _this.remoteTextTrackEls().addTrackElement_(node);
  17501. _this.remoteTextTracks().addTrack(node.track);
  17502. _this.textTracks().addTrack(node.track);
  17503. if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
  17504. crossoriginTracks = true;
  17505. }
  17506. }
  17507. }
  17508. }
  17509. for (var i = 0; i < removeNodes.length; i++) {
  17510. _this.el_.removeChild(removeNodes[i]);
  17511. }
  17512. }
  17513. _this.proxyNativeTracks_();
  17514. if (_this.featuresNativeTextTracks && crossoriginTracks) {
  17515. log.warn(tsml(_templateObject$1()));
  17516. } // prevent iOS Safari from disabling metadata text tracks during native playback
  17517. _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used
  17518. // Our goal should be to get the custom controls on mobile solid everywhere
  17519. // so we can remove this all together. Right now this will block custom
  17520. // controls on touch enabled laptops like the Chrome Pixel
  17521. if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
  17522. _this.setControls(true);
  17523. } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
  17524. // into a `fullscreenchange` event
  17525. _this.proxyWebkitFullscreen_();
  17526. _this.triggerReady();
  17527. return _this;
  17528. }
  17529. /**
  17530. * Dispose of `HTML5` media element and remove all tracks.
  17531. */
  17532. var _proto = Html5.prototype;
  17533. _proto.dispose = function dispose() {
  17534. if (this.el_ && this.el_.resetSourceset_) {
  17535. this.el_.resetSourceset_();
  17536. }
  17537. Html5.disposeMediaElement(this.el_);
  17538. this.options_ = null; // tech will handle clearing of the emulated track list
  17539. _Tech.prototype.dispose.call(this);
  17540. }
  17541. /**
  17542. * Modify the media element so that we can detect when
  17543. * the source is changed. Fires `sourceset` just after the source has changed
  17544. */
  17545. ;
  17546. _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() {
  17547. setupSourceset(this);
  17548. }
  17549. /**
  17550. * When a captions track is enabled in the iOS Safari native player, all other
  17551. * tracks are disabled (including metadata tracks), which nulls all of their
  17552. * associated cue points. This will restore metadata tracks to their pre-fullscreen
  17553. * state in those cases so that cue points are not needlessly lost.
  17554. *
  17555. * @private
  17556. */
  17557. ;
  17558. _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
  17559. var textTracks = this.textTracks();
  17560. var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state
  17561. var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
  17562. metadataTracksPreFullscreenState = [];
  17563. for (var i = 0; i < textTracks.length; i++) {
  17564. var track = textTracks[i];
  17565. if (track.kind === 'metadata') {
  17566. metadataTracksPreFullscreenState.push({
  17567. track: track,
  17568. storedMode: track.mode
  17569. });
  17570. }
  17571. }
  17572. }; // snapshot each metadata track's initial state, and update the snapshot
  17573. // each time there is a track 'change' event
  17574. takeMetadataTrackSnapshot();
  17575. textTracks.addEventListener('change', takeMetadataTrackSnapshot);
  17576. this.on('dispose', function () {
  17577. return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  17578. });
  17579. var restoreTrackMode = function restoreTrackMode() {
  17580. for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
  17581. var storedTrack = metadataTracksPreFullscreenState[i];
  17582. if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
  17583. storedTrack.track.mode = storedTrack.storedMode;
  17584. }
  17585. } // we only want this handler to be executed on the first 'change' event
  17586. textTracks.removeEventListener('change', restoreTrackMode);
  17587. }; // when we enter fullscreen playback, stop updating the snapshot and
  17588. // restore all track modes to their pre-fullscreen state
  17589. this.on('webkitbeginfullscreen', function () {
  17590. textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed
  17591. textTracks.removeEventListener('change', restoreTrackMode);
  17592. textTracks.addEventListener('change', restoreTrackMode);
  17593. }); // start updating the snapshot again after leaving fullscreen
  17594. this.on('webkitendfullscreen', function () {
  17595. // remove the listener before adding it just in case it wasn't previously removed
  17596. textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
  17597. textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
  17598. textTracks.removeEventListener('change', restoreTrackMode);
  17599. });
  17600. }
  17601. /**
  17602. * Attempt to force override of tracks for the given type
  17603. *
  17604. * @param {string} type - Track type to override, possible values include 'Audio',
  17605. * 'Video', and 'Text'.
  17606. * @param {boolean} override - If set to true native audio/video will be overridden,
  17607. * otherwise native audio/video will potentially be used.
  17608. * @private
  17609. */
  17610. ;
  17611. _proto.overrideNative_ = function overrideNative_(type, override) {
  17612. var _this2 = this;
  17613. // If there is no behavioral change don't add/remove listeners
  17614. if (override !== this["featuresNative" + type + "Tracks"]) {
  17615. return;
  17616. }
  17617. var lowerCaseType = type.toLowerCase();
  17618. if (this[lowerCaseType + "TracksListeners_"]) {
  17619. Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) {
  17620. var elTracks = _this2.el()[lowerCaseType + "Tracks"];
  17621. elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]);
  17622. });
  17623. }
  17624. this["featuresNative" + type + "Tracks"] = !override;
  17625. this[lowerCaseType + "TracksListeners_"] = null;
  17626. this.proxyNativeTracksForType_(lowerCaseType);
  17627. }
  17628. /**
  17629. * Attempt to force override of native audio tracks.
  17630. *
  17631. * @param {boolean} override - If set to true native audio will be overridden,
  17632. * otherwise native audio will potentially be used.
  17633. */
  17634. ;
  17635. _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
  17636. this.overrideNative_('Audio', override);
  17637. }
  17638. /**
  17639. * Attempt to force override of native video tracks.
  17640. *
  17641. * @param {boolean} override - If set to true native video will be overridden,
  17642. * otherwise native video will potentially be used.
  17643. */
  17644. ;
  17645. _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
  17646. this.overrideNative_('Video', override);
  17647. }
  17648. /**
  17649. * Proxy native track list events for the given type to our track
  17650. * lists if the browser we are playing in supports that type of track list.
  17651. *
  17652. * @param {string} name - Track type; values include 'audio', 'video', and 'text'
  17653. * @private
  17654. */
  17655. ;
  17656. _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
  17657. var _this3 = this;
  17658. var props = NORMAL[name];
  17659. var elTracks = this.el()[props.getterName];
  17660. var techTracks = this[props.getterName]();
  17661. if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) {
  17662. return;
  17663. }
  17664. var listeners = {
  17665. change: function change(e) {
  17666. techTracks.trigger({
  17667. type: 'change',
  17668. target: techTracks,
  17669. currentTarget: techTracks,
  17670. srcElement: techTracks
  17671. });
  17672. },
  17673. addtrack: function addtrack(e) {
  17674. techTracks.addTrack(e.track);
  17675. },
  17676. removetrack: function removetrack(e) {
  17677. techTracks.removeTrack(e.track);
  17678. }
  17679. };
  17680. var removeOldTracks = function removeOldTracks() {
  17681. var removeTracks = [];
  17682. for (var i = 0; i < techTracks.length; i++) {
  17683. var found = false;
  17684. for (var j = 0; j < elTracks.length; j++) {
  17685. if (elTracks[j] === techTracks[i]) {
  17686. found = true;
  17687. break;
  17688. }
  17689. }
  17690. if (!found) {
  17691. removeTracks.push(techTracks[i]);
  17692. }
  17693. }
  17694. while (removeTracks.length) {
  17695. techTracks.removeTrack(removeTracks.shift());
  17696. }
  17697. };
  17698. this[props.getterName + 'Listeners_'] = listeners;
  17699. Object.keys(listeners).forEach(function (eventName) {
  17700. var listener = listeners[eventName];
  17701. elTracks.addEventListener(eventName, listener);
  17702. _this3.on('dispose', function (e) {
  17703. return elTracks.removeEventListener(eventName, listener);
  17704. });
  17705. }); // Remove (native) tracks that are not used anymore
  17706. this.on('loadstart', removeOldTracks);
  17707. this.on('dispose', function (e) {
  17708. return _this3.off('loadstart', removeOldTracks);
  17709. });
  17710. }
  17711. /**
  17712. * Proxy all native track list events to our track lists if the browser we are playing
  17713. * in supports that type of track list.
  17714. *
  17715. * @private
  17716. */
  17717. ;
  17718. _proto.proxyNativeTracks_ = function proxyNativeTracks_() {
  17719. var _this4 = this;
  17720. NORMAL.names.forEach(function (name) {
  17721. _this4.proxyNativeTracksForType_(name);
  17722. });
  17723. }
  17724. /**
  17725. * Create the `Html5` Tech's DOM element.
  17726. *
  17727. * @return {Element}
  17728. * The element that gets created.
  17729. */
  17730. ;
  17731. _proto.createEl = function createEl$$1() {
  17732. var el = this.options_.tag; // Check if this browser supports moving the element into the box.
  17733. // On the iPhone video will break if you move the element,
  17734. // So we have to create a brand new element.
  17735. // If we ingested the player div, we do not need to move the media element.
  17736. if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
  17737. // If the original tag is still there, clone and remove it.
  17738. if (el) {
  17739. var clone = el.cloneNode(true);
  17740. if (el.parentNode) {
  17741. el.parentNode.insertBefore(clone, el);
  17742. }
  17743. Html5.disposeMediaElement(el);
  17744. el = clone;
  17745. } else {
  17746. el = document.createElement('video'); // determine if native controls should be used
  17747. var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
  17748. var attributes = mergeOptions({}, tagAttributes);
  17749. if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
  17750. delete attributes.controls;
  17751. }
  17752. setAttributes(el, assign(attributes, {
  17753. id: this.options_.techId,
  17754. class: 'vjs-tech'
  17755. }));
  17756. }
  17757. el.playerId = this.options_.playerId;
  17758. }
  17759. if (typeof this.options_.preload !== 'undefined') {
  17760. setAttribute(el, 'preload', this.options_.preload);
  17761. } // Update specific tag settings, in case they were overridden
  17762. // `autoplay` has to be *last* so that `muted` and `playsinline` are present
  17763. // when iOS/Safari or other browsers attempt to autoplay.
  17764. var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
  17765. for (var i = 0; i < settingsAttrs.length; i++) {
  17766. var attr = settingsAttrs[i];
  17767. var value = this.options_[attr];
  17768. if (typeof value !== 'undefined') {
  17769. if (value) {
  17770. setAttribute(el, attr, attr);
  17771. } else {
  17772. removeAttribute(el, attr);
  17773. }
  17774. el[attr] = value;
  17775. }
  17776. }
  17777. return el;
  17778. }
  17779. /**
  17780. * This will be triggered if the loadstart event has already fired, before videojs was
  17781. * ready. Two known examples of when this can happen are:
  17782. * 1. If we're loading the playback object after it has started loading
  17783. * 2. The media is already playing the (often with autoplay on) then
  17784. *
  17785. * This function will fire another loadstart so that videojs can catchup.
  17786. *
  17787. * @fires Tech#loadstart
  17788. *
  17789. * @return {undefined}
  17790. * returns nothing.
  17791. */
  17792. ;
  17793. _proto.handleLateInit_ = function handleLateInit_(el) {
  17794. if (el.networkState === 0 || el.networkState === 3) {
  17795. // The video element hasn't started loading the source yet
  17796. // or didn't find a source
  17797. return;
  17798. }
  17799. if (el.readyState === 0) {
  17800. // NetworkState is set synchronously BUT loadstart is fired at the
  17801. // end of the current stack, usually before setInterval(fn, 0).
  17802. // So at this point we know loadstart may have already fired or is
  17803. // about to fire, and either way the player hasn't seen it yet.
  17804. // We don't want to fire loadstart prematurely here and cause a
  17805. // double loadstart so we'll wait and see if it happens between now
  17806. // and the next loop, and fire it if not.
  17807. // HOWEVER, we also want to make sure it fires before loadedmetadata
  17808. // which could also happen between now and the next loop, so we'll
  17809. // watch for that also.
  17810. var loadstartFired = false;
  17811. var setLoadstartFired = function setLoadstartFired() {
  17812. loadstartFired = true;
  17813. };
  17814. this.on('loadstart', setLoadstartFired);
  17815. var triggerLoadstart = function triggerLoadstart() {
  17816. // We did miss the original loadstart. Make sure the player
  17817. // sees loadstart before loadedmetadata
  17818. if (!loadstartFired) {
  17819. this.trigger('loadstart');
  17820. }
  17821. };
  17822. this.on('loadedmetadata', triggerLoadstart);
  17823. this.ready(function () {
  17824. this.off('loadstart', setLoadstartFired);
  17825. this.off('loadedmetadata', triggerLoadstart);
  17826. if (!loadstartFired) {
  17827. // We did miss the original native loadstart. Fire it now.
  17828. this.trigger('loadstart');
  17829. }
  17830. });
  17831. return;
  17832. } // From here on we know that loadstart already fired and we missed it.
  17833. // The other readyState events aren't as much of a problem if we double
  17834. // them, so not going to go to as much trouble as loadstart to prevent
  17835. // that unless we find reason to.
  17836. var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
  17837. eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
  17838. if (el.readyState >= 2) {
  17839. eventsToTrigger.push('loadeddata');
  17840. } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
  17841. if (el.readyState >= 3) {
  17842. eventsToTrigger.push('canplay');
  17843. } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
  17844. if (el.readyState >= 4) {
  17845. eventsToTrigger.push('canplaythrough');
  17846. } // We still need to give the player time to add event listeners
  17847. this.ready(function () {
  17848. eventsToTrigger.forEach(function (type) {
  17849. this.trigger(type);
  17850. }, this);
  17851. });
  17852. }
  17853. /**
  17854. * Set current time for the `HTML5` tech.
  17855. *
  17856. * @param {number} seconds
  17857. * Set the current time of the media to this.
  17858. */
  17859. ;
  17860. _proto.setCurrentTime = function setCurrentTime(seconds) {
  17861. try {
  17862. this.el_.currentTime = seconds;
  17863. } catch (e) {
  17864. log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady);
  17865. }
  17866. }
  17867. /**
  17868. * Get the current duration of the HTML5 media element.
  17869. *
  17870. * @return {number}
  17871. * The duration of the media or 0 if there is no duration.
  17872. */
  17873. ;
  17874. _proto.duration = function duration() {
  17875. var _this5 = this;
  17876. // Android Chrome will report duration as Infinity for VOD HLS until after
  17877. // playback has started, which triggers the live display erroneously.
  17878. // Return NaN if playback has not started and trigger a durationupdate once
  17879. // the duration can be reliably known.
  17880. if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
  17881. // Wait for the first `timeupdate` with currentTime > 0 - there may be
  17882. // several with 0
  17883. var checkProgress = function checkProgress() {
  17884. if (_this5.el_.currentTime > 0) {
  17885. // Trigger durationchange for genuinely live video
  17886. if (_this5.el_.duration === Infinity) {
  17887. _this5.trigger('durationchange');
  17888. }
  17889. _this5.off('timeupdate', checkProgress);
  17890. }
  17891. };
  17892. this.on('timeupdate', checkProgress);
  17893. return NaN;
  17894. }
  17895. return this.el_.duration || NaN;
  17896. }
  17897. /**
  17898. * Get the current width of the HTML5 media element.
  17899. *
  17900. * @return {number}
  17901. * The width of the HTML5 media element.
  17902. */
  17903. ;
  17904. _proto.width = function width() {
  17905. return this.el_.offsetWidth;
  17906. }
  17907. /**
  17908. * Get the current height of the HTML5 media element.
  17909. *
  17910. * @return {number}
  17911. * The height of the HTML5 media element.
  17912. */
  17913. ;
  17914. _proto.height = function height() {
  17915. return this.el_.offsetHeight;
  17916. }
  17917. /**
  17918. * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
  17919. * `fullscreenchange` event.
  17920. *
  17921. * @private
  17922. * @fires fullscreenchange
  17923. * @listens webkitendfullscreen
  17924. * @listens webkitbeginfullscreen
  17925. * @listens webkitbeginfullscreen
  17926. */
  17927. ;
  17928. _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
  17929. var _this6 = this;
  17930. if (!('webkitDisplayingFullscreen' in this.el_)) {
  17931. return;
  17932. }
  17933. var endFn = function endFn() {
  17934. this.trigger('fullscreenchange', {
  17935. isFullscreen: false
  17936. });
  17937. };
  17938. var beginFn = function beginFn() {
  17939. if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
  17940. this.one('webkitendfullscreen', endFn);
  17941. this.trigger('fullscreenchange', {
  17942. isFullscreen: true
  17943. });
  17944. }
  17945. };
  17946. this.on('webkitbeginfullscreen', beginFn);
  17947. this.on('dispose', function () {
  17948. _this6.off('webkitbeginfullscreen', beginFn);
  17949. _this6.off('webkitendfullscreen', endFn);
  17950. });
  17951. }
  17952. /**
  17953. * Check if fullscreen is supported on the current playback device.
  17954. *
  17955. * @return {boolean}
  17956. * - True if fullscreen is supported.
  17957. * - False if fullscreen is not supported.
  17958. */
  17959. ;
  17960. _proto.supportsFullScreen = function supportsFullScreen() {
  17961. if (typeof this.el_.webkitEnterFullScreen === 'function') {
  17962. var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard
  17963. if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
  17964. return true;
  17965. }
  17966. }
  17967. return false;
  17968. }
  17969. /**
  17970. * Request that the `HTML5` Tech enter fullscreen.
  17971. */
  17972. ;
  17973. _proto.enterFullScreen = function enterFullScreen() {
  17974. var video = this.el_;
  17975. if (video.paused && video.networkState <= video.HAVE_METADATA) {
  17976. // attempt to prime the video element for programmatic access
  17977. // this isn't necessary on the desktop but shouldn't hurt
  17978. this.el_.play(); // playing and pausing synchronously during the transition to fullscreen
  17979. // can get iOS ~6.1 devices into a play/pause loop
  17980. this.setTimeout(function () {
  17981. video.pause();
  17982. video.webkitEnterFullScreen();
  17983. }, 0);
  17984. } else {
  17985. video.webkitEnterFullScreen();
  17986. }
  17987. }
  17988. /**
  17989. * Request that the `HTML5` Tech exit fullscreen.
  17990. */
  17991. ;
  17992. _proto.exitFullScreen = function exitFullScreen() {
  17993. this.el_.webkitExitFullScreen();
  17994. }
  17995. /**
  17996. * A getter/setter for the `Html5` Tech's source object.
  17997. * > Note: Please use {@link Html5#setSource}
  17998. *
  17999. * @param {Tech~SourceObject} [src]
  18000. * The source object you want to set on the `HTML5` techs element.
  18001. *
  18002. * @return {Tech~SourceObject|undefined}
  18003. * - The current source object when a source is not passed in.
  18004. * - undefined when setting
  18005. *
  18006. * @deprecated Since version 5.
  18007. */
  18008. ;
  18009. _proto.src = function src(_src) {
  18010. if (_src === undefined) {
  18011. return this.el_.src;
  18012. } // Setting src through `src` instead of `setSrc` will be deprecated
  18013. this.setSrc(_src);
  18014. }
  18015. /**
  18016. * Reset the tech by removing all sources and then calling
  18017. * {@link Html5.resetMediaElement}.
  18018. */
  18019. ;
  18020. _proto.reset = function reset() {
  18021. Html5.resetMediaElement(this.el_);
  18022. }
  18023. /**
  18024. * Get the current source on the `HTML5` Tech. Falls back to returning the source from
  18025. * the HTML5 media element.
  18026. *
  18027. * @return {Tech~SourceObject}
  18028. * The current source object from the HTML5 tech. With a fallback to the
  18029. * elements source.
  18030. */
  18031. ;
  18032. _proto.currentSrc = function currentSrc() {
  18033. if (this.currentSource_) {
  18034. return this.currentSource_.src;
  18035. }
  18036. return this.el_.currentSrc;
  18037. }
  18038. /**
  18039. * Set controls attribute for the HTML5 media Element.
  18040. *
  18041. * @param {string} val
  18042. * Value to set the controls attribute to
  18043. */
  18044. ;
  18045. _proto.setControls = function setControls(val) {
  18046. this.el_.controls = !!val;
  18047. }
  18048. /**
  18049. * Create and returns a remote {@link TextTrack} object.
  18050. *
  18051. * @param {string} kind
  18052. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  18053. *
  18054. * @param {string} [label]
  18055. * Label to identify the text track
  18056. *
  18057. * @param {string} [language]
  18058. * Two letter language abbreviation
  18059. *
  18060. * @return {TextTrack}
  18061. * The TextTrack that gets created.
  18062. */
  18063. ;
  18064. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  18065. if (!this.featuresNativeTextTracks) {
  18066. return _Tech.prototype.addTextTrack.call(this, kind, label, language);
  18067. }
  18068. return this.el_.addTextTrack(kind, label, language);
  18069. }
  18070. /**
  18071. * Creates either native TextTrack or an emulated TextTrack depending
  18072. * on the value of `featuresNativeTextTracks`
  18073. *
  18074. * @param {Object} options
  18075. * The object should contain the options to initialize the TextTrack with.
  18076. *
  18077. * @param {string} [options.kind]
  18078. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  18079. *
  18080. * @param {string} [options.label]
  18081. * Label to identify the text track
  18082. *
  18083. * @param {string} [options.language]
  18084. * Two letter language abbreviation.
  18085. *
  18086. * @param {boolean} [options.default]
  18087. * Default this track to on.
  18088. *
  18089. * @param {string} [options.id]
  18090. * The internal id to assign this track.
  18091. *
  18092. * @param {string} [options.src]
  18093. * A source url for the track.
  18094. *
  18095. * @return {HTMLTrackElement}
  18096. * The track element that gets created.
  18097. */
  18098. ;
  18099. _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
  18100. if (!this.featuresNativeTextTracks) {
  18101. return _Tech.prototype.createRemoteTextTrack.call(this, options);
  18102. }
  18103. var htmlTrackElement = document.createElement('track');
  18104. if (options.kind) {
  18105. htmlTrackElement.kind = options.kind;
  18106. }
  18107. if (options.label) {
  18108. htmlTrackElement.label = options.label;
  18109. }
  18110. if (options.language || options.srclang) {
  18111. htmlTrackElement.srclang = options.language || options.srclang;
  18112. }
  18113. if (options.default) {
  18114. htmlTrackElement.default = options.default;
  18115. }
  18116. if (options.id) {
  18117. htmlTrackElement.id = options.id;
  18118. }
  18119. if (options.src) {
  18120. htmlTrackElement.src = options.src;
  18121. }
  18122. return htmlTrackElement;
  18123. }
  18124. /**
  18125. * Creates a remote text track object and returns an html track element.
  18126. *
  18127. * @param {Object} options The object should contain values for
  18128. * kind, language, label, and src (location of the WebVTT file)
  18129. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  18130. * automatically removed from the video element whenever the source changes
  18131. * @return {HTMLTrackElement} An Html Track Element.
  18132. * This can be an emulated {@link HTMLTrackElement} or a native one.
  18133. * @deprecated The default value of the "manualCleanup" parameter will default
  18134. * to "false" in upcoming versions of Video.js
  18135. */
  18136. ;
  18137. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  18138. var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
  18139. if (this.featuresNativeTextTracks) {
  18140. this.el().appendChild(htmlTrackElement);
  18141. }
  18142. return htmlTrackElement;
  18143. }
  18144. /**
  18145. * Remove remote `TextTrack` from `TextTrackList` object
  18146. *
  18147. * @param {TextTrack} track
  18148. * `TextTrack` object to remove
  18149. */
  18150. ;
  18151. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  18152. _Tech.prototype.removeRemoteTextTrack.call(this, track);
  18153. if (this.featuresNativeTextTracks) {
  18154. var tracks = this.$$('track');
  18155. var i = tracks.length;
  18156. while (i--) {
  18157. if (track === tracks[i] || track === tracks[i].track) {
  18158. this.el().removeChild(tracks[i]);
  18159. }
  18160. }
  18161. }
  18162. }
  18163. /**
  18164. * Gets available media playback quality metrics as specified by the W3C's Media
  18165. * Playback Quality API.
  18166. *
  18167. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  18168. *
  18169. * @return {Object}
  18170. * An object with supported media playback quality metrics
  18171. */
  18172. ;
  18173. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  18174. if (typeof this.el().getVideoPlaybackQuality === 'function') {
  18175. return this.el().getVideoPlaybackQuality();
  18176. }
  18177. var videoPlaybackQuality = {};
  18178. if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
  18179. videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
  18180. videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
  18181. }
  18182. if (window$1.performance && typeof window$1.performance.now === 'function') {
  18183. videoPlaybackQuality.creationTime = window$1.performance.now();
  18184. } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') {
  18185. videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart;
  18186. }
  18187. return videoPlaybackQuality;
  18188. };
  18189. return Html5;
  18190. }(Tech);
  18191. /* HTML5 Support Testing ---------------------------------------------------- */
  18192. if (isReal()) {
  18193. /**
  18194. * Element for testing browser HTML5 media capabilities
  18195. *
  18196. * @type {Element}
  18197. * @constant
  18198. * @private
  18199. */
  18200. Html5.TEST_VID = document.createElement('video');
  18201. var track = document.createElement('track');
  18202. track.kind = 'captions';
  18203. track.srclang = 'en';
  18204. track.label = 'English';
  18205. Html5.TEST_VID.appendChild(track);
  18206. }
  18207. /**
  18208. * Check if HTML5 media is supported by this browser/device.
  18209. *
  18210. * @return {boolean}
  18211. * - True if HTML5 media is supported.
  18212. * - False if HTML5 media is not supported.
  18213. */
  18214. Html5.isSupported = function () {
  18215. // IE with no Media Player is a LIAR! (#984)
  18216. try {
  18217. Html5.TEST_VID.volume = 0.5;
  18218. } catch (e) {
  18219. return false;
  18220. }
  18221. return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
  18222. };
  18223. /**
  18224. * Check if the tech can support the given type
  18225. *
  18226. * @param {string} type
  18227. * The mimetype to check
  18228. * @return {string} 'probably', 'maybe', or '' (empty string)
  18229. */
  18230. Html5.canPlayType = function (type) {
  18231. return Html5.TEST_VID.canPlayType(type);
  18232. };
  18233. /**
  18234. * Check if the tech can support the given source
  18235. *
  18236. * @param {Object} srcObj
  18237. * The source object
  18238. * @param {Object} options
  18239. * The options passed to the tech
  18240. * @return {string} 'probably', 'maybe', or '' (empty string)
  18241. */
  18242. Html5.canPlaySource = function (srcObj, options) {
  18243. return Html5.canPlayType(srcObj.type);
  18244. };
  18245. /**
  18246. * Check if the volume can be changed in this browser/device.
  18247. * Volume cannot be changed in a lot of mobile devices.
  18248. * Specifically, it can't be changed from 1 on iOS.
  18249. *
  18250. * @return {boolean}
  18251. * - True if volume can be controlled
  18252. * - False otherwise
  18253. */
  18254. Html5.canControlVolume = function () {
  18255. // IE will error if Windows Media Player not installed #3315
  18256. try {
  18257. var volume = Html5.TEST_VID.volume;
  18258. Html5.TEST_VID.volume = volume / 2 + 0.1;
  18259. return volume !== Html5.TEST_VID.volume;
  18260. } catch (e) {
  18261. return false;
  18262. }
  18263. };
  18264. /**
  18265. * Check if the volume can be muted in this browser/device.
  18266. * Some devices, e.g. iOS, don't allow changing volume
  18267. * but permits muting/unmuting.
  18268. *
  18269. * @return {bolean}
  18270. * - True if volume can be muted
  18271. * - False otherwise
  18272. */
  18273. Html5.canMuteVolume = function () {
  18274. try {
  18275. var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always
  18276. // work, so we want to set both property and attribute
  18277. Html5.TEST_VID.muted = !muted;
  18278. if (Html5.TEST_VID.muted) {
  18279. setAttribute(Html5.TEST_VID, 'muted', 'muted');
  18280. } else {
  18281. removeAttribute(Html5.TEST_VID, 'muted', 'muted');
  18282. }
  18283. return muted !== Html5.TEST_VID.muted;
  18284. } catch (e) {
  18285. return false;
  18286. }
  18287. };
  18288. /**
  18289. * Check if the playback rate can be changed in this browser/device.
  18290. *
  18291. * @return {boolean}
  18292. * - True if playback rate can be controlled
  18293. * - False otherwise
  18294. */
  18295. Html5.canControlPlaybackRate = function () {
  18296. // Playback rate API is implemented in Android Chrome, but doesn't do anything
  18297. // https://github.com/videojs/video.js/issues/3180
  18298. if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
  18299. return false;
  18300. } // IE will error if Windows Media Player not installed #3315
  18301. try {
  18302. var playbackRate = Html5.TEST_VID.playbackRate;
  18303. Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
  18304. return playbackRate !== Html5.TEST_VID.playbackRate;
  18305. } catch (e) {
  18306. return false;
  18307. }
  18308. };
  18309. /**
  18310. * Check if we can override a video/audio elements attributes, with
  18311. * Object.defineProperty.
  18312. *
  18313. * @return {boolean}
  18314. * - True if builtin attributes can be overridden
  18315. * - False otherwise
  18316. */
  18317. Html5.canOverrideAttributes = function () {
  18318. // if we cannot overwrite the src/innerHTML property, there is no support
  18319. // iOS 7 safari for instance cannot do this.
  18320. try {
  18321. var noop = function noop() {};
  18322. Object.defineProperty(document.createElement('video'), 'src', {
  18323. get: noop,
  18324. set: noop
  18325. });
  18326. Object.defineProperty(document.createElement('audio'), 'src', {
  18327. get: noop,
  18328. set: noop
  18329. });
  18330. Object.defineProperty(document.createElement('video'), 'innerHTML', {
  18331. get: noop,
  18332. set: noop
  18333. });
  18334. Object.defineProperty(document.createElement('audio'), 'innerHTML', {
  18335. get: noop,
  18336. set: noop
  18337. });
  18338. } catch (e) {
  18339. return false;
  18340. }
  18341. return true;
  18342. };
  18343. /**
  18344. * Check to see if native `TextTrack`s are supported by this browser/device.
  18345. *
  18346. * @return {boolean}
  18347. * - True if native `TextTrack`s are supported.
  18348. * - False otherwise
  18349. */
  18350. Html5.supportsNativeTextTracks = function () {
  18351. return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
  18352. };
  18353. /**
  18354. * Check to see if native `VideoTrack`s are supported by this browser/device
  18355. *
  18356. * @return {boolean}
  18357. * - True if native `VideoTrack`s are supported.
  18358. * - False otherwise
  18359. */
  18360. Html5.supportsNativeVideoTracks = function () {
  18361. return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
  18362. };
  18363. /**
  18364. * Check to see if native `AudioTrack`s are supported by this browser/device
  18365. *
  18366. * @return {boolean}
  18367. * - True if native `AudioTrack`s are supported.
  18368. * - False otherwise
  18369. */
  18370. Html5.supportsNativeAudioTracks = function () {
  18371. return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
  18372. };
  18373. /**
  18374. * An array of events available on the Html5 tech.
  18375. *
  18376. * @private
  18377. * @type {Array}
  18378. */
  18379. 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'];
  18380. /**
  18381. * Boolean indicating whether the `Tech` supports volume control.
  18382. *
  18383. * @type {boolean}
  18384. * @default {@link Html5.canControlVolume}
  18385. */
  18386. Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
  18387. /**
  18388. * Boolean indicating whether the `Tech` supports muting volume.
  18389. *
  18390. * @type {bolean}
  18391. * @default {@link Html5.canMuteVolume}
  18392. */
  18393. Html5.prototype.featuresMuteControl = Html5.canMuteVolume();
  18394. /**
  18395. * Boolean indicating whether the `Tech` supports changing the speed at which the media
  18396. * plays. Examples:
  18397. * - Set player to play 2x (twice) as fast
  18398. * - Set player to play 0.5x (half) as fast
  18399. *
  18400. * @type {boolean}
  18401. * @default {@link Html5.canControlPlaybackRate}
  18402. */
  18403. Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
  18404. /**
  18405. * Boolean indicating whether the `Tech` supports the `sourceset` event.
  18406. *
  18407. * @type {boolean}
  18408. * @default
  18409. */
  18410. Html5.prototype.featuresSourceset = Html5.canOverrideAttributes();
  18411. /**
  18412. * Boolean indicating whether the `HTML5` tech currently supports the media element
  18413. * moving in the DOM. iOS breaks if you move the media element, so this is set this to
  18414. * false there. Everywhere else this should be true.
  18415. *
  18416. * @type {boolean}
  18417. * @default
  18418. */
  18419. Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed.
  18420. // Is this true?
  18421. /**
  18422. * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
  18423. * when going into fullscreen.
  18424. *
  18425. * @type {boolean}
  18426. * @default
  18427. */
  18428. Html5.prototype.featuresFullscreenResize = true;
  18429. /**
  18430. * Boolean indicating whether the `HTML5` tech currently supports the progress event.
  18431. * If this is false, manual `progress` events will be triggered instead.
  18432. *
  18433. * @type {boolean}
  18434. * @default
  18435. */
  18436. Html5.prototype.featuresProgressEvents = true;
  18437. /**
  18438. * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
  18439. * If this is false, manual `timeupdate` events will be triggered instead.
  18440. *
  18441. * @default
  18442. */
  18443. Html5.prototype.featuresTimeupdateEvents = true;
  18444. /**
  18445. * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
  18446. *
  18447. * @type {boolean}
  18448. * @default {@link Html5.supportsNativeTextTracks}
  18449. */
  18450. Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks();
  18451. /**
  18452. * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
  18453. *
  18454. * @type {boolean}
  18455. * @default {@link Html5.supportsNativeVideoTracks}
  18456. */
  18457. Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
  18458. /**
  18459. * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
  18460. *
  18461. * @type {boolean}
  18462. * @default {@link Html5.supportsNativeAudioTracks}
  18463. */
  18464. Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); // HTML5 Feature detection and Device Fixes --------------------------------- //
  18465. var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
  18466. var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
  18467. Html5.patchCanPlayType = function () {
  18468. // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
  18469. // Firefox and Chrome report correctly
  18470. if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
  18471. Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
  18472. if (type && mpegurlRE.test(type)) {
  18473. return 'maybe';
  18474. }
  18475. return canPlayType.call(this, type);
  18476. };
  18477. }
  18478. };
  18479. Html5.unpatchCanPlayType = function () {
  18480. var r = Html5.TEST_VID.constructor.prototype.canPlayType;
  18481. Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
  18482. return r;
  18483. }; // by default, patch the media element
  18484. Html5.patchCanPlayType();
  18485. Html5.disposeMediaElement = function (el) {
  18486. if (!el) {
  18487. return;
  18488. }
  18489. if (el.parentNode) {
  18490. el.parentNode.removeChild(el);
  18491. } // remove any child track or source nodes to prevent their loading
  18492. while (el.hasChildNodes()) {
  18493. el.removeChild(el.firstChild);
  18494. } // remove any src reference. not setting `src=''` because that causes a warning
  18495. // in firefox
  18496. el.removeAttribute('src'); // force the media element to update its loading state by calling load()
  18497. // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
  18498. if (typeof el.load === 'function') {
  18499. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  18500. (function () {
  18501. try {
  18502. el.load();
  18503. } catch (e) {// not supported
  18504. }
  18505. })();
  18506. }
  18507. };
  18508. Html5.resetMediaElement = function (el) {
  18509. if (!el) {
  18510. return;
  18511. }
  18512. var sources = el.querySelectorAll('source');
  18513. var i = sources.length;
  18514. while (i--) {
  18515. el.removeChild(sources[i]);
  18516. } // remove any src reference.
  18517. // not setting `src=''` because that throws an error
  18518. el.removeAttribute('src');
  18519. if (typeof el.load === 'function') {
  18520. // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
  18521. (function () {
  18522. try {
  18523. el.load();
  18524. } catch (e) {// satisfy linter
  18525. }
  18526. })();
  18527. }
  18528. };
  18529. /* Native HTML5 element property wrapping ----------------------------------- */
  18530. // Wrap native boolean attributes with getters that check both property and attribute
  18531. // The list is as followed:
  18532. // muted, defaultMuted, autoplay, controls, loop, playsinline
  18533. [
  18534. /**
  18535. * Get the value of `muted` from the media element. `muted` indicates
  18536. * that the volume for the media should be set to silent. This does not actually change
  18537. * the `volume` attribute.
  18538. *
  18539. * @method Html5#muted
  18540. * @return {boolean}
  18541. * - True if the value of `volume` should be ignored and the audio set to silent.
  18542. * - False if the value of `volume` should be used.
  18543. *
  18544. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  18545. */
  18546. 'muted',
  18547. /**
  18548. * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
  18549. * whether the media should start muted or not. Only changes the default state of the
  18550. * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
  18551. * current state.
  18552. *
  18553. * @method Html5#defaultMuted
  18554. * @return {boolean}
  18555. * - The value of `defaultMuted` from the media element.
  18556. * - True indicates that the media should start muted.
  18557. * - False indicates that the media should not start muted
  18558. *
  18559. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  18560. */
  18561. 'defaultMuted',
  18562. /**
  18563. * Get the value of `autoplay` from the media element. `autoplay` indicates
  18564. * that the media should start to play as soon as the page is ready.
  18565. *
  18566. * @method Html5#autoplay
  18567. * @return {boolean}
  18568. * - The value of `autoplay` from the media element.
  18569. * - True indicates that the media should start as soon as the page loads.
  18570. * - False indicates that the media should not start as soon as the page loads.
  18571. *
  18572. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  18573. */
  18574. 'autoplay',
  18575. /**
  18576. * Get the value of `controls` from the media element. `controls` indicates
  18577. * whether the native media controls should be shown or hidden.
  18578. *
  18579. * @method Html5#controls
  18580. * @return {boolean}
  18581. * - The value of `controls` from the media element.
  18582. * - True indicates that native controls should be showing.
  18583. * - False indicates that native controls should be hidden.
  18584. *
  18585. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
  18586. */
  18587. 'controls',
  18588. /**
  18589. * Get the value of `loop` from the media element. `loop` indicates
  18590. * that the media should return to the start of the media and continue playing once
  18591. * it reaches the end.
  18592. *
  18593. * @method Html5#loop
  18594. * @return {boolean}
  18595. * - The value of `loop` from the media element.
  18596. * - True indicates that playback should seek back to start once
  18597. * the end of a media is reached.
  18598. * - False indicates that playback should not loop back to the start when the
  18599. * end of the media is reached.
  18600. *
  18601. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  18602. */
  18603. 'loop',
  18604. /**
  18605. * Get the value of `playsinline` from the media element. `playsinline` indicates
  18606. * to the browser that non-fullscreen playback is preferred when fullscreen
  18607. * playback is the native default, such as in iOS Safari.
  18608. *
  18609. * @method Html5#playsinline
  18610. * @return {boolean}
  18611. * - The value of `playsinline` from the media element.
  18612. * - True indicates that the media should play inline.
  18613. * - False indicates that the media should not play inline.
  18614. *
  18615. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  18616. */
  18617. 'playsinline'].forEach(function (prop) {
  18618. Html5.prototype[prop] = function () {
  18619. return this.el_[prop] || this.el_.hasAttribute(prop);
  18620. };
  18621. }); // Wrap native boolean attributes with setters that set both property and attribute
  18622. // The list is as followed:
  18623. // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
  18624. // setControls is special-cased above
  18625. [
  18626. /**
  18627. * Set the value of `muted` on the media element. `muted` indicates that the current
  18628. * audio level should be silent.
  18629. *
  18630. * @method Html5#setMuted
  18631. * @param {boolean} muted
  18632. * - True if the audio should be set to silent
  18633. * - False otherwise
  18634. *
  18635. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
  18636. */
  18637. 'muted',
  18638. /**
  18639. * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
  18640. * audio level should be silent, but will only effect the muted level on intial playback..
  18641. *
  18642. * @method Html5.prototype.setDefaultMuted
  18643. * @param {boolean} defaultMuted
  18644. * - True if the audio should be set to silent
  18645. * - False otherwise
  18646. *
  18647. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
  18648. */
  18649. 'defaultMuted',
  18650. /**
  18651. * Set the value of `autoplay` on the media element. `autoplay` indicates
  18652. * that the media should start to play as soon as the page is ready.
  18653. *
  18654. * @method Html5#setAutoplay
  18655. * @param {boolean} autoplay
  18656. * - True indicates that the media should start as soon as the page loads.
  18657. * - False indicates that the media should not start as soon as the page loads.
  18658. *
  18659. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
  18660. */
  18661. 'autoplay',
  18662. /**
  18663. * Set the value of `loop` on the media element. `loop` indicates
  18664. * that the media should return to the start of the media and continue playing once
  18665. * it reaches the end.
  18666. *
  18667. * @method Html5#setLoop
  18668. * @param {boolean} loop
  18669. * - True indicates that playback should seek back to start once
  18670. * the end of a media is reached.
  18671. * - False indicates that playback should not loop back to the start when the
  18672. * end of the media is reached.
  18673. *
  18674. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
  18675. */
  18676. 'loop',
  18677. /**
  18678. * Set the value of `playsinline` from the media element. `playsinline` indicates
  18679. * to the browser that non-fullscreen playback is preferred when fullscreen
  18680. * playback is the native default, such as in iOS Safari.
  18681. *
  18682. * @method Html5#setPlaysinline
  18683. * @param {boolean} playsinline
  18684. * - True indicates that the media should play inline.
  18685. * - False indicates that the media should not play inline.
  18686. *
  18687. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  18688. */
  18689. 'playsinline'].forEach(function (prop) {
  18690. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  18691. this.el_[prop] = v;
  18692. if (v) {
  18693. this.el_.setAttribute(prop, prop);
  18694. } else {
  18695. this.el_.removeAttribute(prop);
  18696. }
  18697. };
  18698. }); // Wrap native properties with a getter
  18699. // The list is as followed
  18700. // paused, currentTime, buffered, volume, poster, preload, error, seeking
  18701. // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
  18702. // readyState, videoWidth, videoHeight
  18703. [
  18704. /**
  18705. * Get the value of `paused` from the media element. `paused` indicates whether the media element
  18706. * is currently paused or not.
  18707. *
  18708. * @method Html5#paused
  18709. * @return {boolean}
  18710. * The value of `paused` from the media element.
  18711. *
  18712. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
  18713. */
  18714. 'paused',
  18715. /**
  18716. * Get the value of `currentTime` from the media element. `currentTime` indicates
  18717. * the current second that the media is at in playback.
  18718. *
  18719. * @method Html5#currentTime
  18720. * @return {number}
  18721. * The value of `currentTime` from the media element.
  18722. *
  18723. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
  18724. */
  18725. 'currentTime',
  18726. /**
  18727. * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
  18728. * object that represents the parts of the media that are already downloaded and
  18729. * available for playback.
  18730. *
  18731. * @method Html5#buffered
  18732. * @return {TimeRange}
  18733. * The value of `buffered` from the media element.
  18734. *
  18735. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
  18736. */
  18737. 'buffered',
  18738. /**
  18739. * Get the value of `volume` from the media element. `volume` indicates
  18740. * the current playback volume of audio for a media. `volume` will be a value from 0
  18741. * (silent) to 1 (loudest and default).
  18742. *
  18743. * @method Html5#volume
  18744. * @return {number}
  18745. * The value of `volume` from the media element. Value will be between 0-1.
  18746. *
  18747. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  18748. */
  18749. 'volume',
  18750. /**
  18751. * Get the value of `poster` from the media element. `poster` indicates
  18752. * that the url of an image file that can/will be shown when no media data is available.
  18753. *
  18754. * @method Html5#poster
  18755. * @return {string}
  18756. * The value of `poster` from the media element. Value will be a url to an
  18757. * image.
  18758. *
  18759. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
  18760. */
  18761. 'poster',
  18762. /**
  18763. * Get the value of `preload` from the media element. `preload` indicates
  18764. * what should download before the media is interacted with. It can have the following
  18765. * values:
  18766. * - none: nothing should be downloaded
  18767. * - metadata: poster and the first few frames of the media may be downloaded to get
  18768. * media dimensions and other metadata
  18769. * - auto: allow the media and metadata for the media to be downloaded before
  18770. * interaction
  18771. *
  18772. * @method Html5#preload
  18773. * @return {string}
  18774. * The value of `preload` from the media element. Will be 'none', 'metadata',
  18775. * or 'auto'.
  18776. *
  18777. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  18778. */
  18779. 'preload',
  18780. /**
  18781. * Get the value of the `error` from the media element. `error` indicates any
  18782. * MediaError that may have occurred during playback. If error returns null there is no
  18783. * current error.
  18784. *
  18785. * @method Html5#error
  18786. * @return {MediaError|null}
  18787. * The value of `error` from the media element. Will be `MediaError` if there
  18788. * is a current error and null otherwise.
  18789. *
  18790. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
  18791. */
  18792. 'error',
  18793. /**
  18794. * Get the value of `seeking` from the media element. `seeking` indicates whether the
  18795. * media is currently seeking to a new position or not.
  18796. *
  18797. * @method Html5#seeking
  18798. * @return {boolean}
  18799. * - The value of `seeking` from the media element.
  18800. * - True indicates that the media is currently seeking to a new position.
  18801. * - False indicates that the media is not seeking to a new position at this time.
  18802. *
  18803. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
  18804. */
  18805. 'seeking',
  18806. /**
  18807. * Get the value of `seekable` from the media element. `seekable` returns a
  18808. * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
  18809. *
  18810. * @method Html5#seekable
  18811. * @return {TimeRange}
  18812. * The value of `seekable` from the media element. A `TimeRange` object
  18813. * indicating the current ranges of time that can be seeked to.
  18814. *
  18815. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
  18816. */
  18817. 'seekable',
  18818. /**
  18819. * Get the value of `ended` from the media element. `ended` indicates whether
  18820. * the media has reached the end or not.
  18821. *
  18822. * @method Html5#ended
  18823. * @return {boolean}
  18824. * - The value of `ended` from the media element.
  18825. * - True indicates that the media has ended.
  18826. * - False indicates that the media has not ended.
  18827. *
  18828. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
  18829. */
  18830. 'ended',
  18831. /**
  18832. * Get the value of `playbackRate` from the media element. `playbackRate` indicates
  18833. * the rate at which the media is currently playing back. Examples:
  18834. * - if playbackRate is set to 2, media will play twice as fast.
  18835. * - if playbackRate is set to 0.5, media will play half as fast.
  18836. *
  18837. * @method Html5#playbackRate
  18838. * @return {number}
  18839. * The value of `playbackRate` from the media element. A number indicating
  18840. * the current playback speed of the media, where 1 is normal speed.
  18841. *
  18842. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  18843. */
  18844. 'playbackRate',
  18845. /**
  18846. * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
  18847. * the rate at which the media is currently playing back. This value will not indicate the current
  18848. * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
  18849. *
  18850. * Examples:
  18851. * - if defaultPlaybackRate is set to 2, media will play twice as fast.
  18852. * - if defaultPlaybackRate is set to 0.5, media will play half as fast.
  18853. *
  18854. * @method Html5.prototype.defaultPlaybackRate
  18855. * @return {number}
  18856. * The value of `defaultPlaybackRate` from the media element. A number indicating
  18857. * the current playback speed of the media, where 1 is normal speed.
  18858. *
  18859. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  18860. */
  18861. 'defaultPlaybackRate',
  18862. /**
  18863. * Get the value of `played` from the media element. `played` returns a `TimeRange`
  18864. * object representing points in the media timeline that have been played.
  18865. *
  18866. * @method Html5#played
  18867. * @return {TimeRange}
  18868. * The value of `played` from the media element. A `TimeRange` object indicating
  18869. * the ranges of time that have been played.
  18870. *
  18871. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
  18872. */
  18873. 'played',
  18874. /**
  18875. * Get the value of `networkState` from the media element. `networkState` indicates
  18876. * the current network state. It returns an enumeration from the following list:
  18877. * - 0: NETWORK_EMPTY
  18878. * - 1: NETWORK_IDLE
  18879. * - 2: NETWORK_LOADING
  18880. * - 3: NETWORK_NO_SOURCE
  18881. *
  18882. * @method Html5#networkState
  18883. * @return {number}
  18884. * The value of `networkState` from the media element. This will be a number
  18885. * from the list in the description.
  18886. *
  18887. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
  18888. */
  18889. 'networkState',
  18890. /**
  18891. * Get the value of `readyState` from the media element. `readyState` indicates
  18892. * the current state of the media element. It returns an enumeration from the
  18893. * following list:
  18894. * - 0: HAVE_NOTHING
  18895. * - 1: HAVE_METADATA
  18896. * - 2: HAVE_CURRENT_DATA
  18897. * - 3: HAVE_FUTURE_DATA
  18898. * - 4: HAVE_ENOUGH_DATA
  18899. *
  18900. * @method Html5#readyState
  18901. * @return {number}
  18902. * The value of `readyState` from the media element. This will be a number
  18903. * from the list in the description.
  18904. *
  18905. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
  18906. */
  18907. 'readyState',
  18908. /**
  18909. * Get the value of `videoWidth` from the video element. `videoWidth` indicates
  18910. * the current width of the video in css pixels.
  18911. *
  18912. * @method Html5#videoWidth
  18913. * @return {number}
  18914. * The value of `videoWidth` from the video element. This will be a number
  18915. * in css pixels.
  18916. *
  18917. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  18918. */
  18919. 'videoWidth',
  18920. /**
  18921. * Get the value of `videoHeight` from the video element. `videoHeight` indicates
  18922. * the current height of the video in css pixels.
  18923. *
  18924. * @method Html5#videoHeight
  18925. * @return {number}
  18926. * The value of `videoHeight` from the video element. This will be a number
  18927. * in css pixels.
  18928. *
  18929. * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
  18930. */
  18931. 'videoHeight'].forEach(function (prop) {
  18932. Html5.prototype[prop] = function () {
  18933. return this.el_[prop];
  18934. };
  18935. }); // Wrap native properties with a setter in this format:
  18936. // set + toTitleCase(name)
  18937. // The list is as follows:
  18938. // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
  18939. [
  18940. /**
  18941. * Set the value of `volume` on the media element. `volume` indicates the current
  18942. * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
  18943. * so on.
  18944. *
  18945. * @method Html5#setVolume
  18946. * @param {number} percentAsDecimal
  18947. * The volume percent as a decimal. Valid range is from 0-1.
  18948. *
  18949. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
  18950. */
  18951. 'volume',
  18952. /**
  18953. * Set the value of `src` on the media element. `src` indicates the current
  18954. * {@link Tech~SourceObject} for the media.
  18955. *
  18956. * @method Html5#setSrc
  18957. * @param {Tech~SourceObject} src
  18958. * The source object to set as the current source.
  18959. *
  18960. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
  18961. */
  18962. 'src',
  18963. /**
  18964. * Set the value of `poster` on the media element. `poster` is the url to
  18965. * an image file that can/will be shown when no media data is available.
  18966. *
  18967. * @method Html5#setPoster
  18968. * @param {string} poster
  18969. * The url to an image that should be used as the `poster` for the media
  18970. * element.
  18971. *
  18972. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
  18973. */
  18974. 'poster',
  18975. /**
  18976. * Set the value of `preload` on the media element. `preload` indicates
  18977. * what should download before the media is interacted with. It can have the following
  18978. * values:
  18979. * - none: nothing should be downloaded
  18980. * - metadata: poster and the first few frames of the media may be downloaded to get
  18981. * media dimensions and other metadata
  18982. * - auto: allow the media and metadata for the media to be downloaded before
  18983. * interaction
  18984. *
  18985. * @method Html5#setPreload
  18986. * @param {string} preload
  18987. * The value of `preload` to set on the media element. Must be 'none', 'metadata',
  18988. * or 'auto'.
  18989. *
  18990. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
  18991. */
  18992. 'preload',
  18993. /**
  18994. * Set the value of `playbackRate` on the media element. `playbackRate` indicates
  18995. * the rate at which the media should play back. Examples:
  18996. * - if playbackRate is set to 2, media will play twice as fast.
  18997. * - if playbackRate is set to 0.5, media will play half as fast.
  18998. *
  18999. * @method Html5#setPlaybackRate
  19000. * @return {number}
  19001. * The value of `playbackRate` from the media element. A number indicating
  19002. * the current playback speed of the media, where 1 is normal speed.
  19003. *
  19004. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
  19005. */
  19006. 'playbackRate',
  19007. /**
  19008. * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
  19009. * the rate at which the media should play back upon initial startup. Changing this value
  19010. * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
  19011. *
  19012. * Example Values:
  19013. * - if playbackRate is set to 2, media will play twice as fast.
  19014. * - if playbackRate is set to 0.5, media will play half as fast.
  19015. *
  19016. * @method Html5.prototype.setDefaultPlaybackRate
  19017. * @return {number}
  19018. * The value of `defaultPlaybackRate` from the media element. A number indicating
  19019. * the current playback speed of the media, where 1 is normal speed.
  19020. *
  19021. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
  19022. */
  19023. 'defaultPlaybackRate'].forEach(function (prop) {
  19024. Html5.prototype['set' + toTitleCase(prop)] = function (v) {
  19025. this.el_[prop] = v;
  19026. };
  19027. }); // wrap native functions with a function
  19028. // The list is as follows:
  19029. // pause, load, play
  19030. [
  19031. /**
  19032. * A wrapper around the media elements `pause` function. This will call the `HTML5`
  19033. * media elements `pause` function.
  19034. *
  19035. * @method Html5#pause
  19036. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
  19037. */
  19038. 'pause',
  19039. /**
  19040. * A wrapper around the media elements `load` function. This will call the `HTML5`s
  19041. * media element `load` function.
  19042. *
  19043. * @method Html5#load
  19044. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
  19045. */
  19046. 'load',
  19047. /**
  19048. * A wrapper around the media elements `play` function. This will call the `HTML5`s
  19049. * media element `play` function.
  19050. *
  19051. * @method Html5#play
  19052. * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
  19053. */
  19054. 'play'].forEach(function (prop) {
  19055. Html5.prototype[prop] = function () {
  19056. return this.el_[prop]();
  19057. };
  19058. });
  19059. Tech.withSourceHandlers(Html5);
  19060. /**
  19061. * Native source handler for Html5, simply passes the source to the media element.
  19062. *
  19063. * @property {Tech~SourceObject} source
  19064. * The source object
  19065. *
  19066. * @property {Html5} tech
  19067. * The instance of the HTML5 tech.
  19068. */
  19069. Html5.nativeSourceHandler = {};
  19070. /**
  19071. * Check if the media element can play the given mime type.
  19072. *
  19073. * @param {string} type
  19074. * The mimetype to check
  19075. *
  19076. * @return {string}
  19077. * 'probably', 'maybe', or '' (empty string)
  19078. */
  19079. Html5.nativeSourceHandler.canPlayType = function (type) {
  19080. // IE without MediaPlayer throws an error (#519)
  19081. try {
  19082. return Html5.TEST_VID.canPlayType(type);
  19083. } catch (e) {
  19084. return '';
  19085. }
  19086. };
  19087. /**
  19088. * Check if the media element can handle a source natively.
  19089. *
  19090. * @param {Tech~SourceObject} source
  19091. * The source object
  19092. *
  19093. * @param {Object} [options]
  19094. * Options to be passed to the tech.
  19095. *
  19096. * @return {string}
  19097. * 'probably', 'maybe', or '' (empty string).
  19098. */
  19099. Html5.nativeSourceHandler.canHandleSource = function (source, options) {
  19100. // If a type was provided we should rely on that
  19101. if (source.type) {
  19102. return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]'
  19103. } else if (source.src) {
  19104. var ext = getFileExtension(source.src);
  19105. return Html5.nativeSourceHandler.canPlayType("video/" + ext);
  19106. }
  19107. return '';
  19108. };
  19109. /**
  19110. * Pass the source to the native media element.
  19111. *
  19112. * @param {Tech~SourceObject} source
  19113. * The source object
  19114. *
  19115. * @param {Html5} tech
  19116. * The instance of the Html5 tech
  19117. *
  19118. * @param {Object} [options]
  19119. * The options to pass to the source
  19120. */
  19121. Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
  19122. tech.setSrc(source.src);
  19123. };
  19124. /**
  19125. * A noop for the native dispose function, as cleanup is not needed.
  19126. */
  19127. Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler
  19128. Html5.registerSourceHandler(Html5.nativeSourceHandler);
  19129. Tech.registerTech('Html5', Html5);
  19130. function _templateObject$2() {
  19131. 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 "]);
  19132. _templateObject$2 = function _templateObject() {
  19133. return data;
  19134. };
  19135. return data;
  19136. }
  19137. // on the player when they happen
  19138. var TECH_EVENTS_RETRIGGER = [
  19139. /**
  19140. * Fired while the user agent is downloading media data.
  19141. *
  19142. * @event Player#progress
  19143. * @type {EventTarget~Event}
  19144. */
  19145. /**
  19146. * Retrigger the `progress` event that was triggered by the {@link Tech}.
  19147. *
  19148. * @private
  19149. * @method Player#handleTechProgress_
  19150. * @fires Player#progress
  19151. * @listens Tech#progress
  19152. */
  19153. 'progress',
  19154. /**
  19155. * Fires when the loading of an audio/video is aborted.
  19156. *
  19157. * @event Player#abort
  19158. * @type {EventTarget~Event}
  19159. */
  19160. /**
  19161. * Retrigger the `abort` event that was triggered by the {@link Tech}.
  19162. *
  19163. * @private
  19164. * @method Player#handleTechAbort_
  19165. * @fires Player#abort
  19166. * @listens Tech#abort
  19167. */
  19168. 'abort',
  19169. /**
  19170. * Fires when the browser is intentionally not getting media data.
  19171. *
  19172. * @event Player#suspend
  19173. * @type {EventTarget~Event}
  19174. */
  19175. /**
  19176. * Retrigger the `suspend` event that was triggered by the {@link Tech}.
  19177. *
  19178. * @private
  19179. * @method Player#handleTechSuspend_
  19180. * @fires Player#suspend
  19181. * @listens Tech#suspend
  19182. */
  19183. 'suspend',
  19184. /**
  19185. * Fires when the current playlist is empty.
  19186. *
  19187. * @event Player#emptied
  19188. * @type {EventTarget~Event}
  19189. */
  19190. /**
  19191. * Retrigger the `emptied` event that was triggered by the {@link Tech}.
  19192. *
  19193. * @private
  19194. * @method Player#handleTechEmptied_
  19195. * @fires Player#emptied
  19196. * @listens Tech#emptied
  19197. */
  19198. 'emptied',
  19199. /**
  19200. * Fires when the browser is trying to get media data, but data is not available.
  19201. *
  19202. * @event Player#stalled
  19203. * @type {EventTarget~Event}
  19204. */
  19205. /**
  19206. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  19207. *
  19208. * @private
  19209. * @method Player#handleTechStalled_
  19210. * @fires Player#stalled
  19211. * @listens Tech#stalled
  19212. */
  19213. 'stalled',
  19214. /**
  19215. * Fires when the browser has loaded meta data for the audio/video.
  19216. *
  19217. * @event Player#loadedmetadata
  19218. * @type {EventTarget~Event}
  19219. */
  19220. /**
  19221. * Retrigger the `stalled` event that was triggered by the {@link Tech}.
  19222. *
  19223. * @private
  19224. * @method Player#handleTechLoadedmetadata_
  19225. * @fires Player#loadedmetadata
  19226. * @listens Tech#loadedmetadata
  19227. */
  19228. 'loadedmetadata',
  19229. /**
  19230. * Fires when the browser has loaded the current frame of the audio/video.
  19231. *
  19232. * @event Player#loadeddata
  19233. * @type {event}
  19234. */
  19235. /**
  19236. * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
  19237. *
  19238. * @private
  19239. * @method Player#handleTechLoaddeddata_
  19240. * @fires Player#loadeddata
  19241. * @listens Tech#loadeddata
  19242. */
  19243. 'loadeddata',
  19244. /**
  19245. * Fires when the current playback position has changed.
  19246. *
  19247. * @event Player#timeupdate
  19248. * @type {event}
  19249. */
  19250. /**
  19251. * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
  19252. *
  19253. * @private
  19254. * @method Player#handleTechTimeUpdate_
  19255. * @fires Player#timeupdate
  19256. * @listens Tech#timeupdate
  19257. */
  19258. 'timeupdate',
  19259. /**
  19260. * Fires when the video's intrinsic dimensions change
  19261. *
  19262. * @event Player#resize
  19263. * @type {event}
  19264. */
  19265. /**
  19266. * Retrigger the `resize` event that was triggered by the {@link Tech}.
  19267. *
  19268. * @private
  19269. * @method Player#handleTechResize_
  19270. * @fires Player#resize
  19271. * @listens Tech#resize
  19272. */
  19273. 'resize',
  19274. /**
  19275. * Fires when the volume has been changed
  19276. *
  19277. * @event Player#volumechange
  19278. * @type {event}
  19279. */
  19280. /**
  19281. * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
  19282. *
  19283. * @private
  19284. * @method Player#handleTechVolumechange_
  19285. * @fires Player#volumechange
  19286. * @listens Tech#volumechange
  19287. */
  19288. 'volumechange',
  19289. /**
  19290. * Fires when the text track has been changed
  19291. *
  19292. * @event Player#texttrackchange
  19293. * @type {event}
  19294. */
  19295. /**
  19296. * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
  19297. *
  19298. * @private
  19299. * @method Player#handleTechTexttrackchange_
  19300. * @fires Player#texttrackchange
  19301. * @listens Tech#texttrackchange
  19302. */
  19303. 'texttrackchange']; // events to queue when playback rate is zero
  19304. // this is a hash for the sole purpose of mapping non-camel-cased event names
  19305. // to camel-cased function names
  19306. var TECH_EVENTS_QUEUE = {
  19307. canplay: 'CanPlay',
  19308. canplaythrough: 'CanPlayThrough',
  19309. playing: 'Playing',
  19310. seeked: 'Seeked'
  19311. };
  19312. var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
  19313. var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny
  19314. // grep: vjs-layout-x-small
  19315. // grep: vjs-layout-small
  19316. // grep: vjs-layout-medium
  19317. // grep: vjs-layout-large
  19318. // grep: vjs-layout-x-large
  19319. // grep: vjs-layout-huge
  19320. BREAKPOINT_ORDER.forEach(function (k) {
  19321. var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k;
  19322. BREAKPOINT_CLASSES[k] = "vjs-layout-" + v;
  19323. });
  19324. var DEFAULT_BREAKPOINTS = {
  19325. tiny: 210,
  19326. xsmall: 320,
  19327. small: 425,
  19328. medium: 768,
  19329. large: 1440,
  19330. xlarge: 2560,
  19331. huge: Infinity
  19332. };
  19333. /**
  19334. * An instance of the `Player` class is created when any of the Video.js setup methods
  19335. * are used to initialize a video.
  19336. *
  19337. * After an instance has been created it can be accessed globally in two ways:
  19338. * 1. By calling `videojs('example_video_1');`
  19339. * 2. By using it directly via `videojs.players.example_video_1;`
  19340. *
  19341. * @extends Component
  19342. */
  19343. var Player =
  19344. /*#__PURE__*/
  19345. function (_Component) {
  19346. _inheritsLoose(Player, _Component);
  19347. /**
  19348. * Create an instance of this class.
  19349. *
  19350. * @param {Element} tag
  19351. * The original video DOM element used for configuring options.
  19352. *
  19353. * @param {Object} [options]
  19354. * Object of option names and values.
  19355. *
  19356. * @param {Component~ReadyCallback} [ready]
  19357. * Ready callback function.
  19358. */
  19359. function Player(tag, options, ready) {
  19360. var _this;
  19361. // Make sure tag ID exists
  19362. tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options
  19363. // The options argument overrides options set in the video tag
  19364. // which overrides globally set options.
  19365. // This latter part coincides with the load order
  19366. // (tag must exist before Player)
  19367. options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up
  19368. // player properties first, and can't use `this` before `super()`
  19369. options.initChildren = false; // Same with creating the element
  19370. options.createEl = false; // don't auto mixin the evented mixin
  19371. options.evented = false; // we don't want the player to report touch activity on itself
  19372. // see enableTouchActivity in Component
  19373. options.reportTouchActivity = false; // If language is not set, get the closest lang attribute
  19374. if (!options.language) {
  19375. if (typeof tag.closest === 'function') {
  19376. var closest = tag.closest('[lang]');
  19377. if (closest && closest.getAttribute) {
  19378. options.language = closest.getAttribute('lang');
  19379. }
  19380. } else {
  19381. var element = tag;
  19382. while (element && element.nodeType === 1) {
  19383. if (getAttributes(element).hasOwnProperty('lang')) {
  19384. options.language = element.getAttribute('lang');
  19385. break;
  19386. }
  19387. element = element.parentNode;
  19388. }
  19389. }
  19390. } // Run base component initializing with new options
  19391. _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners.
  19392. _this.boundDocumentFullscreenChange_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.documentFullscreenChange_);
  19393. _this.boundFullWindowOnEscKey_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.fullWindowOnEscKey);
  19394. _this.boundHandleKeyPress_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleKeyPress); // create logger
  19395. _this.log = createLogger$1(_this.id_); // Tracks when a tech changes the poster
  19396. _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero
  19397. // and a seek is happening
  19398. _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously
  19399. _this.isReady_ = false; // Init state hasStarted_
  19400. _this.hasStarted_ = false; // Init state userActive_
  19401. _this.userActive_ = false; // if the global option object was accidentally blown away by
  19402. // someone, bail early with an informative error
  19403. if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
  19404. throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
  19405. } // Store the original tag used to set options
  19406. _this.tag = tag; // Store the tag attributes used to restore html5 element
  19407. _this.tagAttributes = tag && getAttributes(tag); // Update current language
  19408. _this.language(_this.options_.language); // Update Supported Languages
  19409. if (options.languages) {
  19410. // Normalise player option languages to lowercase
  19411. var languagesToLower = {};
  19412. Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) {
  19413. languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1];
  19414. });
  19415. _this.languages_ = languagesToLower;
  19416. } else {
  19417. _this.languages_ = Player.prototype.options_.languages;
  19418. }
  19419. _this.resetCache_(); // Set poster
  19420. _this.poster_ = options.poster || ''; // Set controls
  19421. _this.controls_ = !!options.controls; // Original tag settings stored in options
  19422. // now remove immediately so native controls don't flash.
  19423. // May be turned back on by HTML5 tech if nativeControlsForTouch is true
  19424. tag.controls = false;
  19425. tag.removeAttribute('controls');
  19426. _this.changingSrc_ = false;
  19427. _this.playCallbacks_ = [];
  19428. _this.playTerminatedQueue_ = []; // the attribute overrides the option
  19429. if (tag.hasAttribute('autoplay')) {
  19430. _this.autoplay(true);
  19431. } else {
  19432. // otherwise use the setter to validate and
  19433. // set the correct value.
  19434. _this.autoplay(_this.options_.autoplay);
  19435. } // check plugins
  19436. if (options.plugins) {
  19437. Object.keys(options.plugins).forEach(function (name$$1) {
  19438. if (typeof _this[name$$1] !== 'function') {
  19439. throw new Error("plugin \"" + name$$1 + "\" does not exist");
  19440. }
  19441. });
  19442. }
  19443. /*
  19444. * Store the internal state of scrubbing
  19445. *
  19446. * @private
  19447. * @return {Boolean} True if the user is scrubbing
  19448. */
  19449. _this.scrubbing_ = false;
  19450. _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus.
  19451. evented(_assertThisInitialized(_assertThisInitialized(_this)), {
  19452. eventBusKey: 'el_'
  19453. });
  19454. if (_this.fluid_) {
  19455. _this.on('playerreset', _this.updateStyleEl_);
  19456. } // We also want to pass the original player options to each component and plugin
  19457. // as well so they don't need to reach back into the player for options later.
  19458. // We also need to do another copy of this.options_ so we don't end up with
  19459. // an infinite loop.
  19460. var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins
  19461. if (options.plugins) {
  19462. Object.keys(options.plugins).forEach(function (name$$1) {
  19463. _this[name$$1](options.plugins[name$$1]);
  19464. });
  19465. }
  19466. _this.options_.playerOptions = playerOptionsCopy;
  19467. _this.middleware_ = [];
  19468. _this.initChildren(); // Set isAudio based on whether or not an audio tag was used
  19469. _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially
  19470. // set because the element doesn't exist yet.
  19471. if (_this.controls()) {
  19472. _this.addClass('vjs-controls-enabled');
  19473. } else {
  19474. _this.addClass('vjs-controls-disabled');
  19475. } // Set ARIA label and region role depending on player type
  19476. _this.el_.setAttribute('role', 'region');
  19477. if (_this.isAudio()) {
  19478. _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
  19479. } else {
  19480. _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
  19481. }
  19482. if (_this.isAudio()) {
  19483. _this.addClass('vjs-audio');
  19484. }
  19485. if (_this.flexNotSupported_()) {
  19486. _this.addClass('vjs-no-flex');
  19487. } // TODO: Make this smarter. Toggle user state between touching/mousing
  19488. // using events, since devices can have both touch and mouse events.
  19489. // TODO: Make this check be performed again when the window switches between monitors
  19490. // (See https://github.com/videojs/video.js/issues/5683)
  19491. if (TOUCH_ENABLED) {
  19492. _this.addClass('vjs-touch-enabled');
  19493. } // iOS Safari has broken hover handling
  19494. if (!IS_IOS) {
  19495. _this.addClass('vjs-workinghover');
  19496. } // Make player easily findable by ID
  19497. Player.players[_this.id_] = _assertThisInitialized(_assertThisInitialized(_this)); // Add a major version class to aid css in plugins
  19498. var majorVersion = version.split('.')[0];
  19499. _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components
  19500. // like the control bar show themselves if needed
  19501. _this.userActive(true);
  19502. _this.reportUserActivity();
  19503. _this.one('play', _this.listenForUserActivity_);
  19504. _this.on('focus', _this.handleFocus);
  19505. _this.on('blur', _this.handleBlur);
  19506. _this.on('stageclick', _this.handleStageClick_);
  19507. _this.breakpoints(_this.options_.breakpoints);
  19508. _this.responsive(_this.options_.responsive);
  19509. return _this;
  19510. }
  19511. /**
  19512. * Destroys the video player and does any necessary cleanup.
  19513. *
  19514. * This is especially helpful if you are dynamically adding and removing videos
  19515. * to/from the DOM.
  19516. *
  19517. * @fires Player#dispose
  19518. */
  19519. var _proto = Player.prototype;
  19520. _proto.dispose = function dispose() {
  19521. var _this2 = this;
  19522. /**
  19523. * Called when the player is being disposed of.
  19524. *
  19525. * @event Player#dispose
  19526. * @type {EventTarget~Event}
  19527. */
  19528. this.trigger('dispose'); // prevent dispose from being called twice
  19529. this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is
  19530. off(document, FullscreenApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  19531. off(document, 'keydown', this.boundFullWindowOnEscKey_);
  19532. off(document, 'keydown', this.boundHandleKeyPress_);
  19533. if (this.styleEl_ && this.styleEl_.parentNode) {
  19534. this.styleEl_.parentNode.removeChild(this.styleEl_);
  19535. this.styleEl_ = null;
  19536. } // Kill reference to this player
  19537. Player.players[this.id_] = null;
  19538. if (this.tag && this.tag.player) {
  19539. this.tag.player = null;
  19540. }
  19541. if (this.el_ && this.el_.player) {
  19542. this.el_.player = null;
  19543. }
  19544. if (this.tech_) {
  19545. this.tech_.dispose();
  19546. this.isPosterFromTech_ = false;
  19547. this.poster_ = '';
  19548. }
  19549. if (this.playerElIngest_) {
  19550. this.playerElIngest_ = null;
  19551. }
  19552. if (this.tag) {
  19553. this.tag = null;
  19554. }
  19555. clearCacheForPlayer(this); // remove all event handlers for track lists
  19556. // all tracks and track listeners are removed on
  19557. // tech dispose
  19558. ALL.names.forEach(function (name$$1) {
  19559. var props = ALL[name$$1];
  19560. var list = _this2[props.getterName](); // if it is not a native list
  19561. // we have to manually remove event listeners
  19562. if (list && list.off) {
  19563. list.off();
  19564. }
  19565. }); // the actual .el_ is removed here
  19566. _Component.prototype.dispose.call(this);
  19567. }
  19568. /**
  19569. * Create the `Player`'s DOM element.
  19570. *
  19571. * @return {Element}
  19572. * The DOM element that gets created.
  19573. */
  19574. ;
  19575. _proto.createEl = function createEl$$1() {
  19576. var tag = this.tag;
  19577. var el;
  19578. var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
  19579. var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
  19580. if (playerElIngest) {
  19581. el = this.el_ = tag.parentNode;
  19582. } else if (!divEmbed) {
  19583. el = this.el_ = _Component.prototype.createEl.call(this, 'div');
  19584. } // Copy over all the attributes from the tag, including ID and class
  19585. // ID will now reference player box, not the video tag
  19586. var attrs = getAttributes(tag);
  19587. if (divEmbed) {
  19588. el = this.el_ = tag;
  19589. tag = this.tag = document.createElement('video');
  19590. while (el.children.length) {
  19591. tag.appendChild(el.firstChild);
  19592. }
  19593. if (!hasClass(el, 'video-js')) {
  19594. addClass(el, 'video-js');
  19595. }
  19596. el.appendChild(tag);
  19597. playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element
  19598. // to our new `video` element. This will move things like
  19599. // `src` or `controls` that were set via js before the player
  19600. // was initialized.
  19601. Object.keys(el).forEach(function (k) {
  19602. tag[k] = el[k];
  19603. });
  19604. } // set tabindex to -1 to remove the video element from the focus order
  19605. tag.setAttribute('tabindex', '-1');
  19606. attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button)
  19607. // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
  19608. // Note that we can't detect if JAWS is being used, but this ARIA attribute
  19609. // doesn't change behavior of IE11 if JAWS is not being used
  19610. if (IE_VERSION) {
  19611. tag.setAttribute('role', 'application');
  19612. attrs.role = 'application';
  19613. } // Remove width/height attrs from tag so CSS can make it 100% width/height
  19614. tag.removeAttribute('width');
  19615. tag.removeAttribute('height');
  19616. if ('width' in attrs) {
  19617. delete attrs.width;
  19618. }
  19619. if ('height' in attrs) {
  19620. delete attrs.height;
  19621. }
  19622. Object.getOwnPropertyNames(attrs).forEach(function (attr) {
  19623. // don't copy over the class attribute to the player element when we're in a div embed
  19624. // the class is already set up properly in the divEmbed case
  19625. // and we want to make sure that the `video-js` class doesn't get lost
  19626. if (!(divEmbed && attr === 'class')) {
  19627. el.setAttribute(attr, attrs[attr]);
  19628. }
  19629. if (divEmbed) {
  19630. tag.setAttribute(attr, attrs[attr]);
  19631. }
  19632. }); // Update tag id/class for use as HTML5 playback tech
  19633. // Might think we should do this after embedding in container so .vjs-tech class
  19634. // doesn't flash 100% width/height, but class only applies with .video-js parent
  19635. tag.playerId = tag.id;
  19636. tag.id += '_html5_api';
  19637. tag.className = 'vjs-tech'; // Make player findable on elements
  19638. tag.player = el.player = this; // Default state of video is paused
  19639. this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height
  19640. // of the player in a way that's still overrideable by CSS, just like the
  19641. // video element
  19642. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
  19643. this.styleEl_ = createStyleElement('vjs-styles-dimensions');
  19644. var defaultsStyleEl = $('.vjs-styles-defaults');
  19645. var head = $('head');
  19646. head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
  19647. }
  19648. this.fill_ = false;
  19649. this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el
  19650. this.width(this.options_.width);
  19651. this.height(this.options_.height);
  19652. this.fill(this.options_.fill);
  19653. this.fluid(this.options_.fluid);
  19654. this.aspectRatio(this.options_.aspectRatio); // Hide any links within the video/audio tag,
  19655. // because IE doesn't hide them completely from screen readers.
  19656. var links = tag.getElementsByTagName('a');
  19657. for (var i = 0; i < links.length; i++) {
  19658. var linkEl = links.item(i);
  19659. addClass(linkEl, 'vjs-hidden');
  19660. linkEl.setAttribute('hidden', 'hidden');
  19661. } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
  19662. // keep track of the original for later so we can know if the source originally failed
  19663. tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container
  19664. if (tag.parentNode && !playerElIngest) {
  19665. tag.parentNode.insertBefore(el, tag);
  19666. } // insert the tag as the first child of the player element
  19667. // then manually add it to the children array so that this.addChild
  19668. // will work properly for other components
  19669. //
  19670. // Breaks iPhone, fixed in HTML5 setup.
  19671. prependTo(tag, el);
  19672. this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player
  19673. // if it's been set to something different to the doc
  19674. this.el_.setAttribute('lang', this.language_);
  19675. this.el_ = el;
  19676. return el;
  19677. }
  19678. /**
  19679. * A getter/setter for the `Player`'s width. Returns the player's configured value.
  19680. * To get the current width use `currentWidth()`.
  19681. *
  19682. * @param {number} [value]
  19683. * The value to set the `Player`'s width to.
  19684. *
  19685. * @return {number}
  19686. * The current width of the `Player` when getting.
  19687. */
  19688. ;
  19689. _proto.width = function width(value) {
  19690. return this.dimension('width', value);
  19691. }
  19692. /**
  19693. * A getter/setter for the `Player`'s height. Returns the player's configured value.
  19694. * To get the current height use `currentheight()`.
  19695. *
  19696. * @param {number} [value]
  19697. * The value to set the `Player`'s heigth to.
  19698. *
  19699. * @return {number}
  19700. * The current height of the `Player` when getting.
  19701. */
  19702. ;
  19703. _proto.height = function height(value) {
  19704. return this.dimension('height', value);
  19705. }
  19706. /**
  19707. * A getter/setter for the `Player`'s width & height.
  19708. *
  19709. * @param {string} dimension
  19710. * This string can be:
  19711. * - 'width'
  19712. * - 'height'
  19713. *
  19714. * @param {number} [value]
  19715. * Value for dimension specified in the first argument.
  19716. *
  19717. * @return {number}
  19718. * The dimension arguments value when getting (width/height).
  19719. */
  19720. ;
  19721. _proto.dimension = function dimension(_dimension, value) {
  19722. var privDimension = _dimension + '_';
  19723. if (value === undefined) {
  19724. return this[privDimension] || 0;
  19725. }
  19726. if (value === '') {
  19727. // If an empty string is given, reset the dimension to be automatic
  19728. this[privDimension] = undefined;
  19729. this.updateStyleEl_();
  19730. return;
  19731. }
  19732. var parsedVal = parseFloat(value);
  19733. if (isNaN(parsedVal)) {
  19734. log.error("Improper value \"" + value + "\" supplied for for " + _dimension);
  19735. return;
  19736. }
  19737. this[privDimension] = parsedVal;
  19738. this.updateStyleEl_();
  19739. }
  19740. /**
  19741. * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
  19742. *
  19743. * Turning this on will turn off fill mode.
  19744. *
  19745. * @param {boolean} [bool]
  19746. * - A value of true adds the class.
  19747. * - A value of false removes the class.
  19748. * - No value will be a getter.
  19749. *
  19750. * @return {boolean|undefined}
  19751. * - The value of fluid when getting.
  19752. * - `undefined` when setting.
  19753. */
  19754. ;
  19755. _proto.fluid = function fluid(bool) {
  19756. if (bool === undefined) {
  19757. return !!this.fluid_;
  19758. }
  19759. this.fluid_ = !!bool;
  19760. if (isEvented(this)) {
  19761. this.off('playerreset', this.updateStyleEl_);
  19762. }
  19763. if (bool) {
  19764. this.addClass('vjs-fluid');
  19765. this.fill(false);
  19766. addEventedCallback(function () {
  19767. this.on('playerreset', this.updateStyleEl_);
  19768. });
  19769. } else {
  19770. this.removeClass('vjs-fluid');
  19771. }
  19772. this.updateStyleEl_();
  19773. }
  19774. /**
  19775. * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
  19776. *
  19777. * Turning this on will turn off fluid mode.
  19778. *
  19779. * @param {boolean} [bool]
  19780. * - A value of true adds the class.
  19781. * - A value of false removes the class.
  19782. * - No value will be a getter.
  19783. *
  19784. * @return {boolean|undefined}
  19785. * - The value of fluid when getting.
  19786. * - `undefined` when setting.
  19787. */
  19788. ;
  19789. _proto.fill = function fill(bool) {
  19790. if (bool === undefined) {
  19791. return !!this.fill_;
  19792. }
  19793. this.fill_ = !!bool;
  19794. if (bool) {
  19795. this.addClass('vjs-fill');
  19796. this.fluid(false);
  19797. } else {
  19798. this.removeClass('vjs-fill');
  19799. }
  19800. }
  19801. /**
  19802. * Get/Set the aspect ratio
  19803. *
  19804. * @param {string} [ratio]
  19805. * Aspect ratio for player
  19806. *
  19807. * @return {string|undefined}
  19808. * returns the current aspect ratio when getting
  19809. */
  19810. /**
  19811. * A getter/setter for the `Player`'s aspect ratio.
  19812. *
  19813. * @param {string} [ratio]
  19814. * The value to set the `Player's aspect ratio to.
  19815. *
  19816. * @return {string|undefined}
  19817. * - The current aspect ratio of the `Player` when getting.
  19818. * - undefined when setting
  19819. */
  19820. ;
  19821. _proto.aspectRatio = function aspectRatio(ratio) {
  19822. if (ratio === undefined) {
  19823. return this.aspectRatio_;
  19824. } // Check for width:height format
  19825. if (!/^\d+\:\d+$/.test(ratio)) {
  19826. throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
  19827. }
  19828. this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode,
  19829. // because in fixed mode you could calculate width and height yourself.
  19830. this.fluid(true);
  19831. this.updateStyleEl_();
  19832. }
  19833. /**
  19834. * Update styles of the `Player` element (height, width and aspect ratio).
  19835. *
  19836. * @private
  19837. * @listens Tech#loadedmetadata
  19838. */
  19839. ;
  19840. _proto.updateStyleEl_ = function updateStyleEl_() {
  19841. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
  19842. var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
  19843. var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
  19844. var techEl = this.tech_ && this.tech_.el();
  19845. if (techEl) {
  19846. if (_width >= 0) {
  19847. techEl.width = _width;
  19848. }
  19849. if (_height >= 0) {
  19850. techEl.height = _height;
  19851. }
  19852. }
  19853. return;
  19854. }
  19855. var width;
  19856. var height;
  19857. var aspectRatio;
  19858. var idClass; // The aspect ratio is either used directly or to calculate width and height.
  19859. if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
  19860. // Use any aspectRatio that's been specifically set
  19861. aspectRatio = this.aspectRatio_;
  19862. } else if (this.videoWidth() > 0) {
  19863. // Otherwise try to get the aspect ratio from the video metadata
  19864. aspectRatio = this.videoWidth() + ':' + this.videoHeight();
  19865. } else {
  19866. // Or use a default. The video element's is 2:1, but 16:9 is more common.
  19867. aspectRatio = '16:9';
  19868. } // Get the ratio as a decimal we can use to calculate dimensions
  19869. var ratioParts = aspectRatio.split(':');
  19870. var ratioMultiplier = ratioParts[1] / ratioParts[0];
  19871. if (this.width_ !== undefined) {
  19872. // Use any width that's been specifically set
  19873. width = this.width_;
  19874. } else if (this.height_ !== undefined) {
  19875. // Or calulate the width from the aspect ratio if a height has been set
  19876. width = this.height_ / ratioMultiplier;
  19877. } else {
  19878. // Or use the video's metadata, or use the video el's default of 300
  19879. width = this.videoWidth() || 300;
  19880. }
  19881. if (this.height_ !== undefined) {
  19882. // Use any height that's been specifically set
  19883. height = this.height_;
  19884. } else {
  19885. // Otherwise calculate the height from the ratio and the width
  19886. height = width * ratioMultiplier;
  19887. } // Ensure the CSS class is valid by starting with an alpha character
  19888. if (/^[^a-zA-Z]/.test(this.id())) {
  19889. idClass = 'dimensions-' + this.id();
  19890. } else {
  19891. idClass = this.id() + '-dimensions';
  19892. } // Ensure the right class is still on the player for the style element
  19893. this.addClass(idClass);
  19894. 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 ");
  19895. }
  19896. /**
  19897. * Load/Create an instance of playback {@link Tech} including element
  19898. * and API methods. Then append the `Tech` element in `Player` as a child.
  19899. *
  19900. * @param {string} techName
  19901. * name of the playback technology
  19902. *
  19903. * @param {string} source
  19904. * video source
  19905. *
  19906. * @private
  19907. */
  19908. ;
  19909. _proto.loadTech_ = function loadTech_(techName, source) {
  19910. var _this3 = this;
  19911. // Pause and remove current playback technology
  19912. if (this.tech_) {
  19913. this.unloadTech_();
  19914. }
  19915. var titleTechName = toTitleCase(techName);
  19916. var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech
  19917. if (titleTechName !== 'Html5' && this.tag) {
  19918. Tech.getTech('Html5').disposeMediaElement(this.tag);
  19919. this.tag.player = null;
  19920. this.tag = null;
  19921. }
  19922. this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously
  19923. this.isReady_ = false; // if autoplay is a string we pass false to the tech
  19924. // because the player is going to handle autoplay on `loadstart`
  19925. var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use.
  19926. var techOptions = {
  19927. source: source,
  19928. autoplay: autoplay,
  19929. 'nativeControlsForTouch': this.options_.nativeControlsForTouch,
  19930. 'playerId': this.id(),
  19931. 'techId': this.id() + "_" + camelTechName + "_api",
  19932. 'playsinline': this.options_.playsinline,
  19933. 'preload': this.options_.preload,
  19934. 'loop': this.options_.loop,
  19935. 'muted': this.options_.muted,
  19936. 'poster': this.poster(),
  19937. 'language': this.language(),
  19938. 'playerElIngest': this.playerElIngest_ || false,
  19939. 'vtt.js': this.options_['vtt.js'],
  19940. 'canOverridePoster': !!this.options_.techCanOverridePoster,
  19941. 'enableSourceset': this.options_.enableSourceset
  19942. };
  19943. ALL.names.forEach(function (name$$1) {
  19944. var props = ALL[name$$1];
  19945. techOptions[props.getterName] = _this3[props.privateName];
  19946. });
  19947. assign(techOptions, this.options_[titleTechName]);
  19948. assign(techOptions, this.options_[camelTechName]);
  19949. assign(techOptions, this.options_[techName.toLowerCase()]);
  19950. if (this.tag) {
  19951. techOptions.tag = this.tag;
  19952. }
  19953. if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
  19954. techOptions.startTime = this.cache_.currentTime;
  19955. } // Initialize tech instance
  19956. var TechClass = Tech.getTech(techName);
  19957. if (!TechClass) {
  19958. throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'");
  19959. }
  19960. this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async
  19961. this.tech_.ready(bind(this, this.handleTechReady_), true);
  19962. textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player
  19963. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  19964. _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]);
  19965. });
  19966. Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
  19967. _this3.on(_this3.tech_, event, function (eventObj) {
  19968. if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) {
  19969. _this3.queuedCallbacks_.push({
  19970. callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3),
  19971. event: eventObj
  19972. });
  19973. return;
  19974. }
  19975. _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj);
  19976. });
  19977. });
  19978. this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
  19979. this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
  19980. this.on(this.tech_, 'waiting', this.handleTechWaiting_);
  19981. this.on(this.tech_, 'ended', this.handleTechEnded_);
  19982. this.on(this.tech_, 'seeking', this.handleTechSeeking_);
  19983. this.on(this.tech_, 'play', this.handleTechPlay_);
  19984. this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
  19985. this.on(this.tech_, 'pause', this.handleTechPause_);
  19986. this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
  19987. this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
  19988. this.on(this.tech_, 'error', this.handleTechError_);
  19989. this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
  19990. this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
  19991. this.on(this.tech_, 'textdata', this.handleTechTextData_);
  19992. this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
  19993. this.usingNativeControls(this.techGet_('controls'));
  19994. if (this.controls() && !this.usingNativeControls()) {
  19995. this.addTechControlsListeners_();
  19996. } // Add the tech element in the DOM if it was not already there
  19997. // Make sure to not insert the original video element if using Html5
  19998. if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
  19999. prependTo(this.tech_.el(), this.el());
  20000. } // Get rid of the original video tag reference after the first tech is loaded
  20001. if (this.tag) {
  20002. this.tag.player = null;
  20003. this.tag = null;
  20004. }
  20005. }
  20006. /**
  20007. * Unload and dispose of the current playback {@link Tech}.
  20008. *
  20009. * @private
  20010. */
  20011. ;
  20012. _proto.unloadTech_ = function unloadTech_() {
  20013. var _this4 = this;
  20014. // Save the current text tracks so that we can reuse the same text tracks with the next tech
  20015. ALL.names.forEach(function (name$$1) {
  20016. var props = ALL[name$$1];
  20017. _this4[props.privateName] = _this4[props.getterName]();
  20018. });
  20019. this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
  20020. this.isReady_ = false;
  20021. this.tech_.dispose();
  20022. this.tech_ = false;
  20023. if (this.isPosterFromTech_) {
  20024. this.poster_ = '';
  20025. this.trigger('posterchange');
  20026. }
  20027. this.isPosterFromTech_ = false;
  20028. }
  20029. /**
  20030. * Return a reference to the current {@link Tech}.
  20031. * It will print a warning by default about the danger of using the tech directly
  20032. * but any argument that is passed in will silence the warning.
  20033. *
  20034. * @param {*} [safety]
  20035. * Anything passed in to silence the warning
  20036. *
  20037. * @return {Tech}
  20038. * The Tech
  20039. */
  20040. ;
  20041. _proto.tech = function tech(safety) {
  20042. if (safety === undefined) {
  20043. log.warn(tsml(_templateObject$2()));
  20044. }
  20045. return this.tech_;
  20046. }
  20047. /**
  20048. * Set up click and touch listeners for the playback element
  20049. *
  20050. * - On desktops: a click on the video itself will toggle playback
  20051. * - On mobile devices: a click on the video toggles controls
  20052. * which is done by toggling the user state between active and
  20053. * inactive
  20054. * - A tap can signal that a user has become active or has become inactive
  20055. * e.g. a quick tap on an iPhone movie should reveal the controls. Another
  20056. * quick tap should hide them again (signaling the user is in an inactive
  20057. * viewing state)
  20058. * - In addition to this, we still want the user to be considered inactive after
  20059. * a few seconds of inactivity.
  20060. *
  20061. * > Note: the only part of iOS interaction we can't mimic with this setup
  20062. * is a touch and hold on the video element counting as activity in order to
  20063. * keep the controls showing, but that shouldn't be an issue. A touch and hold
  20064. * on any controls will still keep the user active
  20065. *
  20066. * @private
  20067. */
  20068. ;
  20069. _proto.addTechControlsListeners_ = function addTechControlsListeners_() {
  20070. // Make sure to remove all the previous listeners in case we are called multiple times.
  20071. this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
  20072. // trigger mousedown/up.
  20073. // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
  20074. // Any touch events are set to block the mousedown event from happening
  20075. this.on(this.tech_, 'mousedown', this.handleTechClick_);
  20076. this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event
  20077. // so we'll check if the controls were already showing before reporting user
  20078. // activity
  20079. this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
  20080. this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
  20081. this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap
  20082. // listener cancels out any reportedUserActivity when setting userActive(false)
  20083. this.on(this.tech_, 'tap', this.handleTechTap_);
  20084. }
  20085. /**
  20086. * Remove the listeners used for click and tap controls. This is needed for
  20087. * toggling to controls disabled, where a tap/touch should do nothing.
  20088. *
  20089. * @private
  20090. */
  20091. ;
  20092. _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() {
  20093. // We don't want to just use `this.off()` because there might be other needed
  20094. // listeners added by techs that extend this.
  20095. this.off(this.tech_, 'tap', this.handleTechTap_);
  20096. this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
  20097. this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
  20098. this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
  20099. this.off(this.tech_, 'mousedown', this.handleTechClick_);
  20100. this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_);
  20101. }
  20102. /**
  20103. * Player waits for the tech to be ready
  20104. *
  20105. * @private
  20106. */
  20107. ;
  20108. _proto.handleTechReady_ = function handleTechReady_() {
  20109. this.triggerReady(); // Keep the same volume as before
  20110. if (this.cache_.volume) {
  20111. this.techCall_('setVolume', this.cache_.volume);
  20112. } // Look if the tech found a higher resolution poster while loading
  20113. this.handleTechPosterChange_(); // Update the duration if available
  20114. this.handleTechDurationChange_();
  20115. }
  20116. /**
  20117. * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
  20118. * function will also trigger {@link Player#firstplay} if it is the first loadstart
  20119. * for a video.
  20120. *
  20121. * @fires Player#loadstart
  20122. * @fires Player#firstplay
  20123. * @listens Tech#loadstart
  20124. * @private
  20125. */
  20126. ;
  20127. _proto.handleTechLoadStart_ = function handleTechLoadStart_() {
  20128. // TODO: Update to use `emptied` event instead. See #1277.
  20129. this.removeClass('vjs-ended');
  20130. this.removeClass('vjs-seeking'); // reset the error state
  20131. this.error(null); // Update the duration
  20132. this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now.
  20133. // The firstplay event relies on both the play and loadstart events
  20134. // which can happen in any order for a new source
  20135. if (!this.paused()) {
  20136. /**
  20137. * Fired when the user agent begins looking for media data
  20138. *
  20139. * @event Player#loadstart
  20140. * @type {EventTarget~Event}
  20141. */
  20142. this.trigger('loadstart');
  20143. this.trigger('firstplay');
  20144. } else {
  20145. // reset the hasStarted state
  20146. this.hasStarted(false);
  20147. this.trigger('loadstart');
  20148. } // autoplay happens after loadstart for the browser,
  20149. // so we mimic that behavior
  20150. this.manualAutoplay_(this.autoplay());
  20151. }
  20152. /**
  20153. * Handle autoplay string values, rather than the typical boolean
  20154. * values that should be handled by the tech. Note that this is not
  20155. * part of any specification. Valid values and what they do can be
  20156. * found on the autoplay getter at Player#autoplay()
  20157. */
  20158. ;
  20159. _proto.manualAutoplay_ = function manualAutoplay_(type) {
  20160. var _this5 = this;
  20161. if (!this.tech_ || typeof type !== 'string') {
  20162. return;
  20163. }
  20164. var muted = function muted() {
  20165. var previouslyMuted = _this5.muted();
  20166. _this5.muted(true);
  20167. var restoreMuted = function restoreMuted() {
  20168. _this5.muted(previouslyMuted);
  20169. }; // restore muted on play terminatation
  20170. _this5.playTerminatedQueue_.push(restoreMuted);
  20171. var mutedPromise = _this5.play();
  20172. if (!isPromise(mutedPromise)) {
  20173. return;
  20174. }
  20175. return mutedPromise.catch(restoreMuted);
  20176. };
  20177. var promise; // if muted defaults to true
  20178. // the only thing we can do is call play
  20179. if (type === 'any' && this.muted() !== true) {
  20180. promise = this.play();
  20181. if (isPromise(promise)) {
  20182. promise = promise.catch(muted);
  20183. }
  20184. } else if (type === 'muted' && this.muted() !== true) {
  20185. promise = muted();
  20186. } else {
  20187. promise = this.play();
  20188. }
  20189. if (!isPromise(promise)) {
  20190. return;
  20191. }
  20192. return promise.then(function () {
  20193. _this5.trigger({
  20194. type: 'autoplay-success',
  20195. autoplay: type
  20196. });
  20197. }).catch(function (e) {
  20198. _this5.trigger({
  20199. type: 'autoplay-failure',
  20200. autoplay: type
  20201. });
  20202. });
  20203. }
  20204. /**
  20205. * Update the internal source caches so that we return the correct source from
  20206. * `src()`, `currentSource()`, and `currentSources()`.
  20207. *
  20208. * > Note: `currentSources` will not be updated if the source that is passed in exists
  20209. * in the current `currentSources` cache.
  20210. *
  20211. *
  20212. * @param {Tech~SourceObject} srcObj
  20213. * A string or object source to update our caches to.
  20214. */
  20215. ;
  20216. _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) {
  20217. if (srcObj === void 0) {
  20218. srcObj = '';
  20219. }
  20220. var src = srcObj;
  20221. var type = '';
  20222. if (typeof src !== 'string') {
  20223. src = srcObj.src;
  20224. type = srcObj.type;
  20225. } // make sure all the caches are set to default values
  20226. // to prevent null checking
  20227. this.cache_.source = this.cache_.source || {};
  20228. this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in
  20229. if (src && !type) {
  20230. type = findMimetype(this, src);
  20231. } // update `currentSource` cache always
  20232. this.cache_.source = mergeOptions({}, srcObj, {
  20233. src: src,
  20234. type: type
  20235. });
  20236. var matchingSources = this.cache_.sources.filter(function (s) {
  20237. return s.src && s.src === src;
  20238. });
  20239. var sourceElSources = [];
  20240. var sourceEls = this.$$('source');
  20241. var matchingSourceEls = [];
  20242. for (var i = 0; i < sourceEls.length; i++) {
  20243. var sourceObj = getAttributes(sourceEls[i]);
  20244. sourceElSources.push(sourceObj);
  20245. if (sourceObj.src && sourceObj.src === src) {
  20246. matchingSourceEls.push(sourceObj.src);
  20247. }
  20248. } // if we have matching source els but not matching sources
  20249. // the current source cache is not up to date
  20250. if (matchingSourceEls.length && !matchingSources.length) {
  20251. this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the
  20252. // sources cache to the `currentSource` cache
  20253. } else if (!matchingSources.length) {
  20254. this.cache_.sources = [this.cache_.source];
  20255. } // update the tech `src` cache
  20256. this.cache_.src = src;
  20257. }
  20258. /**
  20259. * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
  20260. * causing the media element to reload.
  20261. *
  20262. * It will fire for the initial source and each subsequent source.
  20263. * This event is a custom event from Video.js and is triggered by the {@link Tech}.
  20264. *
  20265. * The event object for this event contains a `src` property that will contain the source
  20266. * that was available when the event was triggered. This is generally only necessary if Video.js
  20267. * is switching techs while the source was being changed.
  20268. *
  20269. * It is also fired when `load` is called on the player (or media element)
  20270. * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
  20271. * says that the resource selection algorithm needs to be aborted and restarted.
  20272. * In this case, it is very likely that the `src` property will be set to the
  20273. * empty string `""` to indicate we do not know what the source will be but
  20274. * that it is changing.
  20275. *
  20276. * *This event is currently still experimental and may change in minor releases.*
  20277. * __To use this, pass `enableSourceset` option to the player.__
  20278. *
  20279. * @event Player#sourceset
  20280. * @type {EventTarget~Event}
  20281. * @prop {string} src
  20282. * The source url available when the `sourceset` was triggered.
  20283. * It will be an empty string if we cannot know what the source is
  20284. * but know that the source will change.
  20285. */
  20286. /**
  20287. * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
  20288. *
  20289. * @fires Player#sourceset
  20290. * @listens Tech#sourceset
  20291. * @private
  20292. */
  20293. ;
  20294. _proto.handleTechSourceset_ = function handleTechSourceset_(event) {
  20295. var _this6 = this;
  20296. // only update the source cache when the source
  20297. // was not updated using the player api
  20298. if (!this.changingSrc_) {
  20299. var updateSourceCaches = function updateSourceCaches(src) {
  20300. return _this6.updateSourceCaches_(src);
  20301. };
  20302. var playerSrc = this.currentSource().src;
  20303. var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob
  20304. if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
  20305. // if both the tech source and the player source were updated we assume
  20306. // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
  20307. if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
  20308. updateSourceCaches = function updateSourceCaches() {};
  20309. }
  20310. } // update the source to the intial source right away
  20311. // in some cases this will be empty string
  20312. updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string
  20313. // wait for a `loadstart` to update the cache to `currentSrc`.
  20314. // If a sourceset happens before a `loadstart`, we reset the state
  20315. // as this function will be called again.
  20316. if (!event.src) {
  20317. var updateCache = function updateCache(e) {
  20318. if (e.type !== 'sourceset') {
  20319. var techSrc = _this6.techGet('currentSrc');
  20320. _this6.lastSource_.tech = techSrc;
  20321. _this6.updateSourceCaches_(techSrc);
  20322. }
  20323. _this6.tech_.off(['sourceset', 'loadstart'], updateCache);
  20324. };
  20325. this.tech_.one(['sourceset', 'loadstart'], updateCache);
  20326. }
  20327. }
  20328. this.lastSource_ = {
  20329. player: this.currentSource().src,
  20330. tech: event.src
  20331. };
  20332. this.trigger({
  20333. src: event.src,
  20334. type: 'sourceset'
  20335. });
  20336. }
  20337. /**
  20338. * Add/remove the vjs-has-started class
  20339. *
  20340. * @fires Player#firstplay
  20341. *
  20342. * @param {boolean} request
  20343. * - true: adds the class
  20344. * - false: remove the class
  20345. *
  20346. * @return {boolean}
  20347. * the boolean value of hasStarted_
  20348. */
  20349. ;
  20350. _proto.hasStarted = function hasStarted(request) {
  20351. if (request === undefined) {
  20352. // act as getter, if we have no request to change
  20353. return this.hasStarted_;
  20354. }
  20355. if (request === this.hasStarted_) {
  20356. return;
  20357. }
  20358. this.hasStarted_ = request;
  20359. if (this.hasStarted_) {
  20360. this.addClass('vjs-has-started');
  20361. this.trigger('firstplay');
  20362. } else {
  20363. this.removeClass('vjs-has-started');
  20364. }
  20365. }
  20366. /**
  20367. * Fired whenever the media begins or resumes playback
  20368. *
  20369. * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
  20370. * @fires Player#play
  20371. * @listens Tech#play
  20372. * @private
  20373. */
  20374. ;
  20375. _proto.handleTechPlay_ = function handleTechPlay_() {
  20376. this.removeClass('vjs-ended');
  20377. this.removeClass('vjs-paused');
  20378. this.addClass('vjs-playing'); // hide the poster when the user hits play
  20379. this.hasStarted(true);
  20380. /**
  20381. * Triggered whenever an {@link Tech#play} event happens. Indicates that
  20382. * playback has started or resumed.
  20383. *
  20384. * @event Player#play
  20385. * @type {EventTarget~Event}
  20386. */
  20387. this.trigger('play');
  20388. }
  20389. /**
  20390. * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
  20391. *
  20392. * If there were any events queued while the playback rate was zero, fire
  20393. * those events now.
  20394. *
  20395. * @private
  20396. * @method Player#handleTechRateChange_
  20397. * @fires Player#ratechange
  20398. * @listens Tech#ratechange
  20399. */
  20400. ;
  20401. _proto.handleTechRateChange_ = function handleTechRateChange_() {
  20402. if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
  20403. this.queuedCallbacks_.forEach(function (queued) {
  20404. return queued.callback(queued.event);
  20405. });
  20406. this.queuedCallbacks_ = [];
  20407. }
  20408. this.cache_.lastPlaybackRate = this.tech_.playbackRate();
  20409. /**
  20410. * Fires when the playing speed of the audio/video is changed
  20411. *
  20412. * @event Player#ratechange
  20413. * @type {event}
  20414. */
  20415. this.trigger('ratechange');
  20416. }
  20417. /**
  20418. * Retrigger the `waiting` event that was triggered by the {@link Tech}.
  20419. *
  20420. * @fires Player#waiting
  20421. * @listens Tech#waiting
  20422. * @private
  20423. */
  20424. ;
  20425. _proto.handleTechWaiting_ = function handleTechWaiting_() {
  20426. var _this7 = this;
  20427. this.addClass('vjs-waiting');
  20428. /**
  20429. * A readyState change on the DOM element has caused playback to stop.
  20430. *
  20431. * @event Player#waiting
  20432. * @type {EventTarget~Event}
  20433. */
  20434. this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent
  20435. // premature removal of the waiting class, wait for the time to change.
  20436. var timeWhenWaiting = this.currentTime();
  20437. var timeUpdateListener = function timeUpdateListener() {
  20438. if (timeWhenWaiting !== _this7.currentTime()) {
  20439. _this7.removeClass('vjs-waiting');
  20440. _this7.off('timeupdate', timeUpdateListener);
  20441. }
  20442. };
  20443. this.on('timeupdate', timeUpdateListener);
  20444. }
  20445. /**
  20446. * Retrigger the `canplay` event that was triggered by the {@link Tech}.
  20447. * > Note: This is not consistent between browsers. See #1351
  20448. *
  20449. * @fires Player#canplay
  20450. * @listens Tech#canplay
  20451. * @private
  20452. */
  20453. ;
  20454. _proto.handleTechCanPlay_ = function handleTechCanPlay_() {
  20455. this.removeClass('vjs-waiting');
  20456. /**
  20457. * The media has a readyState of HAVE_FUTURE_DATA or greater.
  20458. *
  20459. * @event Player#canplay
  20460. * @type {EventTarget~Event}
  20461. */
  20462. this.trigger('canplay');
  20463. }
  20464. /**
  20465. * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
  20466. *
  20467. * @fires Player#canplaythrough
  20468. * @listens Tech#canplaythrough
  20469. * @private
  20470. */
  20471. ;
  20472. _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
  20473. this.removeClass('vjs-waiting');
  20474. /**
  20475. * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
  20476. * entire media file can be played without buffering.
  20477. *
  20478. * @event Player#canplaythrough
  20479. * @type {EventTarget~Event}
  20480. */
  20481. this.trigger('canplaythrough');
  20482. }
  20483. /**
  20484. * Retrigger the `playing` event that was triggered by the {@link Tech}.
  20485. *
  20486. * @fires Player#playing
  20487. * @listens Tech#playing
  20488. * @private
  20489. */
  20490. ;
  20491. _proto.handleTechPlaying_ = function handleTechPlaying_() {
  20492. this.removeClass('vjs-waiting');
  20493. /**
  20494. * The media is no longer blocked from playback, and has started playing.
  20495. *
  20496. * @event Player#playing
  20497. * @type {EventTarget~Event}
  20498. */
  20499. this.trigger('playing');
  20500. }
  20501. /**
  20502. * Retrigger the `seeking` event that was triggered by the {@link Tech}.
  20503. *
  20504. * @fires Player#seeking
  20505. * @listens Tech#seeking
  20506. * @private
  20507. */
  20508. ;
  20509. _proto.handleTechSeeking_ = function handleTechSeeking_() {
  20510. this.addClass('vjs-seeking');
  20511. /**
  20512. * Fired whenever the player is jumping to a new time
  20513. *
  20514. * @event Player#seeking
  20515. * @type {EventTarget~Event}
  20516. */
  20517. this.trigger('seeking');
  20518. }
  20519. /**
  20520. * Retrigger the `seeked` event that was triggered by the {@link Tech}.
  20521. *
  20522. * @fires Player#seeked
  20523. * @listens Tech#seeked
  20524. * @private
  20525. */
  20526. ;
  20527. _proto.handleTechSeeked_ = function handleTechSeeked_() {
  20528. this.removeClass('vjs-seeking');
  20529. this.removeClass('vjs-ended');
  20530. /**
  20531. * Fired when the player has finished jumping to a new time
  20532. *
  20533. * @event Player#seeked
  20534. * @type {EventTarget~Event}
  20535. */
  20536. this.trigger('seeked');
  20537. }
  20538. /**
  20539. * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
  20540. *
  20541. * @fires Player#firstplay
  20542. * @listens Tech#firstplay
  20543. * @deprecated As of 6.0 firstplay event is deprecated.
  20544. * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
  20545. * @private
  20546. */
  20547. ;
  20548. _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() {
  20549. // If the first starttime attribute is specified
  20550. // then we will start at the given offset in seconds
  20551. if (this.options_.starttime) {
  20552. log.warn('Passing the `starttime` option to the player will be deprecated in 6.0');
  20553. this.currentTime(this.options_.starttime);
  20554. }
  20555. this.addClass('vjs-has-started');
  20556. /**
  20557. * Fired the first time a video is played. Not part of the HLS spec, and this is
  20558. * probably not the best implementation yet, so use sparingly. If you don't have a
  20559. * reason to prevent playback, use `myPlayer.one('play');` instead.
  20560. *
  20561. * @event Player#firstplay
  20562. * @deprecated As of 6.0 firstplay event is deprecated.
  20563. * @type {EventTarget~Event}
  20564. */
  20565. this.trigger('firstplay');
  20566. }
  20567. /**
  20568. * Retrigger the `pause` event that was triggered by the {@link Tech}.
  20569. *
  20570. * @fires Player#pause
  20571. * @listens Tech#pause
  20572. * @private
  20573. */
  20574. ;
  20575. _proto.handleTechPause_ = function handleTechPause_() {
  20576. this.removeClass('vjs-playing');
  20577. this.addClass('vjs-paused');
  20578. /**
  20579. * Fired whenever the media has been paused
  20580. *
  20581. * @event Player#pause
  20582. * @type {EventTarget~Event}
  20583. */
  20584. this.trigger('pause');
  20585. }
  20586. /**
  20587. * Retrigger the `ended` event that was triggered by the {@link Tech}.
  20588. *
  20589. * @fires Player#ended
  20590. * @listens Tech#ended
  20591. * @private
  20592. */
  20593. ;
  20594. _proto.handleTechEnded_ = function handleTechEnded_() {
  20595. this.addClass('vjs-ended');
  20596. if (this.options_.loop) {
  20597. this.currentTime(0);
  20598. this.play();
  20599. } else if (!this.paused()) {
  20600. this.pause();
  20601. }
  20602. /**
  20603. * Fired when the end of the media resource is reached (currentTime == duration)
  20604. *
  20605. * @event Player#ended
  20606. * @type {EventTarget~Event}
  20607. */
  20608. this.trigger('ended');
  20609. }
  20610. /**
  20611. * Fired when the duration of the media resource is first known or changed
  20612. *
  20613. * @listens Tech#durationchange
  20614. * @private
  20615. */
  20616. ;
  20617. _proto.handleTechDurationChange_ = function handleTechDurationChange_() {
  20618. this.duration(this.techGet_('duration'));
  20619. }
  20620. /**
  20621. * Handle a click on the media element to play/pause
  20622. *
  20623. * @param {EventTarget~Event} event
  20624. * the event that caused this function to trigger
  20625. *
  20626. * @listens Tech#mousedown
  20627. * @private
  20628. */
  20629. ;
  20630. _proto.handleTechClick_ = function handleTechClick_(event) {
  20631. if (!isSingleLeftClick(event)) {
  20632. return;
  20633. } // When controls are disabled a click should not toggle playback because
  20634. // the click is considered a control
  20635. if (!this.controls_) {
  20636. return;
  20637. }
  20638. if (this.paused()) {
  20639. silencePromise(this.play());
  20640. } else {
  20641. this.pause();
  20642. }
  20643. }
  20644. /**
  20645. * Handle a double-click on the media element to enter/exit fullscreen
  20646. *
  20647. * @param {EventTarget~Event} event
  20648. * the event that caused this function to trigger
  20649. *
  20650. * @listens Tech#dblclick
  20651. * @private
  20652. */
  20653. ;
  20654. _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
  20655. if (!this.controls_) {
  20656. return;
  20657. } // we do not want to toggle fullscreen state
  20658. // when double-clicking inside a control bar or a modal
  20659. var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
  20660. return el.contains(event.target);
  20661. });
  20662. if (!inAllowedEls) {
  20663. /*
  20664. * options.userActions.doubleClick
  20665. *
  20666. * If `undefined` or `true`, double-click toggles fullscreen if controls are present
  20667. * Set to `false` to disable double-click handling
  20668. * Set to a function to substitute an external double-click handler
  20669. */
  20670. if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
  20671. if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
  20672. this.options_.userActions.doubleClick.call(this, event);
  20673. } else if (this.isFullscreen()) {
  20674. this.exitFullscreen();
  20675. } else {
  20676. this.requestFullscreen();
  20677. }
  20678. }
  20679. }
  20680. }
  20681. /**
  20682. * Handle a tap on the media element. It will toggle the user
  20683. * activity state, which hides and shows the controls.
  20684. *
  20685. * @listens Tech#tap
  20686. * @private
  20687. */
  20688. ;
  20689. _proto.handleTechTap_ = function handleTechTap_() {
  20690. this.userActive(!this.userActive());
  20691. }
  20692. /**
  20693. * Handle touch to start
  20694. *
  20695. * @listens Tech#touchstart
  20696. * @private
  20697. */
  20698. ;
  20699. _proto.handleTechTouchStart_ = function handleTechTouchStart_() {
  20700. this.userWasActive = this.userActive();
  20701. }
  20702. /**
  20703. * Handle touch to move
  20704. *
  20705. * @listens Tech#touchmove
  20706. * @private
  20707. */
  20708. ;
  20709. _proto.handleTechTouchMove_ = function handleTechTouchMove_() {
  20710. if (this.userWasActive) {
  20711. this.reportUserActivity();
  20712. }
  20713. }
  20714. /**
  20715. * Handle touch to end
  20716. *
  20717. * @param {EventTarget~Event} event
  20718. * the touchend event that triggered
  20719. * this function
  20720. *
  20721. * @listens Tech#touchend
  20722. * @private
  20723. */
  20724. ;
  20725. _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
  20726. // Stop the mouse events from also happening
  20727. event.preventDefault();
  20728. }
  20729. /**
  20730. * native click events on the SWF aren't triggered on IE11, Win8.1RT
  20731. * use stageclick events triggered from inside the SWF instead
  20732. *
  20733. * @private
  20734. * @listens stageclick
  20735. */
  20736. ;
  20737. _proto.handleStageClick_ = function handleStageClick_() {
  20738. this.reportUserActivity();
  20739. }
  20740. /**
  20741. * @private
  20742. */
  20743. ;
  20744. _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() {
  20745. if (this.isFullscreen()) {
  20746. this.addClass('vjs-fullscreen');
  20747. } else {
  20748. this.removeClass('vjs-fullscreen');
  20749. }
  20750. }
  20751. /**
  20752. * when the document fschange event triggers it calls this
  20753. */
  20754. ;
  20755. _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) {
  20756. var fsApi = FullscreenApi;
  20757. this.isFullscreen(document[fsApi.fullscreenElement] === this.el() || this.el().matches(':' + fsApi.fullscreen)); // If cancelling fullscreen, remove event listener.
  20758. if (this.isFullscreen() === false) {
  20759. off(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  20760. }
  20761. if (!prefixedAPI) {
  20762. /**
  20763. * @event Player#fullscreenchange
  20764. * @type {EventTarget~Event}
  20765. */
  20766. this.trigger('fullscreenchange');
  20767. }
  20768. }
  20769. /**
  20770. * Handle Tech Fullscreen Change
  20771. *
  20772. * @param {EventTarget~Event} event
  20773. * the fullscreenchange event that triggered this function
  20774. *
  20775. * @param {Object} data
  20776. * the data that was sent with the event
  20777. *
  20778. * @private
  20779. * @listens Tech#fullscreenchange
  20780. * @fires Player#fullscreenchange
  20781. */
  20782. ;
  20783. _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
  20784. if (data) {
  20785. this.isFullscreen(data.isFullscreen);
  20786. }
  20787. /**
  20788. * Fired when going in and out of fullscreen.
  20789. *
  20790. * @event Player#fullscreenchange
  20791. * @type {EventTarget~Event}
  20792. */
  20793. this.trigger('fullscreenchange');
  20794. }
  20795. /**
  20796. * Fires when an error occurred during the loading of an audio/video.
  20797. *
  20798. * @private
  20799. * @listens Tech#error
  20800. */
  20801. ;
  20802. _proto.handleTechError_ = function handleTechError_() {
  20803. var error = this.tech_.error();
  20804. this.error(error);
  20805. }
  20806. /**
  20807. * Retrigger the `textdata` event that was triggered by the {@link Tech}.
  20808. *
  20809. * @fires Player#textdata
  20810. * @listens Tech#textdata
  20811. * @private
  20812. */
  20813. ;
  20814. _proto.handleTechTextData_ = function handleTechTextData_() {
  20815. var data = null;
  20816. if (arguments.length > 1) {
  20817. data = arguments[1];
  20818. }
  20819. /**
  20820. * Fires when we get a textdata event from tech
  20821. *
  20822. * @event Player#textdata
  20823. * @type {EventTarget~Event}
  20824. */
  20825. this.trigger('textdata', data);
  20826. }
  20827. /**
  20828. * Get object for cached values.
  20829. *
  20830. * @return {Object}
  20831. * get the current object cache
  20832. */
  20833. ;
  20834. _proto.getCache = function getCache() {
  20835. return this.cache_;
  20836. }
  20837. /**
  20838. * Resets the internal cache object.
  20839. *
  20840. * Using this function outside the player constructor or reset method may
  20841. * have unintended side-effects.
  20842. *
  20843. * @private
  20844. */
  20845. ;
  20846. _proto.resetCache_ = function resetCache_() {
  20847. this.cache_ = {
  20848. // Right now, the currentTime is not _really_ cached because it is always
  20849. // retrieved from the tech (see: currentTime). However, for completeness,
  20850. // we set it to zero here to ensure that if we do start actually caching
  20851. // it, we reset it along with everything else.
  20852. currentTime: 0,
  20853. inactivityTimeout: this.options_.inactivityTimeout,
  20854. duration: NaN,
  20855. lastVolume: 1,
  20856. lastPlaybackRate: this.defaultPlaybackRate(),
  20857. media: null,
  20858. src: '',
  20859. source: {},
  20860. sources: [],
  20861. volume: 1
  20862. };
  20863. }
  20864. /**
  20865. * Pass values to the playback tech
  20866. *
  20867. * @param {string} [method]
  20868. * the method to call
  20869. *
  20870. * @param {Object} arg
  20871. * the argument to pass
  20872. *
  20873. * @private
  20874. */
  20875. ;
  20876. _proto.techCall_ = function techCall_(method, arg) {
  20877. // If it's not ready yet, call method when it is
  20878. this.ready(function () {
  20879. if (method in allowedSetters) {
  20880. return set$1(this.middleware_, this.tech_, method, arg);
  20881. } else if (method in allowedMediators) {
  20882. return mediate(this.middleware_, this.tech_, method, arg);
  20883. }
  20884. try {
  20885. if (this.tech_) {
  20886. this.tech_[method](arg);
  20887. }
  20888. } catch (e) {
  20889. log(e);
  20890. throw e;
  20891. }
  20892. }, true);
  20893. }
  20894. /**
  20895. * Get calls can't wait for the tech, and sometimes don't need to.
  20896. *
  20897. * @param {string} method
  20898. * Tech method
  20899. *
  20900. * @return {Function|undefined}
  20901. * the method or undefined
  20902. *
  20903. * @private
  20904. */
  20905. ;
  20906. _proto.techGet_ = function techGet_(method) {
  20907. if (!this.tech_ || !this.tech_.isReady_) {
  20908. return;
  20909. }
  20910. if (method in allowedGetters) {
  20911. return get(this.middleware_, this.tech_, method);
  20912. } else if (method in allowedMediators) {
  20913. return mediate(this.middleware_, this.tech_, method);
  20914. } // Flash likes to die and reload when you hide or reposition it.
  20915. // In these cases the object methods go away and we get errors.
  20916. // When that happens we'll catch the errors and inform tech that it's not ready any more.
  20917. try {
  20918. return this.tech_[method]();
  20919. } catch (e) {
  20920. // When building additional tech libs, an expected method may not be defined yet
  20921. if (this.tech_[method] === undefined) {
  20922. log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e);
  20923. throw e;
  20924. } // When a method isn't available on the object it throws a TypeError
  20925. if (e.name === 'TypeError') {
  20926. log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e);
  20927. this.tech_.isReady_ = false;
  20928. throw e;
  20929. } // If error unknown, just log and throw
  20930. log(e);
  20931. throw e;
  20932. }
  20933. }
  20934. /**
  20935. * Attempt to begin playback at the first opportunity.
  20936. *
  20937. * @return {Promise|undefined}
  20938. * Returns a promise if the browser supports Promises (or one
  20939. * was passed in as an option). This promise will be resolved on
  20940. * the return value of play. If this is undefined it will fulfill the
  20941. * promise chain otherwise the promise chain will be fulfilled when
  20942. * the promise from play is fulfilled.
  20943. */
  20944. ;
  20945. _proto.play = function play() {
  20946. var _this8 = this;
  20947. var PromiseClass = this.options_.Promise || window$1.Promise;
  20948. if (PromiseClass) {
  20949. return new PromiseClass(function (resolve) {
  20950. _this8.play_(resolve);
  20951. });
  20952. }
  20953. return this.play_();
  20954. }
  20955. /**
  20956. * The actual logic for play, takes a callback that will be resolved on the
  20957. * return value of play. This allows us to resolve to the play promise if there
  20958. * is one on modern browsers.
  20959. *
  20960. * @private
  20961. * @param {Function} [callback]
  20962. * The callback that should be called when the techs play is actually called
  20963. */
  20964. ;
  20965. _proto.play_ = function play_(callback) {
  20966. var _this9 = this;
  20967. if (callback === void 0) {
  20968. callback = silencePromise;
  20969. }
  20970. this.playCallbacks_.push(callback);
  20971. var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function
  20972. if (this.waitToPlay_) {
  20973. this.off(['ready', 'loadstart'], this.waitToPlay_);
  20974. this.waitToPlay_ = null;
  20975. } // if the player/tech is not ready or the src itself is not ready
  20976. // queue up a call to play on `ready` or `loadstart`
  20977. if (!this.isReady_ || !isSrcReady) {
  20978. this.waitToPlay_ = function (e) {
  20979. _this9.play_();
  20980. };
  20981. this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
  20982. // in that case, we need to prime the video element by calling load so it'll be ready in time
  20983. if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) {
  20984. this.load();
  20985. }
  20986. return;
  20987. } // If the player/tech is ready and we have a source, we can attempt playback.
  20988. var val = this.techGet_('play'); // play was terminated if the returned value is null
  20989. if (val === null) {
  20990. this.runPlayTerminatedQueue_();
  20991. } else {
  20992. this.runPlayCallbacks_(val);
  20993. }
  20994. }
  20995. /**
  20996. * These functions will be run when if play is terminated. If play
  20997. * runPlayCallbacks_ is run these function will not be run. This allows us
  20998. * to differenciate between a terminated play and an actual call to play.
  20999. */
  21000. ;
  21001. _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() {
  21002. var queue = this.playTerminatedQueue_.slice(0);
  21003. this.playTerminatedQueue_ = [];
  21004. queue.forEach(function (q) {
  21005. q();
  21006. });
  21007. }
  21008. /**
  21009. * When a callback to play is delayed we have to run these
  21010. * callbacks when play is actually called on the tech. This function
  21011. * runs the callbacks that were delayed and accepts the return value
  21012. * from the tech.
  21013. *
  21014. * @param {undefined|Promise} val
  21015. * The return value from the tech.
  21016. */
  21017. ;
  21018. _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) {
  21019. var callbacks = this.playCallbacks_.slice(0);
  21020. this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play
  21021. this.playTerminatedQueue_ = [];
  21022. callbacks.forEach(function (cb) {
  21023. cb(val);
  21024. });
  21025. }
  21026. /**
  21027. * Pause the video playback
  21028. *
  21029. * @return {Player}
  21030. * A reference to the player object this function was called on
  21031. */
  21032. ;
  21033. _proto.pause = function pause() {
  21034. this.techCall_('pause');
  21035. }
  21036. /**
  21037. * Check if the player is paused or has yet to play
  21038. *
  21039. * @return {boolean}
  21040. * - false: if the media is currently playing
  21041. * - true: if media is not currently playing
  21042. */
  21043. ;
  21044. _proto.paused = function paused() {
  21045. // The initial state of paused should be true (in Safari it's actually false)
  21046. return this.techGet_('paused') === false ? false : true;
  21047. }
  21048. /**
  21049. * Get a TimeRange object representing the current ranges of time that the user
  21050. * has played.
  21051. *
  21052. * @return {TimeRange}
  21053. * A time range object that represents all the increments of time that have
  21054. * been played.
  21055. */
  21056. ;
  21057. _proto.played = function played() {
  21058. return this.techGet_('played') || createTimeRanges(0, 0);
  21059. }
  21060. /**
  21061. * Returns whether or not the user is "scrubbing". Scrubbing is
  21062. * when the user has clicked the progress bar handle and is
  21063. * dragging it along the progress bar.
  21064. *
  21065. * @param {boolean} [isScrubbing]
  21066. * whether the user is or is not scrubbing
  21067. *
  21068. * @return {boolean}
  21069. * The value of scrubbing when getting
  21070. */
  21071. ;
  21072. _proto.scrubbing = function scrubbing(isScrubbing) {
  21073. if (typeof isScrubbing === 'undefined') {
  21074. return this.scrubbing_;
  21075. }
  21076. this.scrubbing_ = !!isScrubbing;
  21077. if (isScrubbing) {
  21078. this.addClass('vjs-scrubbing');
  21079. } else {
  21080. this.removeClass('vjs-scrubbing');
  21081. }
  21082. }
  21083. /**
  21084. * Get or set the current time (in seconds)
  21085. *
  21086. * @param {number|string} [seconds]
  21087. * The time to seek to in seconds
  21088. *
  21089. * @return {number}
  21090. * - the current time in seconds when getting
  21091. */
  21092. ;
  21093. _proto.currentTime = function currentTime(seconds) {
  21094. if (typeof seconds !== 'undefined') {
  21095. if (seconds < 0) {
  21096. seconds = 0;
  21097. }
  21098. this.techCall_('setCurrentTime', seconds);
  21099. return;
  21100. } // cache last currentTime and return. default to 0 seconds
  21101. //
  21102. // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
  21103. // currentTime when scrubbing, but may not provide much performance benefit afterall.
  21104. // Should be tested. Also something has to read the actual current time or the cache will
  21105. // never get updated.
  21106. this.cache_.currentTime = this.techGet_('currentTime') || 0;
  21107. return this.cache_.currentTime;
  21108. }
  21109. /**
  21110. * Normally gets the length in time of the video in seconds;
  21111. * in all but the rarest use cases an argument will NOT be passed to the method
  21112. *
  21113. * > **NOTE**: The video must have started loading before the duration can be
  21114. * known, and in the case of Flash, may not be known until the video starts
  21115. * playing.
  21116. *
  21117. * @fires Player#durationchange
  21118. *
  21119. * @param {number} [seconds]
  21120. * The duration of the video to set in seconds
  21121. *
  21122. * @return {number}
  21123. * - The duration of the video in seconds when getting
  21124. */
  21125. ;
  21126. _proto.duration = function duration(seconds) {
  21127. if (seconds === undefined) {
  21128. // return NaN if the duration is not known
  21129. return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
  21130. }
  21131. seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live
  21132. if (seconds < 0) {
  21133. seconds = Infinity;
  21134. }
  21135. if (seconds !== this.cache_.duration) {
  21136. // Cache the last set value for optimized scrubbing (esp. Flash)
  21137. this.cache_.duration = seconds;
  21138. if (seconds === Infinity) {
  21139. this.addClass('vjs-live');
  21140. if (this.options_.liveui && this.player_.liveTracker) {
  21141. this.addClass('vjs-liveui');
  21142. }
  21143. } else {
  21144. this.removeClass('vjs-live');
  21145. this.removeClass('vjs-liveui');
  21146. }
  21147. if (!isNaN(seconds)) {
  21148. // Do not fire durationchange unless the duration value is known.
  21149. // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
  21150. /**
  21151. * @event Player#durationchange
  21152. * @type {EventTarget~Event}
  21153. */
  21154. this.trigger('durationchange');
  21155. }
  21156. }
  21157. }
  21158. /**
  21159. * Calculates how much time is left in the video. Not part
  21160. * of the native video API.
  21161. *
  21162. * @return {number}
  21163. * The time remaining in seconds
  21164. */
  21165. ;
  21166. _proto.remainingTime = function remainingTime() {
  21167. return this.duration() - this.currentTime();
  21168. }
  21169. /**
  21170. * A remaining time function that is intented to be used when
  21171. * the time is to be displayed directly to the user.
  21172. *
  21173. * @return {number}
  21174. * The rounded time remaining in seconds
  21175. */
  21176. ;
  21177. _proto.remainingTimeDisplay = function remainingTimeDisplay() {
  21178. return Math.floor(this.duration()) - Math.floor(this.currentTime());
  21179. } //
  21180. // Kind of like an array of portions of the video that have been downloaded.
  21181. /**
  21182. * Get a TimeRange object with an array of the times of the video
  21183. * that have been downloaded. If you just want the percent of the
  21184. * video that's been downloaded, use bufferedPercent.
  21185. *
  21186. * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
  21187. *
  21188. * @return {TimeRange}
  21189. * A mock TimeRange object (following HTML spec)
  21190. */
  21191. ;
  21192. _proto.buffered = function buffered() {
  21193. var buffered = this.techGet_('buffered');
  21194. if (!buffered || !buffered.length) {
  21195. buffered = createTimeRanges(0, 0);
  21196. }
  21197. return buffered;
  21198. }
  21199. /**
  21200. * Get the percent (as a decimal) of the video that's been downloaded.
  21201. * This method is not a part of the native HTML video API.
  21202. *
  21203. * @return {number}
  21204. * A decimal between 0 and 1 representing the percent
  21205. * that is buffered 0 being 0% and 1 being 100%
  21206. */
  21207. ;
  21208. _proto.bufferedPercent = function bufferedPercent$$1() {
  21209. return bufferedPercent(this.buffered(), this.duration());
  21210. }
  21211. /**
  21212. * Get the ending time of the last buffered time range
  21213. * This is used in the progress bar to encapsulate all time ranges.
  21214. *
  21215. * @return {number}
  21216. * The end of the last buffered time range
  21217. */
  21218. ;
  21219. _proto.bufferedEnd = function bufferedEnd() {
  21220. var buffered = this.buffered();
  21221. var duration = this.duration();
  21222. var end = buffered.end(buffered.length - 1);
  21223. if (end > duration) {
  21224. end = duration;
  21225. }
  21226. return end;
  21227. }
  21228. /**
  21229. * Get or set the current volume of the media
  21230. *
  21231. * @param {number} [percentAsDecimal]
  21232. * The new volume as a decimal percent:
  21233. * - 0 is muted/0%/off
  21234. * - 1.0 is 100%/full
  21235. * - 0.5 is half volume or 50%
  21236. *
  21237. * @return {number}
  21238. * The current volume as a percent when getting
  21239. */
  21240. ;
  21241. _proto.volume = function volume(percentAsDecimal) {
  21242. var vol;
  21243. if (percentAsDecimal !== undefined) {
  21244. // Force value to between 0 and 1
  21245. vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
  21246. this.cache_.volume = vol;
  21247. this.techCall_('setVolume', vol);
  21248. if (vol > 0) {
  21249. this.lastVolume_(vol);
  21250. }
  21251. return;
  21252. } // Default to 1 when returning current volume.
  21253. vol = parseFloat(this.techGet_('volume'));
  21254. return isNaN(vol) ? 1 : vol;
  21255. }
  21256. /**
  21257. * Get the current muted state, or turn mute on or off
  21258. *
  21259. * @param {boolean} [muted]
  21260. * - true to mute
  21261. * - false to unmute
  21262. *
  21263. * @return {boolean}
  21264. * - true if mute is on and getting
  21265. * - false if mute is off and getting
  21266. */
  21267. ;
  21268. _proto.muted = function muted(_muted) {
  21269. if (_muted !== undefined) {
  21270. this.techCall_('setMuted', _muted);
  21271. return;
  21272. }
  21273. return this.techGet_('muted') || false;
  21274. }
  21275. /**
  21276. * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
  21277. * indicates the state of muted on initial playback.
  21278. *
  21279. * ```js
  21280. * var myPlayer = videojs('some-player-id');
  21281. *
  21282. * myPlayer.src("http://www.example.com/path/to/video.mp4");
  21283. *
  21284. * // get, should be false
  21285. * console.log(myPlayer.defaultMuted());
  21286. * // set to true
  21287. * myPlayer.defaultMuted(true);
  21288. * // get should be true
  21289. * console.log(myPlayer.defaultMuted());
  21290. * ```
  21291. *
  21292. * @param {boolean} [defaultMuted]
  21293. * - true to mute
  21294. * - false to unmute
  21295. *
  21296. * @return {boolean|Player}
  21297. * - true if defaultMuted is on and getting
  21298. * - false if defaultMuted is off and getting
  21299. * - A reference to the current player when setting
  21300. */
  21301. ;
  21302. _proto.defaultMuted = function defaultMuted(_defaultMuted) {
  21303. if (_defaultMuted !== undefined) {
  21304. return this.techCall_('setDefaultMuted', _defaultMuted);
  21305. }
  21306. return this.techGet_('defaultMuted') || false;
  21307. }
  21308. /**
  21309. * Get the last volume, or set it
  21310. *
  21311. * @param {number} [percentAsDecimal]
  21312. * The new last volume as a decimal percent:
  21313. * - 0 is muted/0%/off
  21314. * - 1.0 is 100%/full
  21315. * - 0.5 is half volume or 50%
  21316. *
  21317. * @return {number}
  21318. * the current value of lastVolume as a percent when getting
  21319. *
  21320. * @private
  21321. */
  21322. ;
  21323. _proto.lastVolume_ = function lastVolume_(percentAsDecimal) {
  21324. if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
  21325. this.cache_.lastVolume = percentAsDecimal;
  21326. return;
  21327. }
  21328. return this.cache_.lastVolume;
  21329. }
  21330. /**
  21331. * Check if current tech can support native fullscreen
  21332. * (e.g. with built in controls like iOS, so not our flash swf)
  21333. *
  21334. * @return {boolean}
  21335. * if native fullscreen is supported
  21336. */
  21337. ;
  21338. _proto.supportsFullScreen = function supportsFullScreen() {
  21339. return this.techGet_('supportsFullScreen') || false;
  21340. }
  21341. /**
  21342. * Check if the player is in fullscreen mode or tell the player that it
  21343. * is or is not in fullscreen mode.
  21344. *
  21345. * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
  21346. * property and instead document.fullscreenElement is used. But isFullscreen is
  21347. * still a valuable property for internal player workings.
  21348. *
  21349. * @param {boolean} [isFS]
  21350. * Set the players current fullscreen state
  21351. *
  21352. * @return {boolean}
  21353. * - true if fullscreen is on and getting
  21354. * - false if fullscreen is off and getting
  21355. */
  21356. ;
  21357. _proto.isFullscreen = function isFullscreen(isFS) {
  21358. if (isFS !== undefined) {
  21359. this.isFullscreen_ = !!isFS;
  21360. this.toggleFullscreenClass_();
  21361. return;
  21362. }
  21363. return !!this.isFullscreen_;
  21364. }
  21365. /**
  21366. * Increase the size of the video to full screen
  21367. * In some browsers, full screen is not supported natively, so it enters
  21368. * "full window mode", where the video fills the browser window.
  21369. * In browsers and devices that support native full screen, sometimes the
  21370. * browser's default controls will be shown, and not the Video.js custom skin.
  21371. * This includes most mobile devices (iOS, Android) and older versions of
  21372. * Safari.
  21373. *
  21374. * @fires Player#fullscreenchange
  21375. */
  21376. ;
  21377. _proto.requestFullscreen = function requestFullscreen() {
  21378. var fsApi = FullscreenApi;
  21379. this.isFullscreen(true);
  21380. if (fsApi.requestFullscreen) {
  21381. // the browser supports going fullscreen at the element level so we can
  21382. // take the controls fullscreen as well as the video
  21383. // Trigger fullscreenchange event after change
  21384. // We have to specifically add this each time, and remove
  21385. // when canceling fullscreen. Otherwise if there's multiple
  21386. // players on a page, they would all be reacting to the same fullscreen
  21387. // events
  21388. on(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
  21389. this.el_[fsApi.requestFullscreen]();
  21390. } else if (this.tech_.supportsFullScreen()) {
  21391. // we can't take the video.js controls fullscreen but we can go fullscreen
  21392. // with native controls
  21393. this.techCall_('enterFullScreen');
  21394. } else {
  21395. // fullscreen isn't supported so we'll just stretch the video element to
  21396. // fill the viewport
  21397. this.enterFullWindow();
  21398. /**
  21399. * @event Player#fullscreenchange
  21400. * @type {EventTarget~Event}
  21401. */
  21402. this.trigger('fullscreenchange');
  21403. }
  21404. }
  21405. /**
  21406. * Return the video to its normal size after having been in full screen mode
  21407. *
  21408. * @fires Player#fullscreenchange
  21409. */
  21410. ;
  21411. _proto.exitFullscreen = function exitFullscreen() {
  21412. var fsApi = FullscreenApi;
  21413. this.isFullscreen(false); // Check for browser element fullscreen support
  21414. if (fsApi.requestFullscreen) {
  21415. document[fsApi.exitFullscreen]();
  21416. } else if (this.tech_.supportsFullScreen()) {
  21417. this.techCall_('exitFullScreen');
  21418. } else {
  21419. this.exitFullWindow();
  21420. /**
  21421. * @event Player#fullscreenchange
  21422. * @type {EventTarget~Event}
  21423. */
  21424. this.trigger('fullscreenchange');
  21425. }
  21426. }
  21427. /**
  21428. * When fullscreen isn't supported we can stretch the
  21429. * video container to as wide as the browser will let us.
  21430. *
  21431. * @fires Player#enterFullWindow
  21432. */
  21433. ;
  21434. _proto.enterFullWindow = function enterFullWindow() {
  21435. this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off
  21436. this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen
  21437. on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars
  21438. document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles
  21439. addClass(document.body, 'vjs-full-window');
  21440. /**
  21441. * @event Player#enterFullWindow
  21442. * @type {EventTarget~Event}
  21443. */
  21444. this.trigger('enterFullWindow');
  21445. }
  21446. /**
  21447. * Check for call to either exit full window or
  21448. * full screen on ESC key
  21449. *
  21450. * @param {string} event
  21451. * Event to check for key press
  21452. */
  21453. ;
  21454. _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
  21455. if (keycode.isEventKey(event, 'Esc')) {
  21456. if (this.isFullscreen() === true) {
  21457. this.exitFullscreen();
  21458. } else {
  21459. this.exitFullWindow();
  21460. }
  21461. }
  21462. }
  21463. /**
  21464. * Exit full window
  21465. *
  21466. * @fires Player#exitFullWindow
  21467. */
  21468. ;
  21469. _proto.exitFullWindow = function exitFullWindow() {
  21470. this.isFullWindow = false;
  21471. off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars.
  21472. document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles
  21473. removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes
  21474. // this.positionAll();
  21475. /**
  21476. * @event Player#exitFullWindow
  21477. * @type {EventTarget~Event}
  21478. */
  21479. this.trigger('exitFullWindow');
  21480. }
  21481. /**
  21482. * This gets called when a `Player` gains focus via a `focus` event.
  21483. * Turns on listening for `keydown` events. When they happen it
  21484. * calls `this.handleKeyPress`.
  21485. *
  21486. * @param {EventTarget~Event} event
  21487. * The `focus` event that caused this function to be called.
  21488. *
  21489. * @listens focus
  21490. */
  21491. ;
  21492. _proto.handleFocus = function handleFocus(event) {
  21493. // call off first to make sure we don't keep adding keydown handlers
  21494. off(document, 'keydown', this.boundHandleKeyPress_);
  21495. on(document, 'keydown', this.boundHandleKeyPress_);
  21496. }
  21497. /**
  21498. * Called when a `Player` loses focus. Turns off the listener for
  21499. * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
  21500. *
  21501. * @param {EventTarget~Event} event
  21502. * The `blur` event that caused this function to be called.
  21503. *
  21504. * @listens blur
  21505. */
  21506. ;
  21507. _proto.handleBlur = function handleBlur(event) {
  21508. off(document, 'keydown', this.boundHandleKeyPress_);
  21509. }
  21510. /**
  21511. * Called when this Player has focus and a key gets pressed down, or when
  21512. * any Component of this player receives a key press that it doesn't handle.
  21513. * This allows player-wide hotkeys (either as defined below, or optionally
  21514. * by an external function).
  21515. *
  21516. * @param {EventTarget~Event} event
  21517. * The `keydown` event that caused this function to be called.
  21518. *
  21519. * @listens keydown
  21520. */
  21521. ;
  21522. _proto.handleKeyPress = function handleKeyPress(event) {
  21523. if (this.options_.userActions && this.options_.userActions.hotkeys && this.options_.userActions.hotkeys !== false) {
  21524. if (typeof this.options_.userActions.hotkeys === 'function') {
  21525. this.options_.userActions.hotkeys.call(this, event);
  21526. } else {
  21527. this.handleHotkeys(event);
  21528. }
  21529. }
  21530. }
  21531. /**
  21532. * Called when this Player receives a hotkey keydown event.
  21533. * Supported player-wide hotkeys are:
  21534. *
  21535. * f - toggle fullscreen
  21536. * m - toggle mute
  21537. * k or Space - toggle play/pause
  21538. *
  21539. * @param {EventTarget~Event} event
  21540. * The `keydown` event that caused this function to be called.
  21541. */
  21542. ;
  21543. _proto.handleHotkeys = function handleHotkeys(event) {
  21544. var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
  21545. var _hotkeys$fullscreenKe = hotkeys.fullscreenKey,
  21546. fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) {
  21547. return keycode.isEventKey(keydownEvent, 'f');
  21548. } : _hotkeys$fullscreenKe,
  21549. _hotkeys$muteKey = hotkeys.muteKey,
  21550. muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) {
  21551. return keycode.isEventKey(keydownEvent, 'm');
  21552. } : _hotkeys$muteKey,
  21553. _hotkeys$playPauseKey = hotkeys.playPauseKey,
  21554. playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) {
  21555. return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space');
  21556. } : _hotkeys$playPauseKey;
  21557. if (fullscreenKey.call(this, event)) {
  21558. event.preventDefault();
  21559. var FSToggle = Component.getComponent('FullscreenToggle');
  21560. if (document[FullscreenApi.fullscreenEnabled] !== false) {
  21561. FSToggle.prototype.handleClick.call(this);
  21562. }
  21563. } else if (muteKey.call(this, event)) {
  21564. event.preventDefault();
  21565. var MuteToggle = Component.getComponent('MuteToggle');
  21566. MuteToggle.prototype.handleClick.call(this);
  21567. } else if (playPauseKey.call(this, event)) {
  21568. event.preventDefault();
  21569. var PlayToggle = Component.getComponent('PlayToggle');
  21570. PlayToggle.prototype.handleClick.call(this);
  21571. }
  21572. }
  21573. /**
  21574. * Check whether the player can play a given mimetype
  21575. *
  21576. * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
  21577. *
  21578. * @param {string} type
  21579. * The mimetype to check
  21580. *
  21581. * @return {string}
  21582. * 'probably', 'maybe', or '' (empty string)
  21583. */
  21584. ;
  21585. _proto.canPlayType = function canPlayType(type) {
  21586. var can; // Loop through each playback technology in the options order
  21587. for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
  21588. var techName = j[i];
  21589. var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
  21590. // Remove once that deprecated behavior is removed.
  21591. if (!tech) {
  21592. tech = Component.getComponent(techName);
  21593. } // Check if the current tech is defined before continuing
  21594. if (!tech) {
  21595. log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
  21596. continue;
  21597. } // Check if the browser supports this technology
  21598. if (tech.isSupported()) {
  21599. can = tech.canPlayType(type);
  21600. if (can) {
  21601. return can;
  21602. }
  21603. }
  21604. }
  21605. return '';
  21606. }
  21607. /**
  21608. * Select source based on tech-order or source-order
  21609. * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
  21610. * defaults to tech-order selection
  21611. *
  21612. * @param {Array} sources
  21613. * The sources for a media asset
  21614. *
  21615. * @return {Object|boolean}
  21616. * Object of source and tech order or false
  21617. */
  21618. ;
  21619. _proto.selectSource = function selectSource(sources) {
  21620. var _this10 = this;
  21621. // Get only the techs specified in `techOrder` that exist and are supported by the
  21622. // current platform
  21623. var techs = this.options_.techOrder.map(function (techName) {
  21624. return [techName, Tech.getTech(techName)];
  21625. }).filter(function (_ref) {
  21626. var techName = _ref[0],
  21627. tech = _ref[1];
  21628. // Check if the current tech is defined before continuing
  21629. if (tech) {
  21630. // Check if the browser supports this technology
  21631. return tech.isSupported();
  21632. }
  21633. log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
  21634. return false;
  21635. }); // Iterate over each `innerArray` element once per `outerArray` element and execute
  21636. // `tester` with both. If `tester` returns a non-falsy value, exit early and return
  21637. // that value.
  21638. var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
  21639. var found;
  21640. outerArray.some(function (outerChoice) {
  21641. return innerArray.some(function (innerChoice) {
  21642. found = tester(outerChoice, innerChoice);
  21643. if (found) {
  21644. return true;
  21645. }
  21646. });
  21647. });
  21648. return found;
  21649. };
  21650. var foundSourceAndTech;
  21651. var flip = function flip(fn) {
  21652. return function (a, b) {
  21653. return fn(b, a);
  21654. };
  21655. };
  21656. var finder = function finder(_ref2, source) {
  21657. var techName = _ref2[0],
  21658. tech = _ref2[1];
  21659. if (tech.canPlaySource(source, _this10.options_[techName.toLowerCase()])) {
  21660. return {
  21661. source: source,
  21662. tech: techName
  21663. };
  21664. }
  21665. }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
  21666. // to select from them based on their priority.
  21667. if (this.options_.sourceOrder) {
  21668. // Source-first ordering
  21669. foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
  21670. } else {
  21671. // Tech-first ordering
  21672. foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
  21673. }
  21674. return foundSourceAndTech || false;
  21675. }
  21676. /**
  21677. * Get or set the video source.
  21678. *
  21679. * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
  21680. * A SourceObject, an array of SourceObjects, or a string referencing
  21681. * a URL to a media source. It is _highly recommended_ that an object
  21682. * or array of objects is used here, so that source selection
  21683. * algorithms can take the `type` into account.
  21684. *
  21685. * If not provided, this method acts as a getter.
  21686. *
  21687. * @return {string|undefined}
  21688. * If the `source` argument is missing, returns the current source
  21689. * URL. Otherwise, returns nothing/undefined.
  21690. */
  21691. ;
  21692. _proto.src = function src(source) {
  21693. var _this11 = this;
  21694. // getter usage
  21695. if (typeof source === 'undefined') {
  21696. return this.cache_.src || '';
  21697. } // filter out invalid sources and turn our source into
  21698. // an array of source objects
  21699. var sources = filterSource(source); // if a source was passed in then it is invalid because
  21700. // it was filtered to a zero length Array. So we have to
  21701. // show an error
  21702. if (!sources.length) {
  21703. this.setTimeout(function () {
  21704. this.error({
  21705. code: 4,
  21706. message: this.localize(this.options_.notSupportedMessage)
  21707. });
  21708. }, 0);
  21709. return;
  21710. } // intial sources
  21711. this.changingSrc_ = true;
  21712. this.cache_.sources = sources;
  21713. this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware
  21714. setSource(this, sources[0], function (middlewareSource, mws) {
  21715. _this11.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
  21716. // the source that is selected could be out of order from the cache update above this callback.
  21717. _this11.cache_.sources = sources;
  21718. _this11.updateSourceCaches_(middlewareSource);
  21719. var err = _this11.src_(middlewareSource);
  21720. if (err) {
  21721. if (sources.length > 1) {
  21722. return _this11.src(sources.slice(1));
  21723. }
  21724. _this11.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
  21725. _this11.setTimeout(function () {
  21726. this.error({
  21727. code: 4,
  21728. message: this.localize(this.options_.notSupportedMessage)
  21729. });
  21730. }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it
  21731. // this needs a better comment about why this is needed
  21732. _this11.triggerReady();
  21733. return;
  21734. }
  21735. setTech(mws, _this11.tech_);
  21736. });
  21737. }
  21738. /**
  21739. * Set the source object on the tech, returns a boolean that indicates whether
  21740. * there is a tech that can play the source or not
  21741. *
  21742. * @param {Tech~SourceObject} source
  21743. * The source object to set on the Tech
  21744. *
  21745. * @return {boolean}
  21746. * - True if there is no Tech to playback this source
  21747. * - False otherwise
  21748. *
  21749. * @private
  21750. */
  21751. ;
  21752. _proto.src_ = function src_(source) {
  21753. var _this12 = this;
  21754. var sourceTech = this.selectSource([source]);
  21755. if (!sourceTech) {
  21756. return true;
  21757. }
  21758. if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
  21759. this.changingSrc_ = true; // load this technology with the chosen source
  21760. this.loadTech_(sourceTech.tech, sourceTech.source);
  21761. this.tech_.ready(function () {
  21762. _this12.changingSrc_ = false;
  21763. });
  21764. return false;
  21765. } // wait until the tech is ready to set the source
  21766. // and set it synchronously if possible (#2326)
  21767. this.ready(function () {
  21768. // The setSource tech method was added with source handlers
  21769. // so older techs won't support it
  21770. // We need to check the direct prototype for the case where subclasses
  21771. // of the tech do not support source handlers
  21772. if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
  21773. this.techCall_('setSource', source);
  21774. } else {
  21775. this.techCall_('src', source.src);
  21776. }
  21777. this.changingSrc_ = false;
  21778. }, true);
  21779. return false;
  21780. }
  21781. /**
  21782. * Begin loading the src data.
  21783. */
  21784. ;
  21785. _proto.load = function load() {
  21786. this.techCall_('load');
  21787. }
  21788. /**
  21789. * Reset the player. Loads the first tech in the techOrder,
  21790. * removes all the text tracks in the existing `tech`,
  21791. * and calls `reset` on the `tech`.
  21792. */
  21793. ;
  21794. _proto.reset = function reset() {
  21795. var _this13 = this;
  21796. var PromiseClass = this.options_.Promise || window$1.Promise;
  21797. if (this.paused() || !PromiseClass) {
  21798. this.doReset_();
  21799. } else {
  21800. var playPromise = this.play();
  21801. silencePromise(playPromise.then(function () {
  21802. return _this13.doReset_();
  21803. }));
  21804. }
  21805. };
  21806. _proto.doReset_ = function doReset_() {
  21807. if (this.tech_) {
  21808. this.tech_.clearTracks('text');
  21809. }
  21810. this.resetCache_();
  21811. this.poster('');
  21812. this.loadTech_(this.options_.techOrder[0], null);
  21813. this.techCall_('reset');
  21814. this.resetControlBarUI_();
  21815. if (isEvented(this)) {
  21816. this.trigger('playerreset');
  21817. }
  21818. }
  21819. /**
  21820. * Reset Control Bar's UI by calling sub-methods that reset
  21821. * all of Control Bar's components
  21822. */
  21823. ;
  21824. _proto.resetControlBarUI_ = function resetControlBarUI_() {
  21825. this.resetProgressBar_();
  21826. this.resetPlaybackRate_();
  21827. this.resetVolumeBar_();
  21828. }
  21829. /**
  21830. * Reset tech's progress so progress bar is reset in the UI
  21831. */
  21832. ;
  21833. _proto.resetProgressBar_ = function resetProgressBar_() {
  21834. this.currentTime(0);
  21835. var _this$controlBar = this.controlBar,
  21836. durationDisplay = _this$controlBar.durationDisplay,
  21837. remainingTimeDisplay = _this$controlBar.remainingTimeDisplay;
  21838. if (durationDisplay) {
  21839. durationDisplay.updateContent();
  21840. }
  21841. if (remainingTimeDisplay) {
  21842. remainingTimeDisplay.updateContent();
  21843. }
  21844. }
  21845. /**
  21846. * Reset Playback ratio
  21847. */
  21848. ;
  21849. _proto.resetPlaybackRate_ = function resetPlaybackRate_() {
  21850. this.playbackRate(this.defaultPlaybackRate());
  21851. this.handleTechRateChange_();
  21852. }
  21853. /**
  21854. * Reset Volume bar
  21855. */
  21856. ;
  21857. _proto.resetVolumeBar_ = function resetVolumeBar_() {
  21858. this.volume(1.0);
  21859. this.trigger('volumechange');
  21860. }
  21861. /**
  21862. * Returns all of the current source objects.
  21863. *
  21864. * @return {Tech~SourceObject[]}
  21865. * The current source objects
  21866. */
  21867. ;
  21868. _proto.currentSources = function currentSources() {
  21869. var source = this.currentSource();
  21870. var sources = []; // assume `{}` or `{ src }`
  21871. if (Object.keys(source).length !== 0) {
  21872. sources.push(source);
  21873. }
  21874. return this.cache_.sources || sources;
  21875. }
  21876. /**
  21877. * Returns the current source object.
  21878. *
  21879. * @return {Tech~SourceObject}
  21880. * The current source object
  21881. */
  21882. ;
  21883. _proto.currentSource = function currentSource() {
  21884. return this.cache_.source || {};
  21885. }
  21886. /**
  21887. * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
  21888. * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
  21889. *
  21890. * @return {string}
  21891. * The current source
  21892. */
  21893. ;
  21894. _proto.currentSrc = function currentSrc() {
  21895. return this.currentSource() && this.currentSource().src || '';
  21896. }
  21897. /**
  21898. * Get the current source type e.g. video/mp4
  21899. * This can allow you rebuild the current source object so that you could load the same
  21900. * source and tech later
  21901. *
  21902. * @return {string}
  21903. * The source MIME type
  21904. */
  21905. ;
  21906. _proto.currentType = function currentType() {
  21907. return this.currentSource() && this.currentSource().type || '';
  21908. }
  21909. /**
  21910. * Get or set the preload attribute
  21911. *
  21912. * @param {boolean} [value]
  21913. * - true means that we should preload
  21914. * - false means that we should not preload
  21915. *
  21916. * @return {string}
  21917. * The preload attribute value when getting
  21918. */
  21919. ;
  21920. _proto.preload = function preload(value) {
  21921. if (value !== undefined) {
  21922. this.techCall_('setPreload', value);
  21923. this.options_.preload = value;
  21924. return;
  21925. }
  21926. return this.techGet_('preload');
  21927. }
  21928. /**
  21929. * Get or set the autoplay option. When this is a boolean it will
  21930. * modify the attribute on the tech. When this is a string the attribute on
  21931. * the tech will be removed and `Player` will handle autoplay on loadstarts.
  21932. *
  21933. * @param {boolean|string} [value]
  21934. * - true: autoplay using the browser behavior
  21935. * - false: do not autoplay
  21936. * - 'play': call play() on every loadstart
  21937. * - 'muted': call muted() then play() on every loadstart
  21938. * - 'any': call play() on every loadstart. if that fails call muted() then play().
  21939. * - *: values other than those listed here will be set `autoplay` to true
  21940. *
  21941. * @return {boolean|string}
  21942. * The current value of autoplay when getting
  21943. */
  21944. ;
  21945. _proto.autoplay = function autoplay(value) {
  21946. // getter usage
  21947. if (value === undefined) {
  21948. return this.options_.autoplay || false;
  21949. }
  21950. var techAutoplay; // if the value is a valid string set it to that
  21951. if (typeof value === 'string' && /(any|play|muted)/.test(value)) {
  21952. this.options_.autoplay = value;
  21953. this.manualAutoplay_(value);
  21954. techAutoplay = false; // any falsy value sets autoplay to false in the browser,
  21955. // lets do the same
  21956. } else if (!value) {
  21957. this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true
  21958. } else {
  21959. this.options_.autoplay = true;
  21960. }
  21961. techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up
  21962. // a setAutoplay call on tech ready. We do this because the
  21963. // autoplay option will be passed in the constructor and we
  21964. // do not need to set it twice
  21965. if (this.tech_) {
  21966. this.techCall_('setAutoplay', techAutoplay);
  21967. }
  21968. }
  21969. /**
  21970. * Set or unset the playsinline attribute.
  21971. * Playsinline tells the browser that non-fullscreen playback is preferred.
  21972. *
  21973. * @param {boolean} [value]
  21974. * - true means that we should try to play inline by default
  21975. * - false means that we should use the browser's default playback mode,
  21976. * which in most cases is inline. iOS Safari is a notable exception
  21977. * and plays fullscreen by default.
  21978. *
  21979. * @return {string|Player}
  21980. * - the current value of playsinline
  21981. * - the player when setting
  21982. *
  21983. * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
  21984. */
  21985. ;
  21986. _proto.playsinline = function playsinline(value) {
  21987. if (value !== undefined) {
  21988. this.techCall_('setPlaysinline', value);
  21989. this.options_.playsinline = value;
  21990. return this;
  21991. }
  21992. return this.techGet_('playsinline');
  21993. }
  21994. /**
  21995. * Get or set the loop attribute on the video element.
  21996. *
  21997. * @param {boolean} [value]
  21998. * - true means that we should loop the video
  21999. * - false means that we should not loop the video
  22000. *
  22001. * @return {boolean}
  22002. * The current value of loop when getting
  22003. */
  22004. ;
  22005. _proto.loop = function loop(value) {
  22006. if (value !== undefined) {
  22007. this.techCall_('setLoop', value);
  22008. this.options_.loop = value;
  22009. return;
  22010. }
  22011. return this.techGet_('loop');
  22012. }
  22013. /**
  22014. * Get or set the poster image source url
  22015. *
  22016. * @fires Player#posterchange
  22017. *
  22018. * @param {string} [src]
  22019. * Poster image source URL
  22020. *
  22021. * @return {string}
  22022. * The current value of poster when getting
  22023. */
  22024. ;
  22025. _proto.poster = function poster(src) {
  22026. if (src === undefined) {
  22027. return this.poster_;
  22028. } // The correct way to remove a poster is to set as an empty string
  22029. // other falsey values will throw errors
  22030. if (!src) {
  22031. src = '';
  22032. }
  22033. if (src === this.poster_) {
  22034. return;
  22035. } // update the internal poster variable
  22036. this.poster_ = src; // update the tech's poster
  22037. this.techCall_('setPoster', src);
  22038. this.isPosterFromTech_ = false; // alert components that the poster has been set
  22039. /**
  22040. * This event fires when the poster image is changed on the player.
  22041. *
  22042. * @event Player#posterchange
  22043. * @type {EventTarget~Event}
  22044. */
  22045. this.trigger('posterchange');
  22046. }
  22047. /**
  22048. * Some techs (e.g. YouTube) can provide a poster source in an
  22049. * asynchronous way. We want the poster component to use this
  22050. * poster source so that it covers up the tech's controls.
  22051. * (YouTube's play button). However we only want to use this
  22052. * source if the player user hasn't set a poster through
  22053. * the normal APIs.
  22054. *
  22055. * @fires Player#posterchange
  22056. * @listens Tech#posterchange
  22057. * @private
  22058. */
  22059. ;
  22060. _proto.handleTechPosterChange_ = function handleTechPosterChange_() {
  22061. if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
  22062. var newPoster = this.tech_.poster() || '';
  22063. if (newPoster !== this.poster_) {
  22064. this.poster_ = newPoster;
  22065. this.isPosterFromTech_ = true; // Let components know the poster has changed
  22066. this.trigger('posterchange');
  22067. }
  22068. }
  22069. }
  22070. /**
  22071. * Get or set whether or not the controls are showing.
  22072. *
  22073. * @fires Player#controlsenabled
  22074. *
  22075. * @param {boolean} [bool]
  22076. * - true to turn controls on
  22077. * - false to turn controls off
  22078. *
  22079. * @return {boolean}
  22080. * The current value of controls when getting
  22081. */
  22082. ;
  22083. _proto.controls = function controls(bool) {
  22084. if (bool === undefined) {
  22085. return !!this.controls_;
  22086. }
  22087. bool = !!bool; // Don't trigger a change event unless it actually changed
  22088. if (this.controls_ === bool) {
  22089. return;
  22090. }
  22091. this.controls_ = bool;
  22092. if (this.usingNativeControls()) {
  22093. this.techCall_('setControls', bool);
  22094. }
  22095. if (this.controls_) {
  22096. this.removeClass('vjs-controls-disabled');
  22097. this.addClass('vjs-controls-enabled');
  22098. /**
  22099. * @event Player#controlsenabled
  22100. * @type {EventTarget~Event}
  22101. */
  22102. this.trigger('controlsenabled');
  22103. if (!this.usingNativeControls()) {
  22104. this.addTechControlsListeners_();
  22105. }
  22106. } else {
  22107. this.removeClass('vjs-controls-enabled');
  22108. this.addClass('vjs-controls-disabled');
  22109. /**
  22110. * @event Player#controlsdisabled
  22111. * @type {EventTarget~Event}
  22112. */
  22113. this.trigger('controlsdisabled');
  22114. if (!this.usingNativeControls()) {
  22115. this.removeTechControlsListeners_();
  22116. }
  22117. }
  22118. }
  22119. /**
  22120. * Toggle native controls on/off. Native controls are the controls built into
  22121. * devices (e.g. default iPhone controls), Flash, or other techs
  22122. * (e.g. Vimeo Controls)
  22123. * **This should only be set by the current tech, because only the tech knows
  22124. * if it can support native controls**
  22125. *
  22126. * @fires Player#usingnativecontrols
  22127. * @fires Player#usingcustomcontrols
  22128. *
  22129. * @param {boolean} [bool]
  22130. * - true to turn native controls on
  22131. * - false to turn native controls off
  22132. *
  22133. * @return {boolean}
  22134. * The current value of native controls when getting
  22135. */
  22136. ;
  22137. _proto.usingNativeControls = function usingNativeControls(bool) {
  22138. if (bool === undefined) {
  22139. return !!this.usingNativeControls_;
  22140. }
  22141. bool = !!bool; // Don't trigger a change event unless it actually changed
  22142. if (this.usingNativeControls_ === bool) {
  22143. return;
  22144. }
  22145. this.usingNativeControls_ = bool;
  22146. if (this.usingNativeControls_) {
  22147. this.addClass('vjs-using-native-controls');
  22148. /**
  22149. * player is using the native device controls
  22150. *
  22151. * @event Player#usingnativecontrols
  22152. * @type {EventTarget~Event}
  22153. */
  22154. this.trigger('usingnativecontrols');
  22155. } else {
  22156. this.removeClass('vjs-using-native-controls');
  22157. /**
  22158. * player is using the custom HTML controls
  22159. *
  22160. * @event Player#usingcustomcontrols
  22161. * @type {EventTarget~Event}
  22162. */
  22163. this.trigger('usingcustomcontrols');
  22164. }
  22165. }
  22166. /**
  22167. * Set or get the current MediaError
  22168. *
  22169. * @fires Player#error
  22170. *
  22171. * @param {MediaError|string|number} [err]
  22172. * A MediaError or a string/number to be turned
  22173. * into a MediaError
  22174. *
  22175. * @return {MediaError|null}
  22176. * The current MediaError when getting (or null)
  22177. */
  22178. ;
  22179. _proto.error = function error(err) {
  22180. if (err === undefined) {
  22181. return this.error_ || null;
  22182. } // restoring to default
  22183. if (err === null) {
  22184. this.error_ = err;
  22185. this.removeClass('vjs-error');
  22186. if (this.errorDisplay) {
  22187. this.errorDisplay.close();
  22188. }
  22189. return;
  22190. }
  22191. this.error_ = new MediaError(err); // add the vjs-error classname to the player
  22192. this.addClass('vjs-error'); // log the name of the error type and any message
  22193. // IE11 logs "[object object]" and required you to expand message to see error object
  22194. log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_);
  22195. /**
  22196. * @event Player#error
  22197. * @type {EventTarget~Event}
  22198. */
  22199. this.trigger('error');
  22200. return;
  22201. }
  22202. /**
  22203. * Report user activity
  22204. *
  22205. * @param {Object} event
  22206. * Event object
  22207. */
  22208. ;
  22209. _proto.reportUserActivity = function reportUserActivity(event) {
  22210. this.userActivity_ = true;
  22211. }
  22212. /**
  22213. * Get/set if user is active
  22214. *
  22215. * @fires Player#useractive
  22216. * @fires Player#userinactive
  22217. *
  22218. * @param {boolean} [bool]
  22219. * - true if the user is active
  22220. * - false if the user is inactive
  22221. *
  22222. * @return {boolean}
  22223. * The current value of userActive when getting
  22224. */
  22225. ;
  22226. _proto.userActive = function userActive(bool) {
  22227. if (bool === undefined) {
  22228. return this.userActive_;
  22229. }
  22230. bool = !!bool;
  22231. if (bool === this.userActive_) {
  22232. return;
  22233. }
  22234. this.userActive_ = bool;
  22235. if (this.userActive_) {
  22236. this.userActivity_ = true;
  22237. this.removeClass('vjs-user-inactive');
  22238. this.addClass('vjs-user-active');
  22239. /**
  22240. * @event Player#useractive
  22241. * @type {EventTarget~Event}
  22242. */
  22243. this.trigger('useractive');
  22244. return;
  22245. } // Chrome/Safari/IE have bugs where when you change the cursor it can
  22246. // trigger a mousemove event. This causes an issue when you're hiding
  22247. // the cursor when the user is inactive, and a mousemove signals user
  22248. // activity. Making it impossible to go into inactive mode. Specifically
  22249. // this happens in fullscreen when we really need to hide the cursor.
  22250. //
  22251. // When this gets resolved in ALL browsers it can be removed
  22252. // https://code.google.com/p/chromium/issues/detail?id=103041
  22253. if (this.tech_) {
  22254. this.tech_.one('mousemove', function (e) {
  22255. e.stopPropagation();
  22256. e.preventDefault();
  22257. });
  22258. }
  22259. this.userActivity_ = false;
  22260. this.removeClass('vjs-user-active');
  22261. this.addClass('vjs-user-inactive');
  22262. /**
  22263. * @event Player#userinactive
  22264. * @type {EventTarget~Event}
  22265. */
  22266. this.trigger('userinactive');
  22267. }
  22268. /**
  22269. * Listen for user activity based on timeout value
  22270. *
  22271. * @private
  22272. */
  22273. ;
  22274. _proto.listenForUserActivity_ = function listenForUserActivity_() {
  22275. var mouseInProgress;
  22276. var lastMoveX;
  22277. var lastMoveY;
  22278. var handleActivity = bind(this, this.reportUserActivity);
  22279. var handleMouseMove = function handleMouseMove(e) {
  22280. // #1068 - Prevent mousemove spamming
  22281. // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
  22282. if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
  22283. lastMoveX = e.screenX;
  22284. lastMoveY = e.screenY;
  22285. handleActivity();
  22286. }
  22287. };
  22288. var handleMouseDown = function handleMouseDown() {
  22289. handleActivity(); // For as long as the they are touching the device or have their mouse down,
  22290. // we consider them active even if they're not moving their finger or mouse.
  22291. // So we want to continue to update that they are active
  22292. this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time
  22293. // as the activityCheck interval (250) should ensure we never miss the
  22294. // next activityCheck
  22295. mouseInProgress = this.setInterval(handleActivity, 250);
  22296. };
  22297. var handleMouseUp = function handleMouseUp(event) {
  22298. handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down
  22299. this.clearInterval(mouseInProgress);
  22300. }; // Any mouse movement will be considered user activity
  22301. this.on('mousedown', handleMouseDown);
  22302. this.on('mousemove', handleMouseMove);
  22303. this.on('mouseup', handleMouseUp);
  22304. var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
  22305. // controlBar would no longer be hidden by default timeout.
  22306. if (controlBar && !IS_IOS && !IS_ANDROID) {
  22307. controlBar.on('mouseenter', function (event) {
  22308. this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
  22309. this.player().options_.inactivityTimeout = 0;
  22310. });
  22311. controlBar.on('mouseleave', function (event) {
  22312. this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
  22313. });
  22314. } // Listen for keyboard navigation
  22315. // Shouldn't need to use inProgress interval because of key repeat
  22316. this.on('keydown', handleActivity);
  22317. this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into
  22318. // the mousemove/touchmove function itself, to prevent performance degradation.
  22319. // `this.reportUserActivity` simply sets this.userActivity_ to true, which
  22320. // then gets picked up by this loop
  22321. // http://ejohn.org/blog/learning-from-twitter/
  22322. var inactivityTimeout;
  22323. this.setInterval(function () {
  22324. // Check to see if mouse/touch activity has happened
  22325. if (!this.userActivity_) {
  22326. return;
  22327. } // Reset the activity tracker
  22328. this.userActivity_ = false; // If the user state was inactive, set the state to active
  22329. this.userActive(true); // Clear any existing inactivity timeout to start the timer over
  22330. this.clearTimeout(inactivityTimeout);
  22331. var timeout = this.options_.inactivityTimeout;
  22332. if (timeout <= 0) {
  22333. return;
  22334. } // In <timeout> milliseconds, if no more activity has occurred the
  22335. // user will be considered inactive
  22336. inactivityTimeout = this.setTimeout(function () {
  22337. // Protect against the case where the inactivityTimeout can trigger just
  22338. // before the next user activity is picked up by the activity check loop
  22339. // causing a flicker
  22340. if (!this.userActivity_) {
  22341. this.userActive(false);
  22342. }
  22343. }, timeout);
  22344. }, 250);
  22345. }
  22346. /**
  22347. * Gets or sets the current playback rate. A playback rate of
  22348. * 1.0 represents normal speed and 0.5 would indicate half-speed
  22349. * playback, for instance.
  22350. *
  22351. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
  22352. *
  22353. * @param {number} [rate]
  22354. * New playback rate to set.
  22355. *
  22356. * @return {number}
  22357. * The current playback rate when getting or 1.0
  22358. */
  22359. ;
  22360. _proto.playbackRate = function playbackRate(rate) {
  22361. if (rate !== undefined) {
  22362. // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
  22363. // that is registered above
  22364. this.techCall_('setPlaybackRate', rate);
  22365. return;
  22366. }
  22367. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  22368. return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
  22369. }
  22370. return 1.0;
  22371. }
  22372. /**
  22373. * Gets or sets the current default playback rate. A default playback rate of
  22374. * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
  22375. * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
  22376. * not the current playbackRate.
  22377. *
  22378. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
  22379. *
  22380. * @param {number} [rate]
  22381. * New default playback rate to set.
  22382. *
  22383. * @return {number|Player}
  22384. * - The default playback rate when getting or 1.0
  22385. * - the player when setting
  22386. */
  22387. ;
  22388. _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) {
  22389. if (rate !== undefined) {
  22390. return this.techCall_('setDefaultPlaybackRate', rate);
  22391. }
  22392. if (this.tech_ && this.tech_.featuresPlaybackRate) {
  22393. return this.techGet_('defaultPlaybackRate');
  22394. }
  22395. return 1.0;
  22396. }
  22397. /**
  22398. * Gets or sets the audio flag
  22399. *
  22400. * @param {boolean} bool
  22401. * - true signals that this is an audio player
  22402. * - false signals that this is not an audio player
  22403. *
  22404. * @return {boolean}
  22405. * The current value of isAudio when getting
  22406. */
  22407. ;
  22408. _proto.isAudio = function isAudio(bool) {
  22409. if (bool !== undefined) {
  22410. this.isAudio_ = !!bool;
  22411. return;
  22412. }
  22413. return !!this.isAudio_;
  22414. }
  22415. /**
  22416. * A helper method for adding a {@link TextTrack} to our
  22417. * {@link TextTrackList}.
  22418. *
  22419. * In addition to the W3C settings we allow adding additional info through options.
  22420. *
  22421. * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
  22422. *
  22423. * @param {string} [kind]
  22424. * the kind of TextTrack you are adding
  22425. *
  22426. * @param {string} [label]
  22427. * the label to give the TextTrack label
  22428. *
  22429. * @param {string} [language]
  22430. * the language to set on the TextTrack
  22431. *
  22432. * @return {TextTrack|undefined}
  22433. * the TextTrack that was added or undefined
  22434. * if there is no tech
  22435. */
  22436. ;
  22437. _proto.addTextTrack = function addTextTrack(kind, label, language) {
  22438. if (this.tech_) {
  22439. return this.tech_.addTextTrack(kind, label, language);
  22440. }
  22441. }
  22442. /**
  22443. * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will
  22444. * automatically removed from the video element whenever the source changes, unless
  22445. * manualCleanup is set to false.
  22446. *
  22447. * @param {Object} options
  22448. * Options to pass to {@link HTMLTrackElement} during creation. See
  22449. * {@link HTMLTrackElement} for object properties that you should use.
  22450. *
  22451. * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
  22452. *
  22453. * @return {HtmlTrackElement}
  22454. * the HTMLTrackElement that was created and added
  22455. * to the HtmlTrackElementList and the remote
  22456. * TextTrackList
  22457. *
  22458. * @deprecated The default value of the "manualCleanup" parameter will default
  22459. * to "false" in upcoming versions of Video.js
  22460. */
  22461. ;
  22462. _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
  22463. if (this.tech_) {
  22464. return this.tech_.addRemoteTextTrack(options, manualCleanup);
  22465. }
  22466. }
  22467. /**
  22468. * Remove a remote {@link TextTrack} from the respective
  22469. * {@link TextTrackList} and {@link HtmlTrackElementList}.
  22470. *
  22471. * @param {Object} track
  22472. * Remote {@link TextTrack} to remove
  22473. *
  22474. * @return {undefined}
  22475. * does not return anything
  22476. */
  22477. ;
  22478. _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) {
  22479. if (obj === void 0) {
  22480. obj = {};
  22481. }
  22482. var _obj = obj,
  22483. track = _obj.track;
  22484. if (!track) {
  22485. track = obj;
  22486. } // destructure the input into an object with a track argument, defaulting to arguments[0]
  22487. // default the whole argument to an empty object if nothing was passed in
  22488. if (this.tech_) {
  22489. return this.tech_.removeRemoteTextTrack(track);
  22490. }
  22491. }
  22492. /**
  22493. * Gets available media playback quality metrics as specified by the W3C's Media
  22494. * Playback Quality API.
  22495. *
  22496. * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
  22497. *
  22498. * @return {Object|undefined}
  22499. * An object with supported media playback quality metrics or undefined if there
  22500. * is no tech or the tech does not support it.
  22501. */
  22502. ;
  22503. _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
  22504. return this.techGet_('getVideoPlaybackQuality');
  22505. }
  22506. /**
  22507. * Get video width
  22508. *
  22509. * @return {number}
  22510. * current video width
  22511. */
  22512. ;
  22513. _proto.videoWidth = function videoWidth() {
  22514. return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
  22515. }
  22516. /**
  22517. * Get video height
  22518. *
  22519. * @return {number}
  22520. * current video height
  22521. */
  22522. ;
  22523. _proto.videoHeight = function videoHeight() {
  22524. return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
  22525. }
  22526. /**
  22527. * The player's language code
  22528. * NOTE: The language should be set in the player options if you want the
  22529. * the controls to be built with a specific language. Changing the language
  22530. * later will not update controls text.
  22531. *
  22532. * @param {string} [code]
  22533. * the language code to set the player to
  22534. *
  22535. * @return {string}
  22536. * The current language code when getting
  22537. */
  22538. ;
  22539. _proto.language = function language(code) {
  22540. if (code === undefined) {
  22541. return this.language_;
  22542. }
  22543. this.language_ = String(code).toLowerCase();
  22544. }
  22545. /**
  22546. * Get the player's language dictionary
  22547. * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
  22548. * Languages specified directly in the player options have precedence
  22549. *
  22550. * @return {Array}
  22551. * An array of of supported languages
  22552. */
  22553. ;
  22554. _proto.languages = function languages() {
  22555. return mergeOptions(Player.prototype.options_.languages, this.languages_);
  22556. }
  22557. /**
  22558. * returns a JavaScript object reperesenting the current track
  22559. * information. **DOES not return it as JSON**
  22560. *
  22561. * @return {Object}
  22562. * Object representing the current of track info
  22563. */
  22564. ;
  22565. _proto.toJSON = function toJSON() {
  22566. var options = mergeOptions(this.options_);
  22567. var tracks = options.tracks;
  22568. options.tracks = [];
  22569. for (var i = 0; i < tracks.length; i++) {
  22570. var track = tracks[i]; // deep merge tracks and null out player so no circular references
  22571. track = mergeOptions(track);
  22572. track.player = undefined;
  22573. options.tracks[i] = track;
  22574. }
  22575. return options;
  22576. }
  22577. /**
  22578. * Creates a simple modal dialog (an instance of the {@link ModalDialog}
  22579. * component) that immediately overlays the player with arbitrary
  22580. * content and removes itself when closed.
  22581. *
  22582. * @param {string|Function|Element|Array|null} content
  22583. * Same as {@link ModalDialog#content}'s param of the same name.
  22584. * The most straight-forward usage is to provide a string or DOM
  22585. * element.
  22586. *
  22587. * @param {Object} [options]
  22588. * Extra options which will be passed on to the {@link ModalDialog}.
  22589. *
  22590. * @return {ModalDialog}
  22591. * the {@link ModalDialog} that was created
  22592. */
  22593. ;
  22594. _proto.createModal = function createModal(content, options) {
  22595. var _this14 = this;
  22596. options = options || {};
  22597. options.content = content || '';
  22598. var modal = new ModalDialog(this, options);
  22599. this.addChild(modal);
  22600. modal.on('dispose', function () {
  22601. _this14.removeChild(modal);
  22602. });
  22603. modal.open();
  22604. return modal;
  22605. }
  22606. /**
  22607. * Change breakpoint classes when the player resizes.
  22608. *
  22609. * @private
  22610. */
  22611. ;
  22612. _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() {
  22613. if (!this.responsive()) {
  22614. return;
  22615. }
  22616. var currentBreakpoint = this.currentBreakpoint();
  22617. var currentWidth = this.currentWidth();
  22618. for (var i = 0; i < BREAKPOINT_ORDER.length; i++) {
  22619. var candidateBreakpoint = BREAKPOINT_ORDER[i];
  22620. var maxWidth = this.breakpoints_[candidateBreakpoint];
  22621. if (currentWidth <= maxWidth) {
  22622. // The current breakpoint did not change, nothing to do.
  22623. if (currentBreakpoint === candidateBreakpoint) {
  22624. return;
  22625. } // Only remove a class if there is a current breakpoint.
  22626. if (currentBreakpoint) {
  22627. this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
  22628. }
  22629. this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
  22630. this.breakpoint_ = candidateBreakpoint;
  22631. break;
  22632. }
  22633. }
  22634. }
  22635. /**
  22636. * Removes the current breakpoint.
  22637. *
  22638. * @private
  22639. */
  22640. ;
  22641. _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() {
  22642. var className = this.currentBreakpointClass();
  22643. this.breakpoint_ = '';
  22644. if (className) {
  22645. this.removeClass(className);
  22646. }
  22647. }
  22648. /**
  22649. * Get or set breakpoints on the player.
  22650. *
  22651. * Calling this method with an object or `true` will remove any previous
  22652. * custom breakpoints and start from the defaults again.
  22653. *
  22654. * @param {Object|boolean} [breakpoints]
  22655. * If an object is given, it can be used to provide custom
  22656. * breakpoints. If `true` is given, will set default breakpoints.
  22657. * If this argument is not given, will simply return the current
  22658. * breakpoints.
  22659. *
  22660. * @param {number} [breakpoints.tiny]
  22661. * The maximum width for the "vjs-layout-tiny" class.
  22662. *
  22663. * @param {number} [breakpoints.xsmall]
  22664. * The maximum width for the "vjs-layout-x-small" class.
  22665. *
  22666. * @param {number} [breakpoints.small]
  22667. * The maximum width for the "vjs-layout-small" class.
  22668. *
  22669. * @param {number} [breakpoints.medium]
  22670. * The maximum width for the "vjs-layout-medium" class.
  22671. *
  22672. * @param {number} [breakpoints.large]
  22673. * The maximum width for the "vjs-layout-large" class.
  22674. *
  22675. * @param {number} [breakpoints.xlarge]
  22676. * The maximum width for the "vjs-layout-x-large" class.
  22677. *
  22678. * @param {number} [breakpoints.huge]
  22679. * The maximum width for the "vjs-layout-huge" class.
  22680. *
  22681. * @return {Object}
  22682. * An object mapping breakpoint names to maximum width values.
  22683. */
  22684. ;
  22685. _proto.breakpoints = function breakpoints(_breakpoints) {
  22686. // Used as a getter.
  22687. if (_breakpoints === undefined) {
  22688. return assign(this.breakpoints_);
  22689. }
  22690. this.breakpoint_ = '';
  22691. this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently
  22692. // selected breakpoint.
  22693. this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning.
  22694. return assign(this.breakpoints_);
  22695. }
  22696. /**
  22697. * Get or set a flag indicating whether or not this player should adjust
  22698. * its UI based on its dimensions.
  22699. *
  22700. * @param {boolean} value
  22701. * Should be `true` if the player should adjust its UI based on its
  22702. * dimensions; otherwise, should be `false`.
  22703. *
  22704. * @return {boolean}
  22705. * Will be `true` if this player should adjust its UI based on its
  22706. * dimensions; otherwise, will be `false`.
  22707. */
  22708. ;
  22709. _proto.responsive = function responsive(value) {
  22710. // Used as a getter.
  22711. if (value === undefined) {
  22712. return this.responsive_;
  22713. }
  22714. value = Boolean(value);
  22715. var current = this.responsive_; // Nothing changed.
  22716. if (value === current) {
  22717. return;
  22718. } // The value actually changed, set it.
  22719. this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the
  22720. // player is now responsive.
  22721. if (value) {
  22722. this.on('playerresize', this.updateCurrentBreakpoint_);
  22723. this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive.
  22724. } else {
  22725. this.off('playerresize', this.updateCurrentBreakpoint_);
  22726. this.removeCurrentBreakpoint_();
  22727. }
  22728. return value;
  22729. }
  22730. /**
  22731. * Get current breakpoint name, if any.
  22732. *
  22733. * @return {string}
  22734. * If there is currently a breakpoint set, returns a the key from the
  22735. * breakpoints object matching it. Otherwise, returns an empty string.
  22736. */
  22737. ;
  22738. _proto.currentBreakpoint = function currentBreakpoint() {
  22739. return this.breakpoint_;
  22740. }
  22741. /**
  22742. * Get the current breakpoint class name.
  22743. *
  22744. * @return {string}
  22745. * The matching class name (e.g. `"vjs-layout-tiny"` or
  22746. * `"vjs-layout-large"`) for the current breakpoint. Empty string if
  22747. * there is no current breakpoint.
  22748. */
  22749. ;
  22750. _proto.currentBreakpointClass = function currentBreakpointClass() {
  22751. return BREAKPOINT_CLASSES[this.breakpoint_] || '';
  22752. }
  22753. /**
  22754. * An object that describes a single piece of media.
  22755. *
  22756. * Properties that are not part of this type description will be retained; so,
  22757. * this can be viewed as a generic metadata storage mechanism as well.
  22758. *
  22759. * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
  22760. * @typedef {Object} Player~MediaObject
  22761. *
  22762. * @property {string} [album]
  22763. * Unused, except if this object is passed to the `MediaSession`
  22764. * API.
  22765. *
  22766. * @property {string} [artist]
  22767. * Unused, except if this object is passed to the `MediaSession`
  22768. * API.
  22769. *
  22770. * @property {Object[]} [artwork]
  22771. * Unused, except if this object is passed to the `MediaSession`
  22772. * API. If not specified, will be populated via the `poster`, if
  22773. * available.
  22774. *
  22775. * @property {string} [poster]
  22776. * URL to an image that will display before playback.
  22777. *
  22778. * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
  22779. * A single source object, an array of source objects, or a string
  22780. * referencing a URL to a media source. It is _highly recommended_
  22781. * that an object or array of objects is used here, so that source
  22782. * selection algorithms can take the `type` into account.
  22783. *
  22784. * @property {string} [title]
  22785. * Unused, except if this object is passed to the `MediaSession`
  22786. * API.
  22787. *
  22788. * @property {Object[]} [textTracks]
  22789. * An array of objects to be used to create text tracks, following
  22790. * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
  22791. * For ease of removal, these will be created as "remote" text
  22792. * tracks and set to automatically clean up on source changes.
  22793. *
  22794. * These objects may have properties like `src`, `kind`, `label`,
  22795. * and `language`, see {@link Tech#createRemoteTextTrack}.
  22796. */
  22797. /**
  22798. * Populate the player using a {@link Player~MediaObject|MediaObject}.
  22799. *
  22800. * @param {Player~MediaObject} media
  22801. * A media object.
  22802. *
  22803. * @param {Function} ready
  22804. * A callback to be called when the player is ready.
  22805. */
  22806. ;
  22807. _proto.loadMedia = function loadMedia(media, ready) {
  22808. var _this15 = this;
  22809. if (!media || typeof media !== 'object') {
  22810. return;
  22811. }
  22812. this.reset(); // Clone the media object so it cannot be mutated from outside.
  22813. this.cache_.media = mergeOptions(media);
  22814. var _this$cache_$media = this.cache_.media,
  22815. artwork = _this$cache_$media.artwork,
  22816. poster = _this$cache_$media.poster,
  22817. src = _this$cache_$media.src,
  22818. textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`.
  22819. if (!artwork && poster) {
  22820. this.cache_.media.artwork = [{
  22821. src: poster,
  22822. type: getMimetype(poster)
  22823. }];
  22824. }
  22825. if (src) {
  22826. this.src(src);
  22827. }
  22828. if (poster) {
  22829. this.poster(poster);
  22830. }
  22831. if (Array.isArray(textTracks)) {
  22832. textTracks.forEach(function (tt) {
  22833. return _this15.addRemoteTextTrack(tt, false);
  22834. });
  22835. }
  22836. this.ready(ready);
  22837. }
  22838. /**
  22839. * Get a clone of the current {@link Player~MediaObject} for this player.
  22840. *
  22841. * If the `loadMedia` method has not been used, will attempt to return a
  22842. * {@link Player~MediaObject} based on the current state of the player.
  22843. *
  22844. * @return {Player~MediaObject}
  22845. */
  22846. ;
  22847. _proto.getMedia = function getMedia() {
  22848. if (!this.cache_.media) {
  22849. var poster = this.poster();
  22850. var src = this.currentSources();
  22851. var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) {
  22852. return {
  22853. kind: tt.kind,
  22854. label: tt.label,
  22855. language: tt.language,
  22856. src: tt.src
  22857. };
  22858. });
  22859. var media = {
  22860. src: src,
  22861. textTracks: textTracks
  22862. };
  22863. if (poster) {
  22864. media.poster = poster;
  22865. media.artwork = [{
  22866. src: media.poster,
  22867. type: getMimetype(media.poster)
  22868. }];
  22869. }
  22870. return media;
  22871. }
  22872. return mergeOptions(this.cache_.media);
  22873. }
  22874. /**
  22875. * Gets tag settings
  22876. *
  22877. * @param {Element} tag
  22878. * The player tag
  22879. *
  22880. * @return {Object}
  22881. * An object containing all of the settings
  22882. * for a player tag
  22883. */
  22884. ;
  22885. Player.getTagSettings = function getTagSettings(tag) {
  22886. var baseOptions = {
  22887. sources: [],
  22888. tracks: []
  22889. };
  22890. var tagOptions = getAttributes(tag);
  22891. var dataSetup = tagOptions['data-setup'];
  22892. if (hasClass(tag, 'vjs-fill')) {
  22893. tagOptions.fill = true;
  22894. }
  22895. if (hasClass(tag, 'vjs-fluid')) {
  22896. tagOptions.fluid = true;
  22897. } // Check if data-setup attr exists.
  22898. if (dataSetup !== null) {
  22899. // Parse options JSON
  22900. // If empty string, make it a parsable json object.
  22901. var _safeParseTuple = tuple(dataSetup || '{}'),
  22902. err = _safeParseTuple[0],
  22903. data = _safeParseTuple[1];
  22904. if (err) {
  22905. log.error(err);
  22906. }
  22907. assign(tagOptions, data);
  22908. }
  22909. assign(baseOptions, tagOptions); // Get tag children settings
  22910. if (tag.hasChildNodes()) {
  22911. var children = tag.childNodes;
  22912. for (var i = 0, j = children.length; i < j; i++) {
  22913. var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
  22914. var childName = child.nodeName.toLowerCase();
  22915. if (childName === 'source') {
  22916. baseOptions.sources.push(getAttributes(child));
  22917. } else if (childName === 'track') {
  22918. baseOptions.tracks.push(getAttributes(child));
  22919. }
  22920. }
  22921. }
  22922. return baseOptions;
  22923. }
  22924. /**
  22925. * Determine whether or not flexbox is supported
  22926. *
  22927. * @return {boolean}
  22928. * - true if flexbox is supported
  22929. * - false if flexbox is not supported
  22930. */
  22931. ;
  22932. _proto.flexNotSupported_ = function flexNotSupported_() {
  22933. var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
  22934. // common flex features that we can rely on when checking for flex support.
  22935. 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
  22936. 'msFlexOrder' in elem.style);
  22937. };
  22938. return Player;
  22939. }(Component);
  22940. /**
  22941. * Get the {@link VideoTrackList}
  22942. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
  22943. *
  22944. * @return {VideoTrackList}
  22945. * the current video track list
  22946. *
  22947. * @method Player.prototype.videoTracks
  22948. */
  22949. /**
  22950. * Get the {@link AudioTrackList}
  22951. * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
  22952. *
  22953. * @return {AudioTrackList}
  22954. * the current audio track list
  22955. *
  22956. * @method Player.prototype.audioTracks
  22957. */
  22958. /**
  22959. * Get the {@link TextTrackList}
  22960. *
  22961. * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
  22962. *
  22963. * @return {TextTrackList}
  22964. * the current text track list
  22965. *
  22966. * @method Player.prototype.textTracks
  22967. */
  22968. /**
  22969. * Get the remote {@link TextTrackList}
  22970. *
  22971. * @return {TextTrackList}
  22972. * The current remote text track list
  22973. *
  22974. * @method Player.prototype.remoteTextTracks
  22975. */
  22976. /**
  22977. * Get the remote {@link HtmlTrackElementList} tracks.
  22978. *
  22979. * @return {HtmlTrackElementList}
  22980. * The current remote text track element list
  22981. *
  22982. * @method Player.prototype.remoteTextTrackEls
  22983. */
  22984. ALL.names.forEach(function (name$$1) {
  22985. var props = ALL[name$$1];
  22986. Player.prototype[props.getterName] = function () {
  22987. if (this.tech_) {
  22988. return this.tech_[props.getterName]();
  22989. } // if we have not yet loadTech_, we create {video,audio,text}Tracks_
  22990. // these will be passed to the tech during loading
  22991. this[props.privateName] = this[props.privateName] || new props.ListClass();
  22992. return this[props.privateName];
  22993. };
  22994. });
  22995. /**
  22996. * Global enumeration of players.
  22997. *
  22998. * The keys are the player IDs and the values are either the {@link Player}
  22999. * instance or `null` for disposed players.
  23000. *
  23001. * @type {Object}
  23002. */
  23003. Player.players = {};
  23004. var navigator = window$1.navigator;
  23005. /*
  23006. * Player instance options, surfaced using options
  23007. * options = Player.prototype.options_
  23008. * Make changes in options, not here.
  23009. *
  23010. * @type {Object}
  23011. * @private
  23012. */
  23013. Player.prototype.options_ = {
  23014. // Default order of fallback technology
  23015. techOrder: Tech.defaultTechOrder_,
  23016. html5: {},
  23017. flash: {},
  23018. // default inactivity timeout
  23019. inactivityTimeout: 2000,
  23020. // default playback rates
  23021. playbackRates: [],
  23022. // Add playback rate selection by adding rates
  23023. // 'playbackRates': [0.5, 1, 1.5, 2],
  23024. liveui: false,
  23025. // Included control sets
  23026. children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
  23027. language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
  23028. // locales and their language translations
  23029. languages: {},
  23030. // Default message to show when a video cannot be played.
  23031. notSupportedMessage: 'No compatible source was found for this media.',
  23032. breakpoints: {},
  23033. responsive: false
  23034. };
  23035. [
  23036. /**
  23037. * Returns whether or not the player is in the "ended" state.
  23038. *
  23039. * @return {Boolean} True if the player is in the ended state, false if not.
  23040. * @method Player#ended
  23041. */
  23042. 'ended',
  23043. /**
  23044. * Returns whether or not the player is in the "seeking" state.
  23045. *
  23046. * @return {Boolean} True if the player is in the seeking state, false if not.
  23047. * @method Player#seeking
  23048. */
  23049. 'seeking',
  23050. /**
  23051. * Returns the TimeRanges of the media that are currently available
  23052. * for seeking to.
  23053. *
  23054. * @return {TimeRanges} the seekable intervals of the media timeline
  23055. * @method Player#seekable
  23056. */
  23057. 'seekable',
  23058. /**
  23059. * Returns the current state of network activity for the element, from
  23060. * the codes in the list below.
  23061. * - NETWORK_EMPTY (numeric value 0)
  23062. * The element has not yet been initialised. All attributes are in
  23063. * their initial states.
  23064. * - NETWORK_IDLE (numeric value 1)
  23065. * The element's resource selection algorithm is active and has
  23066. * selected a resource, but it is not actually using the network at
  23067. * this time.
  23068. * - NETWORK_LOADING (numeric value 2)
  23069. * The user agent is actively trying to download data.
  23070. * - NETWORK_NO_SOURCE (numeric value 3)
  23071. * The element's resource selection algorithm is active, but it has
  23072. * not yet found a resource to use.
  23073. *
  23074. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
  23075. * @return {number} the current network activity state
  23076. * @method Player#networkState
  23077. */
  23078. 'networkState',
  23079. /**
  23080. * Returns a value that expresses the current state of the element
  23081. * with respect to rendering the current playback position, from the
  23082. * codes in the list below.
  23083. * - HAVE_NOTHING (numeric value 0)
  23084. * No information regarding the media resource is available.
  23085. * - HAVE_METADATA (numeric value 1)
  23086. * Enough of the resource has been obtained that the duration of the
  23087. * resource is available.
  23088. * - HAVE_CURRENT_DATA (numeric value 2)
  23089. * Data for the immediate current playback position is available.
  23090. * - HAVE_FUTURE_DATA (numeric value 3)
  23091. * Data for the immediate current playback position is available, as
  23092. * well as enough data for the user agent to advance the current
  23093. * playback position in the direction of playback.
  23094. * - HAVE_ENOUGH_DATA (numeric value 4)
  23095. * The user agent estimates that enough data is available for
  23096. * playback to proceed uninterrupted.
  23097. *
  23098. * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
  23099. * @return {number} the current playback rendering state
  23100. * @method Player#readyState
  23101. */
  23102. 'readyState'].forEach(function (fn) {
  23103. Player.prototype[fn] = function () {
  23104. return this.techGet_(fn);
  23105. };
  23106. });
  23107. TECH_EVENTS_RETRIGGER.forEach(function (event) {
  23108. Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () {
  23109. return this.trigger(event);
  23110. };
  23111. });
  23112. /**
  23113. * Fired when the player has initial duration and dimension information
  23114. *
  23115. * @event Player#loadedmetadata
  23116. * @type {EventTarget~Event}
  23117. */
  23118. /**
  23119. * Fired when the player has downloaded data at the current playback position
  23120. *
  23121. * @event Player#loadeddata
  23122. * @type {EventTarget~Event}
  23123. */
  23124. /**
  23125. * Fired when the current playback position has changed *
  23126. * During playback this is fired every 15-250 milliseconds, depending on the
  23127. * playback technology in use.
  23128. *
  23129. * @event Player#timeupdate
  23130. * @type {EventTarget~Event}
  23131. */
  23132. /**
  23133. * Fired when the volume changes
  23134. *
  23135. * @event Player#volumechange
  23136. * @type {EventTarget~Event}
  23137. */
  23138. /**
  23139. * Reports whether or not a player has a plugin available.
  23140. *
  23141. * This does not report whether or not the plugin has ever been initialized
  23142. * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
  23143. *
  23144. * @method Player#hasPlugin
  23145. * @param {string} name
  23146. * The name of a plugin.
  23147. *
  23148. * @return {boolean}
  23149. * Whether or not this player has the requested plugin available.
  23150. */
  23151. /**
  23152. * Reports whether or not a player is using a plugin by name.
  23153. *
  23154. * For basic plugins, this only reports whether the plugin has _ever_ been
  23155. * initialized on this player.
  23156. *
  23157. * @method Player#usingPlugin
  23158. * @param {string} name
  23159. * The name of a plugin.
  23160. *
  23161. * @return {boolean}
  23162. * Whether or not this player is using the requested plugin.
  23163. */
  23164. Component.registerComponent('Player', Player);
  23165. /**
  23166. * The base plugin name.
  23167. *
  23168. * @private
  23169. * @constant
  23170. * @type {string}
  23171. */
  23172. var BASE_PLUGIN_NAME = 'plugin';
  23173. /**
  23174. * The key on which a player's active plugins cache is stored.
  23175. *
  23176. * @private
  23177. * @constant
  23178. * @type {string}
  23179. */
  23180. var PLUGIN_CACHE_KEY = 'activePlugins_';
  23181. /**
  23182. * Stores registered plugins in a private space.
  23183. *
  23184. * @private
  23185. * @type {Object}
  23186. */
  23187. var pluginStorage = {};
  23188. /**
  23189. * Reports whether or not a plugin has been registered.
  23190. *
  23191. * @private
  23192. * @param {string} name
  23193. * The name of a plugin.
  23194. *
  23195. * @return {boolean}
  23196. * Whether or not the plugin has been registered.
  23197. */
  23198. var pluginExists = function pluginExists(name) {
  23199. return pluginStorage.hasOwnProperty(name);
  23200. };
  23201. /**
  23202. * Get a single registered plugin by name.
  23203. *
  23204. * @private
  23205. * @param {string} name
  23206. * The name of a plugin.
  23207. *
  23208. * @return {Function|undefined}
  23209. * The plugin (or undefined).
  23210. */
  23211. var getPlugin = function getPlugin(name) {
  23212. return pluginExists(name) ? pluginStorage[name] : undefined;
  23213. };
  23214. /**
  23215. * Marks a plugin as "active" on a player.
  23216. *
  23217. * Also, ensures that the player has an object for tracking active plugins.
  23218. *
  23219. * @private
  23220. * @param {Player} player
  23221. * A Video.js player instance.
  23222. *
  23223. * @param {string} name
  23224. * The name of a plugin.
  23225. */
  23226. var markPluginAsActive = function markPluginAsActive(player, name) {
  23227. player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
  23228. player[PLUGIN_CACHE_KEY][name] = true;
  23229. };
  23230. /**
  23231. * Triggers a pair of plugin setup events.
  23232. *
  23233. * @private
  23234. * @param {Player} player
  23235. * A Video.js player instance.
  23236. *
  23237. * @param {Plugin~PluginEventHash} hash
  23238. * A plugin event hash.
  23239. *
  23240. * @param {boolean} [before]
  23241. * If true, prefixes the event name with "before". In other words,
  23242. * use this to trigger "beforepluginsetup" instead of "pluginsetup".
  23243. */
  23244. var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
  23245. var eventName = (before ? 'before' : '') + 'pluginsetup';
  23246. player.trigger(eventName, hash);
  23247. player.trigger(eventName + ':' + hash.name, hash);
  23248. };
  23249. /**
  23250. * Takes a basic plugin function and returns a wrapper function which marks
  23251. * on the player that the plugin has been activated.
  23252. *
  23253. * @private
  23254. * @param {string} name
  23255. * The name of the plugin.
  23256. *
  23257. * @param {Function} plugin
  23258. * The basic plugin.
  23259. *
  23260. * @return {Function}
  23261. * A wrapper function for the given plugin.
  23262. */
  23263. var createBasicPlugin = function createBasicPlugin(name, plugin) {
  23264. var basicPluginWrapper = function basicPluginWrapper() {
  23265. // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
  23266. // regardless, but we want the hash to be consistent with the hash provided
  23267. // for advanced plugins.
  23268. //
  23269. // The only potentially counter-intuitive thing here is the `instance` in
  23270. // the "pluginsetup" event is the value returned by the `plugin` function.
  23271. triggerSetupEvent(this, {
  23272. name: name,
  23273. plugin: plugin,
  23274. instance: null
  23275. }, true);
  23276. var instance = plugin.apply(this, arguments);
  23277. markPluginAsActive(this, name);
  23278. triggerSetupEvent(this, {
  23279. name: name,
  23280. plugin: plugin,
  23281. instance: instance
  23282. });
  23283. return instance;
  23284. };
  23285. Object.keys(plugin).forEach(function (prop) {
  23286. basicPluginWrapper[prop] = plugin[prop];
  23287. });
  23288. return basicPluginWrapper;
  23289. };
  23290. /**
  23291. * Takes a plugin sub-class and returns a factory function for generating
  23292. * instances of it.
  23293. *
  23294. * This factory function will replace itself with an instance of the requested
  23295. * sub-class of Plugin.
  23296. *
  23297. * @private
  23298. * @param {string} name
  23299. * The name of the plugin.
  23300. *
  23301. * @param {Plugin} PluginSubClass
  23302. * The advanced plugin.
  23303. *
  23304. * @return {Function}
  23305. */
  23306. var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
  23307. // Add a `name` property to the plugin prototype so that each plugin can
  23308. // refer to itself by name.
  23309. PluginSubClass.prototype.name = name;
  23310. return function () {
  23311. triggerSetupEvent(this, {
  23312. name: name,
  23313. plugin: PluginSubClass,
  23314. instance: null
  23315. }, true);
  23316. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  23317. args[_key] = arguments[_key];
  23318. }
  23319. var instance = _construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance.
  23320. this[name] = function () {
  23321. return instance;
  23322. };
  23323. triggerSetupEvent(this, instance.getEventHash());
  23324. return instance;
  23325. };
  23326. };
  23327. /**
  23328. * Parent class for all advanced plugins.
  23329. *
  23330. * @mixes module:evented~EventedMixin
  23331. * @mixes module:stateful~StatefulMixin
  23332. * @fires Player#beforepluginsetup
  23333. * @fires Player#beforepluginsetup:$name
  23334. * @fires Player#pluginsetup
  23335. * @fires Player#pluginsetup:$name
  23336. * @listens Player#dispose
  23337. * @throws {Error}
  23338. * If attempting to instantiate the base {@link Plugin} class
  23339. * directly instead of via a sub-class.
  23340. */
  23341. var Plugin =
  23342. /*#__PURE__*/
  23343. function () {
  23344. /**
  23345. * Creates an instance of this class.
  23346. *
  23347. * Sub-classes should call `super` to ensure plugins are properly initialized.
  23348. *
  23349. * @param {Player} player
  23350. * A Video.js player instance.
  23351. */
  23352. function Plugin(player) {
  23353. if (this.constructor === Plugin) {
  23354. throw new Error('Plugin must be sub-classed; not directly instantiated.');
  23355. }
  23356. this.player = player; // Make this object evented, but remove the added `trigger` method so we
  23357. // use the prototype version instead.
  23358. evented(this);
  23359. delete this.trigger;
  23360. stateful(this, this.constructor.defaultState);
  23361. markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind
  23362. // it later easily.
  23363. this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin.
  23364. player.on('dispose', this.dispose);
  23365. }
  23366. /**
  23367. * Get the version of the plugin that was set on <pluginName>.VERSION
  23368. */
  23369. var _proto = Plugin.prototype;
  23370. _proto.version = function version() {
  23371. return this.constructor.VERSION;
  23372. }
  23373. /**
  23374. * Each event triggered by plugins includes a hash of additional data with
  23375. * conventional properties.
  23376. *
  23377. * This returns that object or mutates an existing hash.
  23378. *
  23379. * @param {Object} [hash={}]
  23380. * An object to be used as event an event hash.
  23381. *
  23382. * @return {Plugin~PluginEventHash}
  23383. * An event hash object with provided properties mixed-in.
  23384. */
  23385. ;
  23386. _proto.getEventHash = function getEventHash(hash) {
  23387. if (hash === void 0) {
  23388. hash = {};
  23389. }
  23390. hash.name = this.name;
  23391. hash.plugin = this.constructor;
  23392. hash.instance = this;
  23393. return hash;
  23394. }
  23395. /**
  23396. * Triggers an event on the plugin object and overrides
  23397. * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
  23398. *
  23399. * @param {string|Object} event
  23400. * An event type or an object with a type property.
  23401. *
  23402. * @param {Object} [hash={}]
  23403. * Additional data hash to merge with a
  23404. * {@link Plugin~PluginEventHash|PluginEventHash}.
  23405. *
  23406. * @return {boolean}
  23407. * Whether or not default was prevented.
  23408. */
  23409. ;
  23410. _proto.trigger = function trigger$$1(event, hash) {
  23411. if (hash === void 0) {
  23412. hash = {};
  23413. }
  23414. return trigger(this.eventBusEl_, event, this.getEventHash(hash));
  23415. }
  23416. /**
  23417. * Handles "statechanged" events on the plugin. No-op by default, override by
  23418. * subclassing.
  23419. *
  23420. * @abstract
  23421. * @param {Event} e
  23422. * An event object provided by a "statechanged" event.
  23423. *
  23424. * @param {Object} e.changes
  23425. * An object describing changes that occurred with the "statechanged"
  23426. * event.
  23427. */
  23428. ;
  23429. _proto.handleStateChanged = function handleStateChanged(e) {}
  23430. /**
  23431. * Disposes a plugin.
  23432. *
  23433. * Subclasses can override this if they want, but for the sake of safety,
  23434. * it's probably best to subscribe the "dispose" event.
  23435. *
  23436. * @fires Plugin#dispose
  23437. */
  23438. ;
  23439. _proto.dispose = function dispose() {
  23440. var name = this.name,
  23441. player = this.player;
  23442. /**
  23443. * Signals that a advanced plugin is about to be disposed.
  23444. *
  23445. * @event Plugin#dispose
  23446. * @type {EventTarget~Event}
  23447. */
  23448. this.trigger('dispose');
  23449. this.off();
  23450. player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up
  23451. // references between the player and the plugin instance and nulling out
  23452. // the plugin's state and replacing methods with a function that throws.
  23453. player[PLUGIN_CACHE_KEY][name] = false;
  23454. this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory
  23455. // function, so that the plugin is ready to be set up again.
  23456. player[name] = createPluginFactory(name, pluginStorage[name]);
  23457. }
  23458. /**
  23459. * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
  23460. *
  23461. * @param {string|Function} plugin
  23462. * If a string, matches the name of a plugin. If a function, will be
  23463. * tested directly.
  23464. *
  23465. * @return {boolean}
  23466. * Whether or not a plugin is a basic plugin.
  23467. */
  23468. ;
  23469. Plugin.isBasic = function isBasic(plugin) {
  23470. var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
  23471. return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
  23472. }
  23473. /**
  23474. * Register a Video.js plugin.
  23475. *
  23476. * @param {string} name
  23477. * The name of the plugin to be registered. Must be a string and
  23478. * must not match an existing plugin or a method on the `Player`
  23479. * prototype.
  23480. *
  23481. * @param {Function} plugin
  23482. * A sub-class of `Plugin` or a function for basic plugins.
  23483. *
  23484. * @return {Function}
  23485. * For advanced plugins, a factory function for that plugin. For
  23486. * basic plugins, a wrapper function that initializes the plugin.
  23487. */
  23488. ;
  23489. Plugin.registerPlugin = function registerPlugin(name, plugin) {
  23490. if (typeof name !== 'string') {
  23491. throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + ".");
  23492. }
  23493. if (pluginExists(name)) {
  23494. log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!");
  23495. } else if (Player.prototype.hasOwnProperty(name)) {
  23496. throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!");
  23497. }
  23498. if (typeof plugin !== 'function') {
  23499. throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + ".");
  23500. }
  23501. pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for
  23502. // the base Plugin class).
  23503. if (name !== BASE_PLUGIN_NAME) {
  23504. if (Plugin.isBasic(plugin)) {
  23505. Player.prototype[name] = createBasicPlugin(name, plugin);
  23506. } else {
  23507. Player.prototype[name] = createPluginFactory(name, plugin);
  23508. }
  23509. }
  23510. return plugin;
  23511. }
  23512. /**
  23513. * De-register a Video.js plugin.
  23514. *
  23515. * @param {string} name
  23516. * The name of the plugin to be de-registered. Must be a string that
  23517. * matches an existing plugin.
  23518. *
  23519. * @throws {Error}
  23520. * If an attempt is made to de-register the base plugin.
  23521. */
  23522. ;
  23523. Plugin.deregisterPlugin = function deregisterPlugin(name) {
  23524. if (name === BASE_PLUGIN_NAME) {
  23525. throw new Error('Cannot de-register base plugin.');
  23526. }
  23527. if (pluginExists(name)) {
  23528. delete pluginStorage[name];
  23529. delete Player.prototype[name];
  23530. }
  23531. }
  23532. /**
  23533. * Gets an object containing multiple Video.js plugins.
  23534. *
  23535. * @param {Array} [names]
  23536. * If provided, should be an array of plugin names. Defaults to _all_
  23537. * plugin names.
  23538. *
  23539. * @return {Object|undefined}
  23540. * An object containing plugin(s) associated with their name(s) or
  23541. * `undefined` if no matching plugins exist).
  23542. */
  23543. ;
  23544. Plugin.getPlugins = function getPlugins(names) {
  23545. if (names === void 0) {
  23546. names = Object.keys(pluginStorage);
  23547. }
  23548. var result;
  23549. names.forEach(function (name) {
  23550. var plugin = getPlugin(name);
  23551. if (plugin) {
  23552. result = result || {};
  23553. result[name] = plugin;
  23554. }
  23555. });
  23556. return result;
  23557. }
  23558. /**
  23559. * Gets a plugin's version, if available
  23560. *
  23561. * @param {string} name
  23562. * The name of a plugin.
  23563. *
  23564. * @return {string}
  23565. * The plugin's version or an empty string.
  23566. */
  23567. ;
  23568. Plugin.getPluginVersion = function getPluginVersion(name) {
  23569. var plugin = getPlugin(name);
  23570. return plugin && plugin.VERSION || '';
  23571. };
  23572. return Plugin;
  23573. }();
  23574. /**
  23575. * Gets a plugin by name if it exists.
  23576. *
  23577. * @static
  23578. * @method getPlugin
  23579. * @memberOf Plugin
  23580. * @param {string} name
  23581. * The name of a plugin.
  23582. *
  23583. * @returns {Function|undefined}
  23584. * The plugin (or `undefined`).
  23585. */
  23586. Plugin.getPlugin = getPlugin;
  23587. /**
  23588. * The name of the base plugin class as it is registered.
  23589. *
  23590. * @type {string}
  23591. */
  23592. Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
  23593. Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
  23594. /**
  23595. * Documented in player.js
  23596. *
  23597. * @ignore
  23598. */
  23599. Player.prototype.usingPlugin = function (name) {
  23600. return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
  23601. };
  23602. /**
  23603. * Documented in player.js
  23604. *
  23605. * @ignore
  23606. */
  23607. Player.prototype.hasPlugin = function (name) {
  23608. return !!pluginExists(name);
  23609. };
  23610. /**
  23611. * Signals that a plugin is about to be set up on a player.
  23612. *
  23613. * @event Player#beforepluginsetup
  23614. * @type {Plugin~PluginEventHash}
  23615. */
  23616. /**
  23617. * Signals that a plugin is about to be set up on a player - by name. The name
  23618. * is the name of the plugin.
  23619. *
  23620. * @event Player#beforepluginsetup:$name
  23621. * @type {Plugin~PluginEventHash}
  23622. */
  23623. /**
  23624. * Signals that a plugin has just been set up on a player.
  23625. *
  23626. * @event Player#pluginsetup
  23627. * @type {Plugin~PluginEventHash}
  23628. */
  23629. /**
  23630. * Signals that a plugin has just been set up on a player - by name. The name
  23631. * is the name of the plugin.
  23632. *
  23633. * @event Player#pluginsetup:$name
  23634. * @type {Plugin~PluginEventHash}
  23635. */
  23636. /**
  23637. * @typedef {Object} Plugin~PluginEventHash
  23638. *
  23639. * @property {string} instance
  23640. * For basic plugins, the return value of the plugin function. For
  23641. * advanced plugins, the plugin instance on which the event is fired.
  23642. *
  23643. * @property {string} name
  23644. * The name of the plugin.
  23645. *
  23646. * @property {string} plugin
  23647. * For basic plugins, the plugin function. For advanced plugins, the
  23648. * plugin class/constructor.
  23649. */
  23650. /**
  23651. * @file extend.js
  23652. * @module extend
  23653. */
  23654. /**
  23655. * A combination of node inherits and babel's inherits (after transpile).
  23656. * Both work the same but node adds `super_` to the subClass
  23657. * and Bable adds the superClass as __proto__. Both seem useful.
  23658. *
  23659. * @param {Object} subClass
  23660. * The class to inherit to
  23661. *
  23662. * @param {Object} superClass
  23663. * The class to inherit from
  23664. *
  23665. * @private
  23666. */
  23667. var _inherits$1 = function _inherits(subClass, superClass) {
  23668. if (typeof superClass !== 'function' && superClass !== null) {
  23669. throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
  23670. }
  23671. subClass.prototype = Object.create(superClass && superClass.prototype, {
  23672. constructor: {
  23673. value: subClass,
  23674. enumerable: false,
  23675. writable: true,
  23676. configurable: true
  23677. }
  23678. });
  23679. if (superClass) {
  23680. // node
  23681. subClass.super_ = superClass;
  23682. }
  23683. };
  23684. /**
  23685. * Used to subclass an existing class by emulating ES subclassing using the
  23686. * `extends` keyword.
  23687. *
  23688. * @function
  23689. * @example
  23690. * var MyComponent = videojs.extend(videojs.getComponent('Component'), {
  23691. * myCustomMethod: function() {
  23692. * // Do things in my method.
  23693. * }
  23694. * });
  23695. *
  23696. * @param {Function} superClass
  23697. * The class to inherit from
  23698. *
  23699. * @param {Object} [subClassMethods={}]
  23700. * Methods of the new class
  23701. *
  23702. * @return {Function}
  23703. * The new class with subClassMethods that inherited superClass.
  23704. */
  23705. var extend$1 = function extend(superClass, subClassMethods) {
  23706. if (subClassMethods === void 0) {
  23707. subClassMethods = {};
  23708. }
  23709. var subClass = function subClass() {
  23710. superClass.apply(this, arguments);
  23711. };
  23712. var methods = {};
  23713. if (typeof subClassMethods === 'object') {
  23714. if (subClassMethods.constructor !== Object.prototype.constructor) {
  23715. subClass = subClassMethods.constructor;
  23716. }
  23717. methods = subClassMethods;
  23718. } else if (typeof subClassMethods === 'function') {
  23719. subClass = subClassMethods;
  23720. }
  23721. _inherits$1(subClass, superClass); // Extend subObj's prototype with functions and other properties from props
  23722. for (var name in methods) {
  23723. if (methods.hasOwnProperty(name)) {
  23724. subClass.prototype[name] = methods[name];
  23725. }
  23726. }
  23727. return subClass;
  23728. };
  23729. /**
  23730. * @file video.js
  23731. * @module videojs
  23732. */
  23733. /**
  23734. * Normalize an `id` value by trimming off a leading `#`
  23735. *
  23736. * @private
  23737. * @param {string} id
  23738. * A string, maybe with a leading `#`.
  23739. *
  23740. * @return {string}
  23741. * The string, without any leading `#`.
  23742. */
  23743. var normalizeId = function normalizeId(id) {
  23744. return id.indexOf('#') === 0 ? id.slice(1) : id;
  23745. };
  23746. /**
  23747. * The `videojs()` function doubles as the main function for users to create a
  23748. * {@link Player} instance as well as the main library namespace.
  23749. *
  23750. * It can also be used as a getter for a pre-existing {@link Player} instance.
  23751. * However, we _strongly_ recommend using `videojs.getPlayer()` for this
  23752. * purpose because it avoids any potential for unintended initialization.
  23753. *
  23754. * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
  23755. * of our JSDoc template, we cannot properly document this as both a function
  23756. * and a namespace, so its function signature is documented here.
  23757. *
  23758. * #### Arguments
  23759. * ##### id
  23760. * string|Element, **required**
  23761. *
  23762. * Video element or video element ID.
  23763. *
  23764. * ##### options
  23765. * Object, optional
  23766. *
  23767. * Options object for providing settings.
  23768. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  23769. *
  23770. * ##### ready
  23771. * {@link Component~ReadyCallback}, optional
  23772. *
  23773. * A function to be called when the {@link Player} and {@link Tech} are ready.
  23774. *
  23775. * #### Return Value
  23776. *
  23777. * The `videojs()` function returns a {@link Player} instance.
  23778. *
  23779. * @namespace
  23780. *
  23781. * @borrows AudioTrack as AudioTrack
  23782. * @borrows Component.getComponent as getComponent
  23783. * @borrows module:computed-style~computedStyle as computedStyle
  23784. * @borrows module:events.on as on
  23785. * @borrows module:events.one as one
  23786. * @borrows module:events.off as off
  23787. * @borrows module:events.trigger as trigger
  23788. * @borrows EventTarget as EventTarget
  23789. * @borrows module:extend~extend as extend
  23790. * @borrows module:fn.bind as bind
  23791. * @borrows module:format-time.formatTime as formatTime
  23792. * @borrows module:format-time.resetFormatTime as resetFormatTime
  23793. * @borrows module:format-time.setFormatTime as setFormatTime
  23794. * @borrows module:merge-options.mergeOptions as mergeOptions
  23795. * @borrows module:middleware.use as use
  23796. * @borrows Player.players as players
  23797. * @borrows Plugin.registerPlugin as registerPlugin
  23798. * @borrows Plugin.deregisterPlugin as deregisterPlugin
  23799. * @borrows Plugin.getPlugins as getPlugins
  23800. * @borrows Plugin.getPlugin as getPlugin
  23801. * @borrows Plugin.getPluginVersion as getPluginVersion
  23802. * @borrows Tech.getTech as getTech
  23803. * @borrows Tech.registerTech as registerTech
  23804. * @borrows TextTrack as TextTrack
  23805. * @borrows module:time-ranges.createTimeRanges as createTimeRange
  23806. * @borrows module:time-ranges.createTimeRanges as createTimeRanges
  23807. * @borrows module:url.isCrossOrigin as isCrossOrigin
  23808. * @borrows module:url.parseUrl as parseUrl
  23809. * @borrows VideoTrack as VideoTrack
  23810. *
  23811. * @param {string|Element} id
  23812. * Video element or video element ID.
  23813. *
  23814. * @param {Object} [options]
  23815. * Options object for providing settings.
  23816. * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
  23817. *
  23818. * @param {Component~ReadyCallback} [ready]
  23819. * A function to be called when the {@link Player} and {@link Tech} are
  23820. * ready.
  23821. *
  23822. * @return {Player}
  23823. * The `videojs()` function returns a {@link Player|Player} instance.
  23824. */
  23825. function videojs$1(id, options, ready) {
  23826. var player = videojs$1.getPlayer(id);
  23827. if (player) {
  23828. if (options) {
  23829. log.warn("Player \"" + id + "\" is already initialised. Options will not be applied.");
  23830. }
  23831. if (ready) {
  23832. player.ready(ready);
  23833. }
  23834. return player;
  23835. }
  23836. var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
  23837. if (!isEl(el)) {
  23838. throw new TypeError('The element or ID supplied is not valid. (videojs)');
  23839. } // document.body.contains(el) will only check if el is contained within that one document.
  23840. // This causes problems for elements in iframes.
  23841. // Instead, use the element's ownerDocument instead of the global document.
  23842. // This will make sure that the element is indeed in the dom of that document.
  23843. // Additionally, check that the document in question has a default view.
  23844. // If the document is no longer attached to the dom, the defaultView of the document will be null.
  23845. if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) {
  23846. log.warn('The element supplied is not included in the DOM');
  23847. }
  23848. options = options || {};
  23849. videojs$1.hooks('beforesetup').forEach(function (hookFunction) {
  23850. var opts = hookFunction(el, mergeOptions(options));
  23851. if (!isObject(opts) || Array.isArray(opts)) {
  23852. log.error('please return an object in beforesetup hooks');
  23853. return;
  23854. }
  23855. options = mergeOptions(options, opts);
  23856. }); // We get the current "Player" component here in case an integration has
  23857. // replaced it with a custom player.
  23858. var PlayerComponent = Component.getComponent('Player');
  23859. player = new PlayerComponent(el, options, ready);
  23860. videojs$1.hooks('setup').forEach(function (hookFunction) {
  23861. return hookFunction(player);
  23862. });
  23863. return player;
  23864. }
  23865. /**
  23866. * An Object that contains lifecycle hooks as keys which point to an array
  23867. * of functions that are run when a lifecycle is triggered
  23868. *
  23869. * @private
  23870. */
  23871. videojs$1.hooks_ = {};
  23872. /**
  23873. * Get a list of hooks for a specific lifecycle
  23874. *
  23875. * @param {string} type
  23876. * the lifecyle to get hooks from
  23877. *
  23878. * @param {Function|Function[]} [fn]
  23879. * Optionally add a hook (or hooks) to the lifecycle that your are getting.
  23880. *
  23881. * @return {Array}
  23882. * an array of hooks, or an empty array if there are none.
  23883. */
  23884. videojs$1.hooks = function (type, fn) {
  23885. videojs$1.hooks_[type] = videojs$1.hooks_[type] || [];
  23886. if (fn) {
  23887. videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn);
  23888. }
  23889. return videojs$1.hooks_[type];
  23890. };
  23891. /**
  23892. * Add a function hook to a specific videojs lifecycle.
  23893. *
  23894. * @param {string} type
  23895. * the lifecycle to hook the function to.
  23896. *
  23897. * @param {Function|Function[]}
  23898. * The function or array of functions to attach.
  23899. */
  23900. videojs$1.hook = function (type, fn) {
  23901. videojs$1.hooks(type, fn);
  23902. };
  23903. /**
  23904. * Add a function hook that will only run once 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.hookOnce = function (type, fn) {
  23913. videojs$1.hooks(type, [].concat(fn).map(function (original) {
  23914. var wrapper = function wrapper() {
  23915. videojs$1.removeHook(type, wrapper);
  23916. return original.apply(void 0, arguments);
  23917. };
  23918. return wrapper;
  23919. }));
  23920. };
  23921. /**
  23922. * Remove a hook from a specific videojs lifecycle.
  23923. *
  23924. * @param {string} type
  23925. * the lifecycle that the function hooked to
  23926. *
  23927. * @param {Function} fn
  23928. * The hooked function to remove
  23929. *
  23930. * @return {boolean}
  23931. * The function that was removed or undef
  23932. */
  23933. videojs$1.removeHook = function (type, fn) {
  23934. var index = videojs$1.hooks(type).indexOf(fn);
  23935. if (index <= -1) {
  23936. return false;
  23937. }
  23938. videojs$1.hooks_[type] = videojs$1.hooks_[type].slice();
  23939. videojs$1.hooks_[type].splice(index, 1);
  23940. return true;
  23941. }; // Add default styles
  23942. if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
  23943. var style$1 = $('.vjs-styles-defaults');
  23944. if (!style$1) {
  23945. style$1 = createStyleElement('vjs-styles-defaults');
  23946. var head = $('head');
  23947. if (head) {
  23948. head.insertBefore(style$1, head.firstChild);
  23949. }
  23950. setTextContent(style$1, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n ");
  23951. }
  23952. } // Run Auto-load players
  23953. // You have to wait at least once in case this script is loaded after your
  23954. // video in the DOM (weird behavior only with minified version)
  23955. autoSetupTimeout(1, videojs$1);
  23956. /**
  23957. * Current Video.js version. Follows [semantic versioning](https://semver.org/).
  23958. *
  23959. * @type {string}
  23960. */
  23961. videojs$1.VERSION = version;
  23962. /**
  23963. * The global options object. These are the settings that take effect
  23964. * if no overrides are specified when the player is created.
  23965. *
  23966. * @type {Object}
  23967. */
  23968. videojs$1.options = Player.prototype.options_;
  23969. /**
  23970. * Get an object with the currently created players, keyed by player ID
  23971. *
  23972. * @return {Object}
  23973. * The created players
  23974. */
  23975. videojs$1.getPlayers = function () {
  23976. return Player.players;
  23977. };
  23978. /**
  23979. * Get a single player based on an ID or DOM element.
  23980. *
  23981. * This is useful if you want to check if an element or ID has an associated
  23982. * Video.js player, but not create one if it doesn't.
  23983. *
  23984. * @param {string|Element} id
  23985. * An HTML element - `<video>`, `<audio>`, or `<video-js>` -
  23986. * or a string matching the `id` of such an element.
  23987. *
  23988. * @return {Player|undefined}
  23989. * A player instance or `undefined` if there is no player instance
  23990. * matching the argument.
  23991. */
  23992. videojs$1.getPlayer = function (id) {
  23993. var players = Player.players;
  23994. var tag;
  23995. if (typeof id === 'string') {
  23996. var nId = normalizeId(id);
  23997. var player = players[nId];
  23998. if (player) {
  23999. return player;
  24000. }
  24001. tag = $('#' + nId);
  24002. } else {
  24003. tag = id;
  24004. }
  24005. if (isEl(tag)) {
  24006. var _tag = tag,
  24007. _player = _tag.player,
  24008. playerId = _tag.playerId; // Element may have a `player` property referring to an already created
  24009. // player instance. If so, return that.
  24010. if (_player || players[playerId]) {
  24011. return _player || players[playerId];
  24012. }
  24013. }
  24014. };
  24015. /**
  24016. * Returns an array of all current players.
  24017. *
  24018. * @return {Array}
  24019. * An array of all players. The array will be in the order that
  24020. * `Object.keys` provides, which could potentially vary between
  24021. * JavaScript engines.
  24022. *
  24023. */
  24024. videojs$1.getAllPlayers = function () {
  24025. return (// Disposed players leave a key with a `null` value, so we need to make sure
  24026. // we filter those out.
  24027. Object.keys(Player.players).map(function (k) {
  24028. return Player.players[k];
  24029. }).filter(Boolean)
  24030. );
  24031. };
  24032. videojs$1.players = Player.players;
  24033. videojs$1.getComponent = Component.getComponent;
  24034. /**
  24035. * Register a component so it can referred to by name. Used when adding to other
  24036. * components, either through addChild `component.addChild('myComponent')` or through
  24037. * default children options `{ children: ['myComponent'] }`.
  24038. *
  24039. * > NOTE: You could also just initialize the component before adding.
  24040. * `component.addChild(new MyComponent());`
  24041. *
  24042. * @param {string} name
  24043. * The class name of the component
  24044. *
  24045. * @param {Component} comp
  24046. * The component class
  24047. *
  24048. * @return {Component}
  24049. * The newly registered component
  24050. */
  24051. videojs$1.registerComponent = function (name$$1, comp) {
  24052. if (Tech.isTech(comp)) {
  24053. log.warn("The " + name$$1 + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)");
  24054. }
  24055. Component.registerComponent.call(Component, name$$1, comp);
  24056. };
  24057. videojs$1.getTech = Tech.getTech;
  24058. videojs$1.registerTech = Tech.registerTech;
  24059. videojs$1.use = use;
  24060. /**
  24061. * An object that can be returned by a middleware to signify
  24062. * that the middleware is being terminated.
  24063. *
  24064. * @type {object}
  24065. * @property {object} middleware.TERMINATOR
  24066. */
  24067. Object.defineProperty(videojs$1, 'middleware', {
  24068. value: {},
  24069. writeable: false,
  24070. enumerable: true
  24071. });
  24072. Object.defineProperty(videojs$1.middleware, 'TERMINATOR', {
  24073. value: TERMINATOR,
  24074. writeable: false,
  24075. enumerable: true
  24076. });
  24077. /**
  24078. * A reference to the {@link module:browser|browser utility module} as an object.
  24079. *
  24080. * @type {Object}
  24081. * @see {@link module:browser|browser}
  24082. */
  24083. videojs$1.browser = browser;
  24084. /**
  24085. * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only
  24086. * included for backward-compatibility with 4.x.
  24087. *
  24088. * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead.
  24089. * @type {boolean}
  24090. */
  24091. videojs$1.TOUCH_ENABLED = TOUCH_ENABLED;
  24092. videojs$1.extend = extend$1;
  24093. videojs$1.mergeOptions = mergeOptions;
  24094. videojs$1.bind = bind;
  24095. videojs$1.registerPlugin = Plugin.registerPlugin;
  24096. videojs$1.deregisterPlugin = Plugin.deregisterPlugin;
  24097. /**
  24098. * Deprecated method to register a plugin with Video.js
  24099. *
  24100. * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead
  24101. *
  24102. * @param {string} name
  24103. * The plugin name
  24104. *
  24105. * @param {Plugin|Function} plugin
  24106. * The plugin sub-class or function
  24107. */
  24108. videojs$1.plugin = function (name$$1, plugin) {
  24109. log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
  24110. return Plugin.registerPlugin(name$$1, plugin);
  24111. };
  24112. videojs$1.getPlugins = Plugin.getPlugins;
  24113. videojs$1.getPlugin = Plugin.getPlugin;
  24114. videojs$1.getPluginVersion = Plugin.getPluginVersion;
  24115. /**
  24116. * Adding languages so that they're available to all players.
  24117. * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
  24118. *
  24119. * @param {string} code
  24120. * The language code or dictionary property
  24121. *
  24122. * @param {Object} data
  24123. * The data values to be translated
  24124. *
  24125. * @return {Object}
  24126. * The resulting language dictionary object
  24127. */
  24128. videojs$1.addLanguage = function (code, data) {
  24129. var _mergeOptions;
  24130. code = ('' + code).toLowerCase();
  24131. videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
  24132. return videojs$1.options.languages[code];
  24133. };
  24134. /**
  24135. * A reference to the {@link module:log|log utility module} as an object.
  24136. *
  24137. * @type {Function}
  24138. * @see {@link module:log|log}
  24139. */
  24140. videojs$1.log = log;
  24141. videojs$1.createLogger = createLogger$1;
  24142. videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges;
  24143. videojs$1.formatTime = formatTime;
  24144. videojs$1.setFormatTime = setFormatTime;
  24145. videojs$1.resetFormatTime = resetFormatTime;
  24146. videojs$1.parseUrl = parseUrl;
  24147. videojs$1.isCrossOrigin = isCrossOrigin;
  24148. videojs$1.EventTarget = EventTarget;
  24149. videojs$1.on = on;
  24150. videojs$1.one = one;
  24151. videojs$1.off = off;
  24152. videojs$1.trigger = trigger;
  24153. /**
  24154. * A cross-browser XMLHttpRequest wrapper.
  24155. *
  24156. * @function
  24157. * @param {Object} options
  24158. * Settings for the request.
  24159. *
  24160. * @return {XMLHttpRequest|XDomainRequest}
  24161. * The request object.
  24162. *
  24163. * @see https://github.com/Raynos/xhr
  24164. */
  24165. videojs$1.xhr = xhr;
  24166. videojs$1.TextTrack = TextTrack;
  24167. videojs$1.AudioTrack = AudioTrack;
  24168. videojs$1.VideoTrack = VideoTrack;
  24169. ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
  24170. videojs$1[k] = function () {
  24171. log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead");
  24172. return Dom[k].apply(null, arguments);
  24173. };
  24174. });
  24175. videojs$1.computedStyle = computedStyle;
  24176. /**
  24177. * A reference to the {@link module:dom|DOM utility module} as an object.
  24178. *
  24179. * @type {Object}
  24180. * @see {@link module:dom|dom}
  24181. */
  24182. videojs$1.dom = Dom;
  24183. /**
  24184. * A reference to the {@link module:url|URL utility module} as an object.
  24185. *
  24186. * @type {Object}
  24187. * @see {@link module:url|url}
  24188. */
  24189. videojs$1.url = Url;
  24190. var urlToolkit = createCommonjsModule(function (module, exports) {
  24191. // see https://tools.ietf.org/html/rfc1808
  24192. /* jshint ignore:start */
  24193. (function (root) {
  24194. /* jshint ignore:end */
  24195. var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
  24196. var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
  24197. var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
  24198. var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
  24199. var URLToolkit = {
  24200. // jshint ignore:line
  24201. // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
  24202. // E.g
  24203. // With opts.alwaysNormalize = false (default, spec compliant)
  24204. // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
  24205. // With opts.alwaysNormalize = true (not spec compliant)
  24206. // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
  24207. buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
  24208. opts = opts || {}; // remove any remaining space and CRLF
  24209. baseURL = baseURL.trim();
  24210. relativeURL = relativeURL.trim();
  24211. if (!relativeURL) {
  24212. // 2a) If the embedded URL is entirely empty, it inherits the
  24213. // entire base URL (i.e., is set equal to the base URL)
  24214. // and we are done.
  24215. if (!opts.alwaysNormalize) {
  24216. return baseURL;
  24217. }
  24218. var basePartsForNormalise = URLToolkit.parseURL(baseURL);
  24219. if (!basePartsForNormalise) {
  24220. throw new Error('Error trying to parse base URL.');
  24221. }
  24222. basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
  24223. return URLToolkit.buildURLFromParts(basePartsForNormalise);
  24224. }
  24225. var relativeParts = URLToolkit.parseURL(relativeURL);
  24226. if (!relativeParts) {
  24227. throw new Error('Error trying to parse relative URL.');
  24228. }
  24229. if (relativeParts.scheme) {
  24230. // 2b) If the embedded URL starts with a scheme name, it is
  24231. // interpreted as an absolute URL and we are done.
  24232. if (!opts.alwaysNormalize) {
  24233. return relativeURL;
  24234. }
  24235. relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
  24236. return URLToolkit.buildURLFromParts(relativeParts);
  24237. }
  24238. var baseParts = URLToolkit.parseURL(baseURL);
  24239. if (!baseParts) {
  24240. throw new Error('Error trying to parse base URL.');
  24241. }
  24242. if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
  24243. // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
  24244. // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
  24245. var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
  24246. baseParts.netLoc = pathParts[1];
  24247. baseParts.path = pathParts[2];
  24248. }
  24249. if (baseParts.netLoc && !baseParts.path) {
  24250. baseParts.path = '/';
  24251. }
  24252. var builtParts = {
  24253. // 2c) Otherwise, the embedded URL inherits the scheme of
  24254. // the base URL.
  24255. scheme: baseParts.scheme,
  24256. netLoc: relativeParts.netLoc,
  24257. path: null,
  24258. params: relativeParts.params,
  24259. query: relativeParts.query,
  24260. fragment: relativeParts.fragment
  24261. };
  24262. if (!relativeParts.netLoc) {
  24263. // 3) If the embedded URL's <net_loc> is non-empty, we skip to
  24264. // Step 7. Otherwise, the embedded URL inherits the <net_loc>
  24265. // (if any) of the base URL.
  24266. builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
  24267. // path is not relative and we skip to Step 7.
  24268. if (relativeParts.path[0] !== '/') {
  24269. if (!relativeParts.path) {
  24270. // 5) If the embedded URL path is empty (and not preceded by a
  24271. // slash), then the embedded URL inherits the base URL path
  24272. builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
  24273. // step 7; otherwise, it inherits the <params> of the base
  24274. // URL (if any) and
  24275. if (!relativeParts.params) {
  24276. builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
  24277. // step 7; otherwise, it inherits the <query> of the base
  24278. // URL (if any) and we skip to step 7.
  24279. if (!relativeParts.query) {
  24280. builtParts.query = baseParts.query;
  24281. }
  24282. }
  24283. } else {
  24284. // 6) The last segment of the base URL's path (anything
  24285. // following the rightmost slash "/", or the entire path if no
  24286. // slash is present) is removed and the embedded URL's path is
  24287. // appended in its place.
  24288. var baseURLPath = baseParts.path;
  24289. var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
  24290. builtParts.path = URLToolkit.normalizePath(newPath);
  24291. }
  24292. }
  24293. }
  24294. if (builtParts.path === null) {
  24295. builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
  24296. }
  24297. return URLToolkit.buildURLFromParts(builtParts);
  24298. },
  24299. parseURL: function parseURL(url) {
  24300. var parts = URL_REGEX.exec(url);
  24301. if (!parts) {
  24302. return null;
  24303. }
  24304. return {
  24305. scheme: parts[1] || '',
  24306. netLoc: parts[2] || '',
  24307. path: parts[3] || '',
  24308. params: parts[4] || '',
  24309. query: parts[5] || '',
  24310. fragment: parts[6] || ''
  24311. };
  24312. },
  24313. normalizePath: function normalizePath(path) {
  24314. // The following operations are
  24315. // then applied, in order, to the new path:
  24316. // 6a) All occurrences of "./", where "." is a complete path
  24317. // segment, are removed.
  24318. // 6b) If the path ends with "." as a complete path segment,
  24319. // that "." is removed.
  24320. path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
  24321. // complete path segment not equal to "..", are removed.
  24322. // Removal of these path segments is performed iteratively,
  24323. // removing the leftmost matching pattern on each iteration,
  24324. // until no matching pattern remains.
  24325. // 6d) If the path ends with "<segment>/..", where <segment> is a
  24326. // complete path segment not equal to "..", that
  24327. // "<segment>/.." is removed.
  24328. while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
  24329. return path.split('').reverse().join('');
  24330. },
  24331. buildURLFromParts: function buildURLFromParts(parts) {
  24332. return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
  24333. }
  24334. };
  24335. /* jshint ignore:start */
  24336. module.exports = URLToolkit;
  24337. })(commonjsGlobal);
  24338. /* jshint ignore:end */
  24339. });
  24340. /*! @name m3u8-parser @version 4.3.0 @license Apache-2.0 */
  24341. function _extends$1() {
  24342. _extends$1 = Object.assign || function (target) {
  24343. for (var i = 1; i < arguments.length; i++) {
  24344. var source = arguments[i];
  24345. for (var key in source) {
  24346. if (Object.prototype.hasOwnProperty.call(source, key)) {
  24347. target[key] = source[key];
  24348. }
  24349. }
  24350. }
  24351. return target;
  24352. };
  24353. return _extends$1.apply(this, arguments);
  24354. }
  24355. function _inheritsLoose$1(subClass, superClass) {
  24356. subClass.prototype = Object.create(superClass.prototype);
  24357. subClass.prototype.constructor = subClass;
  24358. subClass.__proto__ = superClass;
  24359. }
  24360. function _assertThisInitialized$1(self) {
  24361. if (self === void 0) {
  24362. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  24363. }
  24364. return self;
  24365. }
  24366. /**
  24367. * @file stream.js
  24368. */
  24369. /**
  24370. * A lightweight readable stream implementation that handles event dispatching.
  24371. *
  24372. * @class Stream
  24373. */
  24374. var Stream =
  24375. /*#__PURE__*/
  24376. function () {
  24377. function Stream() {
  24378. this.listeners = {};
  24379. }
  24380. /**
  24381. * Add a listener for a specified event type.
  24382. *
  24383. * @param {string} type the event name
  24384. * @param {Function} listener the callback to be invoked when an event of
  24385. * the specified type occurs
  24386. */
  24387. var _proto = Stream.prototype;
  24388. _proto.on = function on(type, listener) {
  24389. if (!this.listeners[type]) {
  24390. this.listeners[type] = [];
  24391. }
  24392. this.listeners[type].push(listener);
  24393. };
  24394. /**
  24395. * Remove a listener for a specified event type.
  24396. *
  24397. * @param {string} type the event name
  24398. * @param {Function} listener a function previously registered for this
  24399. * type of event through `on`
  24400. * @return {boolean} if we could turn it off or not
  24401. */
  24402. _proto.off = function off(type, listener) {
  24403. if (!this.listeners[type]) {
  24404. return false;
  24405. }
  24406. var index = this.listeners[type].indexOf(listener);
  24407. this.listeners[type].splice(index, 1);
  24408. return index > -1;
  24409. };
  24410. /**
  24411. * Trigger an event of the specified type on this stream. Any additional
  24412. * arguments to this function are passed as parameters to event listeners.
  24413. *
  24414. * @param {string} type the event name
  24415. */
  24416. _proto.trigger = function trigger(type) {
  24417. var callbacks = this.listeners[type];
  24418. var i;
  24419. var length;
  24420. var args;
  24421. if (!callbacks) {
  24422. return;
  24423. } // Slicing the arguments on every invocation of this method
  24424. // can add a significant amount of overhead. Avoid the
  24425. // intermediate object creation for the common case of a
  24426. // single callback argument
  24427. if (arguments.length === 2) {
  24428. length = callbacks.length;
  24429. for (i = 0; i < length; ++i) {
  24430. callbacks[i].call(this, arguments[1]);
  24431. }
  24432. } else {
  24433. args = Array.prototype.slice.call(arguments, 1);
  24434. length = callbacks.length;
  24435. for (i = 0; i < length; ++i) {
  24436. callbacks[i].apply(this, args);
  24437. }
  24438. }
  24439. };
  24440. /**
  24441. * Destroys the stream and cleans up.
  24442. */
  24443. _proto.dispose = function dispose() {
  24444. this.listeners = {};
  24445. };
  24446. /**
  24447. * Forwards all `data` events on this stream to the destination stream. The
  24448. * destination stream should provide a method `push` to receive the data
  24449. * events as they arrive.
  24450. *
  24451. * @param {Stream} destination the stream that will receive all `data` events
  24452. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  24453. */
  24454. _proto.pipe = function pipe(destination) {
  24455. this.on('data', function (data) {
  24456. destination.push(data);
  24457. });
  24458. };
  24459. return Stream;
  24460. }();
  24461. /**
  24462. * A stream that buffers string input and generates a `data` event for each
  24463. * line.
  24464. *
  24465. * @class LineStream
  24466. * @extends Stream
  24467. */
  24468. var LineStream =
  24469. /*#__PURE__*/
  24470. function (_Stream) {
  24471. _inheritsLoose$1(LineStream, _Stream);
  24472. function LineStream() {
  24473. var _this;
  24474. _this = _Stream.call(this) || this;
  24475. _this.buffer = '';
  24476. return _this;
  24477. }
  24478. /**
  24479. * Add new data to be parsed.
  24480. *
  24481. * @param {string} data the text to process
  24482. */
  24483. var _proto = LineStream.prototype;
  24484. _proto.push = function push(data) {
  24485. var nextNewline;
  24486. this.buffer += data;
  24487. nextNewline = this.buffer.indexOf('\n');
  24488. for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
  24489. this.trigger('data', this.buffer.substring(0, nextNewline));
  24490. this.buffer = this.buffer.substring(nextNewline + 1);
  24491. }
  24492. };
  24493. return LineStream;
  24494. }(Stream);
  24495. /**
  24496. * "forgiving" attribute list psuedo-grammar:
  24497. * attributes -> keyvalue (',' keyvalue)*
  24498. * keyvalue -> key '=' value
  24499. * key -> [^=]*
  24500. * value -> '"' [^"]* '"' | [^,]*
  24501. */
  24502. var attributeSeparator = function attributeSeparator() {
  24503. var key = '[^=]*';
  24504. var value = '"[^"]*"|[^,]*';
  24505. var keyvalue = '(?:' + key + ')=(?:' + value + ')';
  24506. return new RegExp('(?:^|,)(' + keyvalue + ')');
  24507. };
  24508. /**
  24509. * Parse attributes from a line given the separator
  24510. *
  24511. * @param {string} attributes the attribute line to parse
  24512. */
  24513. var parseAttributes = function parseAttributes(attributes) {
  24514. // split the string using attributes as the separator
  24515. var attrs = attributes.split(attributeSeparator());
  24516. var result = {};
  24517. var i = attrs.length;
  24518. var attr;
  24519. while (i--) {
  24520. // filter out unmatched portions of the string
  24521. if (attrs[i] === '') {
  24522. continue;
  24523. } // split the key and value
  24524. attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
  24525. attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
  24526. attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
  24527. attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
  24528. result[attr[0]] = attr[1];
  24529. }
  24530. return result;
  24531. };
  24532. /**
  24533. * A line-level M3U8 parser event stream. It expects to receive input one
  24534. * line at a time and performs a context-free parse of its contents. A stream
  24535. * interpretation of a manifest can be useful if the manifest is expected to
  24536. * be too large to fit comfortably into memory or the entirety of the input
  24537. * is not immediately available. Otherwise, it's probably much easier to work
  24538. * with a regular `Parser` object.
  24539. *
  24540. * Produces `data` events with an object that captures the parser's
  24541. * interpretation of the input. That object has a property `tag` that is one
  24542. * of `uri`, `comment`, or `tag`. URIs only have a single additional
  24543. * property, `line`, which captures the entirety of the input without
  24544. * interpretation. Comments similarly have a single additional property
  24545. * `text` which is the input without the leading `#`.
  24546. *
  24547. * Tags always have a property `tagType` which is the lower-cased version of
  24548. * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
  24549. * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
  24550. * tags are given the tag type `unknown` and a single additional property
  24551. * `data` with the remainder of the input.
  24552. *
  24553. * @class ParseStream
  24554. * @extends Stream
  24555. */
  24556. var ParseStream =
  24557. /*#__PURE__*/
  24558. function (_Stream) {
  24559. _inheritsLoose$1(ParseStream, _Stream);
  24560. function ParseStream() {
  24561. var _this;
  24562. _this = _Stream.call(this) || this;
  24563. _this.customParsers = [];
  24564. _this.tagMappers = [];
  24565. return _this;
  24566. }
  24567. /**
  24568. * Parses an additional line of input.
  24569. *
  24570. * @param {string} line a single line of an M3U8 file to parse
  24571. */
  24572. var _proto = ParseStream.prototype;
  24573. _proto.push = function push(line) {
  24574. var _this2 = this;
  24575. var match;
  24576. var event; // strip whitespace
  24577. line = line.trim();
  24578. if (line.length === 0) {
  24579. // ignore empty lines
  24580. return;
  24581. } // URIs
  24582. if (line[0] !== '#') {
  24583. this.trigger('data', {
  24584. type: 'uri',
  24585. uri: line
  24586. });
  24587. return;
  24588. } // map tags
  24589. var newLines = this.tagMappers.reduce(function (acc, mapper) {
  24590. var mappedLine = mapper(line); // skip if unchanged
  24591. if (mappedLine === line) {
  24592. return acc;
  24593. }
  24594. return acc.concat([mappedLine]);
  24595. }, [line]);
  24596. newLines.forEach(function (newLine) {
  24597. for (var i = 0; i < _this2.customParsers.length; i++) {
  24598. if (_this2.customParsers[i].call(_this2, newLine)) {
  24599. return;
  24600. }
  24601. } // Comments
  24602. if (newLine.indexOf('#EXT') !== 0) {
  24603. _this2.trigger('data', {
  24604. type: 'comment',
  24605. text: newLine.slice(1)
  24606. });
  24607. return;
  24608. } // strip off any carriage returns here so the regex matching
  24609. // doesn't have to account for them.
  24610. newLine = newLine.replace('\r', ''); // Tags
  24611. match = /^#EXTM3U/.exec(newLine);
  24612. if (match) {
  24613. _this2.trigger('data', {
  24614. type: 'tag',
  24615. tagType: 'm3u'
  24616. });
  24617. return;
  24618. }
  24619. match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine);
  24620. if (match) {
  24621. event = {
  24622. type: 'tag',
  24623. tagType: 'inf'
  24624. };
  24625. if (match[1]) {
  24626. event.duration = parseFloat(match[1]);
  24627. }
  24628. if (match[2]) {
  24629. event.title = match[2];
  24630. }
  24631. _this2.trigger('data', event);
  24632. return;
  24633. }
  24634. match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine);
  24635. if (match) {
  24636. event = {
  24637. type: 'tag',
  24638. tagType: 'targetduration'
  24639. };
  24640. if (match[1]) {
  24641. event.duration = parseInt(match[1], 10);
  24642. }
  24643. _this2.trigger('data', event);
  24644. return;
  24645. }
  24646. match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine);
  24647. if (match) {
  24648. event = {
  24649. type: 'tag',
  24650. tagType: 'totalduration'
  24651. };
  24652. if (match[1]) {
  24653. event.duration = parseInt(match[1], 10);
  24654. }
  24655. _this2.trigger('data', event);
  24656. return;
  24657. }
  24658. match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine);
  24659. if (match) {
  24660. event = {
  24661. type: 'tag',
  24662. tagType: 'version'
  24663. };
  24664. if (match[1]) {
  24665. event.version = parseInt(match[1], 10);
  24666. }
  24667. _this2.trigger('data', event);
  24668. return;
  24669. }
  24670. match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
  24671. if (match) {
  24672. event = {
  24673. type: 'tag',
  24674. tagType: 'media-sequence'
  24675. };
  24676. if (match[1]) {
  24677. event.number = parseInt(match[1], 10);
  24678. }
  24679. _this2.trigger('data', event);
  24680. return;
  24681. }
  24682. match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
  24683. if (match) {
  24684. event = {
  24685. type: 'tag',
  24686. tagType: 'discontinuity-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-PLAYLIST-TYPE:?(.*)?$/.exec(newLine);
  24695. if (match) {
  24696. event = {
  24697. type: 'tag',
  24698. tagType: 'playlist-type'
  24699. };
  24700. if (match[1]) {
  24701. event.playlistType = match[1];
  24702. }
  24703. _this2.trigger('data', event);
  24704. return;
  24705. }
  24706. match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine);
  24707. if (match) {
  24708. event = {
  24709. type: 'tag',
  24710. tagType: 'byterange'
  24711. };
  24712. if (match[1]) {
  24713. event.length = parseInt(match[1], 10);
  24714. }
  24715. if (match[2]) {
  24716. event.offset = parseInt(match[2], 10);
  24717. }
  24718. _this2.trigger('data', event);
  24719. return;
  24720. }
  24721. match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine);
  24722. if (match) {
  24723. event = {
  24724. type: 'tag',
  24725. tagType: 'allow-cache'
  24726. };
  24727. if (match[1]) {
  24728. event.allowed = !/NO/.test(match[1]);
  24729. }
  24730. _this2.trigger('data', event);
  24731. return;
  24732. }
  24733. match = /^#EXT-X-MAP:?(.*)$/.exec(newLine);
  24734. if (match) {
  24735. event = {
  24736. type: 'tag',
  24737. tagType: 'map'
  24738. };
  24739. if (match[1]) {
  24740. var attributes = parseAttributes(match[1]);
  24741. if (attributes.URI) {
  24742. event.uri = attributes.URI;
  24743. }
  24744. if (attributes.BYTERANGE) {
  24745. var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'),
  24746. length = _attributes$BYTERANGE[0],
  24747. offset = _attributes$BYTERANGE[1];
  24748. event.byterange = {};
  24749. if (length) {
  24750. event.byterange.length = parseInt(length, 10);
  24751. }
  24752. if (offset) {
  24753. event.byterange.offset = parseInt(offset, 10);
  24754. }
  24755. }
  24756. }
  24757. _this2.trigger('data', event);
  24758. return;
  24759. }
  24760. match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine);
  24761. if (match) {
  24762. event = {
  24763. type: 'tag',
  24764. tagType: 'stream-inf'
  24765. };
  24766. if (match[1]) {
  24767. event.attributes = parseAttributes(match[1]);
  24768. if (event.attributes.RESOLUTION) {
  24769. var split = event.attributes.RESOLUTION.split('x');
  24770. var resolution = {};
  24771. if (split[0]) {
  24772. resolution.width = parseInt(split[0], 10);
  24773. }
  24774. if (split[1]) {
  24775. resolution.height = parseInt(split[1], 10);
  24776. }
  24777. event.attributes.RESOLUTION = resolution;
  24778. }
  24779. if (event.attributes.BANDWIDTH) {
  24780. event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
  24781. }
  24782. if (event.attributes['PROGRAM-ID']) {
  24783. event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
  24784. }
  24785. }
  24786. _this2.trigger('data', event);
  24787. return;
  24788. }
  24789. match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine);
  24790. if (match) {
  24791. event = {
  24792. type: 'tag',
  24793. tagType: 'media'
  24794. };
  24795. if (match[1]) {
  24796. event.attributes = parseAttributes(match[1]);
  24797. }
  24798. _this2.trigger('data', event);
  24799. return;
  24800. }
  24801. match = /^#EXT-X-ENDLIST/.exec(newLine);
  24802. if (match) {
  24803. _this2.trigger('data', {
  24804. type: 'tag',
  24805. tagType: 'endlist'
  24806. });
  24807. return;
  24808. }
  24809. match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
  24810. if (match) {
  24811. _this2.trigger('data', {
  24812. type: 'tag',
  24813. tagType: 'discontinuity'
  24814. });
  24815. return;
  24816. }
  24817. match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine);
  24818. if (match) {
  24819. event = {
  24820. type: 'tag',
  24821. tagType: 'program-date-time'
  24822. };
  24823. if (match[1]) {
  24824. event.dateTimeString = match[1];
  24825. event.dateTimeObject = new Date(match[1]);
  24826. }
  24827. _this2.trigger('data', event);
  24828. return;
  24829. }
  24830. match = /^#EXT-X-KEY:?(.*)$/.exec(newLine);
  24831. if (match) {
  24832. event = {
  24833. type: 'tag',
  24834. tagType: 'key'
  24835. };
  24836. if (match[1]) {
  24837. event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array
  24838. if (event.attributes.IV) {
  24839. if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
  24840. event.attributes.IV = event.attributes.IV.substring(2);
  24841. }
  24842. event.attributes.IV = event.attributes.IV.match(/.{8}/g);
  24843. event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
  24844. event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
  24845. event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
  24846. event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
  24847. event.attributes.IV = new Uint32Array(event.attributes.IV);
  24848. }
  24849. }
  24850. _this2.trigger('data', event);
  24851. return;
  24852. }
  24853. match = /^#EXT-X-START:?(.*)$/.exec(newLine);
  24854. if (match) {
  24855. event = {
  24856. type: 'tag',
  24857. tagType: 'start'
  24858. };
  24859. if (match[1]) {
  24860. event.attributes = parseAttributes(match[1]);
  24861. event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
  24862. event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
  24863. }
  24864. _this2.trigger('data', event);
  24865. return;
  24866. }
  24867. match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine);
  24868. if (match) {
  24869. event = {
  24870. type: 'tag',
  24871. tagType: 'cue-out-cont'
  24872. };
  24873. if (match[1]) {
  24874. event.data = match[1];
  24875. } else {
  24876. event.data = '';
  24877. }
  24878. _this2.trigger('data', event);
  24879. return;
  24880. }
  24881. match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine);
  24882. if (match) {
  24883. event = {
  24884. type: 'tag',
  24885. tagType: 'cue-out'
  24886. };
  24887. if (match[1]) {
  24888. event.data = match[1];
  24889. } else {
  24890. event.data = '';
  24891. }
  24892. _this2.trigger('data', event);
  24893. return;
  24894. }
  24895. match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine);
  24896. if (match) {
  24897. event = {
  24898. type: 'tag',
  24899. tagType: 'cue-in'
  24900. };
  24901. if (match[1]) {
  24902. event.data = match[1];
  24903. } else {
  24904. event.data = '';
  24905. }
  24906. _this2.trigger('data', event);
  24907. return;
  24908. } // unknown tag type
  24909. _this2.trigger('data', {
  24910. type: 'tag',
  24911. data: newLine.slice(4)
  24912. });
  24913. });
  24914. };
  24915. /**
  24916. * Add a parser for custom headers
  24917. *
  24918. * @param {Object} options a map of options for the added parser
  24919. * @param {RegExp} options.expression a regular expression to match the custom header
  24920. * @param {string} options.customType the custom type to register to the output
  24921. * @param {Function} [options.dataParser] function to parse the line into an object
  24922. * @param {boolean} [options.segment] should tag data be attached to the segment object
  24923. */
  24924. _proto.addParser = function addParser(_ref) {
  24925. var _this3 = this;
  24926. var expression = _ref.expression,
  24927. customType = _ref.customType,
  24928. dataParser = _ref.dataParser,
  24929. segment = _ref.segment;
  24930. if (typeof dataParser !== 'function') {
  24931. dataParser = function dataParser(line) {
  24932. return line;
  24933. };
  24934. }
  24935. this.customParsers.push(function (line) {
  24936. var match = expression.exec(line);
  24937. if (match) {
  24938. _this3.trigger('data', {
  24939. type: 'custom',
  24940. data: dataParser(line),
  24941. customType: customType,
  24942. segment: segment
  24943. });
  24944. return true;
  24945. }
  24946. });
  24947. };
  24948. /**
  24949. * Add a custom header mapper
  24950. *
  24951. * @param {Object} options
  24952. * @param {RegExp} options.expression a regular expression to match the custom header
  24953. * @param {Function} options.map function to translate tag into a different tag
  24954. */
  24955. _proto.addTagMapper = function addTagMapper(_ref2) {
  24956. var expression = _ref2.expression,
  24957. map = _ref2.map;
  24958. var mapFn = function mapFn(line) {
  24959. if (expression.test(line)) {
  24960. return map(line);
  24961. }
  24962. return line;
  24963. };
  24964. this.tagMappers.push(mapFn);
  24965. };
  24966. return ParseStream;
  24967. }(Stream);
  24968. /**
  24969. * A parser for M3U8 files. The current interpretation of the input is
  24970. * exposed as a property `manifest` on parser objects. It's just two lines to
  24971. * create and parse a manifest once you have the contents available as a string:
  24972. *
  24973. * ```js
  24974. * var parser = new m3u8.Parser();
  24975. * parser.push(xhr.responseText);
  24976. * ```
  24977. *
  24978. * New input can later be applied to update the manifest object by calling
  24979. * `push` again.
  24980. *
  24981. * The parser attempts to create a usable manifest object even if the
  24982. * underlying input is somewhat nonsensical. It emits `info` and `warning`
  24983. * events during the parse if it encounters input that seems invalid or
  24984. * requires some property of the manifest object to be defaulted.
  24985. *
  24986. * @class Parser
  24987. * @extends Stream
  24988. */
  24989. var Parser =
  24990. /*#__PURE__*/
  24991. function (_Stream) {
  24992. _inheritsLoose$1(Parser, _Stream);
  24993. function Parser() {
  24994. var _this;
  24995. _this = _Stream.call(this) || this;
  24996. _this.lineStream = new LineStream();
  24997. _this.parseStream = new ParseStream();
  24998. _this.lineStream.pipe(_this.parseStream);
  24999. /* eslint-disable consistent-this */
  25000. var self = _assertThisInitialized$1(_assertThisInitialized$1(_this));
  25001. /* eslint-enable consistent-this */
  25002. var uris = [];
  25003. var currentUri = {}; // if specified, the active EXT-X-MAP definition
  25004. var currentMap; // if specified, the active decryption key
  25005. var _key;
  25006. var noop = function noop() {};
  25007. var defaultMediaGroups = {
  25008. 'AUDIO': {},
  25009. 'VIDEO': {},
  25010. 'CLOSED-CAPTIONS': {},
  25011. 'SUBTITLES': {}
  25012. }; // group segments into numbered timelines delineated by discontinuities
  25013. var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
  25014. _this.manifest = {
  25015. allowCache: true,
  25016. discontinuityStarts: [],
  25017. segments: []
  25018. }; // update the manifest with the m3u8 entry from the parse stream
  25019. _this.parseStream.on('data', function (entry) {
  25020. var mediaGroup;
  25021. var rendition;
  25022. ({
  25023. tag: function tag() {
  25024. // switch based on the tag type
  25025. (({
  25026. 'allow-cache': function allowCache() {
  25027. this.manifest.allowCache = entry.allowed;
  25028. if (!('allowed' in entry)) {
  25029. this.trigger('info', {
  25030. message: 'defaulting allowCache to YES'
  25031. });
  25032. this.manifest.allowCache = true;
  25033. }
  25034. },
  25035. byterange: function byterange() {
  25036. var byterange = {};
  25037. if ('length' in entry) {
  25038. currentUri.byterange = byterange;
  25039. byterange.length = entry.length;
  25040. if (!('offset' in entry)) {
  25041. this.trigger('info', {
  25042. message: 'defaulting offset to zero'
  25043. });
  25044. entry.offset = 0;
  25045. }
  25046. }
  25047. if ('offset' in entry) {
  25048. currentUri.byterange = byterange;
  25049. byterange.offset = entry.offset;
  25050. }
  25051. },
  25052. endlist: function endlist() {
  25053. this.manifest.endList = true;
  25054. },
  25055. inf: function inf() {
  25056. if (!('mediaSequence' in this.manifest)) {
  25057. this.manifest.mediaSequence = 0;
  25058. this.trigger('info', {
  25059. message: 'defaulting media sequence to zero'
  25060. });
  25061. }
  25062. if (!('discontinuitySequence' in this.manifest)) {
  25063. this.manifest.discontinuitySequence = 0;
  25064. this.trigger('info', {
  25065. message: 'defaulting discontinuity sequence to zero'
  25066. });
  25067. }
  25068. if (entry.duration > 0) {
  25069. currentUri.duration = entry.duration;
  25070. }
  25071. if (entry.duration === 0) {
  25072. currentUri.duration = 0.01;
  25073. this.trigger('info', {
  25074. message: 'updating zero segment duration to a small value'
  25075. });
  25076. }
  25077. this.manifest.segments = uris;
  25078. },
  25079. key: function key() {
  25080. if (!entry.attributes) {
  25081. this.trigger('warn', {
  25082. message: 'ignoring key declaration without attribute list'
  25083. });
  25084. return;
  25085. } // clear the active encryption key
  25086. if (entry.attributes.METHOD === 'NONE') {
  25087. _key = null;
  25088. return;
  25089. }
  25090. if (!entry.attributes.URI) {
  25091. this.trigger('warn', {
  25092. message: 'ignoring key declaration without URI'
  25093. });
  25094. return;
  25095. }
  25096. if (!entry.attributes.METHOD) {
  25097. this.trigger('warn', {
  25098. message: 'defaulting key method to AES-128'
  25099. });
  25100. } // setup an encryption key for upcoming segments
  25101. _key = {
  25102. method: entry.attributes.METHOD || 'AES-128',
  25103. uri: entry.attributes.URI
  25104. };
  25105. if (typeof entry.attributes.IV !== 'undefined') {
  25106. _key.iv = entry.attributes.IV;
  25107. }
  25108. },
  25109. 'media-sequence': function mediaSequence() {
  25110. if (!isFinite(entry.number)) {
  25111. this.trigger('warn', {
  25112. message: 'ignoring invalid media sequence: ' + entry.number
  25113. });
  25114. return;
  25115. }
  25116. this.manifest.mediaSequence = entry.number;
  25117. },
  25118. 'discontinuity-sequence': function discontinuitySequence() {
  25119. if (!isFinite(entry.number)) {
  25120. this.trigger('warn', {
  25121. message: 'ignoring invalid discontinuity sequence: ' + entry.number
  25122. });
  25123. return;
  25124. }
  25125. this.manifest.discontinuitySequence = entry.number;
  25126. currentTimeline = entry.number;
  25127. },
  25128. 'playlist-type': function playlistType() {
  25129. if (!/VOD|EVENT/.test(entry.playlistType)) {
  25130. this.trigger('warn', {
  25131. message: 'ignoring unknown playlist type: ' + entry.playlist
  25132. });
  25133. return;
  25134. }
  25135. this.manifest.playlistType = entry.playlistType;
  25136. },
  25137. map: function map() {
  25138. currentMap = {};
  25139. if (entry.uri) {
  25140. currentMap.uri = entry.uri;
  25141. }
  25142. if (entry.byterange) {
  25143. currentMap.byterange = entry.byterange;
  25144. }
  25145. },
  25146. 'stream-inf': function streamInf() {
  25147. this.manifest.playlists = uris;
  25148. this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
  25149. if (!entry.attributes) {
  25150. this.trigger('warn', {
  25151. message: 'ignoring empty stream-inf attributes'
  25152. });
  25153. return;
  25154. }
  25155. if (!currentUri.attributes) {
  25156. currentUri.attributes = {};
  25157. }
  25158. _extends$1(currentUri.attributes, entry.attributes);
  25159. },
  25160. media: function media() {
  25161. this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
  25162. if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
  25163. this.trigger('warn', {
  25164. message: 'ignoring incomplete or missing media group'
  25165. });
  25166. return;
  25167. } // find the media group, creating defaults as necessary
  25168. var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
  25169. mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
  25170. mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
  25171. rendition = {
  25172. default: /yes/i.test(entry.attributes.DEFAULT)
  25173. };
  25174. if (rendition.default) {
  25175. rendition.autoselect = true;
  25176. } else {
  25177. rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
  25178. }
  25179. if (entry.attributes.LANGUAGE) {
  25180. rendition.language = entry.attributes.LANGUAGE;
  25181. }
  25182. if (entry.attributes.URI) {
  25183. rendition.uri = entry.attributes.URI;
  25184. }
  25185. if (entry.attributes['INSTREAM-ID']) {
  25186. rendition.instreamId = entry.attributes['INSTREAM-ID'];
  25187. }
  25188. if (entry.attributes.CHARACTERISTICS) {
  25189. rendition.characteristics = entry.attributes.CHARACTERISTICS;
  25190. }
  25191. if (entry.attributes.FORCED) {
  25192. rendition.forced = /yes/i.test(entry.attributes.FORCED);
  25193. } // insert the new rendition
  25194. mediaGroup[entry.attributes.NAME] = rendition;
  25195. },
  25196. discontinuity: function discontinuity() {
  25197. currentTimeline += 1;
  25198. currentUri.discontinuity = true;
  25199. this.manifest.discontinuityStarts.push(uris.length);
  25200. },
  25201. 'program-date-time': function programDateTime() {
  25202. if (typeof this.manifest.dateTimeString === 'undefined') {
  25203. // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
  25204. // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
  25205. // to the manifest object
  25206. // TODO: Consider removing this in future major version
  25207. this.manifest.dateTimeString = entry.dateTimeString;
  25208. this.manifest.dateTimeObject = entry.dateTimeObject;
  25209. }
  25210. currentUri.dateTimeString = entry.dateTimeString;
  25211. currentUri.dateTimeObject = entry.dateTimeObject;
  25212. },
  25213. targetduration: function targetduration() {
  25214. if (!isFinite(entry.duration) || entry.duration < 0) {
  25215. this.trigger('warn', {
  25216. message: 'ignoring invalid target duration: ' + entry.duration
  25217. });
  25218. return;
  25219. }
  25220. this.manifest.targetDuration = entry.duration;
  25221. },
  25222. totalduration: function totalduration() {
  25223. if (!isFinite(entry.duration) || entry.duration < 0) {
  25224. this.trigger('warn', {
  25225. message: 'ignoring invalid total duration: ' + entry.duration
  25226. });
  25227. return;
  25228. }
  25229. this.manifest.totalDuration = entry.duration;
  25230. },
  25231. start: function start() {
  25232. if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
  25233. this.trigger('warn', {
  25234. message: 'ignoring start declaration without appropriate attribute list'
  25235. });
  25236. return;
  25237. }
  25238. this.manifest.start = {
  25239. timeOffset: entry.attributes['TIME-OFFSET'],
  25240. precise: entry.attributes.PRECISE
  25241. };
  25242. },
  25243. 'cue-out': function cueOut() {
  25244. currentUri.cueOut = entry.data;
  25245. },
  25246. 'cue-out-cont': function cueOutCont() {
  25247. currentUri.cueOutCont = entry.data;
  25248. },
  25249. 'cue-in': function cueIn() {
  25250. currentUri.cueIn = entry.data;
  25251. }
  25252. })[entry.tagType] || noop).call(self);
  25253. },
  25254. uri: function uri() {
  25255. currentUri.uri = entry.uri;
  25256. uris.push(currentUri); // if no explicit duration was declared, use the target duration
  25257. if (this.manifest.targetDuration && !('duration' in currentUri)) {
  25258. this.trigger('warn', {
  25259. message: 'defaulting segment duration to the target duration'
  25260. });
  25261. currentUri.duration = this.manifest.targetDuration;
  25262. } // annotate with encryption information, if necessary
  25263. if (_key) {
  25264. currentUri.key = _key;
  25265. }
  25266. currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
  25267. if (currentMap) {
  25268. currentUri.map = currentMap;
  25269. } // prepare for the next URI
  25270. currentUri = {};
  25271. },
  25272. comment: function comment() {// comments are not important for playback
  25273. },
  25274. custom: function custom() {
  25275. // if this is segment-level data attach the output to the segment
  25276. if (entry.segment) {
  25277. currentUri.custom = currentUri.custom || {};
  25278. currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
  25279. } else {
  25280. this.manifest.custom = this.manifest.custom || {};
  25281. this.manifest.custom[entry.customType] = entry.data;
  25282. }
  25283. }
  25284. })[entry.type].call(self);
  25285. });
  25286. return _this;
  25287. }
  25288. /**
  25289. * Parse the input string and update the manifest object.
  25290. *
  25291. * @param {string} chunk a potentially incomplete portion of the manifest
  25292. */
  25293. var _proto = Parser.prototype;
  25294. _proto.push = function push(chunk) {
  25295. this.lineStream.push(chunk);
  25296. };
  25297. /**
  25298. * Flush any remaining input. This can be handy if the last line of an M3U8
  25299. * manifest did not contain a trailing newline but the file has been
  25300. * completely received.
  25301. */
  25302. _proto.end = function end() {
  25303. // flush any buffered input
  25304. this.lineStream.push('\n');
  25305. };
  25306. /**
  25307. * Add an additional parser for non-standard tags
  25308. *
  25309. * @param {Object} options a map of options for the added parser
  25310. * @param {RegExp} options.expression a regular expression to match the custom header
  25311. * @param {string} options.type the type to register to the output
  25312. * @param {Function} [options.dataParser] function to parse the line into an object
  25313. * @param {boolean} [options.segment] should tag data be attached to the segment object
  25314. */
  25315. _proto.addParser = function addParser(options) {
  25316. this.parseStream.addParser(options);
  25317. };
  25318. /**
  25319. * Add a custom header mapper
  25320. *
  25321. * @param {Object} options
  25322. * @param {RegExp} options.expression a regular expression to match the custom header
  25323. * @param {Function} options.map function to translate tag into a different tag
  25324. */
  25325. _proto.addTagMapper = function addTagMapper(options) {
  25326. this.parseStream.addTagMapper(options);
  25327. };
  25328. return Parser;
  25329. }(Stream);
  25330. /*! @name mpd-parser @version 0.7.0 @license Apache-2.0 */
  25331. var isObject$1 = function isObject(obj) {
  25332. return !!obj && typeof obj === 'object';
  25333. };
  25334. var merge = function merge() {
  25335. for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) {
  25336. objects[_key] = arguments[_key];
  25337. }
  25338. return objects.reduce(function (result, source) {
  25339. Object.keys(source).forEach(function (key) {
  25340. if (Array.isArray(result[key]) && Array.isArray(source[key])) {
  25341. result[key] = result[key].concat(source[key]);
  25342. } else if (isObject$1(result[key]) && isObject$1(source[key])) {
  25343. result[key] = merge(result[key], source[key]);
  25344. } else {
  25345. result[key] = source[key];
  25346. }
  25347. });
  25348. return result;
  25349. }, {});
  25350. };
  25351. var values = function values(o) {
  25352. return Object.keys(o).map(function (k) {
  25353. return o[k];
  25354. });
  25355. };
  25356. var range = function range(start, end) {
  25357. var result = [];
  25358. for (var i = start; i < end; i++) {
  25359. result.push(i);
  25360. }
  25361. return result;
  25362. };
  25363. var flatten = function flatten(lists) {
  25364. return lists.reduce(function (x, y) {
  25365. return x.concat(y);
  25366. }, []);
  25367. };
  25368. var from = function from(list) {
  25369. if (!list.length) {
  25370. return [];
  25371. }
  25372. var result = [];
  25373. for (var i = 0; i < list.length; i++) {
  25374. result.push(list[i]);
  25375. }
  25376. return result;
  25377. };
  25378. var findIndexes = function findIndexes(l, key) {
  25379. return l.reduce(function (a, e, i) {
  25380. if (e[key]) {
  25381. a.push(i);
  25382. }
  25383. return a;
  25384. }, []);
  25385. };
  25386. var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) {
  25387. var mergedPlaylists = values(playlists.reduce(function (acc, playlist) {
  25388. // assuming playlist IDs are the same across periods
  25389. // TODO: handle multiperiod where representation sets are not the same
  25390. // across periods
  25391. var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first
  25392. if (acc[name]) {
  25393. var _acc$name$segments; // first segment of subsequent periods signal a discontinuity
  25394. playlist.segments[0].discontinuity = true;
  25395. (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content
  25396. // has the same contentProtection
  25397. if (playlist.attributes.contentProtection) {
  25398. acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
  25399. }
  25400. } else {
  25401. // first Period
  25402. acc[name] = playlist;
  25403. }
  25404. return acc;
  25405. }, {}));
  25406. return mergedPlaylists.map(function (playlist) {
  25407. playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity');
  25408. return playlist;
  25409. });
  25410. };
  25411. var formatAudioPlaylist = function formatAudioPlaylist(_ref) {
  25412. var _attributes;
  25413. var attributes = _ref.attributes,
  25414. segments = _ref.segments;
  25415. var playlist = {
  25416. attributes: (_attributes = {
  25417. NAME: attributes.id,
  25418. BANDWIDTH: attributes.bandwidth,
  25419. CODECS: attributes.codecs
  25420. }, _attributes['PROGRAM-ID'] = 1, _attributes),
  25421. uri: '',
  25422. endList: (attributes.type || 'static') === 'static',
  25423. timeline: attributes.periodIndex,
  25424. resolvedUri: '',
  25425. targetDuration: attributes.duration,
  25426. segments: segments,
  25427. mediaSequence: segments.length ? segments[0].number : 1
  25428. };
  25429. if (attributes.contentProtection) {
  25430. playlist.contentProtection = attributes.contentProtection;
  25431. }
  25432. return playlist;
  25433. };
  25434. var formatVttPlaylist = function formatVttPlaylist(_ref2) {
  25435. var _attributes2;
  25436. var attributes = _ref2.attributes,
  25437. segments = _ref2.segments;
  25438. if (typeof segments === 'undefined') {
  25439. // vtt tracks may use single file in BaseURL
  25440. segments = [{
  25441. uri: attributes.baseUrl,
  25442. timeline: attributes.periodIndex,
  25443. resolvedUri: attributes.baseUrl || '',
  25444. duration: attributes.sourceDuration,
  25445. number: 0
  25446. }]; // targetDuration should be the same duration as the only segment
  25447. attributes.duration = attributes.sourceDuration;
  25448. }
  25449. return {
  25450. attributes: (_attributes2 = {
  25451. NAME: attributes.id,
  25452. BANDWIDTH: attributes.bandwidth
  25453. }, _attributes2['PROGRAM-ID'] = 1, _attributes2),
  25454. uri: '',
  25455. endList: (attributes.type || 'static') === 'static',
  25456. timeline: attributes.periodIndex,
  25457. resolvedUri: attributes.baseUrl || '',
  25458. targetDuration: attributes.duration,
  25459. segments: segments,
  25460. mediaSequence: segments.length ? segments[0].number : 1
  25461. };
  25462. };
  25463. var organizeAudioPlaylists = function organizeAudioPlaylists(playlists) {
  25464. return playlists.reduce(function (a, playlist) {
  25465. var role = playlist.attributes.role && playlist.attributes.role.value || 'main';
  25466. var language = playlist.attributes.lang || '';
  25467. var label = 'main';
  25468. if (language) {
  25469. label = playlist.attributes.lang + " (" + role + ")";
  25470. } // skip if we already have the highest quality audio for a language
  25471. if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) {
  25472. return a;
  25473. }
  25474. a[label] = {
  25475. language: language,
  25476. autoselect: true,
  25477. default: role === 'main',
  25478. playlists: [formatAudioPlaylist(playlist)],
  25479. uri: ''
  25480. };
  25481. return a;
  25482. }, {});
  25483. };
  25484. var organizeVttPlaylists = function organizeVttPlaylists(playlists) {
  25485. return playlists.reduce(function (a, playlist) {
  25486. var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles
  25487. if (a[label]) {
  25488. return a;
  25489. }
  25490. a[label] = {
  25491. language: label,
  25492. default: false,
  25493. autoselect: false,
  25494. playlists: [formatVttPlaylist(playlist)],
  25495. uri: ''
  25496. };
  25497. return a;
  25498. }, {});
  25499. };
  25500. var formatVideoPlaylist = function formatVideoPlaylist(_ref3) {
  25501. var _attributes3;
  25502. var attributes = _ref3.attributes,
  25503. segments = _ref3.segments;
  25504. var playlist = {
  25505. attributes: (_attributes3 = {
  25506. NAME: attributes.id,
  25507. AUDIO: 'audio',
  25508. SUBTITLES: 'subs',
  25509. RESOLUTION: {
  25510. width: attributes.width,
  25511. height: attributes.height
  25512. },
  25513. CODECS: attributes.codecs,
  25514. BANDWIDTH: attributes.bandwidth
  25515. }, _attributes3['PROGRAM-ID'] = 1, _attributes3),
  25516. uri: '',
  25517. endList: (attributes.type || 'static') === 'static',
  25518. timeline: attributes.periodIndex,
  25519. resolvedUri: '',
  25520. targetDuration: attributes.duration,
  25521. segments: segments,
  25522. mediaSequence: segments.length ? segments[0].number : 1
  25523. };
  25524. if (attributes.contentProtection) {
  25525. playlist.contentProtection = attributes.contentProtection;
  25526. }
  25527. return playlist;
  25528. };
  25529. var toM3u8 = function toM3u8(dashPlaylists) {
  25530. var _mediaGroups;
  25531. if (!dashPlaylists.length) {
  25532. return {};
  25533. } // grab all master attributes
  25534. var _dashPlaylists$0$attr = dashPlaylists[0].attributes,
  25535. duration = _dashPlaylists$0$attr.sourceDuration,
  25536. _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.minimumUpdatePeriod,
  25537. minimumUpdatePeriod = _dashPlaylists$0$attr2 === void 0 ? 0 : _dashPlaylists$0$attr2;
  25538. var videoOnly = function videoOnly(_ref4) {
  25539. var attributes = _ref4.attributes;
  25540. return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video';
  25541. };
  25542. var audioOnly = function audioOnly(_ref5) {
  25543. var attributes = _ref5.attributes;
  25544. return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio';
  25545. };
  25546. var vttOnly = function vttOnly(_ref6) {
  25547. var attributes = _ref6.attributes;
  25548. return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
  25549. };
  25550. var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
  25551. var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
  25552. var vttPlaylists = dashPlaylists.filter(vttOnly);
  25553. var master = {
  25554. allowCache: true,
  25555. discontinuityStarts: [],
  25556. segments: [],
  25557. endList: true,
  25558. mediaGroups: (_mediaGroups = {
  25559. AUDIO: {},
  25560. VIDEO: {}
  25561. }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups),
  25562. uri: '',
  25563. duration: duration,
  25564. playlists: videoPlaylists,
  25565. minimumUpdatePeriod: minimumUpdatePeriod * 1000
  25566. };
  25567. if (audioPlaylists.length) {
  25568. master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists);
  25569. }
  25570. if (vttPlaylists.length) {
  25571. master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists);
  25572. }
  25573. return master;
  25574. };
  25575. var commonjsGlobal$1 = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
  25576. function createCommonjsModule$1(fn, module) {
  25577. return module = {
  25578. exports: {}
  25579. }, fn(module, module.exports), module.exports;
  25580. }
  25581. var urlToolkit$1 = createCommonjsModule$1(function (module, exports) {
  25582. // see https://tools.ietf.org/html/rfc1808
  25583. /* jshint ignore:start */
  25584. (function (root) {
  25585. /* jshint ignore:end */
  25586. var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
  25587. var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
  25588. var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
  25589. var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
  25590. var URLToolkit = {
  25591. // jshint ignore:line
  25592. // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
  25593. // E.g
  25594. // With opts.alwaysNormalize = false (default, spec compliant)
  25595. // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
  25596. // With opts.alwaysNormalize = true (not spec compliant)
  25597. // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
  25598. buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
  25599. opts = opts || {}; // remove any remaining space and CRLF
  25600. baseURL = baseURL.trim();
  25601. relativeURL = relativeURL.trim();
  25602. if (!relativeURL) {
  25603. // 2a) If the embedded URL is entirely empty, it inherits the
  25604. // entire base URL (i.e., is set equal to the base URL)
  25605. // and we are done.
  25606. if (!opts.alwaysNormalize) {
  25607. return baseURL;
  25608. }
  25609. var basePartsForNormalise = URLToolkit.parseURL(baseURL);
  25610. if (!basePartsForNormalise) {
  25611. throw new Error('Error trying to parse base URL.');
  25612. }
  25613. basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
  25614. return URLToolkit.buildURLFromParts(basePartsForNormalise);
  25615. }
  25616. var relativeParts = URLToolkit.parseURL(relativeURL);
  25617. if (!relativeParts) {
  25618. throw new Error('Error trying to parse relative URL.');
  25619. }
  25620. if (relativeParts.scheme) {
  25621. // 2b) If the embedded URL starts with a scheme name, it is
  25622. // interpreted as an absolute URL and we are done.
  25623. if (!opts.alwaysNormalize) {
  25624. return relativeURL;
  25625. }
  25626. relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
  25627. return URLToolkit.buildURLFromParts(relativeParts);
  25628. }
  25629. var baseParts = URLToolkit.parseURL(baseURL);
  25630. if (!baseParts) {
  25631. throw new Error('Error trying to parse base URL.');
  25632. }
  25633. if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
  25634. // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
  25635. // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
  25636. var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
  25637. baseParts.netLoc = pathParts[1];
  25638. baseParts.path = pathParts[2];
  25639. }
  25640. if (baseParts.netLoc && !baseParts.path) {
  25641. baseParts.path = '/';
  25642. }
  25643. var builtParts = {
  25644. // 2c) Otherwise, the embedded URL inherits the scheme of
  25645. // the base URL.
  25646. scheme: baseParts.scheme,
  25647. netLoc: relativeParts.netLoc,
  25648. path: null,
  25649. params: relativeParts.params,
  25650. query: relativeParts.query,
  25651. fragment: relativeParts.fragment
  25652. };
  25653. if (!relativeParts.netLoc) {
  25654. // 3) If the embedded URL's <net_loc> is non-empty, we skip to
  25655. // Step 7. Otherwise, the embedded URL inherits the <net_loc>
  25656. // (if any) of the base URL.
  25657. builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
  25658. // path is not relative and we skip to Step 7.
  25659. if (relativeParts.path[0] !== '/') {
  25660. if (!relativeParts.path) {
  25661. // 5) If the embedded URL path is empty (and not preceded by a
  25662. // slash), then the embedded URL inherits the base URL path
  25663. builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
  25664. // step 7; otherwise, it inherits the <params> of the base
  25665. // URL (if any) and
  25666. if (!relativeParts.params) {
  25667. builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
  25668. // step 7; otherwise, it inherits the <query> of the base
  25669. // URL (if any) and we skip to step 7.
  25670. if (!relativeParts.query) {
  25671. builtParts.query = baseParts.query;
  25672. }
  25673. }
  25674. } else {
  25675. // 6) The last segment of the base URL's path (anything
  25676. // following the rightmost slash "/", or the entire path if no
  25677. // slash is present) is removed and the embedded URL's path is
  25678. // appended in its place.
  25679. var baseURLPath = baseParts.path;
  25680. var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
  25681. builtParts.path = URLToolkit.normalizePath(newPath);
  25682. }
  25683. }
  25684. }
  25685. if (builtParts.path === null) {
  25686. builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
  25687. }
  25688. return URLToolkit.buildURLFromParts(builtParts);
  25689. },
  25690. parseURL: function parseURL(url) {
  25691. var parts = URL_REGEX.exec(url);
  25692. if (!parts) {
  25693. return null;
  25694. }
  25695. return {
  25696. scheme: parts[1] || '',
  25697. netLoc: parts[2] || '',
  25698. path: parts[3] || '',
  25699. params: parts[4] || '',
  25700. query: parts[5] || '',
  25701. fragment: parts[6] || ''
  25702. };
  25703. },
  25704. normalizePath: function normalizePath(path) {
  25705. // The following operations are
  25706. // then applied, in order, to the new path:
  25707. // 6a) All occurrences of "./", where "." is a complete path
  25708. // segment, are removed.
  25709. // 6b) If the path ends with "." as a complete path segment,
  25710. // that "." is removed.
  25711. path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
  25712. // complete path segment not equal to "..", are removed.
  25713. // Removal of these path segments is performed iteratively,
  25714. // removing the leftmost matching pattern on each iteration,
  25715. // until no matching pattern remains.
  25716. // 6d) If the path ends with "<segment>/..", where <segment> is a
  25717. // complete path segment not equal to "..", that
  25718. // "<segment>/.." is removed.
  25719. while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
  25720. return path.split('').reverse().join('');
  25721. },
  25722. buildURLFromParts: function buildURLFromParts(parts) {
  25723. return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
  25724. }
  25725. };
  25726. /* jshint ignore:start */
  25727. module.exports = URLToolkit;
  25728. })(commonjsGlobal$1);
  25729. /* jshint ignore:end */
  25730. });
  25731. var resolveUrl = function resolveUrl(baseUrl, relativeUrl) {
  25732. // return early if we don't need to resolve
  25733. if (/^[a-z]+:/i.test(relativeUrl)) {
  25734. return relativeUrl;
  25735. } // if the base URL is relative then combine with the current location
  25736. if (!/\/\//i.test(baseUrl)) {
  25737. baseUrl = urlToolkit$1.buildAbsoluteURL(window$1.location.href, baseUrl);
  25738. }
  25739. return urlToolkit$1.buildAbsoluteURL(baseUrl, relativeUrl);
  25740. };
  25741. /**
  25742. * @typedef {Object} SingleUri
  25743. * @property {string} uri - relative location of segment
  25744. * @property {string} resolvedUri - resolved location of segment
  25745. * @property {Object} byterange - Object containing information on how to make byte range
  25746. * requests following byte-range-spec per RFC2616.
  25747. * @property {String} byterange.length - length of range request
  25748. * @property {String} byterange.offset - byte offset of range request
  25749. *
  25750. * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
  25751. */
  25752. /**
  25753. * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
  25754. * that conforms to how m3u8-parser is structured
  25755. *
  25756. * @see https://github.com/videojs/m3u8-parser
  25757. *
  25758. * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
  25759. * @param {string} source - source url for segment
  25760. * @param {string} range - optional range used for range calls, follows
  25761. * @return {SingleUri} full segment information transformed into a format similar
  25762. * to m3u8-parser
  25763. */
  25764. var urlTypeToSegment = function urlTypeToSegment(_ref) {
  25765. var _ref$baseUrl = _ref.baseUrl,
  25766. baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl,
  25767. _ref$source = _ref.source,
  25768. source = _ref$source === void 0 ? '' : _ref$source,
  25769. _ref$range = _ref.range,
  25770. range = _ref$range === void 0 ? '' : _ref$range;
  25771. var init = {
  25772. uri: source,
  25773. resolvedUri: resolveUrl(baseUrl || '', source)
  25774. };
  25775. if (range) {
  25776. var ranges = range.split('-');
  25777. var startRange = parseInt(ranges[0], 10);
  25778. var endRange = parseInt(ranges[1], 10);
  25779. init.byterange = {
  25780. length: endRange - startRange,
  25781. offset: startRange
  25782. };
  25783. }
  25784. return init;
  25785. };
  25786. /**
  25787. * Calculates the R (repetition) value for a live stream (for the final segment
  25788. * in a manifest where the r value is negative 1)
  25789. *
  25790. * @param {Object} attributes
  25791. * Object containing all inherited attributes from parent elements with attribute
  25792. * names as keys
  25793. * @param {number} time
  25794. * current time (typically the total time up until the final segment)
  25795. * @param {number} duration
  25796. * duration property for the given <S />
  25797. *
  25798. * @return {number}
  25799. * R value to reach the end of the given period
  25800. */
  25801. var getLiveRValue = function getLiveRValue(attributes, time, duration) {
  25802. var NOW = attributes.NOW,
  25803. clientOffset = attributes.clientOffset,
  25804. availabilityStartTime = attributes.availabilityStartTime,
  25805. _attributes$timescale = attributes.timescale,
  25806. timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
  25807. _attributes$start = attributes.start,
  25808. start = _attributes$start === void 0 ? 0 : _attributes$start,
  25809. _attributes$minimumUp = attributes.minimumUpdatePeriod,
  25810. minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp;
  25811. var now = (NOW + clientOffset) / 1000;
  25812. var periodStartWC = availabilityStartTime + start;
  25813. var periodEndWC = now + minimumUpdatePeriod;
  25814. var periodDuration = periodEndWC - periodStartWC;
  25815. return Math.ceil((periodDuration * timescale - time) / duration);
  25816. };
  25817. /**
  25818. * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
  25819. * timing and duration
  25820. *
  25821. * @param {Object} attributes
  25822. * Object containing all inherited attributes from parent elements with attribute
  25823. * names as keys
  25824. * @param {Object[]} segmentTimeline
  25825. * List of objects representing the attributes of each S element contained within
  25826. *
  25827. * @return {{number: number, duration: number, time: number, timeline: number}[]}
  25828. * List of Objects with segment timing and duration info
  25829. */
  25830. var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) {
  25831. var _attributes$type = attributes.type,
  25832. type = _attributes$type === void 0 ? 'static' : _attributes$type,
  25833. _attributes$minimumUp2 = attributes.minimumUpdatePeriod,
  25834. minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2,
  25835. _attributes$media = attributes.media,
  25836. media = _attributes$media === void 0 ? '' : _attributes$media,
  25837. sourceDuration = attributes.sourceDuration,
  25838. _attributes$timescale2 = attributes.timescale,
  25839. timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
  25840. _attributes$startNumb = attributes.startNumber,
  25841. startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb,
  25842. timeline = attributes.periodIndex;
  25843. var segments = [];
  25844. var time = -1;
  25845. for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
  25846. var S = segmentTimeline[sIndex];
  25847. var duration = S.d;
  25848. var repeat = S.r || 0;
  25849. var segmentTime = S.t || 0;
  25850. if (time < 0) {
  25851. // first segment
  25852. time = segmentTime;
  25853. }
  25854. if (segmentTime && segmentTime > time) {
  25855. // discontinuity
  25856. // TODO: How to handle this type of discontinuity
  25857. // timeline++ here would treat it like HLS discontuity and content would
  25858. // get appended without gap
  25859. // E.G.
  25860. // <S t="0" d="1" />
  25861. // <S d="1" />
  25862. // <S d="1" />
  25863. // <S t="5" d="1" />
  25864. // would have $Time$ values of [0, 1, 2, 5]
  25865. // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
  25866. // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
  25867. // does the value of sourceDuration consider this when calculating arbitrary
  25868. // negative @r repeat value?
  25869. // E.G. Same elements as above with this added at the end
  25870. // <S d="1" r="-1" />
  25871. // with a sourceDuration of 10
  25872. // Would the 2 gaps be included in the time duration calculations resulting in
  25873. // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
  25874. // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
  25875. time = segmentTime;
  25876. }
  25877. var count = void 0;
  25878. if (repeat < 0) {
  25879. var nextS = sIndex + 1;
  25880. if (nextS === segmentTimeline.length) {
  25881. // last segment
  25882. if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
  25883. count = getLiveRValue(attributes, time, duration);
  25884. } else {
  25885. // TODO: This may be incorrect depending on conclusion of TODO above
  25886. count = (sourceDuration * timescale - time) / duration;
  25887. }
  25888. } else {
  25889. count = (segmentTimeline[nextS].t - time) / duration;
  25890. }
  25891. } else {
  25892. count = repeat + 1;
  25893. }
  25894. var end = startNumber + segments.length + count;
  25895. var number = startNumber + segments.length;
  25896. while (number < end) {
  25897. segments.push({
  25898. number: number,
  25899. duration: duration / timescale,
  25900. time: time,
  25901. timeline: timeline
  25902. });
  25903. time += duration;
  25904. number++;
  25905. }
  25906. }
  25907. return segments;
  25908. };
  25909. /**
  25910. * Functions for calculating the range of available segments in static and dynamic
  25911. * manifests.
  25912. */
  25913. var segmentRange = {
  25914. /**
  25915. * Returns the entire range of available segments for a static MPD
  25916. *
  25917. * @param {Object} attributes
  25918. * Inheritied MPD attributes
  25919. * @return {{ start: number, end: number }}
  25920. * The start and end numbers for available segments
  25921. */
  25922. static: function _static(attributes) {
  25923. var duration = attributes.duration,
  25924. _attributes$timescale = attributes.timescale,
  25925. timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
  25926. sourceDuration = attributes.sourceDuration;
  25927. return {
  25928. start: 0,
  25929. end: Math.ceil(sourceDuration / (duration / timescale))
  25930. };
  25931. },
  25932. /**
  25933. * Returns the current live window range of available segments for a dynamic MPD
  25934. *
  25935. * @param {Object} attributes
  25936. * Inheritied MPD attributes
  25937. * @return {{ start: number, end: number }}
  25938. * The start and end numbers for available segments
  25939. */
  25940. dynamic: function dynamic(attributes) {
  25941. var NOW = attributes.NOW,
  25942. clientOffset = attributes.clientOffset,
  25943. availabilityStartTime = attributes.availabilityStartTime,
  25944. _attributes$timescale2 = attributes.timescale,
  25945. timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
  25946. duration = attributes.duration,
  25947. _attributes$start = attributes.start,
  25948. start = _attributes$start === void 0 ? 0 : _attributes$start,
  25949. _attributes$minimumUp = attributes.minimumUpdatePeriod,
  25950. minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp,
  25951. _attributes$timeShift = attributes.timeShiftBufferDepth,
  25952. timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift;
  25953. var now = (NOW + clientOffset) / 1000;
  25954. var periodStartWC = availabilityStartTime + start;
  25955. var periodEndWC = now + minimumUpdatePeriod;
  25956. var periodDuration = periodEndWC - periodStartWC;
  25957. var segmentCount = Math.ceil(periodDuration * timescale / duration);
  25958. var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
  25959. var availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
  25960. return {
  25961. start: Math.max(0, availableStart),
  25962. end: Math.min(segmentCount, availableEnd)
  25963. };
  25964. }
  25965. };
  25966. /**
  25967. * Maps a range of numbers to objects with information needed to build the corresponding
  25968. * segment list
  25969. *
  25970. * @name toSegmentsCallback
  25971. * @function
  25972. * @param {number} number
  25973. * Number of the segment
  25974. * @param {number} index
  25975. * Index of the number in the range list
  25976. * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
  25977. * Object with segment timing and duration info
  25978. */
  25979. /**
  25980. * Returns a callback for Array.prototype.map for mapping a range of numbers to
  25981. * information needed to build the segment list.
  25982. *
  25983. * @param {Object} attributes
  25984. * Inherited MPD attributes
  25985. * @return {toSegmentsCallback}
  25986. * Callback map function
  25987. */
  25988. var toSegments = function toSegments(attributes) {
  25989. return function (number, index) {
  25990. var duration = attributes.duration,
  25991. _attributes$timescale3 = attributes.timescale,
  25992. timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3,
  25993. periodIndex = attributes.periodIndex,
  25994. _attributes$startNumb = attributes.startNumber,
  25995. startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb;
  25996. return {
  25997. number: startNumber + number,
  25998. duration: duration / timescale,
  25999. timeline: periodIndex,
  26000. time: index * duration
  26001. };
  26002. };
  26003. };
  26004. /**
  26005. * Returns a list of objects containing segment timing and duration info used for
  26006. * building the list of segments. This uses the @duration attribute specified
  26007. * in the MPD manifest to derive the range of segments.
  26008. *
  26009. * @param {Object} attributes
  26010. * Inherited MPD attributes
  26011. * @return {{number: number, duration: number, time: number, timeline: number}[]}
  26012. * List of Objects with segment timing and duration info
  26013. */
  26014. var parseByDuration = function parseByDuration(attributes) {
  26015. var _attributes$type = attributes.type,
  26016. type = _attributes$type === void 0 ? 'static' : _attributes$type,
  26017. duration = attributes.duration,
  26018. _attributes$timescale4 = attributes.timescale,
  26019. timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4,
  26020. sourceDuration = attributes.sourceDuration;
  26021. var _segmentRange$type = segmentRange[type](attributes),
  26022. start = _segmentRange$type.start,
  26023. end = _segmentRange$type.end;
  26024. var segments = range(start, end).map(toSegments(attributes));
  26025. if (type === 'static') {
  26026. var index = segments.length - 1; // final segment may be less than full segment duration
  26027. segments[index].duration = sourceDuration - duration / timescale * index;
  26028. }
  26029. return segments;
  26030. };
  26031. var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
  26032. /**
  26033. * Replaces template identifiers with corresponding values. To be used as the callback
  26034. * for String.prototype.replace
  26035. *
  26036. * @name replaceCallback
  26037. * @function
  26038. * @param {string} match
  26039. * Entire match of identifier
  26040. * @param {string} identifier
  26041. * Name of matched identifier
  26042. * @param {string} format
  26043. * Format tag string. Its presence indicates that padding is expected
  26044. * @param {string} width
  26045. * Desired length of the replaced value. Values less than this width shall be left
  26046. * zero padded
  26047. * @return {string}
  26048. * Replacement for the matched identifier
  26049. */
  26050. /**
  26051. * Returns a function to be used as a callback for String.prototype.replace to replace
  26052. * template identifiers
  26053. *
  26054. * @param {Obect} values
  26055. * Object containing values that shall be used to replace known identifiers
  26056. * @param {number} values.RepresentationID
  26057. * Value of the Representation@id attribute
  26058. * @param {number} values.Number
  26059. * Number of the corresponding segment
  26060. * @param {number} values.Bandwidth
  26061. * Value of the Representation@bandwidth attribute.
  26062. * @param {number} values.Time
  26063. * Timestamp value of the corresponding segment
  26064. * @return {replaceCallback}
  26065. * Callback to be used with String.prototype.replace to replace identifiers
  26066. */
  26067. var identifierReplacement = function identifierReplacement(values) {
  26068. return function (match, identifier, format, width) {
  26069. if (match === '$$') {
  26070. // escape sequence
  26071. return '$';
  26072. }
  26073. if (typeof values[identifier] === 'undefined') {
  26074. return match;
  26075. }
  26076. var value = '' + values[identifier];
  26077. if (identifier === 'RepresentationID') {
  26078. // Format tag shall not be present with RepresentationID
  26079. return value;
  26080. }
  26081. if (!format) {
  26082. width = 1;
  26083. } else {
  26084. width = parseInt(width, 10);
  26085. }
  26086. if (value.length >= width) {
  26087. return value;
  26088. }
  26089. return "" + new Array(width - value.length + 1).join('0') + value;
  26090. };
  26091. };
  26092. /**
  26093. * Constructs a segment url from a template string
  26094. *
  26095. * @param {string} url
  26096. * Template string to construct url from
  26097. * @param {Obect} values
  26098. * Object containing values that shall be used to replace known identifiers
  26099. * @param {number} values.RepresentationID
  26100. * Value of the Representation@id attribute
  26101. * @param {number} values.Number
  26102. * Number of the corresponding segment
  26103. * @param {number} values.Bandwidth
  26104. * Value of the Representation@bandwidth attribute.
  26105. * @param {number} values.Time
  26106. * Timestamp value of the corresponding segment
  26107. * @return {string}
  26108. * Segment url with identifiers replaced
  26109. */
  26110. var constructTemplateUrl = function constructTemplateUrl(url, values) {
  26111. return url.replace(identifierPattern, identifierReplacement(values));
  26112. };
  26113. /**
  26114. * Generates a list of objects containing timing and duration information about each
  26115. * segment needed to generate segment uris and the complete segment object
  26116. *
  26117. * @param {Object} attributes
  26118. * Object containing all inherited attributes from parent elements with attribute
  26119. * names as keys
  26120. * @param {Object[]|undefined} segmentTimeline
  26121. * List of objects representing the attributes of each S element contained within
  26122. * the SegmentTimeline element
  26123. * @return {{number: number, duration: number, time: number, timeline: number}[]}
  26124. * List of Objects with segment timing and duration info
  26125. */
  26126. var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) {
  26127. if (!attributes.duration && !segmentTimeline) {
  26128. // if neither @duration or SegmentTimeline are present, then there shall be exactly
  26129. // one media segment
  26130. return [{
  26131. number: attributes.startNumber || 1,
  26132. duration: attributes.sourceDuration,
  26133. time: 0,
  26134. timeline: attributes.periodIndex
  26135. }];
  26136. }
  26137. if (attributes.duration) {
  26138. return parseByDuration(attributes);
  26139. }
  26140. return parseByTimeline(attributes, segmentTimeline);
  26141. };
  26142. /**
  26143. * Generates a list of segments using information provided by the SegmentTemplate element
  26144. *
  26145. * @param {Object} attributes
  26146. * Object containing all inherited attributes from parent elements with attribute
  26147. * names as keys
  26148. * @param {Object[]|undefined} segmentTimeline
  26149. * List of objects representing the attributes of each S element contained within
  26150. * the SegmentTimeline element
  26151. * @return {Object[]}
  26152. * List of segment objects
  26153. */
  26154. var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) {
  26155. var templateValues = {
  26156. RepresentationID: attributes.id,
  26157. Bandwidth: attributes.bandwidth || 0
  26158. };
  26159. var _attributes$initializ = attributes.initialization,
  26160. initialization = _attributes$initializ === void 0 ? {
  26161. sourceURL: '',
  26162. range: ''
  26163. } : _attributes$initializ;
  26164. var mapSegment = urlTypeToSegment({
  26165. baseUrl: attributes.baseUrl,
  26166. source: constructTemplateUrl(initialization.sourceURL, templateValues),
  26167. range: initialization.range
  26168. });
  26169. var segments = parseTemplateInfo(attributes, segmentTimeline);
  26170. return segments.map(function (segment) {
  26171. templateValues.Number = segment.number;
  26172. templateValues.Time = segment.time;
  26173. var uri = constructTemplateUrl(attributes.media || '', templateValues);
  26174. return {
  26175. uri: uri,
  26176. timeline: segment.timeline,
  26177. duration: segment.duration,
  26178. resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
  26179. map: mapSegment,
  26180. number: segment.number
  26181. };
  26182. });
  26183. };
  26184. var errors = {
  26185. INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
  26186. DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
  26187. DASH_INVALID_XML: 'DASH_INVALID_XML',
  26188. NO_BASE_URL: 'NO_BASE_URL',
  26189. MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
  26190. SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
  26191. UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
  26192. };
  26193. /**
  26194. * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
  26195. * to an object that matches the output of a segment in videojs/mpd-parser
  26196. *
  26197. * @param {Object} attributes
  26198. * Object containing all inherited attributes from parent elements with attribute
  26199. * names as keys
  26200. * @param {Object} segmentUrl
  26201. * <SegmentURL> node to translate into a segment object
  26202. * @return {Object} translated segment object
  26203. */
  26204. var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) {
  26205. var baseUrl = attributes.baseUrl,
  26206. _attributes$initializ = attributes.initialization,
  26207. initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ;
  26208. var initSegment = urlTypeToSegment({
  26209. baseUrl: baseUrl,
  26210. source: initialization.sourceURL,
  26211. range: initialization.range
  26212. });
  26213. var segment = urlTypeToSegment({
  26214. baseUrl: baseUrl,
  26215. source: segmentUrl.media,
  26216. range: segmentUrl.mediaRange
  26217. });
  26218. segment.map = initSegment;
  26219. return segment;
  26220. };
  26221. /**
  26222. * Generates a list of segments using information provided by the SegmentList element
  26223. * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
  26224. * node should be translated into segment.
  26225. *
  26226. * @param {Object} attributes
  26227. * Object containing all inherited attributes from parent elements with attribute
  26228. * names as keys
  26229. * @param {Object[]|undefined} segmentTimeline
  26230. * List of objects representing the attributes of each S element contained within
  26231. * the SegmentTimeline element
  26232. * @return {Object.<Array>} list of segments
  26233. */
  26234. var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) {
  26235. var duration = attributes.duration,
  26236. _attributes$segmentUr = attributes.segmentUrls,
  26237. segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR
  26238. // if both SegmentTimeline and @duration are defined, it is outside of spec.
  26239. if (!duration && !segmentTimeline || duration && segmentTimeline) {
  26240. throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
  26241. }
  26242. var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) {
  26243. return SegmentURLToSegmentObject(attributes, segmentUrlObject);
  26244. });
  26245. var segmentTimeInfo;
  26246. if (duration) {
  26247. segmentTimeInfo = parseByDuration(attributes);
  26248. }
  26249. if (segmentTimeline) {
  26250. segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
  26251. }
  26252. var segments = segmentTimeInfo.map(function (segmentTime, index) {
  26253. if (segmentUrlMap[index]) {
  26254. var segment = segmentUrlMap[index];
  26255. segment.timeline = segmentTime.timeline;
  26256. segment.duration = segmentTime.duration;
  26257. segment.number = segmentTime.number;
  26258. return segment;
  26259. } // Since we're mapping we should get rid of any blank segments (in case
  26260. // the given SegmentTimeline is handling for more elements than we have
  26261. // SegmentURLs for).
  26262. }).filter(function (segment) {
  26263. return segment;
  26264. });
  26265. return segments;
  26266. };
  26267. /**
  26268. * Translates SegmentBase into a set of segments.
  26269. * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
  26270. * node should be translated into segment.
  26271. *
  26272. * @param {Object} attributes
  26273. * Object containing all inherited attributes from parent elements with attribute
  26274. * names as keys
  26275. * @return {Object.<Array>} list of segments
  26276. */
  26277. var segmentsFromBase = function segmentsFromBase(attributes) {
  26278. var baseUrl = attributes.baseUrl,
  26279. _attributes$initializ = attributes.initialization,
  26280. initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ,
  26281. sourceDuration = attributes.sourceDuration,
  26282. _attributes$timescale = attributes.timescale,
  26283. timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
  26284. _attributes$indexRang = attributes.indexRange,
  26285. indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang,
  26286. duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
  26287. if (!baseUrl) {
  26288. throw new Error(errors.NO_BASE_URL);
  26289. }
  26290. var initSegment = urlTypeToSegment({
  26291. baseUrl: baseUrl,
  26292. source: initialization.sourceURL,
  26293. range: initialization.range
  26294. });
  26295. var segment = urlTypeToSegment({
  26296. baseUrl: baseUrl,
  26297. source: baseUrl,
  26298. range: indexRange
  26299. });
  26300. segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
  26301. // (since SegmentBase is only for one total segment)
  26302. if (duration) {
  26303. var segmentTimeInfo = parseByDuration(attributes);
  26304. if (segmentTimeInfo.length) {
  26305. segment.duration = segmentTimeInfo[0].duration;
  26306. segment.timeline = segmentTimeInfo[0].timeline;
  26307. }
  26308. } else if (sourceDuration) {
  26309. segment.duration = sourceDuration / timescale;
  26310. segment.timeline = 0;
  26311. } // This is used for mediaSequence
  26312. segment.number = 0;
  26313. return [segment];
  26314. };
  26315. var generateSegments = function generateSegments(_ref) {
  26316. var attributes = _ref.attributes,
  26317. segmentInfo = _ref.segmentInfo;
  26318. var segmentAttributes;
  26319. var segmentsFn;
  26320. if (segmentInfo.template) {
  26321. segmentsFn = segmentsFromTemplate;
  26322. segmentAttributes = merge(attributes, segmentInfo.template);
  26323. } else if (segmentInfo.base) {
  26324. segmentsFn = segmentsFromBase;
  26325. segmentAttributes = merge(attributes, segmentInfo.base);
  26326. } else if (segmentInfo.list) {
  26327. segmentsFn = segmentsFromList;
  26328. segmentAttributes = merge(attributes, segmentInfo.list);
  26329. }
  26330. if (!segmentsFn) {
  26331. return {
  26332. attributes: attributes
  26333. };
  26334. }
  26335. var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which
  26336. // must be in seconds. Since we've generated the segment list, we no longer need
  26337. // @duration to be in @timescale units, so we can convert it here.
  26338. if (segmentAttributes.duration) {
  26339. var _segmentAttributes = segmentAttributes,
  26340. duration = _segmentAttributes.duration,
  26341. _segmentAttributes$ti = _segmentAttributes.timescale,
  26342. timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti;
  26343. segmentAttributes.duration = duration / timescale;
  26344. } else if (segments.length) {
  26345. // if there is no @duration attribute, use the largest segment duration as
  26346. // as target duration
  26347. segmentAttributes.duration = segments.reduce(function (max, segment) {
  26348. return Math.max(max, Math.ceil(segment.duration));
  26349. }, 0);
  26350. } else {
  26351. segmentAttributes.duration = 0;
  26352. }
  26353. return {
  26354. attributes: segmentAttributes,
  26355. segments: segments
  26356. };
  26357. };
  26358. var toPlaylists = function toPlaylists(representations) {
  26359. return representations.map(generateSegments);
  26360. };
  26361. var findChildren = function findChildren(element, name) {
  26362. return from(element.childNodes).filter(function (_ref) {
  26363. var tagName = _ref.tagName;
  26364. return tagName === name;
  26365. });
  26366. };
  26367. var getContent = function getContent(element) {
  26368. return element.textContent.trim();
  26369. };
  26370. var parseDuration = function parseDuration(str) {
  26371. var SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
  26372. var SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
  26373. var SECONDS_IN_DAY = 24 * 60 * 60;
  26374. var SECONDS_IN_HOUR = 60 * 60;
  26375. var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
  26376. var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
  26377. var match = durationRegex.exec(str);
  26378. if (!match) {
  26379. return 0;
  26380. }
  26381. var _match$slice = match.slice(1),
  26382. year = _match$slice[0],
  26383. month = _match$slice[1],
  26384. day = _match$slice[2],
  26385. hour = _match$slice[3],
  26386. minute = _match$slice[4],
  26387. second = _match$slice[5];
  26388. 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);
  26389. };
  26390. var parseDate = function parseDate(str) {
  26391. // Date format without timezone according to ISO 8601
  26392. // YYY-MM-DDThh:mm:ss.ssssss
  26393. 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
  26394. // expressed by ending with 'Z'
  26395. if (dateRegex.test(str)) {
  26396. str += 'Z';
  26397. }
  26398. return Date.parse(str);
  26399. };
  26400. var parsers = {
  26401. /**
  26402. * Specifies the duration of the entire Media Presentation. Format is a duration string
  26403. * as specified in ISO 8601
  26404. *
  26405. * @param {string} value
  26406. * value of attribute as a string
  26407. * @return {number}
  26408. * The duration in seconds
  26409. */
  26410. mediaPresentationDuration: function mediaPresentationDuration(value) {
  26411. return parseDuration(value);
  26412. },
  26413. /**
  26414. * Specifies the Segment availability start time for all Segments referred to in this
  26415. * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
  26416. * time. Format is a date string as specified in ISO 8601
  26417. *
  26418. * @param {string} value
  26419. * value of attribute as a string
  26420. * @return {number}
  26421. * The date as seconds from unix epoch
  26422. */
  26423. availabilityStartTime: function availabilityStartTime(value) {
  26424. return parseDate(value) / 1000;
  26425. },
  26426. /**
  26427. * Specifies the smallest period between potential changes to the MPD. Format is a
  26428. * duration string as specified in ISO 8601
  26429. *
  26430. * @param {string} value
  26431. * value of attribute as a string
  26432. * @return {number}
  26433. * The duration in seconds
  26434. */
  26435. minimumUpdatePeriod: function minimumUpdatePeriod(value) {
  26436. return parseDuration(value);
  26437. },
  26438. /**
  26439. * Specifies the duration of the smallest time shifting buffer for any Representation
  26440. * in the MPD. Format is a duration string as specified in ISO 8601
  26441. *
  26442. * @param {string} value
  26443. * value of attribute as a string
  26444. * @return {number}
  26445. * The duration in seconds
  26446. */
  26447. timeShiftBufferDepth: function timeShiftBufferDepth(value) {
  26448. return parseDuration(value);
  26449. },
  26450. /**
  26451. * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
  26452. * Format is a duration string as specified in ISO 8601
  26453. *
  26454. * @param {string} value
  26455. * value of attribute as a string
  26456. * @return {number}
  26457. * The duration in seconds
  26458. */
  26459. start: function start(value) {
  26460. return parseDuration(value);
  26461. },
  26462. /**
  26463. * Specifies the width of the visual presentation
  26464. *
  26465. * @param {string} value
  26466. * value of attribute as a string
  26467. * @return {number}
  26468. * The parsed width
  26469. */
  26470. width: function width(value) {
  26471. return parseInt(value, 10);
  26472. },
  26473. /**
  26474. * Specifies the height of the visual presentation
  26475. *
  26476. * @param {string} value
  26477. * value of attribute as a string
  26478. * @return {number}
  26479. * The parsed height
  26480. */
  26481. height: function height(value) {
  26482. return parseInt(value, 10);
  26483. },
  26484. /**
  26485. * Specifies the bitrate of the representation
  26486. *
  26487. * @param {string} value
  26488. * value of attribute as a string
  26489. * @return {number}
  26490. * The parsed bandwidth
  26491. */
  26492. bandwidth: function bandwidth(value) {
  26493. return parseInt(value, 10);
  26494. },
  26495. /**
  26496. * Specifies the number of the first Media Segment in this Representation in the Period
  26497. *
  26498. * @param {string} value
  26499. * value of attribute as a string
  26500. * @return {number}
  26501. * The parsed number
  26502. */
  26503. startNumber: function startNumber(value) {
  26504. return parseInt(value, 10);
  26505. },
  26506. /**
  26507. * Specifies the timescale in units per seconds
  26508. *
  26509. * @param {string} value
  26510. * value of attribute as a string
  26511. * @return {number}
  26512. * The aprsed timescale
  26513. */
  26514. timescale: function timescale(value) {
  26515. return parseInt(value, 10);
  26516. },
  26517. /**
  26518. * Specifies the constant approximate Segment duration
  26519. * NOTE: The <Period> element also contains an @duration attribute. This duration
  26520. * specifies the duration of the Period. This attribute is currently not
  26521. * supported by the rest of the parser, however we still check for it to prevent
  26522. * errors.
  26523. *
  26524. * @param {string} value
  26525. * value of attribute as a string
  26526. * @return {number}
  26527. * The parsed duration
  26528. */
  26529. duration: function duration(value) {
  26530. var parsedValue = parseInt(value, 10);
  26531. if (isNaN(parsedValue)) {
  26532. return parseDuration(value);
  26533. }
  26534. return parsedValue;
  26535. },
  26536. /**
  26537. * Specifies the Segment duration, in units of the value of the @timescale.
  26538. *
  26539. * @param {string} value
  26540. * value of attribute as a string
  26541. * @return {number}
  26542. * The parsed duration
  26543. */
  26544. d: function d(value) {
  26545. return parseInt(value, 10);
  26546. },
  26547. /**
  26548. * Specifies the MPD start time, in @timescale units, the first Segment in the series
  26549. * starts relative to the beginning of the Period
  26550. *
  26551. * @param {string} value
  26552. * value of attribute as a string
  26553. * @return {number}
  26554. * The parsed time
  26555. */
  26556. t: function t(value) {
  26557. return parseInt(value, 10);
  26558. },
  26559. /**
  26560. * Specifies the repeat count of the number of following contiguous Segments with the
  26561. * same duration expressed by the value of @d
  26562. *
  26563. * @param {string} value
  26564. * value of attribute as a string
  26565. * @return {number}
  26566. * The parsed number
  26567. */
  26568. r: function r(value) {
  26569. return parseInt(value, 10);
  26570. },
  26571. /**
  26572. * Default parser for all other attributes. Acts as a no-op and just returns the value
  26573. * as a string
  26574. *
  26575. * @param {string} value
  26576. * value of attribute as a string
  26577. * @return {string}
  26578. * Unparsed value
  26579. */
  26580. DEFAULT: function DEFAULT(value) {
  26581. return value;
  26582. }
  26583. };
  26584. /**
  26585. * Gets all the attributes and values of the provided node, parses attributes with known
  26586. * types, and returns an object with attribute names mapped to values.
  26587. *
  26588. * @param {Node} el
  26589. * The node to parse attributes from
  26590. * @return {Object}
  26591. * Object with all attributes of el parsed
  26592. */
  26593. var parseAttributes$1 = function parseAttributes(el) {
  26594. if (!(el && el.attributes)) {
  26595. return {};
  26596. }
  26597. return from(el.attributes).reduce(function (a, e) {
  26598. var parseFn = parsers[e.name] || parsers.DEFAULT;
  26599. a[e.name] = parseFn(e.value);
  26600. return a;
  26601. }, {});
  26602. };
  26603. function decodeB64ToUint8Array(b64Text) {
  26604. var decodedString = window$1.atob(b64Text);
  26605. var array = new Uint8Array(decodedString.length);
  26606. for (var i = 0; i < decodedString.length; i++) {
  26607. array[i] = decodedString.charCodeAt(i);
  26608. }
  26609. return array;
  26610. }
  26611. var keySystemsMap = {
  26612. 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
  26613. 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
  26614. 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
  26615. 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
  26616. };
  26617. /**
  26618. * Builds a list of urls that is the product of the reference urls and BaseURL values
  26619. *
  26620. * @param {string[]} referenceUrls
  26621. * List of reference urls to resolve to
  26622. * @param {Node[]} baseUrlElements
  26623. * List of BaseURL nodes from the mpd
  26624. * @return {string[]}
  26625. * List of resolved urls
  26626. */
  26627. var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) {
  26628. if (!baseUrlElements.length) {
  26629. return referenceUrls;
  26630. }
  26631. return flatten(referenceUrls.map(function (reference) {
  26632. return baseUrlElements.map(function (baseUrlElement) {
  26633. return resolveUrl(reference, getContent(baseUrlElement));
  26634. });
  26635. }));
  26636. };
  26637. /**
  26638. * Contains all Segment information for its containing AdaptationSet
  26639. *
  26640. * @typedef {Object} SegmentInformation
  26641. * @property {Object|undefined} template
  26642. * Contains the attributes for the SegmentTemplate node
  26643. * @property {Object[]|undefined} timeline
  26644. * Contains a list of atrributes for each S node within the SegmentTimeline node
  26645. * @property {Object|undefined} list
  26646. * Contains the attributes for the SegmentList node
  26647. * @property {Object|undefined} base
  26648. * Contains the attributes for the SegmentBase node
  26649. */
  26650. /**
  26651. * Returns all available Segment information contained within the AdaptationSet node
  26652. *
  26653. * @param {Node} adaptationSet
  26654. * The AdaptationSet node to get Segment information from
  26655. * @return {SegmentInformation}
  26656. * The Segment information contained within the provided AdaptationSet
  26657. */
  26658. var getSegmentInformation = function getSegmentInformation(adaptationSet) {
  26659. var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
  26660. var segmentList = findChildren(adaptationSet, 'SegmentList')[0];
  26661. var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) {
  26662. return merge({
  26663. tag: 'SegmentURL'
  26664. }, parseAttributes$1(s));
  26665. });
  26666. var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
  26667. var segmentTimelineParentNode = segmentList || segmentTemplate;
  26668. var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
  26669. var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
  26670. var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
  26671. // @initialization and an <Initialization> node. @initialization can be templated,
  26672. // while the node can have a url and range specified. If the <SegmentTemplate> has
  26673. // both @initialization and an <Initialization> subelement we opt to override with
  26674. // the node, as this interaction is not defined in the spec.
  26675. var template = segmentTemplate && parseAttributes$1(segmentTemplate);
  26676. if (template && segmentInitialization) {
  26677. template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization);
  26678. } else if (template && template.initialization) {
  26679. // If it is @initialization we convert it to an object since this is the format that
  26680. // later functions will rely on for the initialization segment. This is only valid
  26681. // for <SegmentTemplate>
  26682. template.initialization = {
  26683. sourceURL: template.initialization
  26684. };
  26685. }
  26686. var segmentInfo = {
  26687. template: template,
  26688. timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) {
  26689. return parseAttributes$1(s);
  26690. }),
  26691. list: segmentList && merge(parseAttributes$1(segmentList), {
  26692. segmentUrls: segmentUrls,
  26693. initialization: parseAttributes$1(segmentInitialization)
  26694. }),
  26695. base: segmentBase && merge(parseAttributes$1(segmentBase), {
  26696. initialization: parseAttributes$1(segmentInitialization)
  26697. })
  26698. };
  26699. Object.keys(segmentInfo).forEach(function (key) {
  26700. if (!segmentInfo[key]) {
  26701. delete segmentInfo[key];
  26702. }
  26703. });
  26704. return segmentInfo;
  26705. };
  26706. /**
  26707. * Contains Segment information and attributes needed to construct a Playlist object
  26708. * from a Representation
  26709. *
  26710. * @typedef {Object} RepresentationInformation
  26711. * @property {SegmentInformation} segmentInfo
  26712. * Segment information for this Representation
  26713. * @property {Object} attributes
  26714. * Inherited attributes for this Representation
  26715. */
  26716. /**
  26717. * Maps a Representation node to an object containing Segment information and attributes
  26718. *
  26719. * @name inheritBaseUrlsCallback
  26720. * @function
  26721. * @param {Node} representation
  26722. * Representation node from the mpd
  26723. * @return {RepresentationInformation}
  26724. * Representation information needed to construct a Playlist object
  26725. */
  26726. /**
  26727. * Returns a callback for Array.prototype.map for mapping Representation nodes to
  26728. * Segment information and attributes using inherited BaseURL nodes.
  26729. *
  26730. * @param {Object} adaptationSetAttributes
  26731. * Contains attributes inherited by the AdaptationSet
  26732. * @param {string[]} adaptationSetBaseUrls
  26733. * Contains list of resolved base urls inherited by the AdaptationSet
  26734. * @param {SegmentInformation} adaptationSetSegmentInfo
  26735. * Contains Segment information for the AdaptationSet
  26736. * @return {inheritBaseUrlsCallback}
  26737. * Callback map function
  26738. */
  26739. var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) {
  26740. return function (representation) {
  26741. var repBaseUrlElements = findChildren(representation, 'BaseURL');
  26742. var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
  26743. var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation));
  26744. var representationSegmentInfo = getSegmentInformation(representation);
  26745. return repBaseUrls.map(function (baseUrl) {
  26746. return {
  26747. segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
  26748. attributes: merge(attributes, {
  26749. baseUrl: baseUrl
  26750. })
  26751. };
  26752. });
  26753. };
  26754. };
  26755. /**
  26756. * Tranforms a series of content protection nodes to
  26757. * an object containing pssh data by key system
  26758. *
  26759. * @param {Node[]} contentProtectionNodes
  26760. * Content protection nodes
  26761. * @return {Object}
  26762. * Object containing pssh data by key system
  26763. */
  26764. var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) {
  26765. return contentProtectionNodes.reduce(function (acc, node) {
  26766. var attributes = parseAttributes$1(node);
  26767. var keySystem = keySystemsMap[attributes.schemeIdUri];
  26768. if (keySystem) {
  26769. acc[keySystem] = {
  26770. attributes: attributes
  26771. };
  26772. var psshNode = findChildren(node, 'cenc:pssh')[0];
  26773. if (psshNode) {
  26774. var pssh = getContent(psshNode);
  26775. var psshBuffer = pssh && decodeB64ToUint8Array(pssh);
  26776. acc[keySystem].pssh = psshBuffer;
  26777. }
  26778. }
  26779. return acc;
  26780. }, {});
  26781. };
  26782. /**
  26783. * Maps an AdaptationSet node to a list of Representation information objects
  26784. *
  26785. * @name toRepresentationsCallback
  26786. * @function
  26787. * @param {Node} adaptationSet
  26788. * AdaptationSet node from the mpd
  26789. * @return {RepresentationInformation[]}
  26790. * List of objects containing Representaion information
  26791. */
  26792. /**
  26793. * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
  26794. * Representation information objects
  26795. *
  26796. * @param {Object} periodAttributes
  26797. * Contains attributes inherited by the Period
  26798. * @param {string[]} periodBaseUrls
  26799. * Contains list of resolved base urls inherited by the Period
  26800. * @param {string[]} periodSegmentInfo
  26801. * Contains Segment Information at the period level
  26802. * @return {toRepresentationsCallback}
  26803. * Callback map function
  26804. */
  26805. var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) {
  26806. return function (adaptationSet) {
  26807. var adaptationSetAttributes = parseAttributes$1(adaptationSet);
  26808. var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
  26809. var role = findChildren(adaptationSet, 'Role')[0];
  26810. var roleAttributes = {
  26811. role: parseAttributes$1(role)
  26812. };
  26813. var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
  26814. var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
  26815. if (Object.keys(contentProtection).length) {
  26816. attrs = merge(attrs, {
  26817. contentProtection: contentProtection
  26818. });
  26819. }
  26820. var segmentInfo = getSegmentInformation(adaptationSet);
  26821. var representations = findChildren(adaptationSet, 'Representation');
  26822. var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
  26823. return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
  26824. };
  26825. };
  26826. /**
  26827. * Maps an Period node to a list of Representation inforamtion objects for all
  26828. * AdaptationSet nodes contained within the Period
  26829. *
  26830. * @name toAdaptationSetsCallback
  26831. * @function
  26832. * @param {Node} period
  26833. * Period node from the mpd
  26834. * @param {number} periodIndex
  26835. * Index of the Period within the mpd
  26836. * @return {RepresentationInformation[]}
  26837. * List of objects containing Representaion information
  26838. */
  26839. /**
  26840. * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
  26841. * Representation information objects
  26842. *
  26843. * @param {Object} mpdAttributes
  26844. * Contains attributes inherited by the mpd
  26845. * @param {string[]} mpdBaseUrls
  26846. * Contains list of resolved base urls inherited by the mpd
  26847. * @return {toAdaptationSetsCallback}
  26848. * Callback map function
  26849. */
  26850. var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) {
  26851. return function (period, index) {
  26852. var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL'));
  26853. var periodAtt = parseAttributes$1(period);
  26854. var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number
  26855. var periodIndex = window$1.isNaN(parsedPeriodId) ? index : parsedPeriodId;
  26856. var periodAttributes = merge(mpdAttributes, {
  26857. periodIndex: periodIndex
  26858. });
  26859. var adaptationSets = findChildren(period, 'AdaptationSet');
  26860. var periodSegmentInfo = getSegmentInformation(period);
  26861. return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
  26862. };
  26863. };
  26864. /**
  26865. * Traverses the mpd xml tree to generate a list of Representation information objects
  26866. * that have inherited attributes from parent nodes
  26867. *
  26868. * @param {Node} mpd
  26869. * The root node of the mpd
  26870. * @param {Object} options
  26871. * Available options for inheritAttributes
  26872. * @param {string} options.manifestUri
  26873. * The uri source of the mpd
  26874. * @param {number} options.NOW
  26875. * Current time per DASH IOP. Default is current time in ms since epoch
  26876. * @param {number} options.clientOffset
  26877. * Client time difference from NOW (in milliseconds)
  26878. * @return {RepresentationInformation[]}
  26879. * List of objects containing Representation information
  26880. */
  26881. var inheritAttributes = function inheritAttributes(mpd, options) {
  26882. if (options === void 0) {
  26883. options = {};
  26884. }
  26885. var _options = options,
  26886. _options$manifestUri = _options.manifestUri,
  26887. manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri,
  26888. _options$NOW = _options.NOW,
  26889. NOW = _options$NOW === void 0 ? Date.now() : _options$NOW,
  26890. _options$clientOffset = _options.clientOffset,
  26891. clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset;
  26892. var periods = findChildren(mpd, 'Period');
  26893. if (!periods.length) {
  26894. throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
  26895. }
  26896. var mpdAttributes = parseAttributes$1(mpd);
  26897. var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL'));
  26898. mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
  26899. mpdAttributes.NOW = NOW;
  26900. mpdAttributes.clientOffset = clientOffset;
  26901. return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)));
  26902. };
  26903. var stringToMpdXml = function stringToMpdXml(manifestString) {
  26904. if (manifestString === '') {
  26905. throw new Error(errors.DASH_EMPTY_MANIFEST);
  26906. }
  26907. var parser = new window$1.DOMParser();
  26908. var xml = parser.parseFromString(manifestString, 'application/xml');
  26909. var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
  26910. if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
  26911. throw new Error(errors.DASH_INVALID_XML);
  26912. }
  26913. return mpd;
  26914. };
  26915. /**
  26916. * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
  26917. *
  26918. * @param {string} mpd
  26919. * XML string of the MPD manifest
  26920. * @return {Object|null}
  26921. * Attributes of UTCTiming node specified in the manifest. Null if none found
  26922. */
  26923. var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) {
  26924. var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
  26925. if (!UTCTimingNode) {
  26926. return null;
  26927. }
  26928. var attributes = parseAttributes$1(UTCTimingNode);
  26929. switch (attributes.schemeIdUri) {
  26930. case 'urn:mpeg:dash:utc:http-head:2014':
  26931. case 'urn:mpeg:dash:utc:http-head:2012':
  26932. attributes.method = 'HEAD';
  26933. break;
  26934. case 'urn:mpeg:dash:utc:http-xsdate:2014':
  26935. case 'urn:mpeg:dash:utc:http-iso:2014':
  26936. case 'urn:mpeg:dash:utc:http-xsdate:2012':
  26937. case 'urn:mpeg:dash:utc:http-iso:2012':
  26938. attributes.method = 'GET';
  26939. break;
  26940. case 'urn:mpeg:dash:utc:direct:2014':
  26941. case 'urn:mpeg:dash:utc:direct:2012':
  26942. attributes.method = 'DIRECT';
  26943. attributes.value = Date.parse(attributes.value);
  26944. break;
  26945. case 'urn:mpeg:dash:utc:http-ntp:2014':
  26946. case 'urn:mpeg:dash:utc:ntp:2014':
  26947. case 'urn:mpeg:dash:utc:sntp:2014':
  26948. default:
  26949. throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
  26950. }
  26951. return attributes;
  26952. };
  26953. var parse = function parse(manifestString, options) {
  26954. return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)));
  26955. };
  26956. /**
  26957. * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
  26958. *
  26959. * @param {string} manifestString
  26960. * XML string of the MPD manifest
  26961. * @return {Object|null}
  26962. * Attributes of UTCTiming node specified in the manifest. Null if none found
  26963. */
  26964. var parseUTCTiming = function parseUTCTiming(manifestString) {
  26965. return parseUTCTimingScheme(stringToMpdXml(manifestString));
  26966. };
  26967. var toUnsigned = function toUnsigned(value) {
  26968. return value >>> 0;
  26969. };
  26970. var bin = {
  26971. toUnsigned: toUnsigned
  26972. };
  26973. var toUnsigned$1 = bin.toUnsigned;
  26974. var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path
  26975. _findBox = function findBox(data, path) {
  26976. var results = [],
  26977. i,
  26978. size,
  26979. type,
  26980. end,
  26981. subresults;
  26982. if (!path.length) {
  26983. // short-circuit the search for empty paths
  26984. return null;
  26985. }
  26986. for (i = 0; i < data.byteLength;) {
  26987. size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
  26988. type = parseType(data.subarray(i + 4, i + 8));
  26989. end = size > 1 ? i + size : data.byteLength;
  26990. if (type === path[0]) {
  26991. if (path.length === 1) {
  26992. // this is the end of the path and we've found the box we were
  26993. // looking for
  26994. results.push(data.subarray(i + 8, end));
  26995. } else {
  26996. // recursively search for the next box along the path
  26997. subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
  26998. if (subresults.length) {
  26999. results = results.concat(subresults);
  27000. }
  27001. }
  27002. }
  27003. i = end;
  27004. } // we've finished searching all of data
  27005. return results;
  27006. };
  27007. /**
  27008. * Returns the string representation of an ASCII encoded four byte buffer.
  27009. * @param buffer {Uint8Array} a four-byte buffer to translate
  27010. * @return {string} the corresponding string
  27011. */
  27012. parseType = function parseType(buffer) {
  27013. var result = '';
  27014. result += String.fromCharCode(buffer[0]);
  27015. result += String.fromCharCode(buffer[1]);
  27016. result += String.fromCharCode(buffer[2]);
  27017. result += String.fromCharCode(buffer[3]);
  27018. return result;
  27019. };
  27020. /**
  27021. * Parses an MP4 initialization segment and extracts the timescale
  27022. * values for any declared tracks. Timescale values indicate the
  27023. * number of clock ticks per second to assume for time-based values
  27024. * elsewhere in the MP4.
  27025. *
  27026. * To determine the start time of an MP4, you need two pieces of
  27027. * information: the timescale unit and the earliest base media decode
  27028. * time. Multiple timescales can be specified within an MP4 but the
  27029. * base media decode time is always expressed in the timescale from
  27030. * the media header box for the track:
  27031. * ```
  27032. * moov > trak > mdia > mdhd.timescale
  27033. * ```
  27034. * @param init {Uint8Array} the bytes of the init segment
  27035. * @return {object} a hash of track ids to timescale values or null if
  27036. * the init segment is malformed.
  27037. */
  27038. timescale = function timescale(init) {
  27039. var result = {},
  27040. traks = _findBox(init, ['moov', 'trak']); // mdhd timescale
  27041. return traks.reduce(function (result, trak) {
  27042. var tkhd, version, index, id, mdhd;
  27043. tkhd = _findBox(trak, ['tkhd'])[0];
  27044. if (!tkhd) {
  27045. return null;
  27046. }
  27047. version = tkhd[0];
  27048. index = version === 0 ? 12 : 20;
  27049. id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
  27050. mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
  27051. if (!mdhd) {
  27052. return null;
  27053. }
  27054. version = mdhd[0];
  27055. index = version === 0 ? 12 : 20;
  27056. result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
  27057. return result;
  27058. }, result);
  27059. };
  27060. /**
  27061. * Determine the base media decode start time, in seconds, for an MP4
  27062. * fragment. If multiple fragments are specified, the earliest time is
  27063. * returned.
  27064. *
  27065. * The base media decode time can be parsed from track fragment
  27066. * metadata:
  27067. * ```
  27068. * moof > traf > tfdt.baseMediaDecodeTime
  27069. * ```
  27070. * It requires the timescale value from the mdhd to interpret.
  27071. *
  27072. * @param timescale {object} a hash of track ids to timescale values.
  27073. * @return {number} the earliest base media decode start time for the
  27074. * fragment, in seconds
  27075. */
  27076. startTime = function startTime(timescale, fragment) {
  27077. var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
  27078. trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track
  27079. baseTimes = [].concat.apply([], trafs.map(function (traf) {
  27080. return _findBox(traf, ['tfhd']).map(function (tfhd) {
  27081. var id, scale, baseTime; // get the track id from the tfhd
  27082. id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
  27083. scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
  27084. baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
  27085. var version, result;
  27086. version = tfdt[0];
  27087. result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
  27088. if (version === 1) {
  27089. result *= Math.pow(2, 32);
  27090. result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
  27091. }
  27092. return result;
  27093. })[0];
  27094. baseTime = baseTime || Infinity; // convert base time to seconds
  27095. return baseTime / scale;
  27096. });
  27097. })); // return the minimum
  27098. result = Math.min.apply(null, baseTimes);
  27099. return isFinite(result) ? result : 0;
  27100. };
  27101. /**
  27102. * Find the trackIds of the video tracks in this source.
  27103. * Found by parsing the Handler Reference and Track Header Boxes:
  27104. * moov > trak > mdia > hdlr
  27105. * moov > trak > tkhd
  27106. *
  27107. * @param {Uint8Array} init - The bytes of the init segment for this source
  27108. * @return {Number[]} A list of trackIds
  27109. *
  27110. * @see ISO-BMFF-12/2015, Section 8.4.3
  27111. **/
  27112. getVideoTrackIds = function getVideoTrackIds(init) {
  27113. var traks = _findBox(init, ['moov', 'trak']);
  27114. var videoTrackIds = [];
  27115. traks.forEach(function (trak) {
  27116. var hdlrs = _findBox(trak, ['mdia', 'hdlr']);
  27117. var tkhds = _findBox(trak, ['tkhd']);
  27118. hdlrs.forEach(function (hdlr, index) {
  27119. var handlerType = parseType(hdlr.subarray(8, 12));
  27120. var tkhd = tkhds[index];
  27121. var view;
  27122. var version;
  27123. var trackId;
  27124. if (handlerType === 'vide') {
  27125. view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
  27126. version = view.getUint8(0);
  27127. trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
  27128. videoTrackIds.push(trackId);
  27129. }
  27130. });
  27131. });
  27132. return videoTrackIds;
  27133. };
  27134. var probe = {
  27135. findBox: _findBox,
  27136. parseType: parseType,
  27137. timescale: timescale,
  27138. startTime: startTime,
  27139. videoTrackIds: getVideoTrackIds
  27140. };
  27141. /**
  27142. * mux.js
  27143. *
  27144. * Copyright (c) 2015 Brightcove
  27145. * All rights reserved.
  27146. *
  27147. * Functions that generate fragmented MP4s suitable for use with Media
  27148. * Source Extensions.
  27149. */
  27150. var UINT32_MAX = Math.pow(2, 32) - 1;
  27151. 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
  27152. (function () {
  27153. var i;
  27154. types = {
  27155. avc1: [],
  27156. // codingname
  27157. avcC: [],
  27158. btrt: [],
  27159. dinf: [],
  27160. dref: [],
  27161. esds: [],
  27162. ftyp: [],
  27163. hdlr: [],
  27164. mdat: [],
  27165. mdhd: [],
  27166. mdia: [],
  27167. mfhd: [],
  27168. minf: [],
  27169. moof: [],
  27170. moov: [],
  27171. mp4a: [],
  27172. // codingname
  27173. mvex: [],
  27174. mvhd: [],
  27175. sdtp: [],
  27176. smhd: [],
  27177. stbl: [],
  27178. stco: [],
  27179. stsc: [],
  27180. stsd: [],
  27181. stsz: [],
  27182. stts: [],
  27183. styp: [],
  27184. tfdt: [],
  27185. tfhd: [],
  27186. traf: [],
  27187. trak: [],
  27188. trun: [],
  27189. trex: [],
  27190. tkhd: [],
  27191. vmhd: []
  27192. }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
  27193. // don't throw an error
  27194. if (typeof Uint8Array === 'undefined') {
  27195. return;
  27196. }
  27197. for (i in types) {
  27198. if (types.hasOwnProperty(i)) {
  27199. types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
  27200. }
  27201. }
  27202. MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
  27203. AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
  27204. MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
  27205. VIDEO_HDLR = new Uint8Array([0x00, // version 0
  27206. 0x00, 0x00, 0x00, // flags
  27207. 0x00, 0x00, 0x00, 0x00, // pre_defined
  27208. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  27209. 0x00, 0x00, 0x00, 0x00, // reserved
  27210. 0x00, 0x00, 0x00, 0x00, // reserved
  27211. 0x00, 0x00, 0x00, 0x00, // reserved
  27212. 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  27213. ]);
  27214. AUDIO_HDLR = new Uint8Array([0x00, // version 0
  27215. 0x00, 0x00, 0x00, // flags
  27216. 0x00, 0x00, 0x00, 0x00, // pre_defined
  27217. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  27218. 0x00, 0x00, 0x00, 0x00, // reserved
  27219. 0x00, 0x00, 0x00, 0x00, // reserved
  27220. 0x00, 0x00, 0x00, 0x00, // reserved
  27221. 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  27222. ]);
  27223. HDLR_TYPES = {
  27224. video: VIDEO_HDLR,
  27225. audio: AUDIO_HDLR
  27226. };
  27227. DREF = new Uint8Array([0x00, // version 0
  27228. 0x00, 0x00, 0x00, // flags
  27229. 0x00, 0x00, 0x00, 0x01, // entry_count
  27230. 0x00, 0x00, 0x00, 0x0c, // entry_size
  27231. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  27232. 0x00, // version 0
  27233. 0x00, 0x00, 0x01 // entry_flags
  27234. ]);
  27235. SMHD = new Uint8Array([0x00, // version
  27236. 0x00, 0x00, 0x00, // flags
  27237. 0x00, 0x00, // balance, 0 means centered
  27238. 0x00, 0x00 // reserved
  27239. ]);
  27240. STCO = new Uint8Array([0x00, // version
  27241. 0x00, 0x00, 0x00, // flags
  27242. 0x00, 0x00, 0x00, 0x00 // entry_count
  27243. ]);
  27244. STSC = STCO;
  27245. STSZ = new Uint8Array([0x00, // version
  27246. 0x00, 0x00, 0x00, // flags
  27247. 0x00, 0x00, 0x00, 0x00, // sample_size
  27248. 0x00, 0x00, 0x00, 0x00 // sample_count
  27249. ]);
  27250. STTS = STCO;
  27251. VMHD = new Uint8Array([0x00, // version
  27252. 0x00, 0x00, 0x01, // flags
  27253. 0x00, 0x00, // graphicsmode
  27254. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
  27255. ]);
  27256. })();
  27257. box = function box(type) {
  27258. var payload = [],
  27259. size = 0,
  27260. i,
  27261. result,
  27262. view;
  27263. for (i = 1; i < arguments.length; i++) {
  27264. payload.push(arguments[i]);
  27265. }
  27266. i = payload.length; // calculate the total size we need to allocate
  27267. while (i--) {
  27268. size += payload[i].byteLength;
  27269. }
  27270. result = new Uint8Array(size + 8);
  27271. view = new DataView(result.buffer, result.byteOffset, result.byteLength);
  27272. view.setUint32(0, result.byteLength);
  27273. result.set(type, 4); // copy the payload into the result
  27274. for (i = 0, size = 8; i < payload.length; i++) {
  27275. result.set(payload[i], size);
  27276. size += payload[i].byteLength;
  27277. }
  27278. return result;
  27279. };
  27280. dinf = function dinf() {
  27281. return box(types.dinf, box(types.dref, DREF));
  27282. };
  27283. esds = function esds(track) {
  27284. return box(types.esds, new Uint8Array([0x00, // version
  27285. 0x00, 0x00, 0x00, // flags
  27286. // ES_Descriptor
  27287. 0x03, // tag, ES_DescrTag
  27288. 0x19, // length
  27289. 0x00, 0x00, // ES_ID
  27290. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  27291. // DecoderConfigDescriptor
  27292. 0x04, // tag, DecoderConfigDescrTag
  27293. 0x11, // length
  27294. 0x40, // object type
  27295. 0x15, // streamType
  27296. 0x00, 0x06, 0x00, // bufferSizeDB
  27297. 0x00, 0x00, 0xda, 0xc0, // maxBitrate
  27298. 0x00, 0x00, 0xda, 0xc0, // avgBitrate
  27299. // DecoderSpecificInfo
  27300. 0x05, // tag, DecoderSpecificInfoTag
  27301. 0x02, // length
  27302. // ISO/IEC 14496-3, AudioSpecificConfig
  27303. // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  27304. track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
  27305. ]));
  27306. };
  27307. ftyp = function ftyp() {
  27308. return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
  27309. };
  27310. hdlr = function hdlr(type) {
  27311. return box(types.hdlr, HDLR_TYPES[type]);
  27312. };
  27313. mdat = function mdat(data) {
  27314. return box(types.mdat, data);
  27315. };
  27316. mdhd = function mdhd(track) {
  27317. var result = new Uint8Array([0x00, // version 0
  27318. 0x00, 0x00, 0x00, // flags
  27319. 0x00, 0x00, 0x00, 0x02, // creation_time
  27320. 0x00, 0x00, 0x00, 0x03, // modification_time
  27321. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  27322. track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
  27323. 0x55, 0xc4, // 'und' language (undetermined)
  27324. 0x00, 0x00]); // Use the sample rate from the track metadata, when it is
  27325. // defined. The sample rate can be parsed out of an ADTS header, for
  27326. // instance.
  27327. if (track.samplerate) {
  27328. result[12] = track.samplerate >>> 24 & 0xFF;
  27329. result[13] = track.samplerate >>> 16 & 0xFF;
  27330. result[14] = track.samplerate >>> 8 & 0xFF;
  27331. result[15] = track.samplerate & 0xFF;
  27332. }
  27333. return box(types.mdhd, result);
  27334. };
  27335. mdia = function mdia(track) {
  27336. return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
  27337. };
  27338. mfhd = function mfhd(sequenceNumber) {
  27339. return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
  27340. (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
  27341. ]));
  27342. };
  27343. minf = function minf(track) {
  27344. return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
  27345. };
  27346. moof = function moof(sequenceNumber, tracks) {
  27347. var trackFragments = [],
  27348. i = tracks.length; // build traf boxes for each track fragment
  27349. while (i--) {
  27350. trackFragments[i] = traf(tracks[i]);
  27351. }
  27352. return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
  27353. };
  27354. /**
  27355. * Returns a movie box.
  27356. * @param tracks {array} the tracks associated with this movie
  27357. * @see ISO/IEC 14496-12:2012(E), section 8.2.1
  27358. */
  27359. moov = function moov(tracks) {
  27360. var i = tracks.length,
  27361. boxes = [];
  27362. while (i--) {
  27363. boxes[i] = trak(tracks[i]);
  27364. }
  27365. return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
  27366. };
  27367. mvex = function mvex(tracks) {
  27368. var i = tracks.length,
  27369. boxes = [];
  27370. while (i--) {
  27371. boxes[i] = trex(tracks[i]);
  27372. }
  27373. return box.apply(null, [types.mvex].concat(boxes));
  27374. };
  27375. mvhd = function mvhd(duration) {
  27376. var bytes = new Uint8Array([0x00, // version 0
  27377. 0x00, 0x00, 0x00, // flags
  27378. 0x00, 0x00, 0x00, 0x01, // creation_time
  27379. 0x00, 0x00, 0x00, 0x02, // modification_time
  27380. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  27381. (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
  27382. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  27383. 0x01, 0x00, // 1.0 volume
  27384. 0x00, 0x00, // reserved
  27385. 0x00, 0x00, 0x00, 0x00, // reserved
  27386. 0x00, 0x00, 0x00, 0x00, // reserved
  27387. 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
  27388. 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
  27389. 0xff, 0xff, 0xff, 0xff // next_track_ID
  27390. ]);
  27391. return box(types.mvhd, bytes);
  27392. };
  27393. sdtp = function sdtp(track) {
  27394. var samples = track.samples || [],
  27395. bytes = new Uint8Array(4 + samples.length),
  27396. flags,
  27397. i; // leave the full box header (4 bytes) all zero
  27398. // write the sample table
  27399. for (i = 0; i < samples.length; i++) {
  27400. flags = samples[i].flags;
  27401. bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
  27402. }
  27403. return box(types.sdtp, bytes);
  27404. };
  27405. stbl = function stbl(track) {
  27406. return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
  27407. };
  27408. (function () {
  27409. var videoSample, audioSample;
  27410. stsd = function stsd(track) {
  27411. return box(types.stsd, new Uint8Array([0x00, // version 0
  27412. 0x00, 0x00, 0x00, // flags
  27413. 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
  27414. };
  27415. videoSample = function videoSample(track) {
  27416. var sps = track.sps || [],
  27417. pps = track.pps || [],
  27418. sequenceParameterSets = [],
  27419. pictureParameterSets = [],
  27420. i; // assemble the SPSs
  27421. for (i = 0; i < sps.length; i++) {
  27422. sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
  27423. sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
  27424. sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
  27425. } // assemble the PPSs
  27426. for (i = 0; i < pps.length; i++) {
  27427. pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
  27428. pictureParameterSets.push(pps[i].byteLength & 0xFF);
  27429. pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
  27430. }
  27431. return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  27432. 0x00, 0x01, // data_reference_index
  27433. 0x00, 0x00, // pre_defined
  27434. 0x00, 0x00, // reserved
  27435. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  27436. (track.width & 0xff00) >> 8, track.width & 0xff, // width
  27437. (track.height & 0xff00) >> 8, track.height & 0xff, // height
  27438. 0x00, 0x48, 0x00, 0x00, // horizresolution
  27439. 0x00, 0x48, 0x00, 0x00, // vertresolution
  27440. 0x00, 0x00, 0x00, 0x00, // reserved
  27441. 0x00, 0x01, // frame_count
  27442. 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
  27443. 0x00, 0x18, // depth = 24
  27444. 0x11, 0x11 // pre_defined = -1
  27445. ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
  27446. track.profileIdc, // AVCProfileIndication
  27447. track.profileCompatibility, // profile_compatibility
  27448. track.levelIdc, // AVCLevelIndication
  27449. 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
  27450. ].concat([sps.length // numOfSequenceParameterSets
  27451. ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
  27452. ]).concat(pictureParameterSets))), // "PPS"
  27453. box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  27454. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  27455. 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
  27456. );
  27457. };
  27458. audioSample = function audioSample(track) {
  27459. return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
  27460. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  27461. 0x00, 0x01, // data_reference_index
  27462. // AudioSampleEntry, ISO/IEC 14496-12
  27463. 0x00, 0x00, 0x00, 0x00, // reserved
  27464. 0x00, 0x00, 0x00, 0x00, // reserved
  27465. (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
  27466. (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
  27467. 0x00, 0x00, // pre_defined
  27468. 0x00, 0x00, // reserved
  27469. (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
  27470. // MP4AudioSampleEntry, ISO/IEC 14496-14
  27471. ]), esds(track));
  27472. };
  27473. })();
  27474. tkhd = function tkhd(track) {
  27475. var result = new Uint8Array([0x00, // version 0
  27476. 0x00, 0x00, 0x07, // flags
  27477. 0x00, 0x00, 0x00, 0x00, // creation_time
  27478. 0x00, 0x00, 0x00, 0x00, // modification_time
  27479. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  27480. 0x00, 0x00, 0x00, 0x00, // reserved
  27481. (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
  27482. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  27483. 0x00, 0x00, // layer
  27484. 0x00, 0x00, // alternate_group
  27485. 0x01, 0x00, // non-audio track volume
  27486. 0x00, 0x00, // reserved
  27487. 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
  27488. (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
  27489. (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
  27490. ]);
  27491. return box(types.tkhd, result);
  27492. };
  27493. /**
  27494. * Generate a track fragment (traf) box. A traf box collects metadata
  27495. * about tracks in a movie fragment (moof) box.
  27496. */
  27497. traf = function traf(track) {
  27498. var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
  27499. trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
  27500. 0x00, 0x00, 0x3a, // flags
  27501. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  27502. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  27503. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  27504. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  27505. 0x00, 0x00, 0x00, 0x00 // default_sample_flags
  27506. ]));
  27507. upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
  27508. lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
  27509. trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
  27510. 0x00, 0x00, 0x00, // flags
  27511. // baseMediaDecodeTime
  27512. 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
  27513. // the containing moof to the first payload byte of the associated
  27514. // mdat
  27515. dataOffset = 32 + // tfhd
  27516. 20 + // tfdt
  27517. 8 + // traf header
  27518. 16 + // mfhd
  27519. 8 + // moof header
  27520. 8; // mdat header
  27521. // audio tracks require less metadata
  27522. if (track.type === 'audio') {
  27523. trackFragmentRun = trun(track, dataOffset);
  27524. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
  27525. } // video tracks should contain an independent and disposable samples
  27526. // box (sdtp)
  27527. // generate one and adjust offsets to match
  27528. sampleDependencyTable = sdtp(track);
  27529. trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
  27530. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
  27531. };
  27532. /**
  27533. * Generate a track box.
  27534. * @param track {object} a track definition
  27535. * @return {Uint8Array} the track box
  27536. */
  27537. trak = function trak(track) {
  27538. track.duration = track.duration || 0xffffffff;
  27539. return box(types.trak, tkhd(track), mdia(track));
  27540. };
  27541. trex = function trex(track) {
  27542. var result = new Uint8Array([0x00, // version 0
  27543. 0x00, 0x00, 0x00, // flags
  27544. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  27545. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  27546. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  27547. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  27548. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  27549. ]); // the last two bytes of default_sample_flags is the sample
  27550. // degradation priority, a hint about the importance of this sample
  27551. // relative to others. Lower the degradation priority for all sample
  27552. // types other than video.
  27553. if (track.type !== 'video') {
  27554. result[result.length - 1] = 0x00;
  27555. }
  27556. return box(types.trex, result);
  27557. };
  27558. (function () {
  27559. var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
  27560. // duration is present for the first sample, it will be present for
  27561. // all subsequent samples.
  27562. // see ISO/IEC 14496-12:2012, Section 8.8.8.1
  27563. trunHeader = function trunHeader(samples, offset) {
  27564. var durationPresent = 0,
  27565. sizePresent = 0,
  27566. flagsPresent = 0,
  27567. compositionTimeOffset = 0; // trun flag constants
  27568. if (samples.length) {
  27569. if (samples[0].duration !== undefined) {
  27570. durationPresent = 0x1;
  27571. }
  27572. if (samples[0].size !== undefined) {
  27573. sizePresent = 0x2;
  27574. }
  27575. if (samples[0].flags !== undefined) {
  27576. flagsPresent = 0x4;
  27577. }
  27578. if (samples[0].compositionTimeOffset !== undefined) {
  27579. compositionTimeOffset = 0x8;
  27580. }
  27581. }
  27582. return [0x00, // version 0
  27583. 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
  27584. (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
  27585. (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
  27586. ];
  27587. };
  27588. videoTrun = function videoTrun(track, offset) {
  27589. var bytes, samples, sample, i;
  27590. samples = track.samples || [];
  27591. offset += 8 + 12 + 16 * samples.length;
  27592. bytes = trunHeader(samples, offset);
  27593. for (i = 0; i < samples.length; i++) {
  27594. sample = samples[i];
  27595. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  27596. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
  27597. 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
  27598. (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
  27599. ]);
  27600. }
  27601. return box(types.trun, new Uint8Array(bytes));
  27602. };
  27603. audioTrun = function audioTrun(track, offset) {
  27604. var bytes, samples, sample, i;
  27605. samples = track.samples || [];
  27606. offset += 8 + 12 + 8 * samples.length;
  27607. bytes = trunHeader(samples, offset);
  27608. for (i = 0; i < samples.length; i++) {
  27609. sample = samples[i];
  27610. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  27611. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
  27612. }
  27613. return box(types.trun, new Uint8Array(bytes));
  27614. };
  27615. trun = function trun(track, offset) {
  27616. if (track.type === 'audio') {
  27617. return audioTrun(track, offset);
  27618. }
  27619. return videoTrun(track, offset);
  27620. };
  27621. })();
  27622. var mp4Generator = {
  27623. ftyp: ftyp,
  27624. mdat: mdat,
  27625. moof: moof,
  27626. moov: moov,
  27627. initSegment: function initSegment(tracks) {
  27628. var fileType = ftyp(),
  27629. movie = moov(tracks),
  27630. result;
  27631. result = new Uint8Array(fileType.byteLength + movie.byteLength);
  27632. result.set(fileType);
  27633. result.set(movie, fileType.byteLength);
  27634. return result;
  27635. }
  27636. };
  27637. /**
  27638. * mux.js
  27639. *
  27640. * Copyright (c) 2014 Brightcove
  27641. * All rights reserved.
  27642. *
  27643. * A lightweight readable stream implemention that handles event dispatching.
  27644. * Objects that inherit from streams should call init in their constructors.
  27645. */
  27646. var Stream$1 = function Stream() {
  27647. this.init = function () {
  27648. var listeners = {};
  27649. /**
  27650. * Add a listener for a specified event type.
  27651. * @param type {string} the event name
  27652. * @param listener {function} the callback to be invoked when an event of
  27653. * the specified type occurs
  27654. */
  27655. this.on = function (type, listener) {
  27656. if (!listeners[type]) {
  27657. listeners[type] = [];
  27658. }
  27659. listeners[type] = listeners[type].concat(listener);
  27660. };
  27661. /**
  27662. * Remove a listener for a specified event type.
  27663. * @param type {string} the event name
  27664. * @param listener {function} a function previously registered for this
  27665. * type of event through `on`
  27666. */
  27667. this.off = function (type, listener) {
  27668. var index;
  27669. if (!listeners[type]) {
  27670. return false;
  27671. }
  27672. index = listeners[type].indexOf(listener);
  27673. listeners[type] = listeners[type].slice();
  27674. listeners[type].splice(index, 1);
  27675. return index > -1;
  27676. };
  27677. /**
  27678. * Trigger an event of the specified type on this stream. Any additional
  27679. * arguments to this function are passed as parameters to event listeners.
  27680. * @param type {string} the event name
  27681. */
  27682. this.trigger = function (type) {
  27683. var callbacks, i, length, args;
  27684. callbacks = listeners[type];
  27685. if (!callbacks) {
  27686. return;
  27687. } // Slicing the arguments on every invocation of this method
  27688. // can add a significant amount of overhead. Avoid the
  27689. // intermediate object creation for the common case of a
  27690. // single callback argument
  27691. if (arguments.length === 2) {
  27692. length = callbacks.length;
  27693. for (i = 0; i < length; ++i) {
  27694. callbacks[i].call(this, arguments[1]);
  27695. }
  27696. } else {
  27697. args = [];
  27698. i = arguments.length;
  27699. for (i = 1; i < arguments.length; ++i) {
  27700. args.push(arguments[i]);
  27701. }
  27702. length = callbacks.length;
  27703. for (i = 0; i < length; ++i) {
  27704. callbacks[i].apply(this, args);
  27705. }
  27706. }
  27707. };
  27708. /**
  27709. * Destroys the stream and cleans up.
  27710. */
  27711. this.dispose = function () {
  27712. listeners = {};
  27713. };
  27714. };
  27715. };
  27716. /**
  27717. * Forwards all `data` events on this stream to the destination stream. The
  27718. * destination stream should provide a method `push` to receive the data
  27719. * events as they arrive.
  27720. * @param destination {stream} the stream that will receive all `data` events
  27721. * @param autoFlush {boolean} if false, we will not call `flush` on the destination
  27722. * when the current stream emits a 'done' event
  27723. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  27724. */
  27725. Stream$1.prototype.pipe = function (destination) {
  27726. this.on('data', function (data) {
  27727. destination.push(data);
  27728. });
  27729. this.on('done', function (flushSource) {
  27730. destination.flush(flushSource);
  27731. });
  27732. return destination;
  27733. }; // Default stream functions that are expected to be overridden to perform
  27734. // actual work. These are provided by the prototype as a sort of no-op
  27735. // implementation so that we don't have to check for their existence in the
  27736. // `pipe` function above.
  27737. Stream$1.prototype.push = function (data) {
  27738. this.trigger('data', data);
  27739. };
  27740. Stream$1.prototype.flush = function (flushSource) {
  27741. this.trigger('done', flushSource);
  27742. };
  27743. var stream = Stream$1;
  27744. // Convert an array of nal units into an array of frames with each frame being
  27745. // composed of the nal units that make up that frame
  27746. // Also keep track of cummulative data about the frame from the nal units such
  27747. // as the frame duration, starting pts, etc.
  27748. var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
  27749. var i,
  27750. currentNal,
  27751. currentFrame = [],
  27752. frames = [];
  27753. currentFrame.byteLength = 0;
  27754. for (i = 0; i < nalUnits.length; i++) {
  27755. currentNal = nalUnits[i]; // Split on 'aud'-type nal units
  27756. if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  27757. // Since the very first nal unit is expected to be an AUD
  27758. // only push to the frames array when currentFrame is not empty
  27759. if (currentFrame.length) {
  27760. currentFrame.duration = currentNal.dts - currentFrame.dts;
  27761. frames.push(currentFrame);
  27762. }
  27763. currentFrame = [currentNal];
  27764. currentFrame.byteLength = currentNal.data.byteLength;
  27765. currentFrame.pts = currentNal.pts;
  27766. currentFrame.dts = currentNal.dts;
  27767. } else {
  27768. // Specifically flag key frames for ease of use later
  27769. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  27770. currentFrame.keyFrame = true;
  27771. }
  27772. currentFrame.duration = currentNal.dts - currentFrame.dts;
  27773. currentFrame.byteLength += currentNal.data.byteLength;
  27774. currentFrame.push(currentNal);
  27775. }
  27776. } // For the last frame, use the duration of the previous frame if we
  27777. // have nothing better to go on
  27778. if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
  27779. currentFrame.duration = frames[frames.length - 1].duration;
  27780. } // Push the final frame
  27781. frames.push(currentFrame);
  27782. return frames;
  27783. }; // Convert an array of frames into an array of Gop with each Gop being composed
  27784. // of the frames that make up that Gop
  27785. // Also keep track of cummulative data about the Gop from the frames such as the
  27786. // Gop duration, starting pts, etc.
  27787. var groupFramesIntoGops = function groupFramesIntoGops(frames) {
  27788. var i,
  27789. currentFrame,
  27790. currentGop = [],
  27791. gops = []; // We must pre-set some of the values on the Gop since we
  27792. // keep running totals of these values
  27793. currentGop.byteLength = 0;
  27794. currentGop.nalCount = 0;
  27795. currentGop.duration = 0;
  27796. currentGop.pts = frames[0].pts;
  27797. currentGop.dts = frames[0].dts; // store some metadata about all the Gops
  27798. gops.byteLength = 0;
  27799. gops.nalCount = 0;
  27800. gops.duration = 0;
  27801. gops.pts = frames[0].pts;
  27802. gops.dts = frames[0].dts;
  27803. for (i = 0; i < frames.length; i++) {
  27804. currentFrame = frames[i];
  27805. if (currentFrame.keyFrame) {
  27806. // Since the very first frame is expected to be an keyframe
  27807. // only push to the gops array when currentGop is not empty
  27808. if (currentGop.length) {
  27809. gops.push(currentGop);
  27810. gops.byteLength += currentGop.byteLength;
  27811. gops.nalCount += currentGop.nalCount;
  27812. gops.duration += currentGop.duration;
  27813. }
  27814. currentGop = [currentFrame];
  27815. currentGop.nalCount = currentFrame.length;
  27816. currentGop.byteLength = currentFrame.byteLength;
  27817. currentGop.pts = currentFrame.pts;
  27818. currentGop.dts = currentFrame.dts;
  27819. currentGop.duration = currentFrame.duration;
  27820. } else {
  27821. currentGop.duration += currentFrame.duration;
  27822. currentGop.nalCount += currentFrame.length;
  27823. currentGop.byteLength += currentFrame.byteLength;
  27824. currentGop.push(currentFrame);
  27825. }
  27826. }
  27827. if (gops.length && currentGop.duration <= 0) {
  27828. currentGop.duration = gops[gops.length - 1].duration;
  27829. }
  27830. gops.byteLength += currentGop.byteLength;
  27831. gops.nalCount += currentGop.nalCount;
  27832. gops.duration += currentGop.duration; // push the final Gop
  27833. gops.push(currentGop);
  27834. return gops;
  27835. };
  27836. /*
  27837. * Search for the first keyframe in the GOPs and throw away all frames
  27838. * until that keyframe. Then extend the duration of the pulled keyframe
  27839. * and pull the PTS and DTS of the keyframe so that it covers the time
  27840. * range of the frames that were disposed.
  27841. *
  27842. * @param {Array} gops video GOPs
  27843. * @returns {Array} modified video GOPs
  27844. */
  27845. var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
  27846. var currentGop;
  27847. if (!gops[0][0].keyFrame && gops.length > 1) {
  27848. // Remove the first GOP
  27849. currentGop = gops.shift();
  27850. gops.byteLength -= currentGop.byteLength;
  27851. gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
  27852. // first gop to cover the time period of the
  27853. // frames we just removed
  27854. gops[0][0].dts = currentGop.dts;
  27855. gops[0][0].pts = currentGop.pts;
  27856. gops[0][0].duration += currentGop.duration;
  27857. }
  27858. return gops;
  27859. };
  27860. /**
  27861. * Default sample object
  27862. * see ISO/IEC 14496-12:2012, section 8.6.4.3
  27863. */
  27864. var createDefaultSample = function createDefaultSample() {
  27865. return {
  27866. size: 0,
  27867. flags: {
  27868. isLeading: 0,
  27869. dependsOn: 1,
  27870. isDependedOn: 0,
  27871. hasRedundancy: 0,
  27872. degradationPriority: 0,
  27873. isNonSyncSample: 1
  27874. }
  27875. };
  27876. };
  27877. /*
  27878. * Collates information from a video frame into an object for eventual
  27879. * entry into an MP4 sample table.
  27880. *
  27881. * @param {Object} frame the video frame
  27882. * @param {Number} dataOffset the byte offset to position the sample
  27883. * @return {Object} object containing sample table info for a frame
  27884. */
  27885. var sampleForFrame = function sampleForFrame(frame, dataOffset) {
  27886. var sample = createDefaultSample();
  27887. sample.dataOffset = dataOffset;
  27888. sample.compositionTimeOffset = frame.pts - frame.dts;
  27889. sample.duration = frame.duration;
  27890. sample.size = 4 * frame.length; // Space for nal unit size
  27891. sample.size += frame.byteLength;
  27892. if (frame.keyFrame) {
  27893. sample.flags.dependsOn = 2;
  27894. sample.flags.isNonSyncSample = 0;
  27895. }
  27896. return sample;
  27897. }; // generate the track's sample table from an array of gops
  27898. var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
  27899. var h,
  27900. i,
  27901. sample,
  27902. currentGop,
  27903. currentFrame,
  27904. dataOffset = baseDataOffset || 0,
  27905. samples = [];
  27906. for (h = 0; h < gops.length; h++) {
  27907. currentGop = gops[h];
  27908. for (i = 0; i < currentGop.length; i++) {
  27909. currentFrame = currentGop[i];
  27910. sample = sampleForFrame(currentFrame, dataOffset);
  27911. dataOffset += sample.size;
  27912. samples.push(sample);
  27913. }
  27914. }
  27915. return samples;
  27916. }; // generate the track's raw mdat data from an array of gops
  27917. var concatenateNalData = function concatenateNalData(gops) {
  27918. var h,
  27919. i,
  27920. j,
  27921. currentGop,
  27922. currentFrame,
  27923. currentNal,
  27924. dataOffset = 0,
  27925. nalsByteLength = gops.byteLength,
  27926. numberOfNals = gops.nalCount,
  27927. totalByteLength = nalsByteLength + 4 * numberOfNals,
  27928. data = new Uint8Array(totalByteLength),
  27929. view = new DataView(data.buffer); // For each Gop..
  27930. for (h = 0; h < gops.length; h++) {
  27931. currentGop = gops[h]; // For each Frame..
  27932. for (i = 0; i < currentGop.length; i++) {
  27933. currentFrame = currentGop[i]; // For each NAL..
  27934. for (j = 0; j < currentFrame.length; j++) {
  27935. currentNal = currentFrame[j];
  27936. view.setUint32(dataOffset, currentNal.data.byteLength);
  27937. dataOffset += 4;
  27938. data.set(currentNal.data, dataOffset);
  27939. dataOffset += currentNal.data.byteLength;
  27940. }
  27941. }
  27942. }
  27943. return data;
  27944. };
  27945. var frameUtils = {
  27946. groupNalsIntoFrames: groupNalsIntoFrames,
  27947. groupFramesIntoGops: groupFramesIntoGops,
  27948. extendFirstKeyFrame: extendFirstKeyFrame,
  27949. generateSampleTable: generateSampleTable,
  27950. concatenateNalData: concatenateNalData
  27951. };
  27952. var highPrefix = [33, 16, 5, 32, 164, 27];
  27953. var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
  27954. var zeroFill = function zeroFill(count) {
  27955. var a = [];
  27956. while (count--) {
  27957. a.push(0);
  27958. }
  27959. return a;
  27960. };
  27961. var makeTable = function makeTable(metaTable) {
  27962. return Object.keys(metaTable).reduce(function (obj, key) {
  27963. obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
  27964. return arr.concat(part);
  27965. }, []));
  27966. return obj;
  27967. }, {});
  27968. }; // Frames-of-silence to use for filling in missing AAC frames
  27969. var coneOfSilence = {
  27970. 96000: [highPrefix, [227, 64], zeroFill(154), [56]],
  27971. 88200: [highPrefix, [231], zeroFill(170), [56]],
  27972. 64000: [highPrefix, [248, 192], zeroFill(240), [56]],
  27973. 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
  27974. 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
  27975. 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
  27976. 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
  27977. 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
  27978. 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]],
  27979. 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]],
  27980. 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
  27981. };
  27982. var silence = makeTable(coneOfSilence);
  27983. var ONE_SECOND_IN_TS = 90000,
  27984. // 90kHz clock
  27985. secondsToVideoTs,
  27986. secondsToAudioTs,
  27987. videoTsToSeconds,
  27988. audioTsToSeconds,
  27989. audioTsToVideoTs,
  27990. videoTsToAudioTs;
  27991. secondsToVideoTs = function secondsToVideoTs(seconds) {
  27992. return seconds * ONE_SECOND_IN_TS;
  27993. };
  27994. secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
  27995. return seconds * sampleRate;
  27996. };
  27997. videoTsToSeconds = function videoTsToSeconds(timestamp) {
  27998. return timestamp / ONE_SECOND_IN_TS;
  27999. };
  28000. audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
  28001. return timestamp / sampleRate;
  28002. };
  28003. audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
  28004. return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
  28005. };
  28006. videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
  28007. return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
  28008. };
  28009. var clock = {
  28010. secondsToVideoTs: secondsToVideoTs,
  28011. secondsToAudioTs: secondsToAudioTs,
  28012. videoTsToSeconds: videoTsToSeconds,
  28013. audioTsToSeconds: audioTsToSeconds,
  28014. audioTsToVideoTs: audioTsToVideoTs,
  28015. videoTsToAudioTs: videoTsToAudioTs
  28016. };
  28017. var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
  28018. /**
  28019. * Sum the `byteLength` properties of the data in each AAC frame
  28020. */
  28021. var sumFrameByteLengths = function sumFrameByteLengths(array) {
  28022. var i,
  28023. currentObj,
  28024. sum = 0; // sum the byteLength's all each nal unit in the frame
  28025. for (i = 0; i < array.length; i++) {
  28026. currentObj = array[i];
  28027. sum += currentObj.data.byteLength;
  28028. }
  28029. return sum;
  28030. }; // Possibly pad (prefix) the audio track with silence if appending this track
  28031. // would lead to the introduction of a gap in the audio buffer
  28032. var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
  28033. var baseMediaDecodeTimeTs,
  28034. frameDuration = 0,
  28035. audioGapDuration = 0,
  28036. audioFillFrameCount = 0,
  28037. audioFillDuration = 0,
  28038. silentFrame,
  28039. i;
  28040. if (!frames.length) {
  28041. return;
  28042. }
  28043. baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
  28044. frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
  28045. if (audioAppendStartTs && videoBaseMediaDecodeTime) {
  28046. // insert the shortest possible amount (audio gap or audio to video gap)
  28047. audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
  28048. audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
  28049. audioFillDuration = audioFillFrameCount * frameDuration;
  28050. } // don't attempt to fill gaps smaller than a single frame or larger
  28051. // than a half second
  28052. if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
  28053. return;
  28054. }
  28055. silentFrame = silence[track.samplerate];
  28056. if (!silentFrame) {
  28057. // we don't have a silent frame pregenerated for the sample rate, so use a frame
  28058. // from the content instead
  28059. silentFrame = frames[0].data;
  28060. }
  28061. for (i = 0; i < audioFillFrameCount; i++) {
  28062. frames.splice(i, 0, {
  28063. data: silentFrame
  28064. });
  28065. }
  28066. track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
  28067. }; // If the audio segment extends before the earliest allowed dts
  28068. // value, remove AAC frames until starts at or after the earliest
  28069. // allowed DTS so that we don't end up with a negative baseMedia-
  28070. // DecodeTime for the audio track
  28071. var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
  28072. if (track.minSegmentDts >= earliestAllowedDts) {
  28073. return adtsFrames;
  28074. } // We will need to recalculate the earliest segment Dts
  28075. track.minSegmentDts = Infinity;
  28076. return adtsFrames.filter(function (currentFrame) {
  28077. // If this is an allowed frame, keep it and record it's Dts
  28078. if (currentFrame.dts >= earliestAllowedDts) {
  28079. track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
  28080. track.minSegmentPts = track.minSegmentDts;
  28081. return true;
  28082. } // Otherwise, discard it
  28083. return false;
  28084. });
  28085. }; // generate the track's raw mdat data from an array of frames
  28086. var generateSampleTable$1 = function generateSampleTable(frames) {
  28087. var i,
  28088. currentFrame,
  28089. samples = [];
  28090. for (i = 0; i < frames.length; i++) {
  28091. currentFrame = frames[i];
  28092. samples.push({
  28093. size: currentFrame.data.byteLength,
  28094. duration: 1024 // For AAC audio, all samples contain 1024 samples
  28095. });
  28096. }
  28097. return samples;
  28098. }; // generate the track's sample table from an array of frames
  28099. var concatenateFrameData = function concatenateFrameData(frames) {
  28100. var i,
  28101. currentFrame,
  28102. dataOffset = 0,
  28103. data = new Uint8Array(sumFrameByteLengths(frames));
  28104. for (i = 0; i < frames.length; i++) {
  28105. currentFrame = frames[i];
  28106. data.set(currentFrame.data, dataOffset);
  28107. dataOffset += currentFrame.data.byteLength;
  28108. }
  28109. return data;
  28110. };
  28111. var audioFrameUtils = {
  28112. prefixWithSilence: prefixWithSilence,
  28113. trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
  28114. generateSampleTable: generateSampleTable$1,
  28115. concatenateFrameData: concatenateFrameData
  28116. };
  28117. var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock
  28118. /**
  28119. * Store information about the start and end of the track and the
  28120. * duration for each frame/sample we process in order to calculate
  28121. * the baseMediaDecodeTime
  28122. */
  28123. var collectDtsInfo = function collectDtsInfo(track, data) {
  28124. if (typeof data.pts === 'number') {
  28125. if (track.timelineStartInfo.pts === undefined) {
  28126. track.timelineStartInfo.pts = data.pts;
  28127. }
  28128. if (track.minSegmentPts === undefined) {
  28129. track.minSegmentPts = data.pts;
  28130. } else {
  28131. track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
  28132. }
  28133. if (track.maxSegmentPts === undefined) {
  28134. track.maxSegmentPts = data.pts;
  28135. } else {
  28136. track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
  28137. }
  28138. }
  28139. if (typeof data.dts === 'number') {
  28140. if (track.timelineStartInfo.dts === undefined) {
  28141. track.timelineStartInfo.dts = data.dts;
  28142. }
  28143. if (track.minSegmentDts === undefined) {
  28144. track.minSegmentDts = data.dts;
  28145. } else {
  28146. track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
  28147. }
  28148. if (track.maxSegmentDts === undefined) {
  28149. track.maxSegmentDts = data.dts;
  28150. } else {
  28151. track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
  28152. }
  28153. }
  28154. };
  28155. /**
  28156. * Clear values used to calculate the baseMediaDecodeTime between
  28157. * tracks
  28158. */
  28159. var clearDtsInfo = function clearDtsInfo(track) {
  28160. delete track.minSegmentDts;
  28161. delete track.maxSegmentDts;
  28162. delete track.minSegmentPts;
  28163. delete track.maxSegmentPts;
  28164. };
  28165. /**
  28166. * Calculate the track's baseMediaDecodeTime based on the earliest
  28167. * DTS the transmuxer has ever seen and the minimum DTS for the
  28168. * current track
  28169. * @param track {object} track metadata configuration
  28170. * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
  28171. * in the source; false to adjust the first segment to start at 0.
  28172. */
  28173. var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
  28174. var baseMediaDecodeTime,
  28175. scale,
  28176. minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
  28177. if (!keepOriginalTimestamps) {
  28178. minSegmentDts -= track.timelineStartInfo.dts;
  28179. } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
  28180. // we want the start of the first segment to be placed
  28181. baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
  28182. baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
  28183. baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
  28184. if (track.type === 'audio') {
  28185. // Audio has a different clock equal to the sampling_rate so we need to
  28186. // scale the PTS values into the clock rate of the track
  28187. scale = track.samplerate / ONE_SECOND_IN_TS$2;
  28188. baseMediaDecodeTime *= scale;
  28189. baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
  28190. }
  28191. return baseMediaDecodeTime;
  28192. };
  28193. var trackDecodeInfo = {
  28194. clearDtsInfo: clearDtsInfo,
  28195. calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
  28196. collectDtsInfo: collectDtsInfo
  28197. };
  28198. /**
  28199. * mux.js
  28200. *
  28201. * Copyright (c) 2015 Brightcove
  28202. * All rights reserved.
  28203. *
  28204. * Reads in-band caption information from a video elementary
  28205. * stream. Captions must follow the CEA-708 standard for injection
  28206. * into an MPEG-2 transport streams.
  28207. * @see https://en.wikipedia.org/wiki/CEA-708
  28208. * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
  28209. */
  28210. // payload type field to indicate how they are to be
  28211. // interpreted. CEAS-708 caption content is always transmitted with
  28212. // payload type 0x04.
  28213. var USER_DATA_REGISTERED_ITU_T_T35 = 4,
  28214. RBSP_TRAILING_BITS = 128;
  28215. /**
  28216. * Parse a supplemental enhancement information (SEI) NAL unit.
  28217. * Stops parsing once a message of type ITU T T35 has been found.
  28218. *
  28219. * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  28220. * @return {object} the parsed SEI payload
  28221. * @see Rec. ITU-T H.264, 7.3.2.3.1
  28222. */
  28223. var parseSei = function parseSei(bytes) {
  28224. var i = 0,
  28225. result = {
  28226. payloadType: -1,
  28227. payloadSize: 0
  28228. },
  28229. payloadType = 0,
  28230. payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
  28231. while (i < bytes.byteLength) {
  28232. // stop once we have hit the end of the sei_rbsp
  28233. if (bytes[i] === RBSP_TRAILING_BITS) {
  28234. break;
  28235. } // Parse payload type
  28236. while (bytes[i] === 0xFF) {
  28237. payloadType += 255;
  28238. i++;
  28239. }
  28240. payloadType += bytes[i++]; // Parse payload size
  28241. while (bytes[i] === 0xFF) {
  28242. payloadSize += 255;
  28243. i++;
  28244. }
  28245. payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
  28246. // there can only ever be one caption message in a frame's sei
  28247. if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
  28248. result.payloadType = payloadType;
  28249. result.payloadSize = payloadSize;
  28250. result.payload = bytes.subarray(i, i + payloadSize);
  28251. break;
  28252. } // skip the payload and parse the next message
  28253. i += payloadSize;
  28254. payloadType = 0;
  28255. payloadSize = 0;
  28256. }
  28257. return result;
  28258. }; // see ANSI/SCTE 128-1 (2013), section 8.1
  28259. var parseUserData = function parseUserData(sei) {
  28260. // itu_t_t35_contry_code must be 181 (United States) for
  28261. // captions
  28262. if (sei.payload[0] !== 181) {
  28263. return null;
  28264. } // itu_t_t35_provider_code should be 49 (ATSC) for captions
  28265. if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
  28266. return null;
  28267. } // the user_identifier should be "GA94" to indicate ATSC1 data
  28268. if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
  28269. return null;
  28270. } // finally, user_data_type_code should be 0x03 for caption data
  28271. if (sei.payload[7] !== 0x03) {
  28272. return null;
  28273. } // return the user_data_type_structure and strip the trailing
  28274. // marker bits
  28275. return sei.payload.subarray(8, sei.payload.length - 1);
  28276. }; // see CEA-708-D, section 4.4
  28277. var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  28278. var results = [],
  28279. i,
  28280. count,
  28281. offset,
  28282. data; // if this is just filler, return immediately
  28283. if (!(userData[0] & 0x40)) {
  28284. return results;
  28285. } // parse out the cc_data_1 and cc_data_2 fields
  28286. count = userData[0] & 0x1f;
  28287. for (i = 0; i < count; i++) {
  28288. offset = i * 3;
  28289. data = {
  28290. type: userData[offset + 2] & 0x03,
  28291. pts: pts
  28292. }; // capture cc data when cc_valid is 1
  28293. if (userData[offset + 2] & 0x04) {
  28294. data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
  28295. results.push(data);
  28296. }
  28297. }
  28298. return results;
  28299. };
  28300. var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  28301. var length = data.byteLength,
  28302. emulationPreventionBytesPositions = [],
  28303. i = 1,
  28304. newLength,
  28305. newData; // Find all `Emulation Prevention Bytes`
  28306. while (i < length - 2) {
  28307. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  28308. emulationPreventionBytesPositions.push(i + 2);
  28309. i += 2;
  28310. } else {
  28311. i++;
  28312. }
  28313. } // If no Emulation Prevention Bytes were found just return the original
  28314. // array
  28315. if (emulationPreventionBytesPositions.length === 0) {
  28316. return data;
  28317. } // Create a new array to hold the NAL unit data
  28318. newLength = length - emulationPreventionBytesPositions.length;
  28319. newData = new Uint8Array(newLength);
  28320. var sourceIndex = 0;
  28321. for (i = 0; i < newLength; sourceIndex++, i++) {
  28322. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  28323. // Skip this byte
  28324. sourceIndex++; // Remove this position index
  28325. emulationPreventionBytesPositions.shift();
  28326. }
  28327. newData[i] = data[sourceIndex];
  28328. }
  28329. return newData;
  28330. }; // exports
  28331. var captionPacketParser = {
  28332. parseSei: parseSei,
  28333. parseUserData: parseUserData,
  28334. parseCaptionPackets: parseCaptionPackets,
  28335. discardEmulationPreventionBytes: discardEmulationPreventionBytes,
  28336. USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
  28337. };
  28338. // Link To Transport
  28339. // -----------------
  28340. var CaptionStream = function CaptionStream() {
  28341. CaptionStream.prototype.init.call(this);
  28342. this.captionPackets_ = [];
  28343. this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
  28344. new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
  28345. new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
  28346. new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
  28347. ];
  28348. this.reset(); // forward data and done events from CCs to this CaptionStream
  28349. this.ccStreams_.forEach(function (cc) {
  28350. cc.on('data', this.trigger.bind(this, 'data'));
  28351. cc.on('done', this.trigger.bind(this, 'done'));
  28352. }, this);
  28353. };
  28354. CaptionStream.prototype = new stream();
  28355. CaptionStream.prototype.push = function (event) {
  28356. var sei, userData, newCaptionPackets; // only examine SEI NALs
  28357. if (event.nalUnitType !== 'sei_rbsp') {
  28358. return;
  28359. } // parse the sei
  28360. sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
  28361. if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
  28362. return;
  28363. } // parse out the user data payload
  28364. userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
  28365. if (!userData) {
  28366. return;
  28367. } // Sometimes, the same segment # will be downloaded twice. To stop the
  28368. // caption data from being processed twice, we track the latest dts we've
  28369. // received and ignore everything with a dts before that. However, since
  28370. // data for a specific dts can be split across packets on either side of
  28371. // a segment boundary, we need to make sure we *don't* ignore the packets
  28372. // from the *next* segment that have dts === this.latestDts_. By constantly
  28373. // tracking the number of packets received with dts === this.latestDts_, we
  28374. // know how many should be ignored once we start receiving duplicates.
  28375. if (event.dts < this.latestDts_) {
  28376. // We've started getting older data, so set the flag.
  28377. this.ignoreNextEqualDts_ = true;
  28378. return;
  28379. } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
  28380. this.numSameDts_--;
  28381. if (!this.numSameDts_) {
  28382. // We've received the last duplicate packet, time to start processing again
  28383. this.ignoreNextEqualDts_ = false;
  28384. }
  28385. return;
  28386. } // parse out CC data packets and save them for later
  28387. newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
  28388. this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
  28389. if (this.latestDts_ !== event.dts) {
  28390. this.numSameDts_ = 0;
  28391. }
  28392. this.numSameDts_++;
  28393. this.latestDts_ = event.dts;
  28394. };
  28395. CaptionStream.prototype.flush = function () {
  28396. // make sure we actually parsed captions before proceeding
  28397. if (!this.captionPackets_.length) {
  28398. this.ccStreams_.forEach(function (cc) {
  28399. cc.flush();
  28400. }, this);
  28401. return;
  28402. } // In Chrome, the Array#sort function is not stable so add a
  28403. // presortIndex that we can use to ensure we get a stable-sort
  28404. this.captionPackets_.forEach(function (elem, idx) {
  28405. elem.presortIndex = idx;
  28406. }); // sort caption byte-pairs based on their PTS values
  28407. this.captionPackets_.sort(function (a, b) {
  28408. if (a.pts === b.pts) {
  28409. return a.presortIndex - b.presortIndex;
  28410. }
  28411. return a.pts - b.pts;
  28412. });
  28413. this.captionPackets_.forEach(function (packet) {
  28414. if (packet.type < 2) {
  28415. // Dispatch packet to the right Cea608Stream
  28416. this.dispatchCea608Packet(packet);
  28417. } // this is where an 'else' would go for a dispatching packets
  28418. // to a theoretical Cea708Stream that handles SERVICEn data
  28419. }, this);
  28420. this.captionPackets_.length = 0;
  28421. this.ccStreams_.forEach(function (cc) {
  28422. cc.flush();
  28423. }, this);
  28424. return;
  28425. };
  28426. CaptionStream.prototype.reset = function () {
  28427. this.latestDts_ = null;
  28428. this.ignoreNextEqualDts_ = false;
  28429. this.numSameDts_ = 0;
  28430. this.activeCea608Channel_ = [null, null];
  28431. this.ccStreams_.forEach(function (ccStream) {
  28432. ccStream.reset();
  28433. });
  28434. };
  28435. CaptionStream.prototype.dispatchCea608Packet = function (packet) {
  28436. // NOTE: packet.type is the CEA608 field
  28437. if (this.setsChannel1Active(packet)) {
  28438. this.activeCea608Channel_[packet.type] = 0;
  28439. } else if (this.setsChannel2Active(packet)) {
  28440. this.activeCea608Channel_[packet.type] = 1;
  28441. }
  28442. if (this.activeCea608Channel_[packet.type] === null) {
  28443. // If we haven't received anything to set the active channel, discard the
  28444. // data; we don't want jumbled captions
  28445. return;
  28446. }
  28447. this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
  28448. };
  28449. CaptionStream.prototype.setsChannel1Active = function (packet) {
  28450. return (packet.ccData & 0x7800) === 0x1000;
  28451. };
  28452. CaptionStream.prototype.setsChannel2Active = function (packet) {
  28453. return (packet.ccData & 0x7800) === 0x1800;
  28454. }; // ----------------------
  28455. // Session to Application
  28456. // ----------------------
  28457. // This hash maps non-ASCII, special, and extended character codes to their
  28458. // proper Unicode equivalent. The first keys that are only a single byte
  28459. // are the non-standard ASCII characters, which simply map the CEA608 byte
  28460. // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
  28461. // character codes, but have their MSB bitmasked with 0x03 so that a lookup
  28462. // can be performed regardless of the field and data channel on which the
  28463. // character code was received.
  28464. var CHARACTER_TRANSLATION = {
  28465. 0x2a: 0xe1,
  28466. // á
  28467. 0x5c: 0xe9,
  28468. // é
  28469. 0x5e: 0xed,
  28470. // í
  28471. 0x5f: 0xf3,
  28472. // ó
  28473. 0x60: 0xfa,
  28474. // ú
  28475. 0x7b: 0xe7,
  28476. // ç
  28477. 0x7c: 0xf7,
  28478. // ÷
  28479. 0x7d: 0xd1,
  28480. // Ñ
  28481. 0x7e: 0xf1,
  28482. // ñ
  28483. 0x7f: 0x2588,
  28484. // █
  28485. 0x0130: 0xae,
  28486. // ®
  28487. 0x0131: 0xb0,
  28488. // °
  28489. 0x0132: 0xbd,
  28490. // ½
  28491. 0x0133: 0xbf,
  28492. // ¿
  28493. 0x0134: 0x2122,
  28494. // ™
  28495. 0x0135: 0xa2,
  28496. // ¢
  28497. 0x0136: 0xa3,
  28498. // £
  28499. 0x0137: 0x266a,
  28500. // ♪
  28501. 0x0138: 0xe0,
  28502. // à
  28503. 0x0139: 0xa0,
  28504. //
  28505. 0x013a: 0xe8,
  28506. // è
  28507. 0x013b: 0xe2,
  28508. // â
  28509. 0x013c: 0xea,
  28510. // ê
  28511. 0x013d: 0xee,
  28512. // î
  28513. 0x013e: 0xf4,
  28514. // ô
  28515. 0x013f: 0xfb,
  28516. // û
  28517. 0x0220: 0xc1,
  28518. // Á
  28519. 0x0221: 0xc9,
  28520. // É
  28521. 0x0222: 0xd3,
  28522. // Ó
  28523. 0x0223: 0xda,
  28524. // Ú
  28525. 0x0224: 0xdc,
  28526. // Ü
  28527. 0x0225: 0xfc,
  28528. // ü
  28529. 0x0226: 0x2018,
  28530. // ‘
  28531. 0x0227: 0xa1,
  28532. // ¡
  28533. 0x0228: 0x2a,
  28534. // *
  28535. 0x0229: 0x27,
  28536. // '
  28537. 0x022a: 0x2014,
  28538. // —
  28539. 0x022b: 0xa9,
  28540. // ©
  28541. 0x022c: 0x2120,
  28542. // ℠
  28543. 0x022d: 0x2022,
  28544. // •
  28545. 0x022e: 0x201c,
  28546. // “
  28547. 0x022f: 0x201d,
  28548. // ”
  28549. 0x0230: 0xc0,
  28550. // À
  28551. 0x0231: 0xc2,
  28552. // Â
  28553. 0x0232: 0xc7,
  28554. // Ç
  28555. 0x0233: 0xc8,
  28556. // È
  28557. 0x0234: 0xca,
  28558. // Ê
  28559. 0x0235: 0xcb,
  28560. // Ë
  28561. 0x0236: 0xeb,
  28562. // ë
  28563. 0x0237: 0xce,
  28564. // Î
  28565. 0x0238: 0xcf,
  28566. // Ï
  28567. 0x0239: 0xef,
  28568. // ï
  28569. 0x023a: 0xd4,
  28570. // Ô
  28571. 0x023b: 0xd9,
  28572. // Ù
  28573. 0x023c: 0xf9,
  28574. // ù
  28575. 0x023d: 0xdb,
  28576. // Û
  28577. 0x023e: 0xab,
  28578. // «
  28579. 0x023f: 0xbb,
  28580. // »
  28581. 0x0320: 0xc3,
  28582. // Ã
  28583. 0x0321: 0xe3,
  28584. // ã
  28585. 0x0322: 0xcd,
  28586. // Í
  28587. 0x0323: 0xcc,
  28588. // Ì
  28589. 0x0324: 0xec,
  28590. // ì
  28591. 0x0325: 0xd2,
  28592. // Ò
  28593. 0x0326: 0xf2,
  28594. // ò
  28595. 0x0327: 0xd5,
  28596. // Õ
  28597. 0x0328: 0xf5,
  28598. // õ
  28599. 0x0329: 0x7b,
  28600. // {
  28601. 0x032a: 0x7d,
  28602. // }
  28603. 0x032b: 0x5c,
  28604. // \
  28605. 0x032c: 0x5e,
  28606. // ^
  28607. 0x032d: 0x5f,
  28608. // _
  28609. 0x032e: 0x7c,
  28610. // |
  28611. 0x032f: 0x7e,
  28612. // ~
  28613. 0x0330: 0xc4,
  28614. // Ä
  28615. 0x0331: 0xe4,
  28616. // ä
  28617. 0x0332: 0xd6,
  28618. // Ö
  28619. 0x0333: 0xf6,
  28620. // ö
  28621. 0x0334: 0xdf,
  28622. // ß
  28623. 0x0335: 0xa5,
  28624. // ¥
  28625. 0x0336: 0xa4,
  28626. // ¤
  28627. 0x0337: 0x2502,
  28628. // │
  28629. 0x0338: 0xc5,
  28630. // Å
  28631. 0x0339: 0xe5,
  28632. // å
  28633. 0x033a: 0xd8,
  28634. // Ø
  28635. 0x033b: 0xf8,
  28636. // ø
  28637. 0x033c: 0x250c,
  28638. // ┌
  28639. 0x033d: 0x2510,
  28640. // ┐
  28641. 0x033e: 0x2514,
  28642. // └
  28643. 0x033f: 0x2518 // ┘
  28644. };
  28645. var getCharFromCode = function getCharFromCode(code) {
  28646. if (code === null) {
  28647. return '';
  28648. }
  28649. code = CHARACTER_TRANSLATION[code] || code;
  28650. return String.fromCharCode(code);
  28651. }; // the index of the last row in a CEA-608 display buffer
  28652. var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
  28653. // getting it through bit logic.
  28654. 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
  28655. // cells. The "bottom" row is the last element in the outer array.
  28656. var createDisplayBuffer = function createDisplayBuffer() {
  28657. var result = [],
  28658. i = BOTTOM_ROW + 1;
  28659. while (i--) {
  28660. result.push('');
  28661. }
  28662. return result;
  28663. };
  28664. var Cea608Stream = function Cea608Stream(field, dataChannel) {
  28665. Cea608Stream.prototype.init.call(this);
  28666. this.field_ = field || 0;
  28667. this.dataChannel_ = dataChannel || 0;
  28668. this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
  28669. this.setConstants();
  28670. this.reset();
  28671. this.push = function (packet) {
  28672. var data, swap, char0, char1, text; // remove the parity bits
  28673. data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
  28674. if (data === this.lastControlCode_) {
  28675. this.lastControlCode_ = null;
  28676. return;
  28677. } // Store control codes
  28678. if ((data & 0xf000) === 0x1000) {
  28679. this.lastControlCode_ = data;
  28680. } else if (data !== this.PADDING_) {
  28681. this.lastControlCode_ = null;
  28682. }
  28683. char0 = data >>> 8;
  28684. char1 = data & 0xff;
  28685. if (data === this.PADDING_) {
  28686. return;
  28687. } else if (data === this.RESUME_CAPTION_LOADING_) {
  28688. this.mode_ = 'popOn';
  28689. } else if (data === this.END_OF_CAPTION_) {
  28690. // If an EOC is received while in paint-on mode, the displayed caption
  28691. // text should be swapped to non-displayed memory as if it was a pop-on
  28692. // caption. Because of that, we should explicitly switch back to pop-on
  28693. // mode
  28694. this.mode_ = 'popOn';
  28695. this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
  28696. this.flushDisplayed(packet.pts); // flip memory
  28697. swap = this.displayed_;
  28698. this.displayed_ = this.nonDisplayed_;
  28699. this.nonDisplayed_ = swap; // start measuring the time to display the caption
  28700. this.startPts_ = packet.pts;
  28701. } else if (data === this.ROLL_UP_2_ROWS_) {
  28702. this.rollUpRows_ = 2;
  28703. this.setRollUp(packet.pts);
  28704. } else if (data === this.ROLL_UP_3_ROWS_) {
  28705. this.rollUpRows_ = 3;
  28706. this.setRollUp(packet.pts);
  28707. } else if (data === this.ROLL_UP_4_ROWS_) {
  28708. this.rollUpRows_ = 4;
  28709. this.setRollUp(packet.pts);
  28710. } else if (data === this.CARRIAGE_RETURN_) {
  28711. this.clearFormatting(packet.pts);
  28712. this.flushDisplayed(packet.pts);
  28713. this.shiftRowsUp_();
  28714. this.startPts_ = packet.pts;
  28715. } else if (data === this.BACKSPACE_) {
  28716. if (this.mode_ === 'popOn') {
  28717. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  28718. } else {
  28719. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  28720. }
  28721. } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
  28722. this.flushDisplayed(packet.pts);
  28723. this.displayed_ = createDisplayBuffer();
  28724. } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
  28725. this.nonDisplayed_ = createDisplayBuffer();
  28726. } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
  28727. if (this.mode_ !== 'paintOn') {
  28728. // NOTE: This should be removed when proper caption positioning is
  28729. // implemented
  28730. this.flushDisplayed(packet.pts);
  28731. this.displayed_ = createDisplayBuffer();
  28732. }
  28733. this.mode_ = 'paintOn';
  28734. this.startPts_ = packet.pts; // Append special characters to caption text
  28735. } else if (this.isSpecialCharacter(char0, char1)) {
  28736. // Bitmask char0 so that we can apply character transformations
  28737. // regardless of field and data channel.
  28738. // Then byte-shift to the left and OR with char1 so we can pass the
  28739. // entire character code to `getCharFromCode`.
  28740. char0 = (char0 & 0x03) << 8;
  28741. text = getCharFromCode(char0 | char1);
  28742. this[this.mode_](packet.pts, text);
  28743. this.column_++; // Append extended characters to caption text
  28744. } else if (this.isExtCharacter(char0, char1)) {
  28745. // Extended characters always follow their "non-extended" equivalents.
  28746. // IE if a "è" is desired, you'll always receive "eè"; non-compliant
  28747. // decoders are supposed to drop the "è", while compliant decoders
  28748. // backspace the "e" and insert "è".
  28749. // Delete the previous character
  28750. if (this.mode_ === 'popOn') {
  28751. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  28752. } else {
  28753. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  28754. } // Bitmask char0 so that we can apply character transformations
  28755. // regardless of field and data channel.
  28756. // Then byte-shift to the left and OR with char1 so we can pass the
  28757. // entire character code to `getCharFromCode`.
  28758. char0 = (char0 & 0x03) << 8;
  28759. text = getCharFromCode(char0 | char1);
  28760. this[this.mode_](packet.pts, text);
  28761. this.column_++; // Process mid-row codes
  28762. } else if (this.isMidRowCode(char0, char1)) {
  28763. // Attributes are not additive, so clear all formatting
  28764. this.clearFormatting(packet.pts); // According to the standard, mid-row codes
  28765. // should be replaced with spaces, so add one now
  28766. this[this.mode_](packet.pts, ' ');
  28767. this.column_++;
  28768. if ((char1 & 0xe) === 0xe) {
  28769. this.addFormatting(packet.pts, ['i']);
  28770. }
  28771. if ((char1 & 0x1) === 0x1) {
  28772. this.addFormatting(packet.pts, ['u']);
  28773. } // Detect offset control codes and adjust cursor
  28774. } else if (this.isOffsetControlCode(char0, char1)) {
  28775. // Cursor position is set by indent PAC (see below) in 4-column
  28776. // increments, with an additional offset code of 1-3 to reach any
  28777. // of the 32 columns specified by CEA-608. So all we need to do
  28778. // here is increment the column cursor by the given offset.
  28779. this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
  28780. } else if (this.isPAC(char0, char1)) {
  28781. // There's no logic for PAC -> row mapping, so we have to just
  28782. // find the row code in an array and use its index :(
  28783. var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
  28784. if (this.mode_ === 'rollUp') {
  28785. // This implies that the base row is incorrectly set.
  28786. // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
  28787. // of roll-up rows set.
  28788. if (row - this.rollUpRows_ + 1 < 0) {
  28789. row = this.rollUpRows_ - 1;
  28790. }
  28791. this.setRollUp(packet.pts, row);
  28792. }
  28793. if (row !== this.row_) {
  28794. // formatting is only persistent for current row
  28795. this.clearFormatting(packet.pts);
  28796. this.row_ = row;
  28797. } // All PACs can apply underline, so detect and apply
  28798. // (All odd-numbered second bytes set underline)
  28799. if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
  28800. this.addFormatting(packet.pts, ['u']);
  28801. }
  28802. if ((data & 0x10) === 0x10) {
  28803. // We've got an indent level code. Each successive even number
  28804. // increments the column cursor by 4, so we can get the desired
  28805. // column position by bit-shifting to the right (to get n/2)
  28806. // and multiplying by 4.
  28807. this.column_ = ((data & 0xe) >> 1) * 4;
  28808. }
  28809. if (this.isColorPAC(char1)) {
  28810. // it's a color code, though we only support white, which
  28811. // can be either normal or italicized. white italics can be
  28812. // either 0x4e or 0x6e depending on the row, so we just
  28813. // bitwise-and with 0xe to see if italics should be turned on
  28814. if ((char1 & 0xe) === 0xe) {
  28815. this.addFormatting(packet.pts, ['i']);
  28816. }
  28817. } // We have a normal character in char0, and possibly one in char1
  28818. } else if (this.isNormalChar(char0)) {
  28819. if (char1 === 0x00) {
  28820. char1 = null;
  28821. }
  28822. text = getCharFromCode(char0);
  28823. text += getCharFromCode(char1);
  28824. this[this.mode_](packet.pts, text);
  28825. this.column_ += text.length;
  28826. } // finish data processing
  28827. };
  28828. };
  28829. Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
  28830. // display buffer
  28831. Cea608Stream.prototype.flushDisplayed = function (pts) {
  28832. var content = this.displayed_ // remove spaces from the start and end of the string
  28833. .map(function (row) {
  28834. try {
  28835. return row.trim();
  28836. } catch (e) {
  28837. // Ordinarily, this shouldn't happen. However, caption
  28838. // parsing errors should not throw exceptions and
  28839. // break playback.
  28840. // eslint-disable-next-line no-console
  28841. console.error('Skipping malformed caption.');
  28842. return '';
  28843. }
  28844. }) // combine all text rows to display in one cue
  28845. .join('\n') // and remove blank rows from the start and end, but not the middle
  28846. .replace(/^\n+|\n+$/g, '');
  28847. if (content.length) {
  28848. this.trigger('data', {
  28849. startPts: this.startPts_,
  28850. endPts: pts,
  28851. text: content,
  28852. stream: this.name_
  28853. });
  28854. }
  28855. };
  28856. /**
  28857. * Zero out the data, used for startup and on seek
  28858. */
  28859. Cea608Stream.prototype.reset = function () {
  28860. this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
  28861. // actually display captions. If a caption is shifted to a row
  28862. // with a lower index than this, it is cleared from the display
  28863. // buffer
  28864. this.topRow_ = 0;
  28865. this.startPts_ = 0;
  28866. this.displayed_ = createDisplayBuffer();
  28867. this.nonDisplayed_ = createDisplayBuffer();
  28868. this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
  28869. this.column_ = 0;
  28870. this.row_ = BOTTOM_ROW;
  28871. this.rollUpRows_ = 2; // This variable holds currently-applied formatting
  28872. this.formatting_ = [];
  28873. };
  28874. /**
  28875. * Sets up control code and related constants for this instance
  28876. */
  28877. Cea608Stream.prototype.setConstants = function () {
  28878. // The following attributes have these uses:
  28879. // ext_ : char0 for mid-row codes, and the base for extended
  28880. // chars (ext_+0, ext_+1, and ext_+2 are char0s for
  28881. // extended codes)
  28882. // control_: char0 for control codes, except byte-shifted to the
  28883. // left so that we can do this.control_ | CONTROL_CODE
  28884. // offset_: char0 for tab offset codes
  28885. //
  28886. // It's also worth noting that control codes, and _only_ control codes,
  28887. // differ between field 1 and field2. Field 2 control codes are always
  28888. // their field 1 value plus 1. That's why there's the "| field" on the
  28889. // control value.
  28890. if (this.dataChannel_ === 0) {
  28891. this.BASE_ = 0x10;
  28892. this.EXT_ = 0x11;
  28893. this.CONTROL_ = (0x14 | this.field_) << 8;
  28894. this.OFFSET_ = 0x17;
  28895. } else if (this.dataChannel_ === 1) {
  28896. this.BASE_ = 0x18;
  28897. this.EXT_ = 0x19;
  28898. this.CONTROL_ = (0x1c | this.field_) << 8;
  28899. this.OFFSET_ = 0x1f;
  28900. } // Constants for the LSByte command codes recognized by Cea608Stream. This
  28901. // list is not exhaustive. For a more comprehensive listing and semantics see
  28902. // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
  28903. // Padding
  28904. this.PADDING_ = 0x0000; // Pop-on Mode
  28905. this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
  28906. this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
  28907. this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
  28908. this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
  28909. this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
  28910. this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
  28911. this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
  28912. this.BACKSPACE_ = this.CONTROL_ | 0x21;
  28913. this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
  28914. this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
  28915. };
  28916. /**
  28917. * Detects if the 2-byte packet data is a special character
  28918. *
  28919. * Special characters have a second byte in the range 0x30 to 0x3f,
  28920. * with the first byte being 0x11 (for data channel 1) or 0x19 (for
  28921. * data channel 2).
  28922. *
  28923. * @param {Integer} char0 The first byte
  28924. * @param {Integer} char1 The second byte
  28925. * @return {Boolean} Whether the 2 bytes are an special character
  28926. */
  28927. Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
  28928. return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
  28929. };
  28930. /**
  28931. * Detects if the 2-byte packet data is an extended character
  28932. *
  28933. * Extended characters have a second byte in the range 0x20 to 0x3f,
  28934. * with the first byte being 0x12 or 0x13 (for data channel 1) or
  28935. * 0x1a or 0x1b (for data channel 2).
  28936. *
  28937. * @param {Integer} char0 The first byte
  28938. * @param {Integer} char1 The second byte
  28939. * @return {Boolean} Whether the 2 bytes are an extended character
  28940. */
  28941. Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
  28942. return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
  28943. };
  28944. /**
  28945. * Detects if the 2-byte packet is a mid-row code
  28946. *
  28947. * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
  28948. * the first byte being 0x11 (for data channel 1) or 0x19 (for data
  28949. * channel 2).
  28950. *
  28951. * @param {Integer} char0 The first byte
  28952. * @param {Integer} char1 The second byte
  28953. * @return {Boolean} Whether the 2 bytes are a mid-row code
  28954. */
  28955. Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
  28956. return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
  28957. };
  28958. /**
  28959. * Detects if the 2-byte packet is an offset control code
  28960. *
  28961. * Offset control codes have a second byte in the range 0x21 to 0x23,
  28962. * with the first byte being 0x17 (for data channel 1) or 0x1f (for
  28963. * data channel 2).
  28964. *
  28965. * @param {Integer} char0 The first byte
  28966. * @param {Integer} char1 The second byte
  28967. * @return {Boolean} Whether the 2 bytes are an offset control code
  28968. */
  28969. Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
  28970. return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
  28971. };
  28972. /**
  28973. * Detects if the 2-byte packet is a Preamble Address Code
  28974. *
  28975. * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
  28976. * or 0x18 to 0x1f (for data channel 2), with the second byte in the
  28977. * range 0x40 to 0x7f.
  28978. *
  28979. * @param {Integer} char0 The first byte
  28980. * @param {Integer} char1 The second byte
  28981. * @return {Boolean} Whether the 2 bytes are a PAC
  28982. */
  28983. Cea608Stream.prototype.isPAC = function (char0, char1) {
  28984. return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
  28985. };
  28986. /**
  28987. * Detects if a packet's second byte is in the range of a PAC color code
  28988. *
  28989. * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
  28990. * 0x60 to 0x6f.
  28991. *
  28992. * @param {Integer} char1 The second byte
  28993. * @return {Boolean} Whether the byte is a color PAC
  28994. */
  28995. Cea608Stream.prototype.isColorPAC = function (char1) {
  28996. return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
  28997. };
  28998. /**
  28999. * Detects if a single byte is in the range of a normal character
  29000. *
  29001. * Normal text bytes are in the range 0x20 to 0x7f.
  29002. *
  29003. * @param {Integer} char The byte
  29004. * @return {Boolean} Whether the byte is a normal character
  29005. */
  29006. Cea608Stream.prototype.isNormalChar = function (char) {
  29007. return char >= 0x20 && char <= 0x7f;
  29008. };
  29009. /**
  29010. * Configures roll-up
  29011. *
  29012. * @param {Integer} pts Current PTS
  29013. * @param {Integer} newBaseRow Used by PACs to slide the current window to
  29014. * a new position
  29015. */
  29016. Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
  29017. // Reset the base row to the bottom row when switching modes
  29018. if (this.mode_ !== 'rollUp') {
  29019. this.row_ = BOTTOM_ROW;
  29020. this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
  29021. this.flushDisplayed(pts);
  29022. this.nonDisplayed_ = createDisplayBuffer();
  29023. this.displayed_ = createDisplayBuffer();
  29024. }
  29025. if (newBaseRow !== undefined && newBaseRow !== this.row_) {
  29026. // move currently displayed captions (up or down) to the new base row
  29027. for (var i = 0; i < this.rollUpRows_; i++) {
  29028. this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
  29029. this.displayed_[this.row_ - i] = '';
  29030. }
  29031. }
  29032. if (newBaseRow === undefined) {
  29033. newBaseRow = this.row_;
  29034. }
  29035. this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
  29036. }; // Adds the opening HTML tag for the passed character to the caption text,
  29037. // and keeps track of it for later closing
  29038. Cea608Stream.prototype.addFormatting = function (pts, format) {
  29039. this.formatting_ = this.formatting_.concat(format);
  29040. var text = format.reduce(function (text, format) {
  29041. return text + '<' + format + '>';
  29042. }, '');
  29043. this[this.mode_](pts, text);
  29044. }; // Adds HTML closing tags for current formatting to caption text and
  29045. // clears remembered formatting
  29046. Cea608Stream.prototype.clearFormatting = function (pts) {
  29047. if (!this.formatting_.length) {
  29048. return;
  29049. }
  29050. var text = this.formatting_.reverse().reduce(function (text, format) {
  29051. return text + '</' + format + '>';
  29052. }, '');
  29053. this.formatting_ = [];
  29054. this[this.mode_](pts, text);
  29055. }; // Mode Implementations
  29056. Cea608Stream.prototype.popOn = function (pts, text) {
  29057. var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
  29058. baseRow += text;
  29059. this.nonDisplayed_[this.row_] = baseRow;
  29060. };
  29061. Cea608Stream.prototype.rollUp = function (pts, text) {
  29062. var baseRow = this.displayed_[this.row_];
  29063. baseRow += text;
  29064. this.displayed_[this.row_] = baseRow;
  29065. };
  29066. Cea608Stream.prototype.shiftRowsUp_ = function () {
  29067. var i; // clear out inactive rows
  29068. for (i = 0; i < this.topRow_; i++) {
  29069. this.displayed_[i] = '';
  29070. }
  29071. for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
  29072. this.displayed_[i] = '';
  29073. } // shift displayed rows up
  29074. for (i = this.topRow_; i < this.row_; i++) {
  29075. this.displayed_[i] = this.displayed_[i + 1];
  29076. } // clear out the bottom row
  29077. this.displayed_[this.row_] = '';
  29078. };
  29079. Cea608Stream.prototype.paintOn = function (pts, text) {
  29080. var baseRow = this.displayed_[this.row_];
  29081. baseRow += text;
  29082. this.displayed_[this.row_] = baseRow;
  29083. }; // exports
  29084. var captionStream = {
  29085. CaptionStream: CaptionStream,
  29086. Cea608Stream: Cea608Stream
  29087. };
  29088. var streamTypes = {
  29089. H264_STREAM_TYPE: 0x1B,
  29090. ADTS_STREAM_TYPE: 0x0F,
  29091. METADATA_STREAM_TYPE: 0x15
  29092. };
  29093. var MAX_TS = 8589934592;
  29094. var RO_THRESH = 4294967296;
  29095. var handleRollover = function handleRollover(value, reference) {
  29096. var direction = 1;
  29097. if (value > reference) {
  29098. // If the current timestamp value is greater than our reference timestamp and we detect a
  29099. // timestamp rollover, this means the roll over is happening in the opposite direction.
  29100. // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
  29101. // point will be set to a small number, e.g. 1. The user then seeks backwards over the
  29102. // rollover point. In loading this segment, the timestamp values will be very large,
  29103. // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
  29104. // the time stamp to be `value - 2^33`.
  29105. direction = -1;
  29106. } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
  29107. // cause an incorrect adjustment.
  29108. while (Math.abs(reference - value) > RO_THRESH) {
  29109. value += direction * MAX_TS;
  29110. }
  29111. return value;
  29112. };
  29113. var TimestampRolloverStream = function TimestampRolloverStream(type) {
  29114. var lastDTS, referenceDTS;
  29115. TimestampRolloverStream.prototype.init.call(this);
  29116. this.type_ = type;
  29117. this.push = function (data) {
  29118. if (data.type !== this.type_) {
  29119. return;
  29120. }
  29121. if (referenceDTS === undefined) {
  29122. referenceDTS = data.dts;
  29123. }
  29124. data.dts = handleRollover(data.dts, referenceDTS);
  29125. data.pts = handleRollover(data.pts, referenceDTS);
  29126. lastDTS = data.dts;
  29127. this.trigger('data', data);
  29128. };
  29129. this.flush = function () {
  29130. referenceDTS = lastDTS;
  29131. this.trigger('done');
  29132. };
  29133. this.discontinuity = function () {
  29134. referenceDTS = void 0;
  29135. lastDTS = void 0;
  29136. };
  29137. };
  29138. TimestampRolloverStream.prototype = new stream();
  29139. var timestampRolloverStream = {
  29140. TimestampRolloverStream: TimestampRolloverStream,
  29141. handleRollover: handleRollover
  29142. };
  29143. var percentEncode = function percentEncode(bytes, start, end) {
  29144. var i,
  29145. result = '';
  29146. for (i = start; i < end; i++) {
  29147. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  29148. }
  29149. return result;
  29150. },
  29151. // return the string representation of the specified byte range,
  29152. // interpreted as UTf-8.
  29153. parseUtf8 = function parseUtf8(bytes, start, end) {
  29154. return decodeURIComponent(percentEncode(bytes, start, end));
  29155. },
  29156. // return the string representation of the specified byte range,
  29157. // interpreted as ISO-8859-1.
  29158. parseIso88591 = function parseIso88591(bytes, start, end) {
  29159. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  29160. },
  29161. parseSyncSafeInteger = function parseSyncSafeInteger(data) {
  29162. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  29163. },
  29164. tagParsers = {
  29165. TXXX: function TXXX(tag) {
  29166. var i;
  29167. if (tag.data[0] !== 3) {
  29168. // ignore frames with unrecognized character encodings
  29169. return;
  29170. }
  29171. for (i = 1; i < tag.data.length; i++) {
  29172. if (tag.data[i] === 0) {
  29173. // parse the text fields
  29174. tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
  29175. tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
  29176. break;
  29177. }
  29178. }
  29179. tag.data = tag.value;
  29180. },
  29181. WXXX: function WXXX(tag) {
  29182. var i;
  29183. if (tag.data[0] !== 3) {
  29184. // ignore frames with unrecognized character encodings
  29185. return;
  29186. }
  29187. for (i = 1; i < tag.data.length; i++) {
  29188. if (tag.data[i] === 0) {
  29189. // parse the description and URL fields
  29190. tag.description = parseUtf8(tag.data, 1, i);
  29191. tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
  29192. break;
  29193. }
  29194. }
  29195. },
  29196. PRIV: function PRIV(tag) {
  29197. var i;
  29198. for (i = 0; i < tag.data.length; i++) {
  29199. if (tag.data[i] === 0) {
  29200. // parse the description and URL fields
  29201. tag.owner = parseIso88591(tag.data, 0, i);
  29202. break;
  29203. }
  29204. }
  29205. tag.privateData = tag.data.subarray(i + 1);
  29206. tag.data = tag.privateData;
  29207. }
  29208. },
  29209. _MetadataStream;
  29210. _MetadataStream = function MetadataStream(options) {
  29211. var settings = {
  29212. debug: !!(options && options.debug),
  29213. // the bytes of the program-level descriptor field in MP2T
  29214. // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
  29215. // program element descriptors"
  29216. descriptor: options && options.descriptor
  29217. },
  29218. // the total size in bytes of the ID3 tag being parsed
  29219. tagSize = 0,
  29220. // tag data that is not complete enough to be parsed
  29221. buffer = [],
  29222. // the total number of bytes currently in the buffer
  29223. bufferSize = 0,
  29224. i;
  29225. _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
  29226. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  29227. this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
  29228. if (settings.descriptor) {
  29229. for (i = 0; i < settings.descriptor.length; i++) {
  29230. this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
  29231. }
  29232. }
  29233. this.push = function (chunk) {
  29234. var tag, frameStart, frameSize, frame, i, frameHeader;
  29235. if (chunk.type !== 'timed-metadata') {
  29236. return;
  29237. } // if data_alignment_indicator is set in the PES header,
  29238. // we must have the start of a new ID3 tag. Assume anything
  29239. // remaining in the buffer was malformed and throw it out
  29240. if (chunk.dataAlignmentIndicator) {
  29241. bufferSize = 0;
  29242. buffer.length = 0;
  29243. } // ignore events that don't look like ID3 data
  29244. 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))) {
  29245. if (settings.debug) {
  29246. // eslint-disable-next-line no-console
  29247. console.log('Skipping unrecognized metadata packet');
  29248. }
  29249. return;
  29250. } // add this chunk to the data we've collected so far
  29251. buffer.push(chunk);
  29252. bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
  29253. if (buffer.length === 1) {
  29254. // the frame size is transmitted as a 28-bit integer in the
  29255. // last four bytes of the ID3 header.
  29256. // The most significant bit of each byte is dropped and the
  29257. // results concatenated to recover the actual value.
  29258. tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  29259. // convenient for our comparisons to include it
  29260. tagSize += 10;
  29261. } // if the entire frame has not arrived, wait for more data
  29262. if (bufferSize < tagSize) {
  29263. return;
  29264. } // collect the entire frame so it can be parsed
  29265. tag = {
  29266. data: new Uint8Array(tagSize),
  29267. frames: [],
  29268. pts: buffer[0].pts,
  29269. dts: buffer[0].dts
  29270. };
  29271. for (i = 0; i < tagSize;) {
  29272. tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
  29273. i += buffer[0].data.byteLength;
  29274. bufferSize -= buffer[0].data.byteLength;
  29275. buffer.shift();
  29276. } // find the start of the first frame and the end of the tag
  29277. frameStart = 10;
  29278. if (tag.data[5] & 0x40) {
  29279. // advance the frame start past the extended header
  29280. frameStart += 4; // header size field
  29281. frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
  29282. tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
  29283. } // parse one or more ID3 frames
  29284. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  29285. do {
  29286. // determine the number of bytes in this frame
  29287. frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
  29288. if (frameSize < 1) {
  29289. // eslint-disable-next-line no-console
  29290. return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
  29291. }
  29292. frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
  29293. frame = {
  29294. id: frameHeader,
  29295. data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
  29296. };
  29297. frame.key = frame.id;
  29298. if (tagParsers[frame.id]) {
  29299. tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
  29300. // time for raw AAC data
  29301. if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
  29302. var d = frame.data,
  29303. size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  29304. size *= 4;
  29305. size += d[7] & 0x03;
  29306. frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
  29307. // on the value of this frame
  29308. // we couldn't have known the appropriate pts and dts before
  29309. // parsing this ID3 tag so set those values now
  29310. if (tag.pts === undefined && tag.dts === undefined) {
  29311. tag.pts = frame.timeStamp;
  29312. tag.dts = frame.timeStamp;
  29313. }
  29314. this.trigger('timestamp', frame);
  29315. }
  29316. }
  29317. tag.frames.push(frame);
  29318. frameStart += 10; // advance past the frame header
  29319. frameStart += frameSize; // advance past the frame body
  29320. } while (frameStart < tagSize);
  29321. this.trigger('data', tag);
  29322. };
  29323. };
  29324. _MetadataStream.prototype = new stream();
  29325. var metadataStream = _MetadataStream;
  29326. var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
  29327. var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
  29328. var MP2T_PACKET_LENGTH = 188,
  29329. // bytes
  29330. SYNC_BYTE = 0x47;
  29331. /**
  29332. * Splits an incoming stream of binary data into MPEG-2 Transport
  29333. * Stream packets.
  29334. */
  29335. _TransportPacketStream = function TransportPacketStream() {
  29336. var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
  29337. bytesInBuffer = 0;
  29338. _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
  29339. /**
  29340. * Split a stream of data into M2TS packets
  29341. **/
  29342. this.push = function (bytes) {
  29343. var startIndex = 0,
  29344. endIndex = MP2T_PACKET_LENGTH,
  29345. everything; // If there are bytes remaining from the last segment, prepend them to the
  29346. // bytes that were pushed in
  29347. if (bytesInBuffer) {
  29348. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  29349. everything.set(buffer.subarray(0, bytesInBuffer));
  29350. everything.set(bytes, bytesInBuffer);
  29351. bytesInBuffer = 0;
  29352. } else {
  29353. everything = bytes;
  29354. } // While we have enough data for a packet
  29355. while (endIndex < everything.byteLength) {
  29356. // Look for a pair of start and end sync bytes in the data..
  29357. if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
  29358. // We found a packet so emit it and jump one whole packet forward in
  29359. // the stream
  29360. this.trigger('data', everything.subarray(startIndex, endIndex));
  29361. startIndex += MP2T_PACKET_LENGTH;
  29362. endIndex += MP2T_PACKET_LENGTH;
  29363. continue;
  29364. } // If we get here, we have somehow become de-synchronized and we need to step
  29365. // forward one byte at a time until we find a pair of sync bytes that denote
  29366. // a packet
  29367. startIndex++;
  29368. endIndex++;
  29369. } // If there was some data left over at the end of the segment that couldn't
  29370. // possibly be a whole packet, keep it because it might be the start of a packet
  29371. // that continues in the next segment
  29372. if (startIndex < everything.byteLength) {
  29373. buffer.set(everything.subarray(startIndex), 0);
  29374. bytesInBuffer = everything.byteLength - startIndex;
  29375. }
  29376. };
  29377. /**
  29378. * Passes identified M2TS packets to the TransportParseStream to be parsed
  29379. **/
  29380. this.flush = function () {
  29381. // If the buffer contains a whole packet when we are being flushed, emit it
  29382. // and empty the buffer. Otherwise hold onto the data because it may be
  29383. // important for decoding the next segment
  29384. if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
  29385. this.trigger('data', buffer);
  29386. bytesInBuffer = 0;
  29387. }
  29388. this.trigger('done');
  29389. };
  29390. };
  29391. _TransportPacketStream.prototype = new stream();
  29392. /**
  29393. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  29394. * forms of the individual transport stream packets.
  29395. */
  29396. _TransportParseStream = function TransportParseStream() {
  29397. var parsePsi, parsePat, parsePmt, self;
  29398. _TransportParseStream.prototype.init.call(this);
  29399. self = this;
  29400. this.packetsWaitingForPmt = [];
  29401. this.programMapTable = undefined;
  29402. parsePsi = function parsePsi(payload, psi) {
  29403. var offset = 0; // PSI packets may be split into multiple sections and those
  29404. // sections may be split into multiple packets. If a PSI
  29405. // section starts in this packet, the payload_unit_start_indicator
  29406. // will be true and the first byte of the payload will indicate
  29407. // the offset from the current position to the start of the
  29408. // section.
  29409. if (psi.payloadUnitStartIndicator) {
  29410. offset += payload[offset] + 1;
  29411. }
  29412. if (psi.type === 'pat') {
  29413. parsePat(payload.subarray(offset), psi);
  29414. } else {
  29415. parsePmt(payload.subarray(offset), psi);
  29416. }
  29417. };
  29418. parsePat = function parsePat(payload, pat) {
  29419. pat.section_number = payload[7]; // eslint-disable-line camelcase
  29420. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  29421. // skip the PSI header and parse the first PMT entry
  29422. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  29423. pat.pmtPid = self.pmtPid;
  29424. };
  29425. /**
  29426. * Parse out the relevant fields of a Program Map Table (PMT).
  29427. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  29428. * packet. The first byte in this array should be the table_id
  29429. * field.
  29430. * @param pmt {object} the object that should be decorated with
  29431. * fields parsed from the PMT.
  29432. */
  29433. parsePmt = function parsePmt(payload, pmt) {
  29434. var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
  29435. // take effect. We don't believe this should ever be the case
  29436. // for HLS but we'll ignore "forward" PMT declarations if we see
  29437. // them. Future PMT declarations have the current_next_indicator
  29438. // set to zero.
  29439. if (!(payload[5] & 0x01)) {
  29440. return;
  29441. } // overwrite any existing program map table
  29442. self.programMapTable = {
  29443. video: null,
  29444. audio: null,
  29445. 'timed-metadata': {}
  29446. }; // the mapping table ends at the end of the current section
  29447. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  29448. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  29449. // long the program info descriptors are
  29450. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
  29451. offset = 12 + programInfoLength;
  29452. while (offset < tableEnd) {
  29453. var streamType = payload[offset];
  29454. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
  29455. // TODO: should this be done for metadata too? for now maintain behavior of
  29456. // multiple metadata streams
  29457. if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
  29458. self.programMapTable.video = pid;
  29459. } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
  29460. self.programMapTable.audio = pid;
  29461. } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
  29462. // map pid to stream type for metadata streams
  29463. self.programMapTable['timed-metadata'][pid] = streamType;
  29464. } // move to the next table entry
  29465. // skip past the elementary stream descriptors, if present
  29466. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  29467. } // record the map on the packet as well
  29468. pmt.programMapTable = self.programMapTable;
  29469. };
  29470. /**
  29471. * Deliver a new MP2T packet to the next stream in the pipeline.
  29472. */
  29473. this.push = function (packet) {
  29474. var result = {},
  29475. offset = 4;
  29476. result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
  29477. result.pid = packet[1] & 0x1f;
  29478. result.pid <<= 8;
  29479. result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
  29480. // fifth byte of the TS packet header. The adaptation field is
  29481. // used to add stuffing to PES packets that don't fill a complete
  29482. // TS packet, and to specify some forms of timing and control data
  29483. // that we do not currently use.
  29484. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  29485. offset += packet[offset] + 1;
  29486. } // parse the rest of the packet based on the type
  29487. if (result.pid === 0) {
  29488. result.type = 'pat';
  29489. parsePsi(packet.subarray(offset), result);
  29490. this.trigger('data', result);
  29491. } else if (result.pid === this.pmtPid) {
  29492. result.type = 'pmt';
  29493. parsePsi(packet.subarray(offset), result);
  29494. this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
  29495. while (this.packetsWaitingForPmt.length) {
  29496. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  29497. }
  29498. } else if (this.programMapTable === undefined) {
  29499. // When we have not seen a PMT yet, defer further processing of
  29500. // PES packets until one has been parsed
  29501. this.packetsWaitingForPmt.push([packet, offset, result]);
  29502. } else {
  29503. this.processPes_(packet, offset, result);
  29504. }
  29505. };
  29506. this.processPes_ = function (packet, offset, result) {
  29507. // set the appropriate stream type
  29508. if (result.pid === this.programMapTable.video) {
  29509. result.streamType = streamTypes.H264_STREAM_TYPE;
  29510. } else if (result.pid === this.programMapTable.audio) {
  29511. result.streamType = streamTypes.ADTS_STREAM_TYPE;
  29512. } else {
  29513. // if not video or audio, it is timed-metadata or unknown
  29514. // if unknown, streamType will be undefined
  29515. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  29516. }
  29517. result.type = 'pes';
  29518. result.data = packet.subarray(offset);
  29519. this.trigger('data', result);
  29520. };
  29521. };
  29522. _TransportParseStream.prototype = new stream();
  29523. _TransportParseStream.STREAM_TYPES = {
  29524. h264: 0x1b,
  29525. adts: 0x0f
  29526. };
  29527. /**
  29528. * Reconsistutes program elementary stream (PES) packets from parsed
  29529. * transport stream packets. That is, if you pipe an
  29530. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  29531. * events will be events which capture the bytes for individual PES
  29532. * packets plus relevant metadata that has been extracted from the
  29533. * container.
  29534. */
  29535. _ElementaryStream = function ElementaryStream() {
  29536. var self = this,
  29537. // PES packet fragments
  29538. video = {
  29539. data: [],
  29540. size: 0
  29541. },
  29542. audio = {
  29543. data: [],
  29544. size: 0
  29545. },
  29546. timedMetadata = {
  29547. data: [],
  29548. size: 0
  29549. },
  29550. parsePes = function parsePes(payload, pes) {
  29551. var ptsDtsFlags; // get the packet length, this will be 0 for video
  29552. pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
  29553. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
  29554. // and a DTS value. Determine what combination of values is
  29555. // available to work with.
  29556. ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  29557. // performs all bitwise operations on 32-bit integers but javascript
  29558. // supports a much greater range (52-bits) of integer using standard
  29559. // mathematical operations.
  29560. // We construct a 31-bit value using bitwise operators over the 31
  29561. // most significant bits and then multiply by 4 (equal to a left-shift
  29562. // of 2) before we add the final 2 least significant bits of the
  29563. // timestamp (equal to an OR.)
  29564. if (ptsDtsFlags & 0xC0) {
  29565. // the PTS and DTS are not written out directly. For information
  29566. // on how they are encoded, see
  29567. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  29568. pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
  29569. pes.pts *= 4; // Left shift by 2
  29570. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  29571. pes.dts = pes.pts;
  29572. if (ptsDtsFlags & 0x40) {
  29573. pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
  29574. pes.dts *= 4; // Left shift by 2
  29575. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  29576. }
  29577. } // the data section starts immediately after the PES header.
  29578. // pes_header_data_length specifies the number of header bytes
  29579. // that follow the last byte of the field.
  29580. pes.data = payload.subarray(9 + payload[8]);
  29581. },
  29582. /**
  29583. * Pass completely parsed PES packets to the next stream in the pipeline
  29584. **/
  29585. flushStream = function flushStream(stream$$1, type, forceFlush) {
  29586. var packetData = new Uint8Array(stream$$1.size),
  29587. event = {
  29588. type: type
  29589. },
  29590. i = 0,
  29591. offset = 0,
  29592. packetFlushable = false,
  29593. fragment; // do nothing if there is not enough buffered data for a complete
  29594. // PES header
  29595. if (!stream$$1.data.length || stream$$1.size < 9) {
  29596. return;
  29597. }
  29598. event.trackId = stream$$1.data[0].pid; // reassemble the packet
  29599. for (i = 0; i < stream$$1.data.length; i++) {
  29600. fragment = stream$$1.data[i];
  29601. packetData.set(fragment.data, offset);
  29602. offset += fragment.data.byteLength;
  29603. } // parse assembled packet's PES header
  29604. parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
  29605. // check that there is enough stream data to fill the packet
  29606. packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right
  29607. if (forceFlush || packetFlushable) {
  29608. stream$$1.size = 0;
  29609. stream$$1.data.length = 0;
  29610. } // only emit packets that are complete. this is to avoid assembling
  29611. // incomplete PES packets due to poor segmentation
  29612. if (packetFlushable) {
  29613. self.trigger('data', event);
  29614. }
  29615. };
  29616. _ElementaryStream.prototype.init.call(this);
  29617. /**
  29618. * Identifies M2TS packet types and parses PES packets using metadata
  29619. * parsed from the PMT
  29620. **/
  29621. this.push = function (data) {
  29622. ({
  29623. pat: function pat() {// we have to wait for the PMT to arrive as well before we
  29624. // have any meaningful metadata
  29625. },
  29626. pes: function pes() {
  29627. var stream$$1, streamType;
  29628. switch (data.streamType) {
  29629. case streamTypes.H264_STREAM_TYPE:
  29630. case streamTypes.H264_STREAM_TYPE:
  29631. stream$$1 = video;
  29632. streamType = 'video';
  29633. break;
  29634. case streamTypes.ADTS_STREAM_TYPE:
  29635. stream$$1 = audio;
  29636. streamType = 'audio';
  29637. break;
  29638. case streamTypes.METADATA_STREAM_TYPE:
  29639. stream$$1 = timedMetadata;
  29640. streamType = 'timed-metadata';
  29641. break;
  29642. default:
  29643. // ignore unknown stream types
  29644. return;
  29645. } // if a new packet is starting, we can flush the completed
  29646. // packet
  29647. if (data.payloadUnitStartIndicator) {
  29648. flushStream(stream$$1, streamType, true);
  29649. } // buffer this fragment until we are sure we've received the
  29650. // complete payload
  29651. stream$$1.data.push(data);
  29652. stream$$1.size += data.data.byteLength;
  29653. },
  29654. pmt: function pmt() {
  29655. var event = {
  29656. type: 'metadata',
  29657. tracks: []
  29658. },
  29659. programMapTable = data.programMapTable; // translate audio and video streams to tracks
  29660. if (programMapTable.video !== null) {
  29661. event.tracks.push({
  29662. timelineStartInfo: {
  29663. baseMediaDecodeTime: 0
  29664. },
  29665. id: +programMapTable.video,
  29666. codec: 'avc',
  29667. type: 'video'
  29668. });
  29669. }
  29670. if (programMapTable.audio !== null) {
  29671. event.tracks.push({
  29672. timelineStartInfo: {
  29673. baseMediaDecodeTime: 0
  29674. },
  29675. id: +programMapTable.audio,
  29676. codec: 'adts',
  29677. type: 'audio'
  29678. });
  29679. }
  29680. self.trigger('data', event);
  29681. }
  29682. })[data.type]();
  29683. };
  29684. /**
  29685. * Flush any remaining input. Video PES packets may be of variable
  29686. * length. Normally, the start of a new video packet can trigger the
  29687. * finalization of the previous packet. That is not possible if no
  29688. * more video is forthcoming, however. In that case, some other
  29689. * mechanism (like the end of the file) has to be employed. When it is
  29690. * clear that no additional data is forthcoming, calling this method
  29691. * will flush the buffered packets.
  29692. */
  29693. this.flush = function () {
  29694. // !!THIS ORDER IS IMPORTANT!!
  29695. // video first then audio
  29696. flushStream(video, 'video');
  29697. flushStream(audio, 'audio');
  29698. flushStream(timedMetadata, 'timed-metadata');
  29699. this.trigger('done');
  29700. };
  29701. };
  29702. _ElementaryStream.prototype = new stream();
  29703. var m2ts = {
  29704. PAT_PID: 0x0000,
  29705. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
  29706. TransportPacketStream: _TransportPacketStream,
  29707. TransportParseStream: _TransportParseStream,
  29708. ElementaryStream: _ElementaryStream,
  29709. TimestampRolloverStream: TimestampRolloverStream$1,
  29710. CaptionStream: captionStream.CaptionStream,
  29711. Cea608Stream: captionStream.Cea608Stream,
  29712. MetadataStream: metadataStream
  29713. };
  29714. for (var type$1 in streamTypes) {
  29715. if (streamTypes.hasOwnProperty(type$1)) {
  29716. m2ts[type$1] = streamTypes[type$1];
  29717. }
  29718. }
  29719. var m2ts_1 = m2ts;
  29720. var _AdtsStream;
  29721. var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  29722. /*
  29723. * Accepts a ElementaryStream and emits data events with parsed
  29724. * AAC Audio Frames of the individual packets. Input audio in ADTS
  29725. * format is unpacked and re-emitted as AAC frames.
  29726. *
  29727. * @see http://wiki.multimedia.cx/index.php?title=ADTS
  29728. * @see http://wiki.multimedia.cx/?title=Understanding_AAC
  29729. */
  29730. _AdtsStream = function AdtsStream() {
  29731. var buffer;
  29732. _AdtsStream.prototype.init.call(this);
  29733. this.push = function (packet) {
  29734. var i = 0,
  29735. frameNum = 0,
  29736. frameLength,
  29737. protectionSkipBytes,
  29738. frameEnd,
  29739. oldBuffer,
  29740. sampleCount,
  29741. adtsFrameDuration;
  29742. if (packet.type !== 'audio') {
  29743. // ignore non-audio data
  29744. return;
  29745. } // Prepend any data in the buffer to the input data so that we can parse
  29746. // aac frames the cross a PES packet boundary
  29747. if (buffer) {
  29748. oldBuffer = buffer;
  29749. buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
  29750. buffer.set(oldBuffer);
  29751. buffer.set(packet.data, oldBuffer.byteLength);
  29752. } else {
  29753. buffer = packet.data;
  29754. } // unpack any ADTS frames which have been fully received
  29755. // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
  29756. while (i + 5 < buffer.length) {
  29757. // Loook for the start of an ADTS header..
  29758. if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
  29759. // If a valid header was not found, jump one forward and attempt to
  29760. // find a valid ADTS header starting at the next byte
  29761. i++;
  29762. continue;
  29763. } // The protection skip bit tells us if we have 2 bytes of CRC data at the
  29764. // end of the ADTS header
  29765. protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
  29766. // end of the sync sequence
  29767. frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
  29768. sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
  29769. adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
  29770. frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
  29771. // and wait for more data
  29772. if (buffer.byteLength < frameEnd) {
  29773. return;
  29774. } // Otherwise, deliver the complete AAC frame
  29775. this.trigger('data', {
  29776. pts: packet.pts + frameNum * adtsFrameDuration,
  29777. dts: packet.dts + frameNum * adtsFrameDuration,
  29778. sampleCount: sampleCount,
  29779. audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
  29780. channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
  29781. samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
  29782. samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
  29783. // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
  29784. samplesize: 16,
  29785. data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
  29786. }); // If the buffer is empty, clear it and return
  29787. if (buffer.byteLength === frameEnd) {
  29788. buffer = undefined;
  29789. return;
  29790. }
  29791. frameNum++; // Remove the finished frame from the buffer and start the process again
  29792. buffer = buffer.subarray(frameEnd);
  29793. }
  29794. };
  29795. this.flush = function () {
  29796. this.trigger('done');
  29797. };
  29798. };
  29799. _AdtsStream.prototype = new stream();
  29800. var adts = _AdtsStream;
  29801. var ExpGolomb;
  29802. /**
  29803. * Parser for exponential Golomb codes, a variable-bitwidth number encoding
  29804. * scheme used by h264.
  29805. */
  29806. ExpGolomb = function ExpGolomb(workingData) {
  29807. var // the number of bytes left to examine in workingData
  29808. workingBytesAvailable = workingData.byteLength,
  29809. // the current word being examined
  29810. workingWord = 0,
  29811. // :uint
  29812. // the number of bits left to examine in the current word
  29813. workingBitsAvailable = 0; // :uint;
  29814. // ():uint
  29815. this.length = function () {
  29816. return 8 * workingBytesAvailable;
  29817. }; // ():uint
  29818. this.bitsAvailable = function () {
  29819. return 8 * workingBytesAvailable + workingBitsAvailable;
  29820. }; // ():void
  29821. this.loadWord = function () {
  29822. var position = workingData.byteLength - workingBytesAvailable,
  29823. workingBytes = new Uint8Array(4),
  29824. availableBytes = Math.min(4, workingBytesAvailable);
  29825. if (availableBytes === 0) {
  29826. throw new Error('no bytes available');
  29827. }
  29828. workingBytes.set(workingData.subarray(position, position + availableBytes));
  29829. workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
  29830. workingBitsAvailable = availableBytes * 8;
  29831. workingBytesAvailable -= availableBytes;
  29832. }; // (count:int):void
  29833. this.skipBits = function (count) {
  29834. var skipBytes; // :int
  29835. if (workingBitsAvailable > count) {
  29836. workingWord <<= count;
  29837. workingBitsAvailable -= count;
  29838. } else {
  29839. count -= workingBitsAvailable;
  29840. skipBytes = Math.floor(count / 8);
  29841. count -= skipBytes * 8;
  29842. workingBytesAvailable -= skipBytes;
  29843. this.loadWord();
  29844. workingWord <<= count;
  29845. workingBitsAvailable -= count;
  29846. }
  29847. }; // (size:int):uint
  29848. this.readBits = function (size) {
  29849. var bits = Math.min(workingBitsAvailable, size),
  29850. // :uint
  29851. valu = workingWord >>> 32 - bits; // :uint
  29852. // if size > 31, handle error
  29853. workingBitsAvailable -= bits;
  29854. if (workingBitsAvailable > 0) {
  29855. workingWord <<= bits;
  29856. } else if (workingBytesAvailable > 0) {
  29857. this.loadWord();
  29858. }
  29859. bits = size - bits;
  29860. if (bits > 0) {
  29861. return valu << bits | this.readBits(bits);
  29862. }
  29863. return valu;
  29864. }; // ():uint
  29865. this.skipLeadingZeros = function () {
  29866. var leadingZeroCount; // :uint
  29867. for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
  29868. if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
  29869. // the first bit of working word is 1
  29870. workingWord <<= leadingZeroCount;
  29871. workingBitsAvailable -= leadingZeroCount;
  29872. return leadingZeroCount;
  29873. }
  29874. } // we exhausted workingWord and still have not found a 1
  29875. this.loadWord();
  29876. return leadingZeroCount + this.skipLeadingZeros();
  29877. }; // ():void
  29878. this.skipUnsignedExpGolomb = function () {
  29879. this.skipBits(1 + this.skipLeadingZeros());
  29880. }; // ():void
  29881. this.skipExpGolomb = function () {
  29882. this.skipBits(1 + this.skipLeadingZeros());
  29883. }; // ():uint
  29884. this.readUnsignedExpGolomb = function () {
  29885. var clz = this.skipLeadingZeros(); // :uint
  29886. return this.readBits(clz + 1) - 1;
  29887. }; // ():int
  29888. this.readExpGolomb = function () {
  29889. var valu = this.readUnsignedExpGolomb(); // :int
  29890. if (0x01 & valu) {
  29891. // the number is odd if the low order bit is set
  29892. return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
  29893. }
  29894. return -1 * (valu >>> 1); // divide by two then make it negative
  29895. }; // Some convenience functions
  29896. // :Boolean
  29897. this.readBoolean = function () {
  29898. return this.readBits(1) === 1;
  29899. }; // ():int
  29900. this.readUnsignedByte = function () {
  29901. return this.readBits(8);
  29902. };
  29903. this.loadWord();
  29904. };
  29905. var expGolomb = ExpGolomb;
  29906. var _H264Stream, _NalByteStream;
  29907. var PROFILES_WITH_OPTIONAL_SPS_DATA;
  29908. /**
  29909. * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
  29910. */
  29911. _NalByteStream = function NalByteStream() {
  29912. var syncPoint = 0,
  29913. i,
  29914. buffer;
  29915. _NalByteStream.prototype.init.call(this);
  29916. /*
  29917. * Scans a byte stream and triggers a data event with the NAL units found.
  29918. * @param {Object} data Event received from H264Stream
  29919. * @param {Uint8Array} data.data The h264 byte stream to be scanned
  29920. *
  29921. * @see H264Stream.push
  29922. */
  29923. this.push = function (data) {
  29924. var swapBuffer;
  29925. if (!buffer) {
  29926. buffer = data.data;
  29927. } else {
  29928. swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
  29929. swapBuffer.set(buffer);
  29930. swapBuffer.set(data.data, buffer.byteLength);
  29931. buffer = swapBuffer;
  29932. } // Rec. ITU-T H.264, Annex B
  29933. // scan for NAL unit boundaries
  29934. // a match looks like this:
  29935. // 0 0 1 .. NAL .. 0 0 1
  29936. // ^ sync point ^ i
  29937. // or this:
  29938. // 0 0 1 .. NAL .. 0 0 0
  29939. // ^ sync point ^ i
  29940. // advance the sync point to a NAL start, if necessary
  29941. for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
  29942. if (buffer[syncPoint + 2] === 1) {
  29943. // the sync point is properly aligned
  29944. i = syncPoint + 5;
  29945. break;
  29946. }
  29947. }
  29948. while (i < buffer.byteLength) {
  29949. // look at the current byte to determine if we've hit the end of
  29950. // a NAL unit boundary
  29951. switch (buffer[i]) {
  29952. case 0:
  29953. // skip past non-sync sequences
  29954. if (buffer[i - 1] !== 0) {
  29955. i += 2;
  29956. break;
  29957. } else if (buffer[i - 2] !== 0) {
  29958. i++;
  29959. break;
  29960. } // deliver the NAL unit if it isn't empty
  29961. if (syncPoint + 3 !== i - 2) {
  29962. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  29963. } // drop trailing zeroes
  29964. do {
  29965. i++;
  29966. } while (buffer[i] !== 1 && i < buffer.length);
  29967. syncPoint = i - 2;
  29968. i += 3;
  29969. break;
  29970. case 1:
  29971. // skip past non-sync sequences
  29972. if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
  29973. i += 3;
  29974. break;
  29975. } // deliver the NAL unit
  29976. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  29977. syncPoint = i - 2;
  29978. i += 3;
  29979. break;
  29980. default:
  29981. // the current byte isn't a one or zero, so it cannot be part
  29982. // of a sync sequence
  29983. i += 3;
  29984. break;
  29985. }
  29986. } // filter out the NAL units that were delivered
  29987. buffer = buffer.subarray(syncPoint);
  29988. i -= syncPoint;
  29989. syncPoint = 0;
  29990. };
  29991. this.flush = function () {
  29992. // deliver the last buffered NAL unit
  29993. if (buffer && buffer.byteLength > 3) {
  29994. this.trigger('data', buffer.subarray(syncPoint + 3));
  29995. } // reset the stream state
  29996. buffer = null;
  29997. syncPoint = 0;
  29998. this.trigger('done');
  29999. };
  30000. };
  30001. _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
  30002. // see Recommendation ITU-T H.264 (4/2013),
  30003. // 7.3.2.1.1 Sequence parameter set data syntax
  30004. PROFILES_WITH_OPTIONAL_SPS_DATA = {
  30005. 100: true,
  30006. 110: true,
  30007. 122: true,
  30008. 244: true,
  30009. 44: true,
  30010. 83: true,
  30011. 86: true,
  30012. 118: true,
  30013. 128: true,
  30014. 138: true,
  30015. 139: true,
  30016. 134: true
  30017. };
  30018. /**
  30019. * Accepts input from a ElementaryStream and produces H.264 NAL unit data
  30020. * events.
  30021. */
  30022. _H264Stream = function H264Stream() {
  30023. var nalByteStream = new _NalByteStream(),
  30024. self,
  30025. trackId,
  30026. currentPts,
  30027. currentDts,
  30028. discardEmulationPreventionBytes,
  30029. readSequenceParameterSet,
  30030. skipScalingList;
  30031. _H264Stream.prototype.init.call(this);
  30032. self = this;
  30033. /*
  30034. * Pushes a packet from a stream onto the NalByteStream
  30035. *
  30036. * @param {Object} packet - A packet received from a stream
  30037. * @param {Uint8Array} packet.data - The raw bytes of the packet
  30038. * @param {Number} packet.dts - Decode timestamp of the packet
  30039. * @param {Number} packet.pts - Presentation timestamp of the packet
  30040. * @param {Number} packet.trackId - The id of the h264 track this packet came from
  30041. * @param {('video'|'audio')} packet.type - The type of packet
  30042. *
  30043. */
  30044. this.push = function (packet) {
  30045. if (packet.type !== 'video') {
  30046. return;
  30047. }
  30048. trackId = packet.trackId;
  30049. currentPts = packet.pts;
  30050. currentDts = packet.dts;
  30051. nalByteStream.push(packet);
  30052. };
  30053. /*
  30054. * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
  30055. * for the NALUs to the next stream component.
  30056. * Also, preprocess caption and sequence parameter NALUs.
  30057. *
  30058. * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
  30059. * @see NalByteStream.push
  30060. */
  30061. nalByteStream.on('data', function (data) {
  30062. var event = {
  30063. trackId: trackId,
  30064. pts: currentPts,
  30065. dts: currentDts,
  30066. data: data
  30067. };
  30068. switch (data[0] & 0x1f) {
  30069. case 0x05:
  30070. event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
  30071. break;
  30072. case 0x06:
  30073. event.nalUnitType = 'sei_rbsp';
  30074. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  30075. break;
  30076. case 0x07:
  30077. event.nalUnitType = 'seq_parameter_set_rbsp';
  30078. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  30079. event.config = readSequenceParameterSet(event.escapedRBSP);
  30080. break;
  30081. case 0x08:
  30082. event.nalUnitType = 'pic_parameter_set_rbsp';
  30083. break;
  30084. case 0x09:
  30085. event.nalUnitType = 'access_unit_delimiter_rbsp';
  30086. break;
  30087. default:
  30088. break;
  30089. } // This triggers data on the H264Stream
  30090. self.trigger('data', event);
  30091. });
  30092. nalByteStream.on('done', function () {
  30093. self.trigger('done');
  30094. });
  30095. this.flush = function () {
  30096. nalByteStream.flush();
  30097. };
  30098. /**
  30099. * Advance the ExpGolomb decoder past a scaling list. The scaling
  30100. * list is optionally transmitted as part of a sequence parameter
  30101. * set and is not relevant to transmuxing.
  30102. * @param count {number} the number of entries in this scaling list
  30103. * @param expGolombDecoder {object} an ExpGolomb pointed to the
  30104. * start of a scaling list
  30105. * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  30106. */
  30107. skipScalingList = function skipScalingList(count, expGolombDecoder) {
  30108. var lastScale = 8,
  30109. nextScale = 8,
  30110. j,
  30111. deltaScale;
  30112. for (j = 0; j < count; j++) {
  30113. if (nextScale !== 0) {
  30114. deltaScale = expGolombDecoder.readExpGolomb();
  30115. nextScale = (lastScale + deltaScale + 256) % 256;
  30116. }
  30117. lastScale = nextScale === 0 ? lastScale : nextScale;
  30118. }
  30119. };
  30120. /**
  30121. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  30122. * Sequence Payload"
  30123. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  30124. * unit
  30125. * @return {Uint8Array} the RBSP without any Emulation
  30126. * Prevention Bytes
  30127. */
  30128. discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  30129. var length = data.byteLength,
  30130. emulationPreventionBytesPositions = [],
  30131. i = 1,
  30132. newLength,
  30133. newData; // Find all `Emulation Prevention Bytes`
  30134. while (i < length - 2) {
  30135. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  30136. emulationPreventionBytesPositions.push(i + 2);
  30137. i += 2;
  30138. } else {
  30139. i++;
  30140. }
  30141. } // If no Emulation Prevention Bytes were found just return the original
  30142. // array
  30143. if (emulationPreventionBytesPositions.length === 0) {
  30144. return data;
  30145. } // Create a new array to hold the NAL unit data
  30146. newLength = length - emulationPreventionBytesPositions.length;
  30147. newData = new Uint8Array(newLength);
  30148. var sourceIndex = 0;
  30149. for (i = 0; i < newLength; sourceIndex++, i++) {
  30150. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  30151. // Skip this byte
  30152. sourceIndex++; // Remove this position index
  30153. emulationPreventionBytesPositions.shift();
  30154. }
  30155. newData[i] = data[sourceIndex];
  30156. }
  30157. return newData;
  30158. };
  30159. /**
  30160. * Read a sequence parameter set and return some interesting video
  30161. * properties. A sequence parameter set is the H264 metadata that
  30162. * describes the properties of upcoming video frames.
  30163. * @param data {Uint8Array} the bytes of a sequence parameter set
  30164. * @return {object} an object with configuration parsed from the
  30165. * sequence parameter set, including the dimensions of the
  30166. * associated video frames.
  30167. */
  30168. readSequenceParameterSet = function readSequenceParameterSet(data) {
  30169. var frameCropLeftOffset = 0,
  30170. frameCropRightOffset = 0,
  30171. frameCropTopOffset = 0,
  30172. frameCropBottomOffset = 0,
  30173. sarScale = 1,
  30174. expGolombDecoder,
  30175. profileIdc,
  30176. levelIdc,
  30177. profileCompatibility,
  30178. chromaFormatIdc,
  30179. picOrderCntType,
  30180. numRefFramesInPicOrderCntCycle,
  30181. picWidthInMbsMinus1,
  30182. picHeightInMapUnitsMinus1,
  30183. frameMbsOnlyFlag,
  30184. scalingListCount,
  30185. sarRatio,
  30186. aspectRatioIdc,
  30187. i;
  30188. expGolombDecoder = new expGolomb(data);
  30189. profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
  30190. profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
  30191. levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
  30192. expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
  30193. // some profiles have more optional data we don't need
  30194. if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
  30195. chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  30196. if (chromaFormatIdc === 3) {
  30197. expGolombDecoder.skipBits(1); // separate_colour_plane_flag
  30198. }
  30199. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
  30200. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
  30201. expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  30202. if (expGolombDecoder.readBoolean()) {
  30203. // seq_scaling_matrix_present_flag
  30204. scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
  30205. for (i = 0; i < scalingListCount; i++) {
  30206. if (expGolombDecoder.readBoolean()) {
  30207. // seq_scaling_list_present_flag[ i ]
  30208. if (i < 6) {
  30209. skipScalingList(16, expGolombDecoder);
  30210. } else {
  30211. skipScalingList(64, expGolombDecoder);
  30212. }
  30213. }
  30214. }
  30215. }
  30216. }
  30217. expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
  30218. picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  30219. if (picOrderCntType === 0) {
  30220. expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
  30221. } else if (picOrderCntType === 1) {
  30222. expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
  30223. expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
  30224. expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
  30225. numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
  30226. for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  30227. expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
  30228. }
  30229. }
  30230. expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
  30231. expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  30232. picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  30233. picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  30234. frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  30235. if (frameMbsOnlyFlag === 0) {
  30236. expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
  30237. }
  30238. expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
  30239. if (expGolombDecoder.readBoolean()) {
  30240. // frame_cropping_flag
  30241. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  30242. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  30243. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  30244. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  30245. }
  30246. if (expGolombDecoder.readBoolean()) {
  30247. // vui_parameters_present_flag
  30248. if (expGolombDecoder.readBoolean()) {
  30249. // aspect_ratio_info_present_flag
  30250. aspectRatioIdc = expGolombDecoder.readUnsignedByte();
  30251. switch (aspectRatioIdc) {
  30252. case 1:
  30253. sarRatio = [1, 1];
  30254. break;
  30255. case 2:
  30256. sarRatio = [12, 11];
  30257. break;
  30258. case 3:
  30259. sarRatio = [10, 11];
  30260. break;
  30261. case 4:
  30262. sarRatio = [16, 11];
  30263. break;
  30264. case 5:
  30265. sarRatio = [40, 33];
  30266. break;
  30267. case 6:
  30268. sarRatio = [24, 11];
  30269. break;
  30270. case 7:
  30271. sarRatio = [20, 11];
  30272. break;
  30273. case 8:
  30274. sarRatio = [32, 11];
  30275. break;
  30276. case 9:
  30277. sarRatio = [80, 33];
  30278. break;
  30279. case 10:
  30280. sarRatio = [18, 11];
  30281. break;
  30282. case 11:
  30283. sarRatio = [15, 11];
  30284. break;
  30285. case 12:
  30286. sarRatio = [64, 33];
  30287. break;
  30288. case 13:
  30289. sarRatio = [160, 99];
  30290. break;
  30291. case 14:
  30292. sarRatio = [4, 3];
  30293. break;
  30294. case 15:
  30295. sarRatio = [3, 2];
  30296. break;
  30297. case 16:
  30298. sarRatio = [2, 1];
  30299. break;
  30300. case 255:
  30301. {
  30302. sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
  30303. break;
  30304. }
  30305. }
  30306. if (sarRatio) {
  30307. sarScale = sarRatio[0] / sarRatio[1];
  30308. }
  30309. }
  30310. }
  30311. return {
  30312. profileIdc: profileIdc,
  30313. levelIdc: levelIdc,
  30314. profileCompatibility: profileCompatibility,
  30315. width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
  30316. height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
  30317. };
  30318. };
  30319. };
  30320. _H264Stream.prototype = new stream();
  30321. var h264 = {
  30322. H264Stream: _H264Stream,
  30323. NalByteStream: _NalByteStream
  30324. };
  30325. /**
  30326. * mux.js
  30327. *
  30328. * Copyright (c) 2016 Brightcove
  30329. * All rights reserved.
  30330. *
  30331. * Utilities to detect basic properties and metadata about Aac data.
  30332. */
  30333. var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  30334. var isLikelyAacData = function isLikelyAacData(data) {
  30335. if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
  30336. return true;
  30337. }
  30338. return false;
  30339. };
  30340. var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
  30341. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  30342. }; // return a percent-encoded representation of the specified byte range
  30343. // @see http://en.wikipedia.org/wiki/Percent-encoding
  30344. var percentEncode$1 = function percentEncode(bytes, start, end) {
  30345. var i,
  30346. result = '';
  30347. for (i = start; i < end; i++) {
  30348. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  30349. }
  30350. return result;
  30351. }; // return the string representation of the specified byte range,
  30352. // interpreted as ISO-8859-1.
  30353. var parseIso88591$1 = function parseIso88591(bytes, start, end) {
  30354. return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
  30355. };
  30356. var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
  30357. var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
  30358. flags = header[byteIndex + 5],
  30359. footerPresent = (flags & 16) >> 4;
  30360. if (footerPresent) {
  30361. return returnSize + 20;
  30362. }
  30363. return returnSize + 10;
  30364. };
  30365. var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
  30366. var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
  30367. middle = header[byteIndex + 4] << 3,
  30368. highTwo = header[byteIndex + 3] & 0x3 << 11;
  30369. return highTwo | middle | lowThree;
  30370. };
  30371. var parseType$1 = function parseType(header, byteIndex) {
  30372. if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
  30373. return 'timed-metadata';
  30374. } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
  30375. return 'audio';
  30376. }
  30377. return null;
  30378. };
  30379. var parseSampleRate = function parseSampleRate(packet) {
  30380. var i = 0;
  30381. while (i + 5 < packet.length) {
  30382. if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
  30383. // If a valid header was not found, jump one forward and attempt to
  30384. // find a valid ADTS header starting at the next byte
  30385. i++;
  30386. continue;
  30387. }
  30388. return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
  30389. }
  30390. return null;
  30391. };
  30392. var parseAacTimestamp = function parseAacTimestamp(packet) {
  30393. var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
  30394. frameStart = 10;
  30395. if (packet[5] & 0x40) {
  30396. // advance the frame start past the extended header
  30397. frameStart += 4; // header size field
  30398. frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
  30399. } // parse one or more ID3 frames
  30400. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  30401. do {
  30402. // determine the number of bytes in this frame
  30403. frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
  30404. if (frameSize < 1) {
  30405. return null;
  30406. }
  30407. frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
  30408. if (frameHeader === 'PRIV') {
  30409. frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
  30410. for (var i = 0; i < frame.byteLength; i++) {
  30411. if (frame[i] === 0) {
  30412. var owner = parseIso88591$1(frame, 0, i);
  30413. if (owner === 'com.apple.streaming.transportStreamTimestamp') {
  30414. var d = frame.subarray(i + 1);
  30415. var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  30416. size *= 4;
  30417. size += d[7] & 0x03;
  30418. return size;
  30419. }
  30420. break;
  30421. }
  30422. }
  30423. }
  30424. frameStart += 10; // advance past the frame header
  30425. frameStart += frameSize; // advance past the frame body
  30426. } while (frameStart < packet.byteLength);
  30427. return null;
  30428. };
  30429. var utils = {
  30430. isLikelyAacData: isLikelyAacData,
  30431. parseId3TagSize: parseId3TagSize,
  30432. parseAdtsSize: parseAdtsSize,
  30433. parseType: parseType$1,
  30434. parseSampleRate: parseSampleRate,
  30435. parseAacTimestamp: parseAacTimestamp
  30436. };
  30437. var _AacStream;
  30438. /**
  30439. * Splits an incoming stream of binary data into ADTS and ID3 Frames.
  30440. */
  30441. _AacStream = function AacStream() {
  30442. var everything = new Uint8Array(),
  30443. timeStamp = 0;
  30444. _AacStream.prototype.init.call(this);
  30445. this.setTimestamp = function (timestamp) {
  30446. timeStamp = timestamp;
  30447. };
  30448. this.push = function (bytes) {
  30449. var frameSize = 0,
  30450. byteIndex = 0,
  30451. bytesLeft,
  30452. chunk,
  30453. packet,
  30454. tempLength; // If there are bytes remaining from the last segment, prepend them to the
  30455. // bytes that were pushed in
  30456. if (everything.length) {
  30457. tempLength = everything.length;
  30458. everything = new Uint8Array(bytes.byteLength + tempLength);
  30459. everything.set(everything.subarray(0, tempLength));
  30460. everything.set(bytes, tempLength);
  30461. } else {
  30462. everything = bytes;
  30463. }
  30464. while (everything.length - byteIndex >= 3) {
  30465. if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
  30466. // Exit early because we don't have enough to parse
  30467. // the ID3 tag header
  30468. if (everything.length - byteIndex < 10) {
  30469. break;
  30470. } // check framesize
  30471. frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  30472. // to emit a full packet
  30473. // Add to byteIndex to support multiple ID3 tags in sequence
  30474. if (byteIndex + frameSize > everything.length) {
  30475. break;
  30476. }
  30477. chunk = {
  30478. type: 'timed-metadata',
  30479. data: everything.subarray(byteIndex, byteIndex + frameSize)
  30480. };
  30481. this.trigger('data', chunk);
  30482. byteIndex += frameSize;
  30483. continue;
  30484. } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
  30485. // Exit early because we don't have enough to parse
  30486. // the ADTS frame header
  30487. if (everything.length - byteIndex < 7) {
  30488. break;
  30489. }
  30490. frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  30491. // to emit a full packet
  30492. if (byteIndex + frameSize > everything.length) {
  30493. break;
  30494. }
  30495. packet = {
  30496. type: 'audio',
  30497. data: everything.subarray(byteIndex, byteIndex + frameSize),
  30498. pts: timeStamp,
  30499. dts: timeStamp
  30500. };
  30501. this.trigger('data', packet);
  30502. byteIndex += frameSize;
  30503. continue;
  30504. }
  30505. byteIndex++;
  30506. }
  30507. bytesLeft = everything.length - byteIndex;
  30508. if (bytesLeft > 0) {
  30509. everything = everything.subarray(byteIndex);
  30510. } else {
  30511. everything = new Uint8Array();
  30512. }
  30513. };
  30514. };
  30515. _AacStream.prototype = new stream();
  30516. var aac = _AacStream;
  30517. var H264Stream = h264.H264Stream;
  30518. var isLikelyAacData$1 = utils.isLikelyAacData; // constants
  30519. var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
  30520. var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types
  30521. var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
  30522. /**
  30523. * Compare two arrays (even typed) for same-ness
  30524. */
  30525. var arrayEquals = function arrayEquals(a, b) {
  30526. var i;
  30527. if (a.length !== b.length) {
  30528. return false;
  30529. } // compare the value of each element in the array
  30530. for (i = 0; i < a.length; i++) {
  30531. if (a[i] !== b[i]) {
  30532. return false;
  30533. }
  30534. }
  30535. return true;
  30536. };
  30537. var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
  30538. var ptsOffsetFromDts = startPts - startDts,
  30539. decodeDuration = endDts - startDts,
  30540. presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
  30541. // however, the player time values will reflect a start from the baseMediaDecodeTime.
  30542. // In order to provide relevant values for the player times, base timing info on the
  30543. // baseMediaDecodeTime and the DTS and PTS durations of the segment.
  30544. return {
  30545. start: {
  30546. dts: baseMediaDecodeTime,
  30547. pts: baseMediaDecodeTime + ptsOffsetFromDts
  30548. },
  30549. end: {
  30550. dts: baseMediaDecodeTime + decodeDuration,
  30551. pts: baseMediaDecodeTime + presentationDuration
  30552. },
  30553. prependedContentDuration: prependedContentDuration,
  30554. baseMediaDecodeTime: baseMediaDecodeTime
  30555. };
  30556. };
  30557. /**
  30558. * Constructs a single-track, ISO BMFF media segment from AAC data
  30559. * events. The output of this stream can be fed to a SourceBuffer
  30560. * configured with a suitable initialization segment.
  30561. * @param track {object} track metadata configuration
  30562. * @param options {object} transmuxer options object
  30563. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  30564. * in the source; false to adjust the first segment to start at 0.
  30565. */
  30566. _AudioSegmentStream = function AudioSegmentStream(track, options) {
  30567. var adtsFrames = [],
  30568. sequenceNumber = 0,
  30569. earliestAllowedDts = 0,
  30570. audioAppendStartTs = 0,
  30571. videoBaseMediaDecodeTime = Infinity;
  30572. options = options || {};
  30573. _AudioSegmentStream.prototype.init.call(this);
  30574. this.push = function (data) {
  30575. trackDecodeInfo.collectDtsInfo(track, data);
  30576. if (track) {
  30577. AUDIO_PROPERTIES.forEach(function (prop) {
  30578. track[prop] = data[prop];
  30579. });
  30580. } // buffer audio data until end() is called
  30581. adtsFrames.push(data);
  30582. };
  30583. this.setEarliestDts = function (earliestDts) {
  30584. earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
  30585. };
  30586. this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  30587. videoBaseMediaDecodeTime = baseMediaDecodeTime;
  30588. };
  30589. this.setAudioAppendStart = function (timestamp) {
  30590. audioAppendStartTs = timestamp;
  30591. };
  30592. this.flush = function () {
  30593. var frames, moof, mdat, boxes; // return early if no audio data has been observed
  30594. if (adtsFrames.length === 0) {
  30595. this.trigger('done', 'AudioSegmentStream');
  30596. return;
  30597. }
  30598. frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
  30599. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  30600. audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
  30601. // samples (that is, adts frames) in the audio data
  30602. track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
  30603. mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
  30604. adtsFrames = [];
  30605. moof = mp4Generator.moof(sequenceNumber, [track]);
  30606. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
  30607. sequenceNumber++;
  30608. boxes.set(moof);
  30609. boxes.set(mdat, moof.byteLength);
  30610. trackDecodeInfo.clearDtsInfo(track);
  30611. this.trigger('data', {
  30612. track: track,
  30613. boxes: boxes
  30614. });
  30615. this.trigger('done', 'AudioSegmentStream');
  30616. };
  30617. };
  30618. _AudioSegmentStream.prototype = new stream();
  30619. /**
  30620. * Constructs a single-track, ISO BMFF media segment from H264 data
  30621. * events. The output of this stream can be fed to a SourceBuffer
  30622. * configured with a suitable initialization segment.
  30623. * @param track {object} track metadata configuration
  30624. * @param options {object} transmuxer options object
  30625. * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
  30626. * gopsToAlignWith list when attempting to align gop pts
  30627. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  30628. * in the source; false to adjust the first segment to start at 0.
  30629. */
  30630. _VideoSegmentStream = function VideoSegmentStream(track, options) {
  30631. var sequenceNumber = 0,
  30632. nalUnits = [],
  30633. gopsToAlignWith = [],
  30634. config,
  30635. pps;
  30636. options = options || {};
  30637. _VideoSegmentStream.prototype.init.call(this);
  30638. delete track.minPTS;
  30639. this.gopCache_ = [];
  30640. /**
  30641. * Constructs a ISO BMFF segment given H264 nalUnits
  30642. * @param {Object} nalUnit A data event representing a nalUnit
  30643. * @param {String} nalUnit.nalUnitType
  30644. * @param {Object} nalUnit.config Properties for a mp4 track
  30645. * @param {Uint8Array} nalUnit.data The nalUnit bytes
  30646. * @see lib/codecs/h264.js
  30647. **/
  30648. this.push = function (nalUnit) {
  30649. trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
  30650. if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
  30651. config = nalUnit.config;
  30652. track.sps = [nalUnit.data];
  30653. VIDEO_PROPERTIES.forEach(function (prop) {
  30654. track[prop] = config[prop];
  30655. }, this);
  30656. }
  30657. if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
  30658. pps = nalUnit.data;
  30659. track.pps = [nalUnit.data];
  30660. } // buffer video until flush() is called
  30661. nalUnits.push(nalUnit);
  30662. };
  30663. /**
  30664. * Pass constructed ISO BMFF track and boxes on to the
  30665. * next stream in the pipeline
  30666. **/
  30667. this.flush = function () {
  30668. var frames,
  30669. gopForFusion,
  30670. gops,
  30671. moof,
  30672. mdat,
  30673. boxes,
  30674. prependedContentDuration = 0,
  30675. firstGop,
  30676. lastGop; // Throw away nalUnits at the start of the byte stream until
  30677. // we find the first AUD
  30678. while (nalUnits.length) {
  30679. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  30680. break;
  30681. }
  30682. nalUnits.shift();
  30683. } // Return early if no video data has been observed
  30684. if (nalUnits.length === 0) {
  30685. this.resetStream_();
  30686. this.trigger('done', 'VideoSegmentStream');
  30687. return;
  30688. } // Organize the raw nal-units into arrays that represent
  30689. // higher-level constructs such as frames and gops
  30690. // (group-of-pictures)
  30691. frames = frameUtils.groupNalsIntoFrames(nalUnits);
  30692. gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
  30693. // a problem since MSE (on Chrome) requires a leading keyframe.
  30694. //
  30695. // We have two approaches to repairing this situation:
  30696. // 1) GOP-FUSION:
  30697. // This is where we keep track of the GOPS (group-of-pictures)
  30698. // from previous fragments and attempt to find one that we can
  30699. // prepend to the current fragment in order to create a valid
  30700. // fragment.
  30701. // 2) KEYFRAME-PULLING:
  30702. // Here we search for the first keyframe in the fragment and
  30703. // throw away all the frames between the start of the fragment
  30704. // and that keyframe. We then extend the duration and pull the
  30705. // PTS of the keyframe forward so that it covers the time range
  30706. // of the frames that were disposed of.
  30707. //
  30708. // #1 is far prefereable over #2 which can cause "stuttering" but
  30709. // requires more things to be just right.
  30710. if (!gops[0][0].keyFrame) {
  30711. // Search for a gop for fusion from our gopCache
  30712. gopForFusion = this.getGopForFusion_(nalUnits[0], track);
  30713. if (gopForFusion) {
  30714. // in order to provide more accurate timing information about the segment, save
  30715. // the number of seconds prepended to the original segment due to GOP fusion
  30716. prependedContentDuration = gopForFusion.duration;
  30717. gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
  30718. // new gop at the beginning
  30719. gops.byteLength += gopForFusion.byteLength;
  30720. gops.nalCount += gopForFusion.nalCount;
  30721. gops.pts = gopForFusion.pts;
  30722. gops.dts = gopForFusion.dts;
  30723. gops.duration += gopForFusion.duration;
  30724. } else {
  30725. // If we didn't find a candidate gop fall back to keyframe-pulling
  30726. gops = frameUtils.extendFirstKeyFrame(gops);
  30727. }
  30728. } // Trim gops to align with gopsToAlignWith
  30729. if (gopsToAlignWith.length) {
  30730. var alignedGops;
  30731. if (options.alignGopsAtEnd) {
  30732. alignedGops = this.alignGopsAtEnd_(gops);
  30733. } else {
  30734. alignedGops = this.alignGopsAtStart_(gops);
  30735. }
  30736. if (!alignedGops) {
  30737. // save all the nals in the last GOP into the gop cache
  30738. this.gopCache_.unshift({
  30739. gop: gops.pop(),
  30740. pps: track.pps,
  30741. sps: track.sps
  30742. }); // Keep a maximum of 6 GOPs in the cache
  30743. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  30744. nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
  30745. this.resetStream_();
  30746. this.trigger('done', 'VideoSegmentStream');
  30747. return;
  30748. } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
  30749. // when recalculated before sending off to CoalesceStream
  30750. trackDecodeInfo.clearDtsInfo(track);
  30751. gops = alignedGops;
  30752. }
  30753. trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
  30754. // samples (that is, frames) in the video data
  30755. track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
  30756. mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
  30757. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  30758. this.trigger('processedGopsInfo', gops.map(function (gop) {
  30759. return {
  30760. pts: gop.pts,
  30761. dts: gop.dts,
  30762. byteLength: gop.byteLength
  30763. };
  30764. }));
  30765. firstGop = gops[0];
  30766. lastGop = gops[gops.length - 1];
  30767. 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
  30768. this.gopCache_.unshift({
  30769. gop: gops.pop(),
  30770. pps: track.pps,
  30771. sps: track.sps
  30772. }); // Keep a maximum of 6 GOPs in the cache
  30773. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  30774. nalUnits = [];
  30775. this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
  30776. this.trigger('timelineStartInfo', track.timelineStartInfo);
  30777. moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
  30778. // throwing away hundreds of media segment fragments
  30779. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
  30780. sequenceNumber++;
  30781. boxes.set(moof);
  30782. boxes.set(mdat, moof.byteLength);
  30783. this.trigger('data', {
  30784. track: track,
  30785. boxes: boxes
  30786. });
  30787. this.resetStream_(); // Continue with the flush process now
  30788. this.trigger('done', 'VideoSegmentStream');
  30789. };
  30790. this.resetStream_ = function () {
  30791. trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
  30792. // for instance, when we are rendition switching
  30793. config = undefined;
  30794. pps = undefined;
  30795. }; // Search for a candidate Gop for gop-fusion from the gop cache and
  30796. // return it or return null if no good candidate was found
  30797. this.getGopForFusion_ = function (nalUnit) {
  30798. var halfSecond = 45000,
  30799. // Half-a-second in a 90khz clock
  30800. allowableOverlap = 10000,
  30801. // About 3 frames @ 30fps
  30802. nearestDistance = Infinity,
  30803. dtsDistance,
  30804. nearestGopObj,
  30805. currentGop,
  30806. currentGopObj,
  30807. i; // Search for the GOP nearest to the beginning of this nal unit
  30808. for (i = 0; i < this.gopCache_.length; i++) {
  30809. currentGopObj = this.gopCache_[i];
  30810. currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
  30811. if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
  30812. continue;
  30813. } // Reject Gops that would require a negative baseMediaDecodeTime
  30814. if (currentGop.dts < track.timelineStartInfo.dts) {
  30815. continue;
  30816. } // The distance between the end of the gop and the start of the nalUnit
  30817. dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
  30818. // a half-second of the nal unit
  30819. if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
  30820. // Always use the closest GOP we found if there is more than
  30821. // one candidate
  30822. if (!nearestGopObj || nearestDistance > dtsDistance) {
  30823. nearestGopObj = currentGopObj;
  30824. nearestDistance = dtsDistance;
  30825. }
  30826. }
  30827. }
  30828. if (nearestGopObj) {
  30829. return nearestGopObj.gop;
  30830. }
  30831. return null;
  30832. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  30833. // of gopsToAlignWith starting from the START of the list
  30834. this.alignGopsAtStart_ = function (gops) {
  30835. var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
  30836. byteLength = gops.byteLength;
  30837. nalCount = gops.nalCount;
  30838. duration = gops.duration;
  30839. alignIndex = gopIndex = 0;
  30840. while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
  30841. align = gopsToAlignWith[alignIndex];
  30842. gop = gops[gopIndex];
  30843. if (align.pts === gop.pts) {
  30844. break;
  30845. }
  30846. if (gop.pts > align.pts) {
  30847. // this current gop starts after the current gop we want to align on, so increment
  30848. // align index
  30849. alignIndex++;
  30850. continue;
  30851. } // current gop starts before the current gop we want to align on. so increment gop
  30852. // index
  30853. gopIndex++;
  30854. byteLength -= gop.byteLength;
  30855. nalCount -= gop.nalCount;
  30856. duration -= gop.duration;
  30857. }
  30858. if (gopIndex === 0) {
  30859. // no gops to trim
  30860. return gops;
  30861. }
  30862. if (gopIndex === gops.length) {
  30863. // all gops trimmed, skip appending all gops
  30864. return null;
  30865. }
  30866. alignedGops = gops.slice(gopIndex);
  30867. alignedGops.byteLength = byteLength;
  30868. alignedGops.duration = duration;
  30869. alignedGops.nalCount = nalCount;
  30870. alignedGops.pts = alignedGops[0].pts;
  30871. alignedGops.dts = alignedGops[0].dts;
  30872. return alignedGops;
  30873. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  30874. // of gopsToAlignWith starting from the END of the list
  30875. this.alignGopsAtEnd_ = function (gops) {
  30876. var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
  30877. alignIndex = gopsToAlignWith.length - 1;
  30878. gopIndex = gops.length - 1;
  30879. alignEndIndex = null;
  30880. matchFound = false;
  30881. while (alignIndex >= 0 && gopIndex >= 0) {
  30882. align = gopsToAlignWith[alignIndex];
  30883. gop = gops[gopIndex];
  30884. if (align.pts === gop.pts) {
  30885. matchFound = true;
  30886. break;
  30887. }
  30888. if (align.pts > gop.pts) {
  30889. alignIndex--;
  30890. continue;
  30891. }
  30892. if (alignIndex === gopsToAlignWith.length - 1) {
  30893. // gop.pts is greater than the last alignment candidate. If no match is found
  30894. // by the end of this loop, we still want to append gops that come after this
  30895. // point
  30896. alignEndIndex = gopIndex;
  30897. }
  30898. gopIndex--;
  30899. }
  30900. if (!matchFound && alignEndIndex === null) {
  30901. return null;
  30902. }
  30903. var trimIndex;
  30904. if (matchFound) {
  30905. trimIndex = gopIndex;
  30906. } else {
  30907. trimIndex = alignEndIndex;
  30908. }
  30909. if (trimIndex === 0) {
  30910. return gops;
  30911. }
  30912. var alignedGops = gops.slice(trimIndex);
  30913. var metadata = alignedGops.reduce(function (total, gop) {
  30914. total.byteLength += gop.byteLength;
  30915. total.duration += gop.duration;
  30916. total.nalCount += gop.nalCount;
  30917. return total;
  30918. }, {
  30919. byteLength: 0,
  30920. duration: 0,
  30921. nalCount: 0
  30922. });
  30923. alignedGops.byteLength = metadata.byteLength;
  30924. alignedGops.duration = metadata.duration;
  30925. alignedGops.nalCount = metadata.nalCount;
  30926. alignedGops.pts = alignedGops[0].pts;
  30927. alignedGops.dts = alignedGops[0].dts;
  30928. return alignedGops;
  30929. };
  30930. this.alignGopsWith = function (newGopsToAlignWith) {
  30931. gopsToAlignWith = newGopsToAlignWith;
  30932. };
  30933. };
  30934. _VideoSegmentStream.prototype = new stream();
  30935. /**
  30936. * A Stream that can combine multiple streams (ie. audio & video)
  30937. * into a single output segment for MSE. Also supports audio-only
  30938. * and video-only streams.
  30939. * @param options {object} transmuxer options object
  30940. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  30941. * in the source; false to adjust the first segment to start at media timeline start.
  30942. */
  30943. _CoalesceStream = function CoalesceStream(options, metadataStream) {
  30944. // Number of Tracks per output segment
  30945. // If greater than 1, we combine multiple
  30946. // tracks into a single segment
  30947. this.numberOfTracks = 0;
  30948. this.metadataStream = metadataStream;
  30949. options = options || {};
  30950. if (typeof options.remux !== 'undefined') {
  30951. this.remuxTracks = !!options.remux;
  30952. } else {
  30953. this.remuxTracks = true;
  30954. }
  30955. if (typeof options.keepOriginalTimestamps === 'boolean') {
  30956. this.keepOriginalTimestamps = options.keepOriginalTimestamps;
  30957. }
  30958. this.pendingTracks = [];
  30959. this.videoTrack = null;
  30960. this.pendingBoxes = [];
  30961. this.pendingCaptions = [];
  30962. this.pendingMetadata = [];
  30963. this.pendingBytes = 0;
  30964. this.emittedTracks = 0;
  30965. _CoalesceStream.prototype.init.call(this); // Take output from multiple
  30966. this.push = function (output) {
  30967. // buffer incoming captions until the associated video segment
  30968. // finishes
  30969. if (output.text) {
  30970. return this.pendingCaptions.push(output);
  30971. } // buffer incoming id3 tags until the final flush
  30972. if (output.frames) {
  30973. return this.pendingMetadata.push(output);
  30974. } // Add this track to the list of pending tracks and store
  30975. // important information required for the construction of
  30976. // the final segment
  30977. this.pendingTracks.push(output.track);
  30978. this.pendingBoxes.push(output.boxes);
  30979. this.pendingBytes += output.boxes.byteLength;
  30980. if (output.track.type === 'video') {
  30981. this.videoTrack = output.track;
  30982. }
  30983. if (output.track.type === 'audio') {
  30984. this.audioTrack = output.track;
  30985. }
  30986. };
  30987. };
  30988. _CoalesceStream.prototype = new stream();
  30989. _CoalesceStream.prototype.flush = function (flushSource) {
  30990. var offset = 0,
  30991. event = {
  30992. captions: [],
  30993. captionStreams: {},
  30994. metadata: [],
  30995. info: {}
  30996. },
  30997. caption,
  30998. id3,
  30999. initSegment,
  31000. timelineStartPts = 0,
  31001. i;
  31002. if (this.pendingTracks.length < this.numberOfTracks) {
  31003. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  31004. // Return because we haven't received a flush from a data-generating
  31005. // portion of the segment (meaning that we have only recieved meta-data
  31006. // or captions.)
  31007. return;
  31008. } else if (this.remuxTracks) {
  31009. // Return until we have enough tracks from the pipeline to remux (if we
  31010. // are remuxing audio and video into a single MP4)
  31011. return;
  31012. } else if (this.pendingTracks.length === 0) {
  31013. // In the case where we receive a flush without any data having been
  31014. // received we consider it an emitted track for the purposes of coalescing
  31015. // `done` events.
  31016. // We do this for the case where there is an audio and video track in the
  31017. // segment but no audio data. (seen in several playlists with alternate
  31018. // audio tracks and no audio present in the main TS segments.)
  31019. this.emittedTracks++;
  31020. if (this.emittedTracks >= this.numberOfTracks) {
  31021. this.trigger('done');
  31022. this.emittedTracks = 0;
  31023. }
  31024. return;
  31025. }
  31026. }
  31027. if (this.videoTrack) {
  31028. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  31029. VIDEO_PROPERTIES.forEach(function (prop) {
  31030. event.info[prop] = this.videoTrack[prop];
  31031. }, this);
  31032. } else if (this.audioTrack) {
  31033. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  31034. AUDIO_PROPERTIES.forEach(function (prop) {
  31035. event.info[prop] = this.audioTrack[prop];
  31036. }, this);
  31037. }
  31038. if (this.pendingTracks.length === 1) {
  31039. event.type = this.pendingTracks[0].type;
  31040. } else {
  31041. event.type = 'combined';
  31042. }
  31043. this.emittedTracks += this.pendingTracks.length;
  31044. initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
  31045. event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
  31046. // and track definitions
  31047. event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
  31048. event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
  31049. for (i = 0; i < this.pendingBoxes.length; i++) {
  31050. event.data.set(this.pendingBoxes[i], offset);
  31051. offset += this.pendingBoxes[i].byteLength;
  31052. } // Translate caption PTS times into second offsets to match the
  31053. // video timeline for the segment, and add track info
  31054. for (i = 0; i < this.pendingCaptions.length; i++) {
  31055. caption = this.pendingCaptions[i];
  31056. caption.startTime = caption.startPts;
  31057. if (!this.keepOriginalTimestamps) {
  31058. caption.startTime -= timelineStartPts;
  31059. }
  31060. caption.startTime /= 90e3;
  31061. caption.endTime = caption.endPts;
  31062. if (!this.keepOriginalTimestamps) {
  31063. caption.endTime -= timelineStartPts;
  31064. }
  31065. caption.endTime /= 90e3;
  31066. event.captionStreams[caption.stream] = true;
  31067. event.captions.push(caption);
  31068. } // Translate ID3 frame PTS times into second offsets to match the
  31069. // video timeline for the segment
  31070. for (i = 0; i < this.pendingMetadata.length; i++) {
  31071. id3 = this.pendingMetadata[i];
  31072. id3.cueTime = id3.pts;
  31073. if (!this.keepOriginalTimestamps) {
  31074. id3.cueTime -= timelineStartPts;
  31075. }
  31076. id3.cueTime /= 90e3;
  31077. event.metadata.push(id3);
  31078. } // We add this to every single emitted segment even though we only need
  31079. // it for the first
  31080. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  31081. this.pendingTracks.length = 0;
  31082. this.videoTrack = null;
  31083. this.pendingBoxes.length = 0;
  31084. this.pendingCaptions.length = 0;
  31085. this.pendingBytes = 0;
  31086. this.pendingMetadata.length = 0; // Emit the built segment
  31087. this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted
  31088. if (this.emittedTracks >= this.numberOfTracks) {
  31089. this.trigger('done');
  31090. this.emittedTracks = 0;
  31091. }
  31092. };
  31093. /**
  31094. * A Stream that expects MP2T binary data as input and produces
  31095. * corresponding media segments, suitable for use with Media Source
  31096. * Extension (MSE) implementations that support the ISO BMFF byte
  31097. * stream format, like Chrome.
  31098. */
  31099. _Transmuxer = function Transmuxer(options) {
  31100. var self = this,
  31101. hasFlushed = true,
  31102. videoTrack,
  31103. audioTrack;
  31104. _Transmuxer.prototype.init.call(this);
  31105. options = options || {};
  31106. this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
  31107. this.transmuxPipeline_ = {};
  31108. this.setupAacPipeline = function () {
  31109. var pipeline = {};
  31110. this.transmuxPipeline_ = pipeline;
  31111. pipeline.type = 'aac';
  31112. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  31113. pipeline.aacStream = new aac();
  31114. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  31115. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  31116. pipeline.adtsStream = new adts();
  31117. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  31118. pipeline.headOfPipeline = pipeline.aacStream;
  31119. pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  31120. pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
  31121. pipeline.metadataStream.on('timestamp', function (frame) {
  31122. pipeline.aacStream.setTimestamp(frame.timeStamp);
  31123. });
  31124. pipeline.aacStream.on('data', function (data) {
  31125. if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
  31126. audioTrack = audioTrack || {
  31127. timelineStartInfo: {
  31128. baseMediaDecodeTime: self.baseMediaDecodeTime
  31129. },
  31130. codec: 'adts',
  31131. type: 'audio'
  31132. }; // hook up the audio segment stream to the first track with aac data
  31133. pipeline.coalesceStream.numberOfTracks++;
  31134. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  31135. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  31136. }
  31137. }); // Re-emit any data coming from the coalesce stream to the outside world
  31138. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  31139. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  31140. };
  31141. this.setupTsPipeline = function () {
  31142. var pipeline = {};
  31143. this.transmuxPipeline_ = pipeline;
  31144. pipeline.type = 'ts';
  31145. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  31146. pipeline.packetStream = new m2ts_1.TransportPacketStream();
  31147. pipeline.parseStream = new m2ts_1.TransportParseStream();
  31148. pipeline.elementaryStream = new m2ts_1.ElementaryStream();
  31149. pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
  31150. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  31151. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  31152. pipeline.adtsStream = new adts();
  31153. pipeline.h264Stream = new H264Stream();
  31154. pipeline.captionStream = new m2ts_1.CaptionStream();
  31155. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  31156. pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
  31157. pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!!
  31158. // demux the streams
  31159. pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
  31160. pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  31161. pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
  31162. pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
  31163. pipeline.elementaryStream.on('data', function (data) {
  31164. var i;
  31165. if (data.type === 'metadata') {
  31166. i = data.tracks.length; // scan the tracks listed in the metadata
  31167. while (i--) {
  31168. if (!videoTrack && data.tracks[i].type === 'video') {
  31169. videoTrack = data.tracks[i];
  31170. videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  31171. } else if (!audioTrack && data.tracks[i].type === 'audio') {
  31172. audioTrack = data.tracks[i];
  31173. audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  31174. }
  31175. } // hook up the video segment stream to the first track with h264 data
  31176. if (videoTrack && !pipeline.videoSegmentStream) {
  31177. pipeline.coalesceStream.numberOfTracks++;
  31178. pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
  31179. pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
  31180. // When video emits timelineStartInfo data after a flush, we forward that
  31181. // info to the AudioSegmentStream, if it exists, because video timeline
  31182. // data takes precedence.
  31183. if (audioTrack) {
  31184. audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
  31185. // very earliest DTS we have seen in video because Chrome will
  31186. // interpret any video track with a baseMediaDecodeTime that is
  31187. // non-zero as a gap.
  31188. pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
  31189. }
  31190. });
  31191. pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
  31192. pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
  31193. pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
  31194. if (audioTrack) {
  31195. pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
  31196. }
  31197. }); // Set up the final part of the video pipeline
  31198. pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
  31199. }
  31200. if (audioTrack && !pipeline.audioSegmentStream) {
  31201. // hook up the audio segment stream to the first track with aac data
  31202. pipeline.coalesceStream.numberOfTracks++;
  31203. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  31204. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  31205. }
  31206. }
  31207. }); // Re-emit any data coming from the coalesce stream to the outside world
  31208. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  31209. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  31210. }; // hook up the segment streams once track metadata is delivered
  31211. this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  31212. var pipeline = this.transmuxPipeline_;
  31213. if (!options.keepOriginalTimestamps) {
  31214. this.baseMediaDecodeTime = baseMediaDecodeTime;
  31215. }
  31216. if (audioTrack) {
  31217. audioTrack.timelineStartInfo.dts = undefined;
  31218. audioTrack.timelineStartInfo.pts = undefined;
  31219. trackDecodeInfo.clearDtsInfo(audioTrack);
  31220. if (!options.keepOriginalTimestamps) {
  31221. audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  31222. }
  31223. if (pipeline.audioTimestampRolloverStream) {
  31224. pipeline.audioTimestampRolloverStream.discontinuity();
  31225. }
  31226. }
  31227. if (videoTrack) {
  31228. if (pipeline.videoSegmentStream) {
  31229. pipeline.videoSegmentStream.gopCache_ = [];
  31230. pipeline.videoTimestampRolloverStream.discontinuity();
  31231. }
  31232. videoTrack.timelineStartInfo.dts = undefined;
  31233. videoTrack.timelineStartInfo.pts = undefined;
  31234. trackDecodeInfo.clearDtsInfo(videoTrack);
  31235. pipeline.captionStream.reset();
  31236. if (!options.keepOriginalTimestamps) {
  31237. videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  31238. }
  31239. }
  31240. if (pipeline.timedMetadataTimestampRolloverStream) {
  31241. pipeline.timedMetadataTimestampRolloverStream.discontinuity();
  31242. }
  31243. };
  31244. this.setAudioAppendStart = function (timestamp) {
  31245. if (audioTrack) {
  31246. this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
  31247. }
  31248. };
  31249. this.alignGopsWith = function (gopsToAlignWith) {
  31250. if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
  31251. this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
  31252. }
  31253. }; // feed incoming data to the front of the parsing pipeline
  31254. this.push = function (data) {
  31255. if (hasFlushed) {
  31256. var isAac = isLikelyAacData$1(data);
  31257. if (isAac && this.transmuxPipeline_.type !== 'aac') {
  31258. this.setupAacPipeline();
  31259. } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
  31260. this.setupTsPipeline();
  31261. }
  31262. hasFlushed = false;
  31263. }
  31264. this.transmuxPipeline_.headOfPipeline.push(data);
  31265. }; // flush any buffered data
  31266. this.flush = function () {
  31267. hasFlushed = true; // Start at the top of the pipeline and flush all pending work
  31268. this.transmuxPipeline_.headOfPipeline.flush();
  31269. }; // Caption data has to be reset when seeking outside buffered range
  31270. this.resetCaptions = function () {
  31271. if (this.transmuxPipeline_.captionStream) {
  31272. this.transmuxPipeline_.captionStream.reset();
  31273. }
  31274. };
  31275. };
  31276. _Transmuxer.prototype = new stream();
  31277. var transmuxer = {
  31278. Transmuxer: _Transmuxer,
  31279. VideoSegmentStream: _VideoSegmentStream,
  31280. AudioSegmentStream: _AudioSegmentStream,
  31281. AUDIO_PROPERTIES: AUDIO_PROPERTIES,
  31282. VIDEO_PROPERTIES: VIDEO_PROPERTIES,
  31283. // exported for testing
  31284. generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
  31285. };
  31286. var inspectMp4,
  31287. _textifyMp,
  31288. parseType$2 = probe.parseType,
  31289. parseMp4Date = function parseMp4Date(seconds) {
  31290. return new Date(seconds * 1000 - 2082844800000);
  31291. },
  31292. parseSampleFlags = function parseSampleFlags(flags) {
  31293. return {
  31294. isLeading: (flags[0] & 0x0c) >>> 2,
  31295. dependsOn: flags[0] & 0x03,
  31296. isDependedOn: (flags[1] & 0xc0) >>> 6,
  31297. hasRedundancy: (flags[1] & 0x30) >>> 4,
  31298. paddingValue: (flags[1] & 0x0e) >>> 1,
  31299. isNonSyncSample: flags[1] & 0x01,
  31300. degradationPriority: flags[2] << 8 | flags[3]
  31301. };
  31302. },
  31303. nalParse = function nalParse(avcStream) {
  31304. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  31305. result = [],
  31306. i,
  31307. length;
  31308. for (i = 0; i + 4 < avcStream.length; i += length) {
  31309. length = avcView.getUint32(i);
  31310. i += 4; // bail if this doesn't appear to be an H264 stream
  31311. if (length <= 0) {
  31312. result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
  31313. continue;
  31314. }
  31315. switch (avcStream[i] & 0x1F) {
  31316. case 0x01:
  31317. result.push('slice_layer_without_partitioning_rbsp');
  31318. break;
  31319. case 0x05:
  31320. result.push('slice_layer_without_partitioning_rbsp_idr');
  31321. break;
  31322. case 0x06:
  31323. result.push('sei_rbsp');
  31324. break;
  31325. case 0x07:
  31326. result.push('seq_parameter_set_rbsp');
  31327. break;
  31328. case 0x08:
  31329. result.push('pic_parameter_set_rbsp');
  31330. break;
  31331. case 0x09:
  31332. result.push('access_unit_delimiter_rbsp');
  31333. break;
  31334. default:
  31335. result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
  31336. break;
  31337. }
  31338. }
  31339. return result;
  31340. },
  31341. // registry of handlers for individual mp4 box types
  31342. parse$1 = {
  31343. // codingname, not a first-class box type. stsd entries share the
  31344. // same format as real boxes so the parsing infrastructure can be
  31345. // shared
  31346. avc1: function avc1(data) {
  31347. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  31348. return {
  31349. dataReferenceIndex: view.getUint16(6),
  31350. width: view.getUint16(24),
  31351. height: view.getUint16(26),
  31352. horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
  31353. vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
  31354. frameCount: view.getUint16(40),
  31355. depth: view.getUint16(74),
  31356. config: inspectMp4(data.subarray(78, data.byteLength))
  31357. };
  31358. },
  31359. avcC: function avcC(data) {
  31360. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31361. result = {
  31362. configurationVersion: data[0],
  31363. avcProfileIndication: data[1],
  31364. profileCompatibility: data[2],
  31365. avcLevelIndication: data[3],
  31366. lengthSizeMinusOne: data[4] & 0x03,
  31367. sps: [],
  31368. pps: []
  31369. },
  31370. numOfSequenceParameterSets = data[5] & 0x1f,
  31371. numOfPictureParameterSets,
  31372. nalSize,
  31373. offset,
  31374. i; // iterate past any SPSs
  31375. offset = 6;
  31376. for (i = 0; i < numOfSequenceParameterSets; i++) {
  31377. nalSize = view.getUint16(offset);
  31378. offset += 2;
  31379. result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  31380. offset += nalSize;
  31381. } // iterate past any PPSs
  31382. numOfPictureParameterSets = data[offset];
  31383. offset++;
  31384. for (i = 0; i < numOfPictureParameterSets; i++) {
  31385. nalSize = view.getUint16(offset);
  31386. offset += 2;
  31387. result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  31388. offset += nalSize;
  31389. }
  31390. return result;
  31391. },
  31392. btrt: function btrt(data) {
  31393. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  31394. return {
  31395. bufferSizeDB: view.getUint32(0),
  31396. maxBitrate: view.getUint32(4),
  31397. avgBitrate: view.getUint32(8)
  31398. };
  31399. },
  31400. esds: function esds(data) {
  31401. return {
  31402. version: data[0],
  31403. flags: new Uint8Array(data.subarray(1, 4)),
  31404. esId: data[6] << 8 | data[7],
  31405. streamPriority: data[8] & 0x1f,
  31406. decoderConfig: {
  31407. objectProfileIndication: data[11],
  31408. streamType: data[12] >>> 2 & 0x3f,
  31409. bufferSize: data[13] << 16 | data[14] << 8 | data[15],
  31410. maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
  31411. avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
  31412. decoderConfigDescriptor: {
  31413. tag: data[24],
  31414. length: data[25],
  31415. audioObjectType: data[26] >>> 3 & 0x1f,
  31416. samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
  31417. channelConfiguration: data[27] >>> 3 & 0x0f
  31418. }
  31419. }
  31420. };
  31421. },
  31422. ftyp: function ftyp(data) {
  31423. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31424. result = {
  31425. majorBrand: parseType$2(data.subarray(0, 4)),
  31426. minorVersion: view.getUint32(4),
  31427. compatibleBrands: []
  31428. },
  31429. i = 8;
  31430. while (i < data.byteLength) {
  31431. result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4)));
  31432. i += 4;
  31433. }
  31434. return result;
  31435. },
  31436. dinf: function dinf(data) {
  31437. return {
  31438. boxes: inspectMp4(data)
  31439. };
  31440. },
  31441. dref: function dref(data) {
  31442. return {
  31443. version: data[0],
  31444. flags: new Uint8Array(data.subarray(1, 4)),
  31445. dataReferences: inspectMp4(data.subarray(8))
  31446. };
  31447. },
  31448. hdlr: function hdlr(data) {
  31449. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31450. result = {
  31451. version: view.getUint8(0),
  31452. flags: new Uint8Array(data.subarray(1, 4)),
  31453. handlerType: parseType$2(data.subarray(8, 12)),
  31454. name: ''
  31455. },
  31456. i = 8; // parse out the name field
  31457. for (i = 24; i < data.byteLength; i++) {
  31458. if (data[i] === 0x00) {
  31459. // the name field is null-terminated
  31460. i++;
  31461. break;
  31462. }
  31463. result.name += String.fromCharCode(data[i]);
  31464. } // decode UTF-8 to javascript's internal representation
  31465. // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
  31466. result.name = decodeURIComponent(escape(result.name));
  31467. return result;
  31468. },
  31469. mdat: function mdat(data) {
  31470. return {
  31471. byteLength: data.byteLength,
  31472. nals: nalParse(data)
  31473. };
  31474. },
  31475. mdhd: function mdhd(data) {
  31476. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31477. i = 4,
  31478. language,
  31479. result = {
  31480. version: view.getUint8(0),
  31481. flags: new Uint8Array(data.subarray(1, 4)),
  31482. language: ''
  31483. };
  31484. if (result.version === 1) {
  31485. i += 4;
  31486. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  31487. i += 8;
  31488. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  31489. i += 4;
  31490. result.timescale = view.getUint32(i);
  31491. i += 8;
  31492. result.duration = view.getUint32(i); // truncating top 4 bytes
  31493. } else {
  31494. result.creationTime = parseMp4Date(view.getUint32(i));
  31495. i += 4;
  31496. result.modificationTime = parseMp4Date(view.getUint32(i));
  31497. i += 4;
  31498. result.timescale = view.getUint32(i);
  31499. i += 4;
  31500. result.duration = view.getUint32(i);
  31501. }
  31502. i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
  31503. // each field is the packed difference between its ASCII value and 0x60
  31504. language = view.getUint16(i);
  31505. result.language += String.fromCharCode((language >> 10) + 0x60);
  31506. result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
  31507. result.language += String.fromCharCode((language & 0x1f) + 0x60);
  31508. return result;
  31509. },
  31510. mdia: function mdia(data) {
  31511. return {
  31512. boxes: inspectMp4(data)
  31513. };
  31514. },
  31515. mfhd: function mfhd(data) {
  31516. return {
  31517. version: data[0],
  31518. flags: new Uint8Array(data.subarray(1, 4)),
  31519. sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  31520. };
  31521. },
  31522. minf: function minf(data) {
  31523. return {
  31524. boxes: inspectMp4(data)
  31525. };
  31526. },
  31527. // codingname, not a first-class box type. stsd entries share the
  31528. // same format as real boxes so the parsing infrastructure can be
  31529. // shared
  31530. mp4a: function mp4a(data) {
  31531. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31532. result = {
  31533. // 6 bytes reserved
  31534. dataReferenceIndex: view.getUint16(6),
  31535. // 4 + 4 bytes reserved
  31536. channelcount: view.getUint16(16),
  31537. samplesize: view.getUint16(18),
  31538. // 2 bytes pre_defined
  31539. // 2 bytes reserved
  31540. samplerate: view.getUint16(24) + view.getUint16(26) / 65536
  31541. }; // if there are more bytes to process, assume this is an ISO/IEC
  31542. // 14496-14 MP4AudioSampleEntry and parse the ESDBox
  31543. if (data.byteLength > 28) {
  31544. result.streamDescriptor = inspectMp4(data.subarray(28))[0];
  31545. }
  31546. return result;
  31547. },
  31548. moof: function moof(data) {
  31549. return {
  31550. boxes: inspectMp4(data)
  31551. };
  31552. },
  31553. moov: function moov(data) {
  31554. return {
  31555. boxes: inspectMp4(data)
  31556. };
  31557. },
  31558. mvex: function mvex(data) {
  31559. return {
  31560. boxes: inspectMp4(data)
  31561. };
  31562. },
  31563. mvhd: function mvhd(data) {
  31564. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31565. i = 4,
  31566. result = {
  31567. version: view.getUint8(0),
  31568. flags: new Uint8Array(data.subarray(1, 4))
  31569. };
  31570. if (result.version === 1) {
  31571. i += 4;
  31572. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  31573. i += 8;
  31574. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  31575. i += 4;
  31576. result.timescale = view.getUint32(i);
  31577. i += 8;
  31578. result.duration = view.getUint32(i); // truncating top 4 bytes
  31579. } else {
  31580. result.creationTime = parseMp4Date(view.getUint32(i));
  31581. i += 4;
  31582. result.modificationTime = parseMp4Date(view.getUint32(i));
  31583. i += 4;
  31584. result.timescale = view.getUint32(i);
  31585. i += 4;
  31586. result.duration = view.getUint32(i);
  31587. }
  31588. i += 4; // convert fixed-point, base 16 back to a number
  31589. result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
  31590. i += 4;
  31591. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  31592. i += 2;
  31593. i += 2;
  31594. i += 2 * 4;
  31595. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  31596. i += 9 * 4;
  31597. i += 6 * 4;
  31598. result.nextTrackId = view.getUint32(i);
  31599. return result;
  31600. },
  31601. pdin: function pdin(data) {
  31602. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  31603. return {
  31604. version: view.getUint8(0),
  31605. flags: new Uint8Array(data.subarray(1, 4)),
  31606. rate: view.getUint32(4),
  31607. initialDelay: view.getUint32(8)
  31608. };
  31609. },
  31610. sdtp: function sdtp(data) {
  31611. var result = {
  31612. version: data[0],
  31613. flags: new Uint8Array(data.subarray(1, 4)),
  31614. samples: []
  31615. },
  31616. i;
  31617. for (i = 4; i < data.byteLength; i++) {
  31618. result.samples.push({
  31619. dependsOn: (data[i] & 0x30) >> 4,
  31620. isDependedOn: (data[i] & 0x0c) >> 2,
  31621. hasRedundancy: data[i] & 0x03
  31622. });
  31623. }
  31624. return result;
  31625. },
  31626. sidx: function sidx(data) {
  31627. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31628. result = {
  31629. version: data[0],
  31630. flags: new Uint8Array(data.subarray(1, 4)),
  31631. references: [],
  31632. referenceId: view.getUint32(4),
  31633. timescale: view.getUint32(8),
  31634. earliestPresentationTime: view.getUint32(12),
  31635. firstOffset: view.getUint32(16)
  31636. },
  31637. referenceCount = view.getUint16(22),
  31638. i;
  31639. for (i = 24; referenceCount; i += 12, referenceCount--) {
  31640. result.references.push({
  31641. referenceType: (data[i] & 0x80) >>> 7,
  31642. referencedSize: view.getUint32(i) & 0x7FFFFFFF,
  31643. subsegmentDuration: view.getUint32(i + 4),
  31644. startsWithSap: !!(data[i + 8] & 0x80),
  31645. sapType: (data[i + 8] & 0x70) >>> 4,
  31646. sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
  31647. });
  31648. }
  31649. return result;
  31650. },
  31651. smhd: function smhd(data) {
  31652. return {
  31653. version: data[0],
  31654. flags: new Uint8Array(data.subarray(1, 4)),
  31655. balance: data[4] + data[5] / 256
  31656. };
  31657. },
  31658. stbl: function stbl(data) {
  31659. return {
  31660. boxes: inspectMp4(data)
  31661. };
  31662. },
  31663. stco: function stco(data) {
  31664. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31665. result = {
  31666. version: data[0],
  31667. flags: new Uint8Array(data.subarray(1, 4)),
  31668. chunkOffsets: []
  31669. },
  31670. entryCount = view.getUint32(4),
  31671. i;
  31672. for (i = 8; entryCount; i += 4, entryCount--) {
  31673. result.chunkOffsets.push(view.getUint32(i));
  31674. }
  31675. return result;
  31676. },
  31677. stsc: function stsc(data) {
  31678. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31679. entryCount = view.getUint32(4),
  31680. result = {
  31681. version: data[0],
  31682. flags: new Uint8Array(data.subarray(1, 4)),
  31683. sampleToChunks: []
  31684. },
  31685. i;
  31686. for (i = 8; entryCount; i += 12, entryCount--) {
  31687. result.sampleToChunks.push({
  31688. firstChunk: view.getUint32(i),
  31689. samplesPerChunk: view.getUint32(i + 4),
  31690. sampleDescriptionIndex: view.getUint32(i + 8)
  31691. });
  31692. }
  31693. return result;
  31694. },
  31695. stsd: function stsd(data) {
  31696. return {
  31697. version: data[0],
  31698. flags: new Uint8Array(data.subarray(1, 4)),
  31699. sampleDescriptions: inspectMp4(data.subarray(8))
  31700. };
  31701. },
  31702. stsz: function stsz(data) {
  31703. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31704. result = {
  31705. version: data[0],
  31706. flags: new Uint8Array(data.subarray(1, 4)),
  31707. sampleSize: view.getUint32(4),
  31708. entries: []
  31709. },
  31710. i;
  31711. for (i = 12; i < data.byteLength; i += 4) {
  31712. result.entries.push(view.getUint32(i));
  31713. }
  31714. return result;
  31715. },
  31716. stts: function stts(data) {
  31717. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31718. result = {
  31719. version: data[0],
  31720. flags: new Uint8Array(data.subarray(1, 4)),
  31721. timeToSamples: []
  31722. },
  31723. entryCount = view.getUint32(4),
  31724. i;
  31725. for (i = 8; entryCount; i += 8, entryCount--) {
  31726. result.timeToSamples.push({
  31727. sampleCount: view.getUint32(i),
  31728. sampleDelta: view.getUint32(i + 4)
  31729. });
  31730. }
  31731. return result;
  31732. },
  31733. styp: function styp(data) {
  31734. return parse$1.ftyp(data);
  31735. },
  31736. tfdt: function tfdt(data) {
  31737. var result = {
  31738. version: data[0],
  31739. flags: new Uint8Array(data.subarray(1, 4)),
  31740. baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  31741. };
  31742. if (result.version === 1) {
  31743. result.baseMediaDecodeTime *= Math.pow(2, 32);
  31744. result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11];
  31745. }
  31746. return result;
  31747. },
  31748. tfhd: function tfhd(data) {
  31749. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31750. result = {
  31751. version: data[0],
  31752. flags: new Uint8Array(data.subarray(1, 4)),
  31753. trackId: view.getUint32(4)
  31754. },
  31755. baseDataOffsetPresent = result.flags[2] & 0x01,
  31756. sampleDescriptionIndexPresent = result.flags[2] & 0x02,
  31757. defaultSampleDurationPresent = result.flags[2] & 0x08,
  31758. defaultSampleSizePresent = result.flags[2] & 0x10,
  31759. defaultSampleFlagsPresent = result.flags[2] & 0x20,
  31760. durationIsEmpty = result.flags[0] & 0x010000,
  31761. defaultBaseIsMoof = result.flags[0] & 0x020000,
  31762. i;
  31763. i = 8;
  31764. if (baseDataOffsetPresent) {
  31765. i += 4; // truncate top 4 bytes
  31766. // FIXME: should we read the full 64 bits?
  31767. result.baseDataOffset = view.getUint32(12);
  31768. i += 4;
  31769. }
  31770. if (sampleDescriptionIndexPresent) {
  31771. result.sampleDescriptionIndex = view.getUint32(i);
  31772. i += 4;
  31773. }
  31774. if (defaultSampleDurationPresent) {
  31775. result.defaultSampleDuration = view.getUint32(i);
  31776. i += 4;
  31777. }
  31778. if (defaultSampleSizePresent) {
  31779. result.defaultSampleSize = view.getUint32(i);
  31780. i += 4;
  31781. }
  31782. if (defaultSampleFlagsPresent) {
  31783. result.defaultSampleFlags = view.getUint32(i);
  31784. }
  31785. if (durationIsEmpty) {
  31786. result.durationIsEmpty = true;
  31787. }
  31788. if (!baseDataOffsetPresent && defaultBaseIsMoof) {
  31789. result.baseDataOffsetIsMoof = true;
  31790. }
  31791. return result;
  31792. },
  31793. tkhd: function tkhd(data) {
  31794. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31795. i = 4,
  31796. result = {
  31797. version: view.getUint8(0),
  31798. flags: new Uint8Array(data.subarray(1, 4))
  31799. };
  31800. if (result.version === 1) {
  31801. i += 4;
  31802. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  31803. i += 8;
  31804. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  31805. i += 4;
  31806. result.trackId = view.getUint32(i);
  31807. i += 4;
  31808. i += 8;
  31809. result.duration = view.getUint32(i); // truncating top 4 bytes
  31810. } else {
  31811. result.creationTime = parseMp4Date(view.getUint32(i));
  31812. i += 4;
  31813. result.modificationTime = parseMp4Date(view.getUint32(i));
  31814. i += 4;
  31815. result.trackId = view.getUint32(i);
  31816. i += 4;
  31817. i += 4;
  31818. result.duration = view.getUint32(i);
  31819. }
  31820. i += 4;
  31821. i += 2 * 4;
  31822. result.layer = view.getUint16(i);
  31823. i += 2;
  31824. result.alternateGroup = view.getUint16(i);
  31825. i += 2; // convert fixed-point, base 16 back to a number
  31826. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  31827. i += 2;
  31828. i += 2;
  31829. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  31830. i += 9 * 4;
  31831. result.width = view.getUint16(i) + view.getUint16(i + 2) / 16;
  31832. i += 4;
  31833. result.height = view.getUint16(i) + view.getUint16(i + 2) / 16;
  31834. return result;
  31835. },
  31836. traf: function traf(data) {
  31837. return {
  31838. boxes: inspectMp4(data)
  31839. };
  31840. },
  31841. trak: function trak(data) {
  31842. return {
  31843. boxes: inspectMp4(data)
  31844. };
  31845. },
  31846. trex: function trex(data) {
  31847. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  31848. return {
  31849. version: data[0],
  31850. flags: new Uint8Array(data.subarray(1, 4)),
  31851. trackId: view.getUint32(4),
  31852. defaultSampleDescriptionIndex: view.getUint32(8),
  31853. defaultSampleDuration: view.getUint32(12),
  31854. defaultSampleSize: view.getUint32(16),
  31855. sampleDependsOn: data[20] & 0x03,
  31856. sampleIsDependedOn: (data[21] & 0xc0) >> 6,
  31857. sampleHasRedundancy: (data[21] & 0x30) >> 4,
  31858. samplePaddingValue: (data[21] & 0x0e) >> 1,
  31859. sampleIsDifferenceSample: !!(data[21] & 0x01),
  31860. sampleDegradationPriority: view.getUint16(22)
  31861. };
  31862. },
  31863. trun: function trun(data) {
  31864. var result = {
  31865. version: data[0],
  31866. flags: new Uint8Array(data.subarray(1, 4)),
  31867. samples: []
  31868. },
  31869. view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  31870. // Flag interpretation
  31871. dataOffsetPresent = result.flags[2] & 0x01,
  31872. // compare with 2nd byte of 0x1
  31873. firstSampleFlagsPresent = result.flags[2] & 0x04,
  31874. // compare with 2nd byte of 0x4
  31875. sampleDurationPresent = result.flags[1] & 0x01,
  31876. // compare with 2nd byte of 0x100
  31877. sampleSizePresent = result.flags[1] & 0x02,
  31878. // compare with 2nd byte of 0x200
  31879. sampleFlagsPresent = result.flags[1] & 0x04,
  31880. // compare with 2nd byte of 0x400
  31881. sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
  31882. // compare with 2nd byte of 0x800
  31883. sampleCount = view.getUint32(4),
  31884. offset = 8,
  31885. sample;
  31886. if (dataOffsetPresent) {
  31887. // 32 bit signed integer
  31888. result.dataOffset = view.getInt32(offset);
  31889. offset += 4;
  31890. } // Overrides the flags for the first sample only. The order of
  31891. // optional values will be: duration, size, compositionTimeOffset
  31892. if (firstSampleFlagsPresent && sampleCount) {
  31893. sample = {
  31894. flags: parseSampleFlags(data.subarray(offset, offset + 4))
  31895. };
  31896. offset += 4;
  31897. if (sampleDurationPresent) {
  31898. sample.duration = view.getUint32(offset);
  31899. offset += 4;
  31900. }
  31901. if (sampleSizePresent) {
  31902. sample.size = view.getUint32(offset);
  31903. offset += 4;
  31904. }
  31905. if (sampleCompositionTimeOffsetPresent) {
  31906. // Note: this should be a signed int if version is 1
  31907. sample.compositionTimeOffset = view.getUint32(offset);
  31908. offset += 4;
  31909. }
  31910. result.samples.push(sample);
  31911. sampleCount--;
  31912. }
  31913. while (sampleCount--) {
  31914. sample = {};
  31915. if (sampleDurationPresent) {
  31916. sample.duration = view.getUint32(offset);
  31917. offset += 4;
  31918. }
  31919. if (sampleSizePresent) {
  31920. sample.size = view.getUint32(offset);
  31921. offset += 4;
  31922. }
  31923. if (sampleFlagsPresent) {
  31924. sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
  31925. offset += 4;
  31926. }
  31927. if (sampleCompositionTimeOffsetPresent) {
  31928. // Note: this should be a signed int if version is 1
  31929. sample.compositionTimeOffset = view.getUint32(offset);
  31930. offset += 4;
  31931. }
  31932. result.samples.push(sample);
  31933. }
  31934. return result;
  31935. },
  31936. 'url ': function url(data) {
  31937. return {
  31938. version: data[0],
  31939. flags: new Uint8Array(data.subarray(1, 4))
  31940. };
  31941. },
  31942. vmhd: function vmhd(data) {
  31943. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  31944. return {
  31945. version: data[0],
  31946. flags: new Uint8Array(data.subarray(1, 4)),
  31947. graphicsmode: view.getUint16(4),
  31948. opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
  31949. };
  31950. }
  31951. };
  31952. /**
  31953. * Return a javascript array of box objects parsed from an ISO base
  31954. * media file.
  31955. * @param data {Uint8Array} the binary data of the media to be inspected
  31956. * @return {array} a javascript array of potentially nested box objects
  31957. */
  31958. inspectMp4 = function inspectMp4(data) {
  31959. var i = 0,
  31960. result = [],
  31961. view,
  31962. size,
  31963. type,
  31964. end,
  31965. box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
  31966. var ab = new ArrayBuffer(data.length);
  31967. var v = new Uint8Array(ab);
  31968. for (var z = 0; z < data.length; ++z) {
  31969. v[z] = data[z];
  31970. }
  31971. view = new DataView(ab);
  31972. while (i < data.byteLength) {
  31973. // parse box data
  31974. size = view.getUint32(i);
  31975. type = parseType$2(data.subarray(i + 4, i + 8));
  31976. end = size > 1 ? i + size : data.byteLength; // parse type-specific data
  31977. box = (parse$1[type] || function (data) {
  31978. return {
  31979. data: data
  31980. };
  31981. })(data.subarray(i + 8, end));
  31982. box.size = size;
  31983. box.type = type; // store this box and move to the next
  31984. result.push(box);
  31985. i = end;
  31986. }
  31987. return result;
  31988. };
  31989. /**
  31990. * Returns a textual representation of the javascript represtentation
  31991. * of an MP4 file. You can use it as an alternative to
  31992. * JSON.stringify() to compare inspected MP4s.
  31993. * @param inspectedMp4 {array} the parsed array of boxes in an MP4
  31994. * file
  31995. * @param depth {number} (optional) the number of ancestor boxes of
  31996. * the elements of inspectedMp4. Assumed to be zero if unspecified.
  31997. * @return {string} a text representation of the parsed MP4
  31998. */
  31999. _textifyMp = function textifyMp4(inspectedMp4, depth) {
  32000. var indent;
  32001. depth = depth || 0;
  32002. indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
  32003. return inspectedMp4.map(function (box, index) {
  32004. // list the box type first at the current indentation level
  32005. return indent + box.type + '\n' + // the type is already included and handle child boxes separately
  32006. Object.keys(box).filter(function (key) {
  32007. return key !== 'type' && key !== 'boxes'; // output all the box properties
  32008. }).map(function (key) {
  32009. var prefix = indent + ' ' + key + ': ',
  32010. value = box[key]; // print out raw bytes as hexademical
  32011. if (value instanceof Uint8Array || value instanceof Uint32Array) {
  32012. var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) {
  32013. return ' ' + ('00' + byte.toString(16)).slice(-2);
  32014. }).join('').match(/.{1,24}/g);
  32015. if (!bytes) {
  32016. return prefix + '<>';
  32017. }
  32018. if (bytes.length === 1) {
  32019. return prefix + '<' + bytes.join('').slice(1) + '>';
  32020. }
  32021. return prefix + '<\n' + bytes.map(function (line) {
  32022. return indent + ' ' + line;
  32023. }).join('\n') + '\n' + indent + ' >';
  32024. } // stringify generic objects
  32025. return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
  32026. if (index === 0) {
  32027. return line;
  32028. }
  32029. return indent + ' ' + line;
  32030. }).join('\n');
  32031. }).join('\n') + ( // recursively textify the child boxes
  32032. box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
  32033. }).join('\n');
  32034. };
  32035. var mp4Inspector = {
  32036. inspect: inspectMp4,
  32037. textify: _textifyMp,
  32038. parseTfdt: parse$1.tfdt,
  32039. parseHdlr: parse$1.hdlr,
  32040. parseTfhd: parse$1.tfhd,
  32041. parseTrun: parse$1.trun,
  32042. parseSidx: parse$1.sidx
  32043. };
  32044. var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
  32045. var CaptionStream$1 = captionStream.CaptionStream;
  32046. /**
  32047. * Maps an offset in the mdat to a sample based on the the size of the samples.
  32048. * Assumes that `parseSamples` has been called first.
  32049. *
  32050. * @param {Number} offset - The offset into the mdat
  32051. * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
  32052. * @return {?Object} The matching sample, or null if no match was found.
  32053. *
  32054. * @see ISO-BMFF-12/2015, Section 8.8.8
  32055. **/
  32056. var mapToSample = function mapToSample(offset, samples) {
  32057. var approximateOffset = offset;
  32058. for (var i = 0; i < samples.length; i++) {
  32059. var sample = samples[i];
  32060. if (approximateOffset < sample.size) {
  32061. return sample;
  32062. }
  32063. approximateOffset -= sample.size;
  32064. }
  32065. return null;
  32066. };
  32067. /**
  32068. * Finds SEI nal units contained in a Media Data Box.
  32069. * Assumes that `parseSamples` has been called first.
  32070. *
  32071. * @param {Uint8Array} avcStream - The bytes of the mdat
  32072. * @param {Object[]} samples - The samples parsed out by `parseSamples`
  32073. * @param {Number} trackId - The trackId of this video track
  32074. * @return {Object[]} seiNals - the parsed SEI NALUs found.
  32075. * The contents of the seiNal should match what is expected by
  32076. * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
  32077. *
  32078. * @see ISO-BMFF-12/2015, Section 8.1.1
  32079. * @see Rec. ITU-T H.264, 7.3.2.3.1
  32080. **/
  32081. var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
  32082. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  32083. result = [],
  32084. seiNal,
  32085. i,
  32086. length,
  32087. lastMatchedSample;
  32088. for (i = 0; i + 4 < avcStream.length; i += length) {
  32089. length = avcView.getUint32(i);
  32090. i += 4; // Bail if this doesn't appear to be an H264 stream
  32091. if (length <= 0) {
  32092. continue;
  32093. }
  32094. switch (avcStream[i] & 0x1F) {
  32095. case 0x06:
  32096. var data = avcStream.subarray(i + 1, i + 1 + length);
  32097. var matchingSample = mapToSample(i, samples);
  32098. seiNal = {
  32099. nalUnitType: 'sei_rbsp',
  32100. size: length,
  32101. data: data,
  32102. escapedRBSP: discardEmulationPreventionBytes$1(data),
  32103. trackId: trackId
  32104. };
  32105. if (matchingSample) {
  32106. seiNal.pts = matchingSample.pts;
  32107. seiNal.dts = matchingSample.dts;
  32108. lastMatchedSample = matchingSample;
  32109. } else {
  32110. // If a matching sample cannot be found, use the last
  32111. // sample's values as they should be as close as possible
  32112. seiNal.pts = lastMatchedSample.pts;
  32113. seiNal.dts = lastMatchedSample.dts;
  32114. }
  32115. result.push(seiNal);
  32116. break;
  32117. default:
  32118. break;
  32119. }
  32120. }
  32121. return result;
  32122. };
  32123. /**
  32124. * Parses sample information out of Track Run Boxes and calculates
  32125. * the absolute presentation and decode timestamps of each sample.
  32126. *
  32127. * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
  32128. * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
  32129. @see ISO-BMFF-12/2015, Section 8.8.12
  32130. * @param {Object} tfhd - The parsed Track Fragment Header
  32131. * @see inspect.parseTfhd
  32132. * @return {Object[]} the parsed samples
  32133. *
  32134. * @see ISO-BMFF-12/2015, Section 8.8.8
  32135. **/
  32136. var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
  32137. var currentDts = baseMediaDecodeTime;
  32138. var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
  32139. var defaultSampleSize = tfhd.defaultSampleSize || 0;
  32140. var trackId = tfhd.trackId;
  32141. var allSamples = [];
  32142. truns.forEach(function (trun) {
  32143. // Note: We currently do not parse the sample table as well
  32144. // as the trun. It's possible some sources will require this.
  32145. // moov > trak > mdia > minf > stbl
  32146. var trackRun = mp4Inspector.parseTrun(trun);
  32147. var samples = trackRun.samples;
  32148. samples.forEach(function (sample) {
  32149. if (sample.duration === undefined) {
  32150. sample.duration = defaultSampleDuration;
  32151. }
  32152. if (sample.size === undefined) {
  32153. sample.size = defaultSampleSize;
  32154. }
  32155. sample.trackId = trackId;
  32156. sample.dts = currentDts;
  32157. if (sample.compositionTimeOffset === undefined) {
  32158. sample.compositionTimeOffset = 0;
  32159. }
  32160. sample.pts = currentDts + sample.compositionTimeOffset;
  32161. currentDts += sample.duration;
  32162. });
  32163. allSamples = allSamples.concat(samples);
  32164. });
  32165. return allSamples;
  32166. };
  32167. /**
  32168. * Parses out caption nals from an FMP4 segment's video tracks.
  32169. *
  32170. * @param {Uint8Array} segment - The bytes of a single segment
  32171. * @param {Number} videoTrackId - The trackId of a video track in the segment
  32172. * @return {Object.<Number, Object[]>} A mapping of video trackId to
  32173. * a list of seiNals found in that track
  32174. **/
  32175. var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
  32176. // To get the samples
  32177. var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units
  32178. var mdats = probe.findBox(segment, ['mdat']);
  32179. var captionNals = {};
  32180. var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
  32181. mdats.forEach(function (mdat, index) {
  32182. var matchingTraf = trafs[index];
  32183. mdatTrafPairs.push({
  32184. mdat: mdat,
  32185. traf: matchingTraf
  32186. });
  32187. });
  32188. mdatTrafPairs.forEach(function (pair) {
  32189. var mdat = pair.mdat;
  32190. var traf = pair.traf;
  32191. var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
  32192. var headerInfo = mp4Inspector.parseTfhd(tfhd[0]);
  32193. var trackId = headerInfo.trackId;
  32194. var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
  32195. var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
  32196. var truns = probe.findBox(traf, ['trun']);
  32197. var samples;
  32198. var seiNals; // Only parse video data for the chosen video track
  32199. if (videoTrackId === trackId && truns.length > 0) {
  32200. samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
  32201. seiNals = findSeiNals(mdat, samples, trackId);
  32202. if (!captionNals[trackId]) {
  32203. captionNals[trackId] = [];
  32204. }
  32205. captionNals[trackId] = captionNals[trackId].concat(seiNals);
  32206. }
  32207. });
  32208. return captionNals;
  32209. };
  32210. /**
  32211. * Parses out inband captions from an MP4 container and returns
  32212. * caption objects that can be used by WebVTT and the TextTrack API.
  32213. * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
  32214. * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
  32215. * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
  32216. *
  32217. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  32218. * @param {Number} trackId - The id of the video track to parse
  32219. * @param {Number} timescale - The timescale for the video track from the init segment
  32220. *
  32221. * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
  32222. * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
  32223. * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
  32224. * @return {String} parsedCaptions[].text - The visible content of the caption
  32225. **/
  32226. var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
  32227. var seiNals;
  32228. if (!trackId) {
  32229. return null;
  32230. }
  32231. seiNals = parseCaptionNals(segment, trackId);
  32232. return {
  32233. seiNals: seiNals[trackId],
  32234. timescale: timescale
  32235. };
  32236. };
  32237. /**
  32238. * Converts SEI NALUs into captions that can be used by video.js
  32239. **/
  32240. var CaptionParser = function CaptionParser() {
  32241. var isInitialized = false;
  32242. var captionStream$$1; // Stores segments seen before trackId and timescale are set
  32243. var segmentCache; // Stores video track ID of the track being parsed
  32244. var trackId; // Stores the timescale of the track being parsed
  32245. var timescale; // Stores captions parsed so far
  32246. var parsedCaptions;
  32247. /**
  32248. * A method to indicate whether a CaptionParser has been initalized
  32249. * @returns {Boolean}
  32250. **/
  32251. this.isInitialized = function () {
  32252. return isInitialized;
  32253. };
  32254. /**
  32255. * Initializes the underlying CaptionStream, SEI NAL parsing
  32256. * and management, and caption collection
  32257. **/
  32258. this.init = function () {
  32259. captionStream$$1 = new CaptionStream$1();
  32260. isInitialized = true; // Collect dispatched captions
  32261. captionStream$$1.on('data', function (event) {
  32262. // Convert to seconds in the source's timescale
  32263. event.startTime = event.startPts / timescale;
  32264. event.endTime = event.endPts / timescale;
  32265. parsedCaptions.captions.push(event);
  32266. parsedCaptions.captionStreams[event.stream] = true;
  32267. });
  32268. };
  32269. /**
  32270. * Determines if a new video track will be selected
  32271. * or if the timescale changed
  32272. * @return {Boolean}
  32273. **/
  32274. this.isNewInit = function (videoTrackIds, timescales) {
  32275. if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
  32276. return false;
  32277. }
  32278. return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
  32279. };
  32280. /**
  32281. * Parses out SEI captions and interacts with underlying
  32282. * CaptionStream to return dispatched captions
  32283. *
  32284. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  32285. * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
  32286. * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
  32287. * @see parseEmbeddedCaptions
  32288. * @see m2ts/caption-stream.js
  32289. **/
  32290. this.parse = function (segment, videoTrackIds, timescales) {
  32291. var parsedData;
  32292. if (!this.isInitialized()) {
  32293. return null; // This is not likely to be a video segment
  32294. } else if (!videoTrackIds || !timescales) {
  32295. return null;
  32296. } else if (this.isNewInit(videoTrackIds, timescales)) {
  32297. // Use the first video track only as there is no
  32298. // mechanism to switch to other video tracks
  32299. trackId = videoTrackIds[0];
  32300. timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
  32301. // data until we have one
  32302. } else if (!trackId || !timescale) {
  32303. segmentCache.push(segment);
  32304. return null;
  32305. } // Now that a timescale and trackId is set, parse cached segments
  32306. while (segmentCache.length > 0) {
  32307. var cachedSegment = segmentCache.shift();
  32308. this.parse(cachedSegment, videoTrackIds, timescales);
  32309. }
  32310. parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
  32311. if (parsedData === null || !parsedData.seiNals) {
  32312. return null;
  32313. }
  32314. this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
  32315. this.flushStream();
  32316. return parsedCaptions;
  32317. };
  32318. /**
  32319. * Pushes SEI NALUs onto CaptionStream
  32320. * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
  32321. * Assumes that `parseCaptionNals` has been called first
  32322. * @see m2ts/caption-stream.js
  32323. **/
  32324. this.pushNals = function (nals) {
  32325. if (!this.isInitialized() || !nals || nals.length === 0) {
  32326. return null;
  32327. }
  32328. nals.forEach(function (nal) {
  32329. captionStream$$1.push(nal);
  32330. });
  32331. };
  32332. /**
  32333. * Flushes underlying CaptionStream to dispatch processed, displayable captions
  32334. * @see m2ts/caption-stream.js
  32335. **/
  32336. this.flushStream = function () {
  32337. if (!this.isInitialized()) {
  32338. return null;
  32339. }
  32340. captionStream$$1.flush();
  32341. };
  32342. /**
  32343. * Reset caption buckets for new data
  32344. **/
  32345. this.clearParsedCaptions = function () {
  32346. parsedCaptions.captions = [];
  32347. parsedCaptions.captionStreams = {};
  32348. };
  32349. /**
  32350. * Resets underlying CaptionStream
  32351. * @see m2ts/caption-stream.js
  32352. **/
  32353. this.resetCaptionStream = function () {
  32354. if (!this.isInitialized()) {
  32355. return null;
  32356. }
  32357. captionStream$$1.reset();
  32358. };
  32359. /**
  32360. * Convenience method to clear all captions flushed from the
  32361. * CaptionStream and still being parsed
  32362. * @see m2ts/caption-stream.js
  32363. **/
  32364. this.clearAllCaptions = function () {
  32365. this.clearParsedCaptions();
  32366. this.resetCaptionStream();
  32367. };
  32368. /**
  32369. * Reset caption parser
  32370. **/
  32371. this.reset = function () {
  32372. segmentCache = [];
  32373. trackId = null;
  32374. timescale = null;
  32375. if (!parsedCaptions) {
  32376. parsedCaptions = {
  32377. captions: [],
  32378. // CC1, CC2, CC3, CC4
  32379. captionStreams: {}
  32380. };
  32381. } else {
  32382. this.clearParsedCaptions();
  32383. }
  32384. this.resetCaptionStream();
  32385. };
  32386. this.reset();
  32387. };
  32388. var captionParser = CaptionParser;
  32389. var mp4 = {
  32390. generator: mp4Generator,
  32391. probe: probe,
  32392. Transmuxer: transmuxer.Transmuxer,
  32393. AudioSegmentStream: transmuxer.AudioSegmentStream,
  32394. VideoSegmentStream: transmuxer.VideoSegmentStream,
  32395. CaptionParser: captionParser
  32396. };
  32397. var mp4_6 = mp4.CaptionParser;
  32398. var parsePid = function parsePid(packet) {
  32399. var pid = packet[1] & 0x1f;
  32400. pid <<= 8;
  32401. pid |= packet[2];
  32402. return pid;
  32403. };
  32404. var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
  32405. return !!(packet[1] & 0x40);
  32406. };
  32407. var parseAdaptionField = function parseAdaptionField(packet) {
  32408. var offset = 0; // if an adaption field is present, its length is specified by the
  32409. // fifth byte of the TS packet header. The adaptation field is
  32410. // used to add stuffing to PES packets that don't fill a complete
  32411. // TS packet, and to specify some forms of timing and control data
  32412. // that we do not currently use.
  32413. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  32414. offset += packet[4] + 1;
  32415. }
  32416. return offset;
  32417. };
  32418. var parseType$3 = function parseType(packet, pmtPid) {
  32419. var pid = parsePid(packet);
  32420. if (pid === 0) {
  32421. return 'pat';
  32422. } else if (pid === pmtPid) {
  32423. return 'pmt';
  32424. } else if (pmtPid) {
  32425. return 'pes';
  32426. }
  32427. return null;
  32428. };
  32429. var parsePat = function parsePat(packet) {
  32430. var pusi = parsePayloadUnitStartIndicator(packet);
  32431. var offset = 4 + parseAdaptionField(packet);
  32432. if (pusi) {
  32433. offset += packet[offset] + 1;
  32434. }
  32435. return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
  32436. };
  32437. var parsePmt = function parsePmt(packet) {
  32438. var programMapTable = {};
  32439. var pusi = parsePayloadUnitStartIndicator(packet);
  32440. var payloadOffset = 4 + parseAdaptionField(packet);
  32441. if (pusi) {
  32442. payloadOffset += packet[payloadOffset] + 1;
  32443. } // PMTs can be sent ahead of the time when they should actually
  32444. // take effect. We don't believe this should ever be the case
  32445. // for HLS but we'll ignore "forward" PMT declarations if we see
  32446. // them. Future PMT declarations have the current_next_indicator
  32447. // set to zero.
  32448. if (!(packet[payloadOffset + 5] & 0x01)) {
  32449. return;
  32450. }
  32451. var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
  32452. sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
  32453. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  32454. // long the program info descriptors are
  32455. programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
  32456. var offset = 12 + programInfoLength;
  32457. while (offset < tableEnd) {
  32458. var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
  32459. programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
  32460. // skip past the elementary stream descriptors, if present
  32461. offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
  32462. }
  32463. return programMapTable;
  32464. };
  32465. var parsePesType = function parsePesType(packet, programMapTable) {
  32466. var pid = parsePid(packet);
  32467. var type = programMapTable[pid];
  32468. switch (type) {
  32469. case streamTypes.H264_STREAM_TYPE:
  32470. return 'video';
  32471. case streamTypes.ADTS_STREAM_TYPE:
  32472. return 'audio';
  32473. case streamTypes.METADATA_STREAM_TYPE:
  32474. return 'timed-metadata';
  32475. default:
  32476. return null;
  32477. }
  32478. };
  32479. var parsePesTime = function parsePesTime(packet) {
  32480. var pusi = parsePayloadUnitStartIndicator(packet);
  32481. if (!pusi) {
  32482. return null;
  32483. }
  32484. var offset = 4 + parseAdaptionField(packet);
  32485. if (offset >= packet.byteLength) {
  32486. // From the H 222.0 MPEG-TS spec
  32487. // "For transport stream packets carrying PES packets, stuffing is needed when there
  32488. // is insufficient PES packet data to completely fill the transport stream packet
  32489. // payload bytes. Stuffing is accomplished by defining an adaptation field longer than
  32490. // the sum of the lengths of the data elements in it, so that the payload bytes
  32491. // remaining after the adaptation field exactly accommodates the available PES packet
  32492. // data."
  32493. //
  32494. // If the offset is >= the length of the packet, then the packet contains no data
  32495. // and instead is just adaption field stuffing bytes
  32496. return null;
  32497. }
  32498. var pes = null;
  32499. var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
  32500. // and a DTS value. Determine what combination of values is
  32501. // available to work with.
  32502. ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  32503. // performs all bitwise operations on 32-bit integers but javascript
  32504. // supports a much greater range (52-bits) of integer using standard
  32505. // mathematical operations.
  32506. // We construct a 31-bit value using bitwise operators over the 31
  32507. // most significant bits and then multiply by 4 (equal to a left-shift
  32508. // of 2) before we add the final 2 least significant bits of the
  32509. // timestamp (equal to an OR.)
  32510. if (ptsDtsFlags & 0xC0) {
  32511. pes = {}; // the PTS and DTS are not written out directly. For information
  32512. // on how they are encoded, see
  32513. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  32514. 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;
  32515. pes.pts *= 4; // Left shift by 2
  32516. pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
  32517. pes.dts = pes.pts;
  32518. if (ptsDtsFlags & 0x40) {
  32519. 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;
  32520. pes.dts *= 4; // Left shift by 2
  32521. pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
  32522. }
  32523. }
  32524. return pes;
  32525. };
  32526. var parseNalUnitType = function parseNalUnitType(type) {
  32527. switch (type) {
  32528. case 0x05:
  32529. return 'slice_layer_without_partitioning_rbsp_idr';
  32530. case 0x06:
  32531. return 'sei_rbsp';
  32532. case 0x07:
  32533. return 'seq_parameter_set_rbsp';
  32534. case 0x08:
  32535. return 'pic_parameter_set_rbsp';
  32536. case 0x09:
  32537. return 'access_unit_delimiter_rbsp';
  32538. default:
  32539. return null;
  32540. }
  32541. };
  32542. var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
  32543. var offset = 4 + parseAdaptionField(packet);
  32544. var frameBuffer = packet.subarray(offset);
  32545. var frameI = 0;
  32546. var frameSyncPoint = 0;
  32547. var foundKeyFrame = false;
  32548. var nalType; // advance the sync point to a NAL start, if necessary
  32549. for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
  32550. if (frameBuffer[frameSyncPoint + 2] === 1) {
  32551. // the sync point is properly aligned
  32552. frameI = frameSyncPoint + 5;
  32553. break;
  32554. }
  32555. }
  32556. while (frameI < frameBuffer.byteLength) {
  32557. // look at the current byte to determine if we've hit the end of
  32558. // a NAL unit boundary
  32559. switch (frameBuffer[frameI]) {
  32560. case 0:
  32561. // skip past non-sync sequences
  32562. if (frameBuffer[frameI - 1] !== 0) {
  32563. frameI += 2;
  32564. break;
  32565. } else if (frameBuffer[frameI - 2] !== 0) {
  32566. frameI++;
  32567. break;
  32568. }
  32569. if (frameSyncPoint + 3 !== frameI - 2) {
  32570. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  32571. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  32572. foundKeyFrame = true;
  32573. }
  32574. } // drop trailing zeroes
  32575. do {
  32576. frameI++;
  32577. } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
  32578. frameSyncPoint = frameI - 2;
  32579. frameI += 3;
  32580. break;
  32581. case 1:
  32582. // skip past non-sync sequences
  32583. if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
  32584. frameI += 3;
  32585. break;
  32586. }
  32587. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  32588. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  32589. foundKeyFrame = true;
  32590. }
  32591. frameSyncPoint = frameI - 2;
  32592. frameI += 3;
  32593. break;
  32594. default:
  32595. // the current byte isn't a one or zero, so it cannot be part
  32596. // of a sync sequence
  32597. frameI += 3;
  32598. break;
  32599. }
  32600. }
  32601. frameBuffer = frameBuffer.subarray(frameSyncPoint);
  32602. frameI -= frameSyncPoint;
  32603. frameSyncPoint = 0; // parse the final nal
  32604. if (frameBuffer && frameBuffer.byteLength > 3) {
  32605. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  32606. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  32607. foundKeyFrame = true;
  32608. }
  32609. }
  32610. return foundKeyFrame;
  32611. };
  32612. var probe$1 = {
  32613. parseType: parseType$3,
  32614. parsePat: parsePat,
  32615. parsePmt: parsePmt,
  32616. parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
  32617. parsePesType: parsePesType,
  32618. parsePesTime: parsePesTime,
  32619. videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
  32620. };
  32621. var handleRollover$1 = timestampRolloverStream.handleRollover;
  32622. var probe$2 = {};
  32623. probe$2.ts = probe$1;
  32624. probe$2.aac = utils;
  32625. var PES_TIMESCALE = 90000,
  32626. MP2T_PACKET_LENGTH$1 = 188,
  32627. // bytes
  32628. SYNC_BYTE$1 = 0x47;
  32629. /**
  32630. * walks through segment data looking for pat and pmt packets to parse out
  32631. * program map table information
  32632. */
  32633. var parsePsi_ = function parsePsi_(bytes, pmt) {
  32634. var startIndex = 0,
  32635. endIndex = MP2T_PACKET_LENGTH$1,
  32636. packet,
  32637. type;
  32638. while (endIndex < bytes.byteLength) {
  32639. // Look for a pair of start and end sync bytes in the data..
  32640. if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
  32641. // We found a packet
  32642. packet = bytes.subarray(startIndex, endIndex);
  32643. type = probe$2.ts.parseType(packet, pmt.pid);
  32644. switch (type) {
  32645. case 'pat':
  32646. if (!pmt.pid) {
  32647. pmt.pid = probe$2.ts.parsePat(packet);
  32648. }
  32649. break;
  32650. case 'pmt':
  32651. if (!pmt.table) {
  32652. pmt.table = probe$2.ts.parsePmt(packet);
  32653. }
  32654. break;
  32655. default:
  32656. break;
  32657. } // Found the pat and pmt, we can stop walking the segment
  32658. if (pmt.pid && pmt.table) {
  32659. return;
  32660. }
  32661. startIndex += MP2T_PACKET_LENGTH$1;
  32662. endIndex += MP2T_PACKET_LENGTH$1;
  32663. continue;
  32664. } // If we get here, we have somehow become de-synchronized and we need to step
  32665. // forward one byte at a time until we find a pair of sync bytes that denote
  32666. // a packet
  32667. startIndex++;
  32668. endIndex++;
  32669. }
  32670. };
  32671. /**
  32672. * walks through the segment data from the start and end to get timing information
  32673. * for the first and last audio pes packets
  32674. */
  32675. var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) {
  32676. var startIndex = 0,
  32677. endIndex = MP2T_PACKET_LENGTH$1,
  32678. packet,
  32679. type,
  32680. pesType,
  32681. pusi,
  32682. parsed;
  32683. var endLoop = false; // Start walking from start of segment to get first audio packet
  32684. while (endIndex <= bytes.byteLength) {
  32685. // Look for a pair of start and end sync bytes in the data..
  32686. if (bytes[startIndex] === SYNC_BYTE$1 && (bytes[endIndex] === SYNC_BYTE$1 || endIndex === bytes.byteLength)) {
  32687. // We found a packet
  32688. packet = bytes.subarray(startIndex, endIndex);
  32689. type = probe$2.ts.parseType(packet, pmt.pid);
  32690. switch (type) {
  32691. case 'pes':
  32692. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  32693. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  32694. if (pesType === 'audio' && pusi) {
  32695. parsed = probe$2.ts.parsePesTime(packet);
  32696. if (parsed) {
  32697. parsed.type = 'audio';
  32698. result.audio.push(parsed);
  32699. endLoop = true;
  32700. }
  32701. }
  32702. break;
  32703. default:
  32704. break;
  32705. }
  32706. if (endLoop) {
  32707. break;
  32708. }
  32709. startIndex += MP2T_PACKET_LENGTH$1;
  32710. endIndex += MP2T_PACKET_LENGTH$1;
  32711. continue;
  32712. } // If we get here, we have somehow become de-synchronized and we need to step
  32713. // forward one byte at a time until we find a pair of sync bytes that denote
  32714. // a packet
  32715. startIndex++;
  32716. endIndex++;
  32717. } // Start walking from end of segment to get last audio packet
  32718. endIndex = bytes.byteLength;
  32719. startIndex = endIndex - MP2T_PACKET_LENGTH$1;
  32720. endLoop = false;
  32721. while (startIndex >= 0) {
  32722. // Look for a pair of start and end sync bytes in the data..
  32723. if (bytes[startIndex] === SYNC_BYTE$1 && (bytes[endIndex] === SYNC_BYTE$1 || endIndex === bytes.byteLength)) {
  32724. // We found a packet
  32725. packet = bytes.subarray(startIndex, endIndex);
  32726. type = probe$2.ts.parseType(packet, pmt.pid);
  32727. switch (type) {
  32728. case 'pes':
  32729. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  32730. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  32731. if (pesType === 'audio' && pusi) {
  32732. parsed = probe$2.ts.parsePesTime(packet);
  32733. if (parsed) {
  32734. parsed.type = 'audio';
  32735. result.audio.push(parsed);
  32736. endLoop = true;
  32737. }
  32738. }
  32739. break;
  32740. default:
  32741. break;
  32742. }
  32743. if (endLoop) {
  32744. break;
  32745. }
  32746. startIndex -= MP2T_PACKET_LENGTH$1;
  32747. endIndex -= MP2T_PACKET_LENGTH$1;
  32748. continue;
  32749. } // If we get here, we have somehow become de-synchronized and we need to step
  32750. // forward one byte at a time until we find a pair of sync bytes that denote
  32751. // a packet
  32752. startIndex--;
  32753. endIndex--;
  32754. }
  32755. };
  32756. /**
  32757. * walks through the segment data from the start and end to get timing information
  32758. * for the first and last video pes packets as well as timing information for the first
  32759. * key frame.
  32760. */
  32761. var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) {
  32762. var startIndex = 0,
  32763. endIndex = MP2T_PACKET_LENGTH$1,
  32764. packet,
  32765. type,
  32766. pesType,
  32767. pusi,
  32768. parsed,
  32769. frame,
  32770. i,
  32771. pes;
  32772. var endLoop = false;
  32773. var currentFrame = {
  32774. data: [],
  32775. size: 0
  32776. }; // Start walking from start of segment to get first video packet
  32777. while (endIndex < bytes.byteLength) {
  32778. // Look for a pair of start and end sync bytes in the data..
  32779. if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
  32780. // We found a packet
  32781. packet = bytes.subarray(startIndex, endIndex);
  32782. type = probe$2.ts.parseType(packet, pmt.pid);
  32783. switch (type) {
  32784. case 'pes':
  32785. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  32786. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  32787. if (pesType === 'video') {
  32788. if (pusi && !endLoop) {
  32789. parsed = probe$2.ts.parsePesTime(packet);
  32790. if (parsed) {
  32791. parsed.type = 'video';
  32792. result.video.push(parsed);
  32793. endLoop = true;
  32794. }
  32795. }
  32796. if (!result.firstKeyFrame) {
  32797. if (pusi) {
  32798. if (currentFrame.size !== 0) {
  32799. frame = new Uint8Array(currentFrame.size);
  32800. i = 0;
  32801. while (currentFrame.data.length) {
  32802. pes = currentFrame.data.shift();
  32803. frame.set(pes, i);
  32804. i += pes.byteLength;
  32805. }
  32806. if (probe$2.ts.videoPacketContainsKeyFrame(frame)) {
  32807. result.firstKeyFrame = probe$2.ts.parsePesTime(frame);
  32808. result.firstKeyFrame.type = 'video';
  32809. }
  32810. currentFrame.size = 0;
  32811. }
  32812. }
  32813. currentFrame.data.push(packet);
  32814. currentFrame.size += packet.byteLength;
  32815. }
  32816. }
  32817. break;
  32818. default:
  32819. break;
  32820. }
  32821. if (endLoop && result.firstKeyFrame) {
  32822. break;
  32823. }
  32824. startIndex += MP2T_PACKET_LENGTH$1;
  32825. endIndex += MP2T_PACKET_LENGTH$1;
  32826. continue;
  32827. } // If we get here, we have somehow become de-synchronized and we need to step
  32828. // forward one byte at a time until we find a pair of sync bytes that denote
  32829. // a packet
  32830. startIndex++;
  32831. endIndex++;
  32832. } // Start walking from end of segment to get last video packet
  32833. endIndex = bytes.byteLength;
  32834. startIndex = endIndex - MP2T_PACKET_LENGTH$1;
  32835. endLoop = false;
  32836. while (startIndex >= 0) {
  32837. // Look for a pair of start and end sync bytes in the data..
  32838. if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
  32839. // We found a packet
  32840. packet = bytes.subarray(startIndex, endIndex);
  32841. type = probe$2.ts.parseType(packet, pmt.pid);
  32842. switch (type) {
  32843. case 'pes':
  32844. pesType = probe$2.ts.parsePesType(packet, pmt.table);
  32845. pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
  32846. if (pesType === 'video' && pusi) {
  32847. parsed = probe$2.ts.parsePesTime(packet);
  32848. if (parsed) {
  32849. parsed.type = 'video';
  32850. result.video.push(parsed);
  32851. endLoop = true;
  32852. }
  32853. }
  32854. break;
  32855. default:
  32856. break;
  32857. }
  32858. if (endLoop) {
  32859. break;
  32860. }
  32861. startIndex -= MP2T_PACKET_LENGTH$1;
  32862. endIndex -= MP2T_PACKET_LENGTH$1;
  32863. continue;
  32864. } // If we get here, we have somehow become de-synchronized and we need to step
  32865. // forward one byte at a time until we find a pair of sync bytes that denote
  32866. // a packet
  32867. startIndex--;
  32868. endIndex--;
  32869. }
  32870. };
  32871. /**
  32872. * Adjusts the timestamp information for the segment to account for
  32873. * rollover and convert to seconds based on pes packet timescale (90khz clock)
  32874. */
  32875. var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) {
  32876. if (segmentInfo.audio && segmentInfo.audio.length) {
  32877. var audioBaseTimestamp = baseTimestamp;
  32878. if (typeof audioBaseTimestamp === 'undefined') {
  32879. audioBaseTimestamp = segmentInfo.audio[0].dts;
  32880. }
  32881. segmentInfo.audio.forEach(function (info) {
  32882. info.dts = handleRollover$1(info.dts, audioBaseTimestamp);
  32883. info.pts = handleRollover$1(info.pts, audioBaseTimestamp); // time in seconds
  32884. info.dtsTime = info.dts / PES_TIMESCALE;
  32885. info.ptsTime = info.pts / PES_TIMESCALE;
  32886. });
  32887. }
  32888. if (segmentInfo.video && segmentInfo.video.length) {
  32889. var videoBaseTimestamp = baseTimestamp;
  32890. if (typeof videoBaseTimestamp === 'undefined') {
  32891. videoBaseTimestamp = segmentInfo.video[0].dts;
  32892. }
  32893. segmentInfo.video.forEach(function (info) {
  32894. info.dts = handleRollover$1(info.dts, videoBaseTimestamp);
  32895. info.pts = handleRollover$1(info.pts, videoBaseTimestamp); // time in seconds
  32896. info.dtsTime = info.dts / PES_TIMESCALE;
  32897. info.ptsTime = info.pts / PES_TIMESCALE;
  32898. });
  32899. if (segmentInfo.firstKeyFrame) {
  32900. var frame = segmentInfo.firstKeyFrame;
  32901. frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp);
  32902. frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); // time in seconds
  32903. frame.dtsTime = frame.dts / PES_TIMESCALE;
  32904. frame.ptsTime = frame.dts / PES_TIMESCALE;
  32905. }
  32906. }
  32907. };
  32908. /**
  32909. * inspects the aac data stream for start and end time information
  32910. */
  32911. var inspectAac_ = function inspectAac_(bytes) {
  32912. var endLoop = false,
  32913. audioCount = 0,
  32914. sampleRate = null,
  32915. timestamp = null,
  32916. frameSize = 0,
  32917. byteIndex = 0,
  32918. packet;
  32919. while (bytes.length - byteIndex >= 3) {
  32920. var type = probe$2.aac.parseType(bytes, byteIndex);
  32921. switch (type) {
  32922. case 'timed-metadata':
  32923. // Exit early because we don't have enough to parse
  32924. // the ID3 tag header
  32925. if (bytes.length - byteIndex < 10) {
  32926. endLoop = true;
  32927. break;
  32928. }
  32929. frameSize = probe$2.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
  32930. // to emit a full packet
  32931. if (frameSize > bytes.length) {
  32932. endLoop = true;
  32933. break;
  32934. }
  32935. if (timestamp === null) {
  32936. packet = bytes.subarray(byteIndex, byteIndex + frameSize);
  32937. timestamp = probe$2.aac.parseAacTimestamp(packet);
  32938. }
  32939. byteIndex += frameSize;
  32940. break;
  32941. case 'audio':
  32942. // Exit early because we don't have enough to parse
  32943. // the ADTS frame header
  32944. if (bytes.length - byteIndex < 7) {
  32945. endLoop = true;
  32946. break;
  32947. }
  32948. frameSize = probe$2.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
  32949. // to emit a full packet
  32950. if (frameSize > bytes.length) {
  32951. endLoop = true;
  32952. break;
  32953. }
  32954. if (sampleRate === null) {
  32955. packet = bytes.subarray(byteIndex, byteIndex + frameSize);
  32956. sampleRate = probe$2.aac.parseSampleRate(packet);
  32957. }
  32958. audioCount++;
  32959. byteIndex += frameSize;
  32960. break;
  32961. default:
  32962. byteIndex++;
  32963. break;
  32964. }
  32965. if (endLoop) {
  32966. return null;
  32967. }
  32968. }
  32969. if (sampleRate === null || timestamp === null) {
  32970. return null;
  32971. }
  32972. var audioTimescale = PES_TIMESCALE / sampleRate;
  32973. var result = {
  32974. audio: [{
  32975. type: 'audio',
  32976. dts: timestamp,
  32977. pts: timestamp
  32978. }, {
  32979. type: 'audio',
  32980. dts: timestamp + audioCount * 1024 * audioTimescale,
  32981. pts: timestamp + audioCount * 1024 * audioTimescale
  32982. }]
  32983. };
  32984. return result;
  32985. };
  32986. /**
  32987. * inspects the transport stream segment data for start and end time information
  32988. * of the audio and video tracks (when present) as well as the first key frame's
  32989. * start time.
  32990. */
  32991. var inspectTs_ = function inspectTs_(bytes) {
  32992. var pmt = {
  32993. pid: null,
  32994. table: null
  32995. };
  32996. var result = {};
  32997. parsePsi_(bytes, pmt);
  32998. for (var pid in pmt.table) {
  32999. if (pmt.table.hasOwnProperty(pid)) {
  33000. var type = pmt.table[pid];
  33001. switch (type) {
  33002. case streamTypes.H264_STREAM_TYPE:
  33003. result.video = [];
  33004. parseVideoPes_(bytes, pmt, result);
  33005. if (result.video.length === 0) {
  33006. delete result.video;
  33007. }
  33008. break;
  33009. case streamTypes.ADTS_STREAM_TYPE:
  33010. result.audio = [];
  33011. parseAudioPes_(bytes, pmt, result);
  33012. if (result.audio.length === 0) {
  33013. delete result.audio;
  33014. }
  33015. break;
  33016. default:
  33017. break;
  33018. }
  33019. }
  33020. }
  33021. return result;
  33022. };
  33023. /**
  33024. * Inspects segment byte data and returns an object with start and end timing information
  33025. *
  33026. * @param {Uint8Array} bytes The segment byte data
  33027. * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
  33028. * timestamps for rollover. This value must be in 90khz clock.
  33029. * @return {Object} Object containing start and end frame timing info of segment.
  33030. */
  33031. var inspect = function inspect(bytes, baseTimestamp) {
  33032. var isAacData = probe$2.aac.isLikelyAacData(bytes);
  33033. var result;
  33034. if (isAacData) {
  33035. result = inspectAac_(bytes);
  33036. } else {
  33037. result = inspectTs_(bytes);
  33038. }
  33039. if (!result || !result.audio && !result.video) {
  33040. return null;
  33041. }
  33042. adjustTimestamp_(result, baseTimestamp);
  33043. return result;
  33044. };
  33045. var tsInspector = {
  33046. inspect: inspect,
  33047. parseAudioPes_: parseAudioPes_
  33048. };
  33049. /*
  33050. * pkcs7.pad
  33051. * https://github.com/brightcove/pkcs7
  33052. *
  33053. * Copyright (c) 2014 Brightcove
  33054. * Licensed under the apache2 license.
  33055. */
  33056. /**
  33057. * Returns the subarray of a Uint8Array without PKCS#7 padding.
  33058. * @param padded {Uint8Array} unencrypted bytes that have been padded
  33059. * @return {Uint8Array} the unpadded bytes
  33060. * @see http://tools.ietf.org/html/rfc5652
  33061. */
  33062. function unpad(padded) {
  33063. return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
  33064. }
  33065. var classCallCheck = function classCallCheck(instance, Constructor) {
  33066. if (!(instance instanceof Constructor)) {
  33067. throw new TypeError("Cannot call a class as a function");
  33068. }
  33069. };
  33070. var createClass = function () {
  33071. function defineProperties(target, props) {
  33072. for (var i = 0; i < props.length; i++) {
  33073. var descriptor = props[i];
  33074. descriptor.enumerable = descriptor.enumerable || false;
  33075. descriptor.configurable = true;
  33076. if ("value" in descriptor) descriptor.writable = true;
  33077. Object.defineProperty(target, descriptor.key, descriptor);
  33078. }
  33079. }
  33080. return function (Constructor, protoProps, staticProps) {
  33081. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  33082. if (staticProps) defineProperties(Constructor, staticProps);
  33083. return Constructor;
  33084. };
  33085. }();
  33086. var inherits = function inherits(subClass, superClass) {
  33087. if (typeof superClass !== "function" && superClass !== null) {
  33088. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  33089. }
  33090. subClass.prototype = Object.create(superClass && superClass.prototype, {
  33091. constructor: {
  33092. value: subClass,
  33093. enumerable: false,
  33094. writable: true,
  33095. configurable: true
  33096. }
  33097. });
  33098. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  33099. };
  33100. var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
  33101. if (!self) {
  33102. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  33103. }
  33104. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  33105. };
  33106. /**
  33107. * @file aes.js
  33108. *
  33109. * This file contains an adaptation of the AES decryption algorithm
  33110. * from the Standford Javascript Cryptography Library. That work is
  33111. * covered by the following copyright and permissions notice:
  33112. *
  33113. * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
  33114. * All rights reserved.
  33115. *
  33116. * Redistribution and use in source and binary forms, with or without
  33117. * modification, are permitted provided that the following conditions are
  33118. * met:
  33119. *
  33120. * 1. Redistributions of source code must retain the above copyright
  33121. * notice, this list of conditions and the following disclaimer.
  33122. *
  33123. * 2. Redistributions in binary form must reproduce the above
  33124. * copyright notice, this list of conditions and the following
  33125. * disclaimer in the documentation and/or other materials provided
  33126. * with the distribution.
  33127. *
  33128. * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
  33129. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  33130. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  33131. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
  33132. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  33133. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  33134. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  33135. * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  33136. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  33137. * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
  33138. * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33139. *
  33140. * The views and conclusions contained in the software and documentation
  33141. * are those of the authors and should not be interpreted as representing
  33142. * official policies, either expressed or implied, of the authors.
  33143. */
  33144. /**
  33145. * Expand the S-box tables.
  33146. *
  33147. * @private
  33148. */
  33149. var precompute = function precompute() {
  33150. var tables = [[[], [], [], [], []], [[], [], [], [], []]];
  33151. var encTable = tables[0];
  33152. var decTable = tables[1];
  33153. var sbox = encTable[4];
  33154. var sboxInv = decTable[4];
  33155. var i = void 0;
  33156. var x = void 0;
  33157. var xInv = void 0;
  33158. var d = [];
  33159. var th = [];
  33160. var x2 = void 0;
  33161. var x4 = void 0;
  33162. var x8 = void 0;
  33163. var s = void 0;
  33164. var tEnc = void 0;
  33165. var tDec = void 0; // Compute double and third tables
  33166. for (i = 0; i < 256; i++) {
  33167. th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
  33168. }
  33169. for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
  33170. // Compute sbox
  33171. s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
  33172. s = s >> 8 ^ s & 255 ^ 99;
  33173. sbox[x] = s;
  33174. sboxInv[s] = x; // Compute MixColumns
  33175. x8 = d[x4 = d[x2 = d[x]]];
  33176. tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
  33177. tEnc = d[s] * 0x101 ^ s * 0x1010100;
  33178. for (i = 0; i < 4; i++) {
  33179. encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
  33180. decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
  33181. }
  33182. } // Compactify. Considerable speedup on Firefox.
  33183. for (i = 0; i < 5; i++) {
  33184. encTable[i] = encTable[i].slice(0);
  33185. decTable[i] = decTable[i].slice(0);
  33186. }
  33187. return tables;
  33188. };
  33189. var aesTables = null;
  33190. /**
  33191. * Schedule out an AES key for both encryption and decryption. This
  33192. * is a low-level class. Use a cipher mode to do bulk encryption.
  33193. *
  33194. * @class AES
  33195. * @param key {Array} The key as an array of 4, 6 or 8 words.
  33196. */
  33197. var AES = function () {
  33198. function AES(key) {
  33199. classCallCheck(this, AES);
  33200. /**
  33201. * The expanded S-box and inverse S-box tables. These will be computed
  33202. * on the client so that we don't have to send them down the wire.
  33203. *
  33204. * There are two tables, _tables[0] is for encryption and
  33205. * _tables[1] is for decryption.
  33206. *
  33207. * The first 4 sub-tables are the expanded S-box with MixColumns. The
  33208. * last (_tables[01][4]) is the S-box itself.
  33209. *
  33210. * @private
  33211. */
  33212. // if we have yet to precompute the S-box tables
  33213. // do so now
  33214. if (!aesTables) {
  33215. aesTables = precompute();
  33216. } // then make a copy of that object for use
  33217. 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()]];
  33218. var i = void 0;
  33219. var j = void 0;
  33220. var tmp = void 0;
  33221. var encKey = void 0;
  33222. var decKey = void 0;
  33223. var sbox = this._tables[0][4];
  33224. var decTable = this._tables[1];
  33225. var keyLen = key.length;
  33226. var rcon = 1;
  33227. if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
  33228. throw new Error('Invalid aes key size');
  33229. }
  33230. encKey = key.slice(0);
  33231. decKey = [];
  33232. this._key = [encKey, decKey]; // schedule encryption keys
  33233. for (i = keyLen; i < 4 * keyLen + 28; i++) {
  33234. tmp = encKey[i - 1]; // apply sbox
  33235. if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
  33236. tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
  33237. if (i % keyLen === 0) {
  33238. tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
  33239. rcon = rcon << 1 ^ (rcon >> 7) * 283;
  33240. }
  33241. }
  33242. encKey[i] = encKey[i - keyLen] ^ tmp;
  33243. } // schedule decryption keys
  33244. for (j = 0; i; j++, i--) {
  33245. tmp = encKey[j & 3 ? i : i - 4];
  33246. if (i <= 4 || j < 4) {
  33247. decKey[j] = tmp;
  33248. } else {
  33249. decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
  33250. }
  33251. }
  33252. }
  33253. /**
  33254. * Decrypt 16 bytes, specified as four 32-bit words.
  33255. *
  33256. * @param {Number} encrypted0 the first word to decrypt
  33257. * @param {Number} encrypted1 the second word to decrypt
  33258. * @param {Number} encrypted2 the third word to decrypt
  33259. * @param {Number} encrypted3 the fourth word to decrypt
  33260. * @param {Int32Array} out the array to write the decrypted words
  33261. * into
  33262. * @param {Number} offset the offset into the output array to start
  33263. * writing results
  33264. * @return {Array} The plaintext.
  33265. */
  33266. AES.prototype.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
  33267. var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
  33268. var a = encrypted0 ^ key[0];
  33269. var b = encrypted3 ^ key[1];
  33270. var c = encrypted2 ^ key[2];
  33271. var d = encrypted1 ^ key[3];
  33272. var a2 = void 0;
  33273. var b2 = void 0;
  33274. var c2 = void 0; // key.length === 2 ?
  33275. var nInnerRounds = key.length / 4 - 2;
  33276. var i = void 0;
  33277. var kIndex = 4;
  33278. var table = this._tables[1]; // load up the tables
  33279. var table0 = table[0];
  33280. var table1 = table[1];
  33281. var table2 = table[2];
  33282. var table3 = table[3];
  33283. var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
  33284. for (i = 0; i < nInnerRounds; i++) {
  33285. a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
  33286. b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
  33287. c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
  33288. d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
  33289. kIndex += 4;
  33290. a = a2;
  33291. b = b2;
  33292. c = c2;
  33293. } // Last round.
  33294. for (i = 0; i < 4; i++) {
  33295. out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
  33296. a2 = a;
  33297. a = b;
  33298. b = c;
  33299. c = d;
  33300. d = a2;
  33301. }
  33302. };
  33303. return AES;
  33304. }();
  33305. /**
  33306. * @file stream.js
  33307. */
  33308. /**
  33309. * A lightweight readable stream implemention that handles event dispatching.
  33310. *
  33311. * @class Stream
  33312. */
  33313. var Stream$2 = function () {
  33314. function Stream() {
  33315. classCallCheck(this, Stream);
  33316. this.listeners = {};
  33317. }
  33318. /**
  33319. * Add a listener for a specified event type.
  33320. *
  33321. * @param {String} type the event name
  33322. * @param {Function} listener the callback to be invoked when an event of
  33323. * the specified type occurs
  33324. */
  33325. Stream.prototype.on = function on(type, listener) {
  33326. if (!this.listeners[type]) {
  33327. this.listeners[type] = [];
  33328. }
  33329. this.listeners[type].push(listener);
  33330. };
  33331. /**
  33332. * Remove a listener for a specified event type.
  33333. *
  33334. * @param {String} type the event name
  33335. * @param {Function} listener a function previously registered for this
  33336. * type of event through `on`
  33337. * @return {Boolean} if we could turn it off or not
  33338. */
  33339. Stream.prototype.off = function off(type, listener) {
  33340. if (!this.listeners[type]) {
  33341. return false;
  33342. }
  33343. var index = this.listeners[type].indexOf(listener);
  33344. this.listeners[type].splice(index, 1);
  33345. return index > -1;
  33346. };
  33347. /**
  33348. * Trigger an event of the specified type on this stream. Any additional
  33349. * arguments to this function are passed as parameters to event listeners.
  33350. *
  33351. * @param {String} type the event name
  33352. */
  33353. Stream.prototype.trigger = function trigger(type) {
  33354. var callbacks = this.listeners[type];
  33355. if (!callbacks) {
  33356. return;
  33357. } // Slicing the arguments on every invocation of this method
  33358. // can add a significant amount of overhead. Avoid the
  33359. // intermediate object creation for the common case of a
  33360. // single callback argument
  33361. if (arguments.length === 2) {
  33362. var length = callbacks.length;
  33363. for (var i = 0; i < length; ++i) {
  33364. callbacks[i].call(this, arguments[1]);
  33365. }
  33366. } else {
  33367. var args = Array.prototype.slice.call(arguments, 1);
  33368. var _length = callbacks.length;
  33369. for (var _i = 0; _i < _length; ++_i) {
  33370. callbacks[_i].apply(this, args);
  33371. }
  33372. }
  33373. };
  33374. /**
  33375. * Destroys the stream and cleans up.
  33376. */
  33377. Stream.prototype.dispose = function dispose() {
  33378. this.listeners = {};
  33379. };
  33380. /**
  33381. * Forwards all `data` events on this stream to the destination stream. The
  33382. * destination stream should provide a method `push` to receive the data
  33383. * events as they arrive.
  33384. *
  33385. * @param {Stream} destination the stream that will receive all `data` events
  33386. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  33387. */
  33388. Stream.prototype.pipe = function pipe(destination) {
  33389. this.on('data', function (data) {
  33390. destination.push(data);
  33391. });
  33392. };
  33393. return Stream;
  33394. }();
  33395. /**
  33396. * @file async-stream.js
  33397. */
  33398. /**
  33399. * A wrapper around the Stream class to use setTiemout
  33400. * and run stream "jobs" Asynchronously
  33401. *
  33402. * @class AsyncStream
  33403. * @extends Stream
  33404. */
  33405. var AsyncStream = function (_Stream) {
  33406. inherits(AsyncStream, _Stream);
  33407. function AsyncStream() {
  33408. classCallCheck(this, AsyncStream);
  33409. var _this = possibleConstructorReturn(this, _Stream.call(this, Stream$2));
  33410. _this.jobs = [];
  33411. _this.delay = 1;
  33412. _this.timeout_ = null;
  33413. return _this;
  33414. }
  33415. /**
  33416. * process an async job
  33417. *
  33418. * @private
  33419. */
  33420. AsyncStream.prototype.processJob_ = function processJob_() {
  33421. this.jobs.shift()();
  33422. if (this.jobs.length) {
  33423. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  33424. } else {
  33425. this.timeout_ = null;
  33426. }
  33427. };
  33428. /**
  33429. * push a job into the stream
  33430. *
  33431. * @param {Function} job the job to push into the stream
  33432. */
  33433. AsyncStream.prototype.push = function push(job) {
  33434. this.jobs.push(job);
  33435. if (!this.timeout_) {
  33436. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  33437. }
  33438. };
  33439. return AsyncStream;
  33440. }(Stream$2);
  33441. /**
  33442. * @file decrypter.js
  33443. *
  33444. * An asynchronous implementation of AES-128 CBC decryption with
  33445. * PKCS#7 padding.
  33446. */
  33447. /**
  33448. * Convert network-order (big-endian) bytes into their little-endian
  33449. * representation.
  33450. */
  33451. var ntoh = function ntoh(word) {
  33452. return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
  33453. };
  33454. /**
  33455. * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
  33456. *
  33457. * @param {Uint8Array} encrypted the encrypted bytes
  33458. * @param {Uint32Array} key the bytes of the decryption key
  33459. * @param {Uint32Array} initVector the initialization vector (IV) to
  33460. * use for the first round of CBC.
  33461. * @return {Uint8Array} the decrypted bytes
  33462. *
  33463. * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
  33464. * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
  33465. * @see https://tools.ietf.org/html/rfc2315
  33466. */
  33467. var decrypt = function decrypt(encrypted, key, initVector) {
  33468. // word-level access to the encrypted bytes
  33469. var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
  33470. var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
  33471. var decrypted = new Uint8Array(encrypted.byteLength);
  33472. var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
  33473. // decrypted data
  33474. var init0 = void 0;
  33475. var init1 = void 0;
  33476. var init2 = void 0;
  33477. var init3 = void 0;
  33478. var encrypted0 = void 0;
  33479. var encrypted1 = void 0;
  33480. var encrypted2 = void 0;
  33481. var encrypted3 = void 0; // iteration variable
  33482. var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
  33483. // passed-in reference and easier access
  33484. init0 = initVector[0];
  33485. init1 = initVector[1];
  33486. init2 = initVector[2];
  33487. init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
  33488. // to each decrypted block
  33489. for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
  33490. // convert big-endian (network order) words into little-endian
  33491. // (javascript order)
  33492. encrypted0 = ntoh(encrypted32[wordIx]);
  33493. encrypted1 = ntoh(encrypted32[wordIx + 1]);
  33494. encrypted2 = ntoh(encrypted32[wordIx + 2]);
  33495. encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
  33496. decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
  33497. // plaintext
  33498. decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
  33499. decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
  33500. decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
  33501. decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
  33502. init0 = encrypted0;
  33503. init1 = encrypted1;
  33504. init2 = encrypted2;
  33505. init3 = encrypted3;
  33506. }
  33507. return decrypted;
  33508. };
  33509. /**
  33510. * The `Decrypter` class that manages decryption of AES
  33511. * data through `AsyncStream` objects and the `decrypt`
  33512. * function
  33513. *
  33514. * @param {Uint8Array} encrypted the encrypted bytes
  33515. * @param {Uint32Array} key the bytes of the decryption key
  33516. * @param {Uint32Array} initVector the initialization vector (IV) to
  33517. * @param {Function} done the function to run when done
  33518. * @class Decrypter
  33519. */
  33520. var Decrypter = function () {
  33521. function Decrypter(encrypted, key, initVector, done) {
  33522. classCallCheck(this, Decrypter);
  33523. var step = Decrypter.STEP;
  33524. var encrypted32 = new Int32Array(encrypted.buffer);
  33525. var decrypted = new Uint8Array(encrypted.byteLength);
  33526. var i = 0;
  33527. this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
  33528. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  33529. for (i = step; i < encrypted32.length; i += step) {
  33530. initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
  33531. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  33532. } // invoke the done() callback when everything is finished
  33533. this.asyncStream_.push(function () {
  33534. // remove pkcs#7 padding from the decrypted bytes
  33535. done(null, unpad(decrypted));
  33536. });
  33537. }
  33538. /**
  33539. * a getter for step the maximum number of bytes to process at one time
  33540. *
  33541. * @return {Number} the value of step 32000
  33542. */
  33543. /**
  33544. * @private
  33545. */
  33546. Decrypter.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
  33547. return function () {
  33548. var bytes = decrypt(encrypted, key, initVector);
  33549. decrypted.set(bytes, encrypted.byteOffset);
  33550. };
  33551. };
  33552. createClass(Decrypter, null, [{
  33553. key: 'STEP',
  33554. get: function get$$1() {
  33555. // 4 * 8000;
  33556. return 32000;
  33557. }
  33558. }]);
  33559. return Decrypter;
  33560. }();
  33561. /**
  33562. * @videojs/http-streaming
  33563. * @version 1.9.3
  33564. * @copyright 2019 Brightcove, Inc
  33565. * @license Apache-2.0
  33566. */
  33567. /**
  33568. * @file resolve-url.js - Handling how URLs are resolved and manipulated
  33569. */
  33570. var resolveUrl$1 = function resolveUrl(baseURL, relativeURL) {
  33571. // return early if we don't need to resolve
  33572. if (/^[a-z]+:/i.test(relativeURL)) {
  33573. return relativeURL;
  33574. } // if the base URL is relative then combine with the current location
  33575. if (!/\/\//i.test(baseURL)) {
  33576. baseURL = urlToolkit.buildAbsoluteURL(window$1.location.href, baseURL);
  33577. }
  33578. return urlToolkit.buildAbsoluteURL(baseURL, relativeURL);
  33579. };
  33580. /**
  33581. * Checks whether xhr request was redirected and returns correct url depending
  33582. * on `handleManifestRedirects` option
  33583. *
  33584. * @api private
  33585. *
  33586. * @param {String} url - an url being requested
  33587. * @param {XMLHttpRequest} req - xhr request result
  33588. *
  33589. * @return {String}
  33590. */
  33591. var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) {
  33592. // To understand how the responseURL below is set and generated:
  33593. // - https://fetch.spec.whatwg.org/#concept-response-url
  33594. // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
  33595. if (handleManifestRedirect && req.responseURL && url !== req.responseURL) {
  33596. return req.responseURL;
  33597. }
  33598. return url;
  33599. };
  33600. var classCallCheck$1 = function classCallCheck(instance, Constructor) {
  33601. if (!(instance instanceof Constructor)) {
  33602. throw new TypeError("Cannot call a class as a function");
  33603. }
  33604. };
  33605. var createClass$1 = function () {
  33606. function defineProperties(target, props) {
  33607. for (var i = 0; i < props.length; i++) {
  33608. var descriptor = props[i];
  33609. descriptor.enumerable = descriptor.enumerable || false;
  33610. descriptor.configurable = true;
  33611. if ("value" in descriptor) descriptor.writable = true;
  33612. Object.defineProperty(target, descriptor.key, descriptor);
  33613. }
  33614. }
  33615. return function (Constructor, protoProps, staticProps) {
  33616. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  33617. if (staticProps) defineProperties(Constructor, staticProps);
  33618. return Constructor;
  33619. };
  33620. }();
  33621. var get$1 = function get(object, property, receiver) {
  33622. if (object === null) object = Function.prototype;
  33623. var desc = Object.getOwnPropertyDescriptor(object, property);
  33624. if (desc === undefined) {
  33625. var parent = Object.getPrototypeOf(object);
  33626. if (parent === null) {
  33627. return undefined;
  33628. } else {
  33629. return get(parent, property, receiver);
  33630. }
  33631. } else if ("value" in desc) {
  33632. return desc.value;
  33633. } else {
  33634. var getter = desc.get;
  33635. if (getter === undefined) {
  33636. return undefined;
  33637. }
  33638. return getter.call(receiver);
  33639. }
  33640. };
  33641. var inherits$1 = function inherits(subClass, superClass) {
  33642. if (typeof superClass !== "function" && superClass !== null) {
  33643. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  33644. }
  33645. subClass.prototype = Object.create(superClass && superClass.prototype, {
  33646. constructor: {
  33647. value: subClass,
  33648. enumerable: false,
  33649. writable: true,
  33650. configurable: true
  33651. }
  33652. });
  33653. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  33654. };
  33655. var possibleConstructorReturn$1 = function possibleConstructorReturn(self, call) {
  33656. if (!self) {
  33657. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  33658. }
  33659. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  33660. };
  33661. var slicedToArray = function () {
  33662. function sliceIterator(arr, i) {
  33663. var _arr = [];
  33664. var _n = true;
  33665. var _d = false;
  33666. var _e = undefined;
  33667. try {
  33668. for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
  33669. _arr.push(_s.value);
  33670. if (i && _arr.length === i) break;
  33671. }
  33672. } catch (err) {
  33673. _d = true;
  33674. _e = err;
  33675. } finally {
  33676. try {
  33677. if (!_n && _i["return"]) _i["return"]();
  33678. } finally {
  33679. if (_d) throw _e;
  33680. }
  33681. }
  33682. return _arr;
  33683. }
  33684. return function (arr, i) {
  33685. if (Array.isArray(arr)) {
  33686. return arr;
  33687. } else if (Symbol.iterator in Object(arr)) {
  33688. return sliceIterator(arr, i);
  33689. } else {
  33690. throw new TypeError("Invalid attempt to destructure non-iterable instance");
  33691. }
  33692. };
  33693. }();
  33694. /**
  33695. * @file playlist-loader.js
  33696. *
  33697. * A state machine that manages the loading, caching, and updating of
  33698. * M3U8 playlists.
  33699. *
  33700. */
  33701. var mergeOptions$1 = videojs$1.mergeOptions,
  33702. EventTarget$1 = videojs$1.EventTarget,
  33703. log$1 = videojs$1.log;
  33704. /**
  33705. * Loops through all supported media groups in master and calls the provided
  33706. * callback for each group
  33707. *
  33708. * @param {Object} master
  33709. * The parsed master manifest object
  33710. * @param {Function} callback
  33711. * Callback to call for each media group
  33712. */
  33713. var forEachMediaGroup = function forEachMediaGroup(master, callback) {
  33714. ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
  33715. for (var groupKey in master.mediaGroups[mediaType]) {
  33716. for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
  33717. var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
  33718. callback(mediaProperties, mediaType, groupKey, labelKey);
  33719. }
  33720. }
  33721. });
  33722. };
  33723. /**
  33724. * Returns a new array of segments that is the result of merging
  33725. * properties from an older list of segments onto an updated
  33726. * list. No properties on the updated playlist will be overridden.
  33727. *
  33728. * @param {Array} original the outdated list of segments
  33729. * @param {Array} update the updated list of segments
  33730. * @param {Number=} offset the index of the first update
  33731. * segment in the original segment list. For non-live playlists,
  33732. * this should always be zero and does not need to be
  33733. * specified. For live playlists, it should be the difference
  33734. * between the media sequence numbers in the original and updated
  33735. * playlists.
  33736. * @return a list of merged segment objects
  33737. */
  33738. var updateSegments = function updateSegments(original, update, offset) {
  33739. var result = update.slice();
  33740. offset = offset || 0;
  33741. var length = Math.min(original.length, update.length + offset);
  33742. for (var i = offset; i < length; i++) {
  33743. result[i - offset] = mergeOptions$1(original[i], result[i - offset]);
  33744. }
  33745. return result;
  33746. };
  33747. var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
  33748. if (!segment.resolvedUri) {
  33749. segment.resolvedUri = resolveUrl$1(baseUri, segment.uri);
  33750. }
  33751. if (segment.key && !segment.key.resolvedUri) {
  33752. segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri);
  33753. }
  33754. if (segment.map && !segment.map.resolvedUri) {
  33755. segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri);
  33756. }
  33757. };
  33758. /**
  33759. * Returns a new master playlist that is the result of merging an
  33760. * updated media playlist into the original version. If the
  33761. * updated media playlist does not match any of the playlist
  33762. * entries in the original master playlist, null is returned.
  33763. *
  33764. * @param {Object} master a parsed master M3U8 object
  33765. * @param {Object} media a parsed media M3U8 object
  33766. * @return {Object} a new object that represents the original
  33767. * master playlist with the updated media playlist merged in, or
  33768. * null if the merge produced no change.
  33769. */
  33770. var updateMaster = function updateMaster(master, media) {
  33771. var result = mergeOptions$1(master, {});
  33772. var playlist = result.playlists[media.uri];
  33773. if (!playlist) {
  33774. return null;
  33775. } // consider the playlist unchanged if the number of segments is equal, the media
  33776. // sequence number is unchanged, and this playlist hasn't become the end of the playlist
  33777. if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) {
  33778. return null;
  33779. }
  33780. var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists
  33781. if (playlist.segments) {
  33782. mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
  33783. } // resolve any segment URIs to prevent us from having to do it later
  33784. mergedPlaylist.segments.forEach(function (segment) {
  33785. resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
  33786. }); // TODO Right now in the playlists array there are two references to each playlist, one
  33787. // that is referenced by index, and one by URI. The index reference may no longer be
  33788. // necessary.
  33789. for (var i = 0; i < result.playlists.length; i++) {
  33790. if (result.playlists[i].uri === media.uri) {
  33791. result.playlists[i] = mergedPlaylist;
  33792. }
  33793. }
  33794. result.playlists[media.uri] = mergedPlaylist;
  33795. return result;
  33796. };
  33797. var setupMediaPlaylists = function setupMediaPlaylists(master) {
  33798. // setup by-URI lookups and resolve media playlist URIs
  33799. var i = master.playlists.length;
  33800. while (i--) {
  33801. var playlist = master.playlists[i];
  33802. master.playlists[playlist.uri] = playlist;
  33803. playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri);
  33804. playlist.id = i;
  33805. if (!playlist.attributes) {
  33806. // Although the spec states an #EXT-X-STREAM-INF tag MUST have a
  33807. // BANDWIDTH attribute, we can play the stream without it. This means a poorly
  33808. // formatted master playlist may not have an attribute list. An attributes
  33809. // property is added here to prevent undefined references when we encounter
  33810. // this scenario.
  33811. playlist.attributes = {};
  33812. log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
  33813. }
  33814. }
  33815. };
  33816. var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
  33817. forEachMediaGroup(master, function (properties) {
  33818. if (properties.uri) {
  33819. properties.resolvedUri = resolveUrl$1(master.uri, properties.uri);
  33820. }
  33821. });
  33822. };
  33823. /**
  33824. * Calculates the time to wait before refreshing a live playlist
  33825. *
  33826. * @param {Object} media
  33827. * The current media
  33828. * @param {Boolean} update
  33829. * True if there were any updates from the last refresh, false otherwise
  33830. * @return {Number}
  33831. * The time in ms to wait before refreshing the live playlist
  33832. */
  33833. var refreshDelay = function refreshDelay(media, update) {
  33834. var lastSegment = media.segments[media.segments.length - 1];
  33835. var delay = void 0;
  33836. if (update && lastSegment && lastSegment.duration) {
  33837. delay = lastSegment.duration * 1000;
  33838. } else {
  33839. // if the playlist is unchanged since the last reload or last segment duration
  33840. // cannot be determined, try again after half the target duration
  33841. delay = (media.targetDuration || 10) * 500;
  33842. }
  33843. return delay;
  33844. };
  33845. /**
  33846. * Load a playlist from a remote location
  33847. *
  33848. * @class PlaylistLoader
  33849. * @extends Stream
  33850. * @param {String} srcUrl the url to start with
  33851. * @param {Boolean} withCredentials the withCredentials xhr option
  33852. * @constructor
  33853. */
  33854. var PlaylistLoader = function (_EventTarget) {
  33855. inherits$1(PlaylistLoader, _EventTarget);
  33856. function PlaylistLoader(srcUrl, hls) {
  33857. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  33858. classCallCheck$1(this, PlaylistLoader);
  33859. var _this = possibleConstructorReturn$1(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this));
  33860. var _options$withCredenti = options.withCredentials,
  33861. withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
  33862. _options$handleManife = options.handleManifestRedirects,
  33863. handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
  33864. _this.srcUrl = srcUrl;
  33865. _this.hls_ = hls;
  33866. _this.withCredentials = withCredentials;
  33867. _this.handleManifestRedirects = handleManifestRedirects;
  33868. var hlsOptions = hls.options_;
  33869. _this.customTagParsers = hlsOptions && hlsOptions.customTagParsers || [];
  33870. _this.customTagMappers = hlsOptions && hlsOptions.customTagMappers || [];
  33871. if (!_this.srcUrl) {
  33872. throw new Error('A non-empty playlist URL is required');
  33873. } // initialize the loader state
  33874. _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
  33875. _this.on('mediaupdatetimeout', function () {
  33876. if (_this.state !== 'HAVE_METADATA') {
  33877. // only refresh the media playlist if no other activity is going on
  33878. return;
  33879. }
  33880. _this.state = 'HAVE_CURRENT_METADATA';
  33881. _this.request = _this.hls_.xhr({
  33882. uri: resolveUrl$1(_this.master.uri, _this.media().uri),
  33883. withCredentials: _this.withCredentials
  33884. }, function (error, req) {
  33885. // disposed
  33886. if (!_this.request) {
  33887. return;
  33888. }
  33889. if (error) {
  33890. return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA');
  33891. }
  33892. _this.haveMetadata(_this.request, _this.media().uri);
  33893. });
  33894. });
  33895. return _this;
  33896. }
  33897. createClass$1(PlaylistLoader, [{
  33898. key: 'playlistRequestError',
  33899. value: function playlistRequestError(xhr, url, startingState) {
  33900. // any in-flight request is now finished
  33901. this.request = null;
  33902. if (startingState) {
  33903. this.state = startingState;
  33904. }
  33905. this.error = {
  33906. playlist: this.master.playlists[url],
  33907. status: xhr.status,
  33908. message: 'HLS playlist request error at URL: ' + url,
  33909. responseText: xhr.responseText,
  33910. code: xhr.status >= 500 ? 4 : 2
  33911. };
  33912. this.trigger('error');
  33913. } // update the playlist loader's state in response to a new or
  33914. // updated playlist.
  33915. }, {
  33916. key: 'haveMetadata',
  33917. value: function haveMetadata(xhr, url) {
  33918. var _this2 = this; // any in-flight request is now finished
  33919. this.request = null;
  33920. this.state = 'HAVE_METADATA';
  33921. var parser = new Parser(); // adding custom tag parsers
  33922. this.customTagParsers.forEach(function (customParser) {
  33923. return parser.addParser(customParser);
  33924. }); // adding custom tag mappers
  33925. this.customTagMappers.forEach(function (mapper) {
  33926. return parser.addTagMapper(mapper);
  33927. });
  33928. parser.push(xhr.responseText);
  33929. parser.end();
  33930. parser.manifest.uri = url; // m3u8-parser does not attach an attributes property to media playlists so make
  33931. // sure that the property is attached to avoid undefined reference errors
  33932. parser.manifest.attributes = parser.manifest.attributes || {}; // merge this playlist into the master
  33933. var update = updateMaster(this.master, parser.manifest);
  33934. this.targetDuration = parser.manifest.targetDuration;
  33935. if (update) {
  33936. this.master = update;
  33937. this.media_ = this.master.playlists[parser.manifest.uri];
  33938. } else {
  33939. this.trigger('playlistunchanged');
  33940. } // refresh live playlists after a target duration passes
  33941. if (!this.media().endList) {
  33942. window$1.clearTimeout(this.mediaUpdateTimeout);
  33943. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  33944. _this2.trigger('mediaupdatetimeout');
  33945. }, refreshDelay(this.media(), !!update));
  33946. }
  33947. this.trigger('loadedplaylist');
  33948. }
  33949. /**
  33950. * Abort any outstanding work and clean up.
  33951. */
  33952. }, {
  33953. key: 'dispose',
  33954. value: function dispose() {
  33955. this.stopRequest();
  33956. window$1.clearTimeout(this.mediaUpdateTimeout);
  33957. }
  33958. }, {
  33959. key: 'stopRequest',
  33960. value: function stopRequest() {
  33961. if (this.request) {
  33962. var oldRequest = this.request;
  33963. this.request = null;
  33964. oldRequest.onreadystatechange = null;
  33965. oldRequest.abort();
  33966. }
  33967. }
  33968. /**
  33969. * When called without any arguments, returns the currently
  33970. * active media playlist. When called with a single argument,
  33971. * triggers the playlist loader to asynchronously switch to the
  33972. * specified media playlist. Calling this method while the
  33973. * loader is in the HAVE_NOTHING causes an error to be emitted
  33974. * but otherwise has no effect.
  33975. *
  33976. * @param {Object=} playlist the parsed media playlist
  33977. * object to switch to
  33978. * @return {Playlist} the current loaded media
  33979. */
  33980. }, {
  33981. key: 'media',
  33982. value: function media(playlist) {
  33983. var _this3 = this; // getter
  33984. if (!playlist) {
  33985. return this.media_;
  33986. } // setter
  33987. if (this.state === 'HAVE_NOTHING') {
  33988. throw new Error('Cannot switch media playlist from ' + this.state);
  33989. }
  33990. var startingState = this.state; // find the playlist object if the target playlist has been
  33991. // specified by URI
  33992. if (typeof playlist === 'string') {
  33993. if (!this.master.playlists[playlist]) {
  33994. throw new Error('Unknown playlist URI: ' + playlist);
  33995. }
  33996. playlist = this.master.playlists[playlist];
  33997. }
  33998. var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to fully loaded playlists immediately
  33999. if (this.master.playlists[playlist.uri].endList) {
  34000. // abort outstanding playlist requests
  34001. if (this.request) {
  34002. this.request.onreadystatechange = null;
  34003. this.request.abort();
  34004. this.request = null;
  34005. }
  34006. this.state = 'HAVE_METADATA';
  34007. this.media_ = playlist; // trigger media change if the active media has been updated
  34008. if (mediaChange) {
  34009. this.trigger('mediachanging');
  34010. this.trigger('mediachange');
  34011. }
  34012. return;
  34013. } // switching to the active playlist is a no-op
  34014. if (!mediaChange) {
  34015. return;
  34016. }
  34017. this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
  34018. if (this.request) {
  34019. if (playlist.resolvedUri === this.request.url) {
  34020. // requesting to switch to the same playlist multiple times
  34021. // has no effect after the first
  34022. return;
  34023. }
  34024. this.request.onreadystatechange = null;
  34025. this.request.abort();
  34026. this.request = null;
  34027. } // request the new playlist
  34028. if (this.media_) {
  34029. this.trigger('mediachanging');
  34030. }
  34031. this.request = this.hls_.xhr({
  34032. uri: playlist.resolvedUri,
  34033. withCredentials: this.withCredentials
  34034. }, function (error, req) {
  34035. // disposed
  34036. if (!_this3.request) {
  34037. return;
  34038. }
  34039. playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req);
  34040. if (error) {
  34041. return _this3.playlistRequestError(_this3.request, playlist.uri, startingState);
  34042. }
  34043. _this3.haveMetadata(req, playlist.uri); // fire loadedmetadata the first time a media playlist is loaded
  34044. if (startingState === 'HAVE_MASTER') {
  34045. _this3.trigger('loadedmetadata');
  34046. } else {
  34047. _this3.trigger('mediachange');
  34048. }
  34049. });
  34050. }
  34051. /**
  34052. * pause loading of the playlist
  34053. */
  34054. }, {
  34055. key: 'pause',
  34056. value: function pause() {
  34057. this.stopRequest();
  34058. window$1.clearTimeout(this.mediaUpdateTimeout);
  34059. if (this.state === 'HAVE_NOTHING') {
  34060. // If we pause the loader before any data has been retrieved, its as if we never
  34061. // started, so reset to an unstarted state.
  34062. this.started = false;
  34063. } // Need to restore state now that no activity is happening
  34064. if (this.state === 'SWITCHING_MEDIA') {
  34065. // if the loader was in the process of switching media, it should either return to
  34066. // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
  34067. // playlist yet. This is determined by the existence of loader.media_
  34068. if (this.media_) {
  34069. this.state = 'HAVE_METADATA';
  34070. } else {
  34071. this.state = 'HAVE_MASTER';
  34072. }
  34073. } else if (this.state === 'HAVE_CURRENT_METADATA') {
  34074. this.state = 'HAVE_METADATA';
  34075. }
  34076. }
  34077. /**
  34078. * start loading of the playlist
  34079. */
  34080. }, {
  34081. key: 'load',
  34082. value: function load(isFinalRendition) {
  34083. var _this4 = this;
  34084. window$1.clearTimeout(this.mediaUpdateTimeout);
  34085. var media = this.media();
  34086. if (isFinalRendition) {
  34087. var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
  34088. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  34089. return _this4.load();
  34090. }, delay);
  34091. return;
  34092. }
  34093. if (!this.started) {
  34094. this.start();
  34095. return;
  34096. }
  34097. if (media && !media.endList) {
  34098. this.trigger('mediaupdatetimeout');
  34099. } else {
  34100. this.trigger('loadedplaylist');
  34101. }
  34102. }
  34103. /**
  34104. * start loading of the playlist
  34105. */
  34106. }, {
  34107. key: 'start',
  34108. value: function start() {
  34109. var _this5 = this;
  34110. this.started = true; // request the specified URL
  34111. this.request = this.hls_.xhr({
  34112. uri: this.srcUrl,
  34113. withCredentials: this.withCredentials
  34114. }, function (error, req) {
  34115. // disposed
  34116. if (!_this5.request) {
  34117. return;
  34118. } // clear the loader's request reference
  34119. _this5.request = null;
  34120. if (error) {
  34121. _this5.error = {
  34122. status: req.status,
  34123. message: 'HLS playlist request error at URL: ' + _this5.srcUrl,
  34124. responseText: req.responseText,
  34125. // MEDIA_ERR_NETWORK
  34126. code: 2
  34127. };
  34128. if (_this5.state === 'HAVE_NOTHING') {
  34129. _this5.started = false;
  34130. }
  34131. return _this5.trigger('error');
  34132. }
  34133. var parser = new Parser(); // adding custom tag parsers
  34134. _this5.customTagParsers.forEach(function (customParser) {
  34135. return parser.addParser(customParser);
  34136. }); // adding custom tag mappers
  34137. _this5.customTagMappers.forEach(function (mapper) {
  34138. return parser.addTagMapper(mapper);
  34139. });
  34140. parser.push(req.responseText);
  34141. parser.end();
  34142. _this5.state = 'HAVE_MASTER';
  34143. _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
  34144. parser.manifest.uri = _this5.srcUrl; // loaded a master playlist
  34145. if (parser.manifest.playlists) {
  34146. _this5.master = parser.manifest;
  34147. setupMediaPlaylists(_this5.master);
  34148. resolveMediaGroupUris(_this5.master);
  34149. _this5.trigger('loadedplaylist');
  34150. if (!_this5.request) {
  34151. // no media playlist was specifically selected so start
  34152. // from the first listed one
  34153. _this5.media(parser.manifest.playlists[0]);
  34154. }
  34155. return;
  34156. } // loaded a media playlist
  34157. // infer a master playlist if none was previously requested
  34158. _this5.master = {
  34159. mediaGroups: {
  34160. 'AUDIO': {},
  34161. 'VIDEO': {},
  34162. 'CLOSED-CAPTIONS': {},
  34163. 'SUBTITLES': {}
  34164. },
  34165. uri: window$1.location.href,
  34166. playlists: [{
  34167. uri: _this5.srcUrl,
  34168. id: 0,
  34169. resolvedUri: _this5.srcUrl,
  34170. // m3u8-parser does not attach an attributes property to media playlists so make
  34171. // sure that the property is attached to avoid undefined reference errors
  34172. attributes: {}
  34173. }]
  34174. };
  34175. _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0];
  34176. _this5.haveMetadata(req, _this5.srcUrl);
  34177. return _this5.trigger('loadedmetadata');
  34178. });
  34179. }
  34180. }]);
  34181. return PlaylistLoader;
  34182. }(EventTarget$1);
  34183. /**
  34184. * @file playlist.js
  34185. *
  34186. * Playlist related utilities.
  34187. */
  34188. var createTimeRange = videojs$1.createTimeRange;
  34189. /**
  34190. * walk backward until we find a duration we can use
  34191. * or return a failure
  34192. *
  34193. * @param {Playlist} playlist the playlist to walk through
  34194. * @param {Number} endSequence the mediaSequence to stop walking on
  34195. */
  34196. var backwardDuration = function backwardDuration(playlist, endSequence) {
  34197. var result = 0;
  34198. var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
  34199. // the interval, use it
  34200. var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
  34201. // information that is earlier than endSequence
  34202. if (segment) {
  34203. if (typeof segment.start !== 'undefined') {
  34204. return {
  34205. result: segment.start,
  34206. precise: true
  34207. };
  34208. }
  34209. if (typeof segment.end !== 'undefined') {
  34210. return {
  34211. result: segment.end - segment.duration,
  34212. precise: true
  34213. };
  34214. }
  34215. }
  34216. while (i--) {
  34217. segment = playlist.segments[i];
  34218. if (typeof segment.end !== 'undefined') {
  34219. return {
  34220. result: result + segment.end,
  34221. precise: true
  34222. };
  34223. }
  34224. result += segment.duration;
  34225. if (typeof segment.start !== 'undefined') {
  34226. return {
  34227. result: result + segment.start,
  34228. precise: true
  34229. };
  34230. }
  34231. }
  34232. return {
  34233. result: result,
  34234. precise: false
  34235. };
  34236. };
  34237. /**
  34238. * walk forward until we find a duration we can use
  34239. * or return a failure
  34240. *
  34241. * @param {Playlist} playlist the playlist to walk through
  34242. * @param {Number} endSequence the mediaSequence to stop walking on
  34243. */
  34244. var forwardDuration = function forwardDuration(playlist, endSequence) {
  34245. var result = 0;
  34246. var segment = void 0;
  34247. var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
  34248. // information
  34249. for (; i < playlist.segments.length; i++) {
  34250. segment = playlist.segments[i];
  34251. if (typeof segment.start !== 'undefined') {
  34252. return {
  34253. result: segment.start - result,
  34254. precise: true
  34255. };
  34256. }
  34257. result += segment.duration;
  34258. if (typeof segment.end !== 'undefined') {
  34259. return {
  34260. result: segment.end - result,
  34261. precise: true
  34262. };
  34263. }
  34264. } // indicate we didn't find a useful duration estimate
  34265. return {
  34266. result: -1,
  34267. precise: false
  34268. };
  34269. };
  34270. /**
  34271. * Calculate the media duration from the segments associated with a
  34272. * playlist. The duration of a subinterval of the available segments
  34273. * may be calculated by specifying an end index.
  34274. *
  34275. * @param {Object} playlist a media playlist object
  34276. * @param {Number=} endSequence an exclusive upper boundary
  34277. * for the playlist. Defaults to playlist length.
  34278. * @param {Number} expired the amount of time that has dropped
  34279. * off the front of the playlist in a live scenario
  34280. * @return {Number} the duration between the first available segment
  34281. * and end index.
  34282. */
  34283. var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
  34284. var backward = void 0;
  34285. var forward = void 0;
  34286. if (typeof endSequence === 'undefined') {
  34287. endSequence = playlist.mediaSequence + playlist.segments.length;
  34288. }
  34289. if (endSequence < playlist.mediaSequence) {
  34290. return 0;
  34291. } // do a backward walk to estimate the duration
  34292. backward = backwardDuration(playlist, endSequence);
  34293. if (backward.precise) {
  34294. // if we were able to base our duration estimate on timing
  34295. // information provided directly from the Media Source, return
  34296. // it
  34297. return backward.result;
  34298. } // walk forward to see if a precise duration estimate can be made
  34299. // that way
  34300. forward = forwardDuration(playlist, endSequence);
  34301. if (forward.precise) {
  34302. // we found a segment that has been buffered and so it's
  34303. // position is known precisely
  34304. return forward.result;
  34305. } // return the less-precise, playlist-based duration estimate
  34306. return backward.result + expired;
  34307. };
  34308. /**
  34309. * Calculates the duration of a playlist. If a start and end index
  34310. * are specified, the duration will be for the subset of the media
  34311. * timeline between those two indices. The total duration for live
  34312. * playlists is always Infinity.
  34313. *
  34314. * @param {Object} playlist a media playlist object
  34315. * @param {Number=} endSequence an exclusive upper
  34316. * boundary for the playlist. Defaults to the playlist media
  34317. * sequence number plus its length.
  34318. * @param {Number=} expired the amount of time that has
  34319. * dropped off the front of the playlist in a live scenario
  34320. * @return {Number} the duration between the start index and end
  34321. * index.
  34322. */
  34323. var duration = function duration(playlist, endSequence, expired) {
  34324. if (!playlist) {
  34325. return 0;
  34326. }
  34327. if (typeof expired !== 'number') {
  34328. expired = 0;
  34329. } // if a slice of the total duration is not requested, use
  34330. // playlist-level duration indicators when they're present
  34331. if (typeof endSequence === 'undefined') {
  34332. // if present, use the duration specified in the playlist
  34333. if (playlist.totalDuration) {
  34334. return playlist.totalDuration;
  34335. } // duration should be Infinity for live playlists
  34336. if (!playlist.endList) {
  34337. return window$1.Infinity;
  34338. }
  34339. } // calculate the total duration based on the segment durations
  34340. return intervalDuration(playlist, endSequence, expired);
  34341. };
  34342. /**
  34343. * Calculate the time between two indexes in the current playlist
  34344. * neight the start- nor the end-index need to be within the current
  34345. * playlist in which case, the targetDuration of the playlist is used
  34346. * to approximate the durations of the segments
  34347. *
  34348. * @param {Object} playlist a media playlist object
  34349. * @param {Number} startIndex
  34350. * @param {Number} endIndex
  34351. * @return {Number} the number of seconds between startIndex and endIndex
  34352. */
  34353. var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
  34354. var durations = 0;
  34355. if (startIndex > endIndex) {
  34356. var _ref = [endIndex, startIndex];
  34357. startIndex = _ref[0];
  34358. endIndex = _ref[1];
  34359. }
  34360. if (startIndex < 0) {
  34361. for (var i = startIndex; i < Math.min(0, endIndex); i++) {
  34362. durations += playlist.targetDuration;
  34363. }
  34364. startIndex = 0;
  34365. }
  34366. for (var _i = startIndex; _i < endIndex; _i++) {
  34367. durations += playlist.segments[_i].duration;
  34368. }
  34369. return durations;
  34370. };
  34371. /**
  34372. * Determines the media index of the segment corresponding to the safe edge of the live
  34373. * window which is the duration of the last segment plus 2 target durations from the end
  34374. * of the playlist.
  34375. *
  34376. * @param {Object} playlist
  34377. * a media playlist object
  34378. * @return {Number}
  34379. * The media index of the segment at the safe live point. 0 if there is no "safe"
  34380. * point.
  34381. * @function safeLiveIndex
  34382. */
  34383. var safeLiveIndex = function safeLiveIndex(playlist) {
  34384. if (!playlist.segments.length) {
  34385. return 0;
  34386. }
  34387. var i = playlist.segments.length - 1;
  34388. var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration;
  34389. var safeDistance = distanceFromEnd + playlist.targetDuration * 2;
  34390. while (i--) {
  34391. distanceFromEnd += playlist.segments[i].duration;
  34392. if (distanceFromEnd >= safeDistance) {
  34393. break;
  34394. }
  34395. }
  34396. return Math.max(0, i);
  34397. };
  34398. /**
  34399. * Calculates the playlist end time
  34400. *
  34401. * @param {Object} playlist a media playlist object
  34402. * @param {Number=} expired the amount of time that has
  34403. * dropped off the front of the playlist in a live scenario
  34404. * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
  34405. * playlist end calculation should consider the safe live end
  34406. * (truncate the playlist end by three segments). This is normally
  34407. * used for calculating the end of the playlist's seekable range.
  34408. * @returns {Number} the end time of playlist
  34409. * @function playlistEnd
  34410. */
  34411. var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) {
  34412. if (!playlist || !playlist.segments) {
  34413. return null;
  34414. }
  34415. if (playlist.endList) {
  34416. return duration(playlist);
  34417. }
  34418. if (expired === null) {
  34419. return null;
  34420. }
  34421. expired = expired || 0;
  34422. var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length;
  34423. return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
  34424. };
  34425. /**
  34426. * Calculates the interval of time that is currently seekable in a
  34427. * playlist. The returned time ranges are relative to the earliest
  34428. * moment in the specified playlist that is still available. A full
  34429. * seekable implementation for live streams would need to offset
  34430. * these values by the duration of content that has expired from the
  34431. * stream.
  34432. *
  34433. * @param {Object} playlist a media playlist object
  34434. * dropped off the front of the playlist in a live scenario
  34435. * @param {Number=} expired the amount of time that has
  34436. * dropped off the front of the playlist in a live scenario
  34437. * @return {TimeRanges} the periods of time that are valid targets
  34438. * for seeking
  34439. */
  34440. var seekable = function seekable(playlist, expired) {
  34441. var useSafeLiveEnd = true;
  34442. var seekableStart = expired || 0;
  34443. var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd);
  34444. if (seekableEnd === null) {
  34445. return createTimeRange();
  34446. }
  34447. return createTimeRange(seekableStart, seekableEnd);
  34448. };
  34449. var isWholeNumber = function isWholeNumber(num) {
  34450. return num - Math.floor(num) === 0;
  34451. };
  34452. var roundSignificantDigit = function roundSignificantDigit(increment, num) {
  34453. // If we have a whole number, just add 1 to it
  34454. if (isWholeNumber(num)) {
  34455. return num + increment * 0.1;
  34456. }
  34457. var numDecimalDigits = num.toString().split('.')[1].length;
  34458. for (var i = 1; i <= numDecimalDigits; i++) {
  34459. var scale = Math.pow(10, i);
  34460. var temp = num * scale;
  34461. if (isWholeNumber(temp) || i === numDecimalDigits) {
  34462. return (temp + increment) / scale;
  34463. }
  34464. }
  34465. };
  34466. var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1);
  34467. var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1);
  34468. /**
  34469. * Determine the index and estimated starting time of the segment that
  34470. * contains a specified playback position in a media playlist.
  34471. *
  34472. * @param {Object} playlist the media playlist to query
  34473. * @param {Number} currentTime The number of seconds since the earliest
  34474. * possible position to determine the containing segment for
  34475. * @param {Number} startIndex
  34476. * @param {Number} startTime
  34477. * @return {Object}
  34478. */
  34479. var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
  34480. var i = void 0;
  34481. var segment = void 0;
  34482. var numSegments = playlist.segments.length;
  34483. var time = currentTime - startTime;
  34484. if (time < 0) {
  34485. // Walk backward from startIndex in the playlist, adding durations
  34486. // until we find a segment that contains `time` and return it
  34487. if (startIndex > 0) {
  34488. for (i = startIndex - 1; i >= 0; i--) {
  34489. segment = playlist.segments[i];
  34490. time += floorLeastSignificantDigit(segment.duration);
  34491. if (time > 0) {
  34492. return {
  34493. mediaIndex: i,
  34494. startTime: startTime - sumDurations(playlist, startIndex, i)
  34495. };
  34496. }
  34497. }
  34498. } // We were unable to find a good segment within the playlist
  34499. // so select the first segment
  34500. return {
  34501. mediaIndex: 0,
  34502. startTime: currentTime
  34503. };
  34504. } // When startIndex is negative, we first walk forward to first segment
  34505. // adding target durations. If we "run out of time" before getting to
  34506. // the first segment, return the first segment
  34507. if (startIndex < 0) {
  34508. for (i = startIndex; i < 0; i++) {
  34509. time -= playlist.targetDuration;
  34510. if (time < 0) {
  34511. return {
  34512. mediaIndex: 0,
  34513. startTime: currentTime
  34514. };
  34515. }
  34516. }
  34517. startIndex = 0;
  34518. } // Walk forward from startIndex in the playlist, subtracting durations
  34519. // until we find a segment that contains `time` and return it
  34520. for (i = startIndex; i < numSegments; i++) {
  34521. segment = playlist.segments[i];
  34522. time -= ceilLeastSignificantDigit(segment.duration);
  34523. if (time < 0) {
  34524. return {
  34525. mediaIndex: i,
  34526. startTime: startTime + sumDurations(playlist, startIndex, i)
  34527. };
  34528. }
  34529. } // We are out of possible candidates so load the last one...
  34530. return {
  34531. mediaIndex: numSegments - 1,
  34532. startTime: currentTime
  34533. };
  34534. };
  34535. /**
  34536. * Check whether the playlist is blacklisted or not.
  34537. *
  34538. * @param {Object} playlist the media playlist object
  34539. * @return {boolean} whether the playlist is blacklisted or not
  34540. * @function isBlacklisted
  34541. */
  34542. var isBlacklisted = function isBlacklisted(playlist) {
  34543. return playlist.excludeUntil && playlist.excludeUntil > Date.now();
  34544. };
  34545. /**
  34546. * Check whether the playlist is compatible with current playback configuration or has
  34547. * been blacklisted permanently for being incompatible.
  34548. *
  34549. * @param {Object} playlist the media playlist object
  34550. * @return {boolean} whether the playlist is incompatible or not
  34551. * @function isIncompatible
  34552. */
  34553. var isIncompatible = function isIncompatible(playlist) {
  34554. return playlist.excludeUntil && playlist.excludeUntil === Infinity;
  34555. };
  34556. /**
  34557. * Check whether the playlist is enabled or not.
  34558. *
  34559. * @param {Object} playlist the media playlist object
  34560. * @return {boolean} whether the playlist is enabled or not
  34561. * @function isEnabled
  34562. */
  34563. var isEnabled = function isEnabled(playlist) {
  34564. var blacklisted = isBlacklisted(playlist);
  34565. return !playlist.disabled && !blacklisted;
  34566. };
  34567. /**
  34568. * Check whether the playlist has been manually disabled through the representations api.
  34569. *
  34570. * @param {Object} playlist the media playlist object
  34571. * @return {boolean} whether the playlist is disabled manually or not
  34572. * @function isDisabled
  34573. */
  34574. var isDisabled = function isDisabled(playlist) {
  34575. return playlist.disabled;
  34576. };
  34577. /**
  34578. * Returns whether the current playlist is an AES encrypted HLS stream
  34579. *
  34580. * @return {Boolean} true if it's an AES encrypted HLS stream
  34581. */
  34582. var isAes = function isAes(media) {
  34583. for (var i = 0; i < media.segments.length; i++) {
  34584. if (media.segments[i].key) {
  34585. return true;
  34586. }
  34587. }
  34588. return false;
  34589. };
  34590. /**
  34591. * Returns whether the current playlist contains fMP4
  34592. *
  34593. * @return {Boolean} true if the playlist contains fMP4
  34594. */
  34595. var isFmp4 = function isFmp4(media) {
  34596. for (var i = 0; i < media.segments.length; i++) {
  34597. if (media.segments[i].map) {
  34598. return true;
  34599. }
  34600. }
  34601. return false;
  34602. };
  34603. /**
  34604. * Checks if the playlist has a value for the specified attribute
  34605. *
  34606. * @param {String} attr
  34607. * Attribute to check for
  34608. * @param {Object} playlist
  34609. * The media playlist object
  34610. * @return {Boolean}
  34611. * Whether the playlist contains a value for the attribute or not
  34612. * @function hasAttribute
  34613. */
  34614. var hasAttribute = function hasAttribute(attr, playlist) {
  34615. return playlist.attributes && playlist.attributes[attr];
  34616. };
  34617. /**
  34618. * Estimates the time required to complete a segment download from the specified playlist
  34619. *
  34620. * @param {Number} segmentDuration
  34621. * Duration of requested segment
  34622. * @param {Number} bandwidth
  34623. * Current measured bandwidth of the player
  34624. * @param {Object} playlist
  34625. * The media playlist object
  34626. * @param {Number=} bytesReceived
  34627. * Number of bytes already received for the request. Defaults to 0
  34628. * @return {Number|NaN}
  34629. * The estimated time to request the segment. NaN if bandwidth information for
  34630. * the given playlist is unavailable
  34631. * @function estimateSegmentRequestTime
  34632. */
  34633. var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) {
  34634. var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
  34635. if (!hasAttribute('BANDWIDTH', playlist)) {
  34636. return NaN;
  34637. }
  34638. var size = segmentDuration * playlist.attributes.BANDWIDTH;
  34639. return (size - bytesReceived * 8) / bandwidth;
  34640. };
  34641. /*
  34642. * Returns whether the current playlist is the lowest rendition
  34643. *
  34644. * @return {Boolean} true if on lowest rendition
  34645. */
  34646. var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
  34647. if (master.playlists.length === 1) {
  34648. return true;
  34649. }
  34650. var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
  34651. return master.playlists.filter(function (playlist) {
  34652. if (!isEnabled(playlist)) {
  34653. return false;
  34654. }
  34655. return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
  34656. }).length === 0;
  34657. }; // exports
  34658. var Playlist = {
  34659. duration: duration,
  34660. seekable: seekable,
  34661. safeLiveIndex: safeLiveIndex,
  34662. getMediaInfoForTime: getMediaInfoForTime,
  34663. isEnabled: isEnabled,
  34664. isDisabled: isDisabled,
  34665. isBlacklisted: isBlacklisted,
  34666. isIncompatible: isIncompatible,
  34667. playlistEnd: playlistEnd,
  34668. isAes: isAes,
  34669. isFmp4: isFmp4,
  34670. hasAttribute: hasAttribute,
  34671. estimateSegmentRequestTime: estimateSegmentRequestTime,
  34672. isLowestEnabledRendition: isLowestEnabledRendition
  34673. };
  34674. /**
  34675. * @file xhr.js
  34676. */
  34677. var videojsXHR = videojs$1.xhr,
  34678. mergeOptions$1$1 = videojs$1.mergeOptions;
  34679. var xhrFactory = function xhrFactory() {
  34680. var xhr = function XhrFunction(options, callback) {
  34681. // Add a default timeout for all hls requests
  34682. options = mergeOptions$1$1({
  34683. timeout: 45e3
  34684. }, options); // Allow an optional user-specified function to modify the option
  34685. // object before we construct the xhr request
  34686. var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest;
  34687. if (beforeRequest && typeof beforeRequest === 'function') {
  34688. var newOptions = beforeRequest(options);
  34689. if (newOptions) {
  34690. options = newOptions;
  34691. }
  34692. }
  34693. var request = videojsXHR(options, function (error, response) {
  34694. var reqResponse = request.response;
  34695. if (!error && reqResponse) {
  34696. request.responseTime = Date.now();
  34697. request.roundTripTime = request.responseTime - request.requestTime;
  34698. request.bytesReceived = reqResponse.byteLength || reqResponse.length;
  34699. if (!request.bandwidth) {
  34700. request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
  34701. }
  34702. }
  34703. if (response.headers) {
  34704. request.responseHeaders = response.headers;
  34705. } // videojs.xhr now uses a specific code on the error
  34706. // object to signal that a request has timed out instead
  34707. // of setting a boolean on the request object
  34708. if (error && error.code === 'ETIMEDOUT') {
  34709. request.timedout = true;
  34710. } // videojs.xhr no longer considers status codes outside of 200 and 0
  34711. // (for file uris) to be errors, but the old XHR did, so emulate that
  34712. // behavior. Status 206 may be used in response to byterange requests.
  34713. if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
  34714. error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
  34715. }
  34716. callback(error, request);
  34717. });
  34718. var originalAbort = request.abort;
  34719. request.abort = function () {
  34720. request.aborted = true;
  34721. return originalAbort.apply(request, arguments);
  34722. };
  34723. request.uri = options.uri;
  34724. request.requestTime = Date.now();
  34725. return request;
  34726. };
  34727. return xhr;
  34728. };
  34729. /**
  34730. * @file bin-utils.js
  34731. */
  34732. /**
  34733. * convert a TimeRange to text
  34734. *
  34735. * @param {TimeRange} range the timerange to use for conversion
  34736. * @param {Number} i the iterator on the range to convert
  34737. */
  34738. var textRange = function textRange(range, i) {
  34739. return range.start(i) + '-' + range.end(i);
  34740. };
  34741. /**
  34742. * format a number as hex string
  34743. *
  34744. * @param {Number} e The number
  34745. * @param {Number} i the iterator
  34746. */
  34747. var formatHexString = function formatHexString(e, i) {
  34748. var value = e.toString(16);
  34749. return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
  34750. };
  34751. var formatAsciiString = function formatAsciiString(e) {
  34752. if (e >= 0x20 && e < 0x7e) {
  34753. return String.fromCharCode(e);
  34754. }
  34755. return '.';
  34756. };
  34757. /**
  34758. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  34759. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  34760. *
  34761. * @param {Object} message
  34762. * Object of properties and values to send to the web worker
  34763. * @return {Object}
  34764. * Modified message with TypedArray values expanded
  34765. * @function createTransferableMessage
  34766. */
  34767. var createTransferableMessage = function createTransferableMessage(message) {
  34768. var transferable = {};
  34769. Object.keys(message).forEach(function (key) {
  34770. var value = message[key];
  34771. if (ArrayBuffer.isView(value)) {
  34772. transferable[key] = {
  34773. bytes: value.buffer,
  34774. byteOffset: value.byteOffset,
  34775. byteLength: value.byteLength
  34776. };
  34777. } else {
  34778. transferable[key] = value;
  34779. }
  34780. });
  34781. return transferable;
  34782. };
  34783. /**
  34784. * Returns a unique string identifier for a media initialization
  34785. * segment.
  34786. */
  34787. var initSegmentId = function initSegmentId(initSegment) {
  34788. var byterange = initSegment.byterange || {
  34789. length: Infinity,
  34790. offset: 0
  34791. };
  34792. return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
  34793. };
  34794. /**
  34795. * utils to help dump binary data to the console
  34796. */
  34797. var hexDump = function hexDump(data) {
  34798. var bytes = Array.prototype.slice.call(data);
  34799. var step = 16;
  34800. var result = '';
  34801. var hex = void 0;
  34802. var ascii = void 0;
  34803. for (var j = 0; j < bytes.length / step; j++) {
  34804. hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
  34805. ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
  34806. result += hex + ' ' + ascii + '\n';
  34807. }
  34808. return result;
  34809. };
  34810. var tagDump = function tagDump(_ref) {
  34811. var bytes = _ref.bytes;
  34812. return hexDump(bytes);
  34813. };
  34814. var textRanges = function textRanges(ranges) {
  34815. var result = '';
  34816. var i = void 0;
  34817. for (i = 0; i < ranges.length; i++) {
  34818. result += textRange(ranges, i) + ' ';
  34819. }
  34820. return result;
  34821. };
  34822. var utils$1 =
  34823. /*#__PURE__*/
  34824. Object.freeze({
  34825. createTransferableMessage: createTransferableMessage,
  34826. initSegmentId: initSegmentId,
  34827. hexDump: hexDump,
  34828. tagDump: tagDump,
  34829. textRanges: textRanges
  34830. }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
  34831. // Add 25% to the segment duration to account for small discrepencies in segment timing.
  34832. // 25% was arbitrarily chosen, and may need to be refined over time.
  34833. var SEGMENT_END_FUDGE_PERCENT = 0.25;
  34834. /**
  34835. * Converts a player time (any time that can be gotten/set from player.currentTime(),
  34836. * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
  34837. * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
  34838. *
  34839. * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
  34840. * point" (a point where we have a mapping from program time to player time, with player
  34841. * time being the post transmux start of the segment).
  34842. *
  34843. * For more details, see [this doc](../../docs/program-time-from-player-time.md).
  34844. *
  34845. * @param {Number} playerTime the player time
  34846. * @param {Object} segment the segment which contains the player time
  34847. * @return {Date} program time
  34848. */
  34849. var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) {
  34850. if (!segment.dateTimeObject) {
  34851. // Can't convert without an "anchor point" for the program time (i.e., a time that can
  34852. // be used to map the start of a segment with a real world time).
  34853. return null;
  34854. }
  34855. var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
  34856. var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
  34857. var startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
  34858. var offsetFromSegmentStart = playerTime - startOfSegment;
  34859. return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
  34860. };
  34861. var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) {
  34862. return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
  34863. };
  34864. /**
  34865. * Finds a segment that contains the time requested given as an ISO-8601 string. The
  34866. * returned segment might be an estimate or an accurate match.
  34867. *
  34868. * @param {String} programTime The ISO-8601 programTime to find a match for
  34869. * @param {Object} playlist A playlist object to search within
  34870. */
  34871. var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) {
  34872. // Assumptions:
  34873. // - verifyProgramDateTimeTags has already been run
  34874. // - live streams have been started
  34875. var dateTimeObject = void 0;
  34876. try {
  34877. dateTimeObject = new Date(programTime);
  34878. } catch (e) {
  34879. return null;
  34880. }
  34881. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  34882. return null;
  34883. }
  34884. var segment = playlist.segments[0];
  34885. if (dateTimeObject < segment.dateTimeObject) {
  34886. // Requested time is before stream start.
  34887. return null;
  34888. }
  34889. for (var i = 0; i < playlist.segments.length - 1; i++) {
  34890. segment = playlist.segments[i];
  34891. var nextSegmentStart = playlist.segments[i + 1].dateTimeObject;
  34892. if (dateTimeObject < nextSegmentStart) {
  34893. break;
  34894. }
  34895. }
  34896. var lastSegment = playlist.segments[playlist.segments.length - 1];
  34897. var lastSegmentStart = lastSegment.dateTimeObject;
  34898. var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
  34899. var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
  34900. if (dateTimeObject > lastSegmentEnd) {
  34901. // Beyond the end of the stream, or our best guess of the end of the stream.
  34902. return null;
  34903. }
  34904. if (dateTimeObject > lastSegmentStart) {
  34905. segment = lastSegment;
  34906. }
  34907. return {
  34908. segment: segment,
  34909. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
  34910. // Although, given that all segments have accurate date time objects, the segment
  34911. // selected should be accurate, unless the video has been transmuxed at some point
  34912. // (determined by the presence of the videoTimingInfo object), the segment's "player
  34913. // time" (the start time in the player) can't be considered accurate.
  34914. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  34915. };
  34916. };
  34917. /**
  34918. * Finds a segment that contains the given player time(in seconds).
  34919. *
  34920. * @param {Number} time The player time to find a match for
  34921. * @param {Object} playlist A playlist object to search within
  34922. */
  34923. var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) {
  34924. // Assumptions:
  34925. // - there will always be a segment.duration
  34926. // - we can start from zero
  34927. // - segments are in time order
  34928. if (!playlist || !playlist.segments || playlist.segments.length === 0) {
  34929. return null;
  34930. }
  34931. var segmentEnd = 0;
  34932. var segment = void 0;
  34933. for (var i = 0; i < playlist.segments.length; i++) {
  34934. segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
  34935. // should contain the most accurate values we have for the segment's player times.
  34936. //
  34937. // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
  34938. // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
  34939. // calculate an end value.
  34940. segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
  34941. if (time <= segmentEnd) {
  34942. break;
  34943. }
  34944. }
  34945. var lastSegment = playlist.segments[playlist.segments.length - 1];
  34946. if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
  34947. // The time requested is beyond the stream end.
  34948. return null;
  34949. }
  34950. if (time > segmentEnd) {
  34951. // The time is within or beyond the last segment.
  34952. //
  34953. // Check to see if the time is beyond a reasonable guess of the end of the stream.
  34954. if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
  34955. // Technically, because the duration value is only an estimate, the time may still
  34956. // exist in the last segment, however, there isn't enough information to make even
  34957. // a reasonable estimate.
  34958. return null;
  34959. }
  34960. segment = lastSegment;
  34961. }
  34962. return {
  34963. segment: segment,
  34964. estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
  34965. // Because videoTimingInfo is only set after transmux, it is the only way to get
  34966. // accurate timing values.
  34967. type: segment.videoTimingInfo ? 'accurate' : 'estimate'
  34968. };
  34969. };
  34970. /**
  34971. * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
  34972. * If the offset returned is positive, the programTime occurs after the
  34973. * comparisonTimestamp.
  34974. * If the offset is negative, the programTime occurs before the comparisonTimestamp.
  34975. *
  34976. * @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against
  34977. * @param {String} programTime The programTime as an ISO-8601 string
  34978. * @return {Number} offset
  34979. */
  34980. var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) {
  34981. var segmentDateTime = void 0;
  34982. var programDateTime = void 0;
  34983. try {
  34984. segmentDateTime = new Date(comparisonTimeStamp);
  34985. programDateTime = new Date(programTime);
  34986. } catch (e) {// TODO handle error
  34987. }
  34988. var segmentTimeEpoch = segmentDateTime.getTime();
  34989. var programTimeEpoch = programDateTime.getTime();
  34990. return (programTimeEpoch - segmentTimeEpoch) / 1000;
  34991. };
  34992. /**
  34993. * Checks that all segments in this playlist have programDateTime tags.
  34994. *
  34995. * @param {Object} playlist A playlist object
  34996. */
  34997. var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) {
  34998. if (!playlist.segments || playlist.segments.length === 0) {
  34999. return false;
  35000. }
  35001. for (var i = 0; i < playlist.segments.length; i++) {
  35002. var segment = playlist.segments[i];
  35003. if (!segment.dateTimeObject) {
  35004. return false;
  35005. }
  35006. }
  35007. return true;
  35008. };
  35009. /**
  35010. * Returns the programTime of the media given a playlist and a playerTime.
  35011. * The playlist must have programDateTime tags for a programDateTime tag to be returned.
  35012. * If the segments containing the time requested have not been buffered yet, an estimate
  35013. * may be returned to the callback.
  35014. *
  35015. * @param {Object} args
  35016. * @param {Object} args.playlist A playlist object to search within
  35017. * @param {Number} time A playerTime in seconds
  35018. * @param {Function} callback(err, programTime)
  35019. * @returns {String} err.message A detailed error message
  35020. * @returns {Object} programTime
  35021. * @returns {Number} programTime.mediaSeconds The streamTime in seconds
  35022. * @returns {String} programTime.programDateTime The programTime as an ISO-8601 String
  35023. */
  35024. var getProgramTime = function getProgramTime(_ref) {
  35025. var playlist = _ref.playlist,
  35026. _ref$time = _ref.time,
  35027. time = _ref$time === undefined ? undefined : _ref$time,
  35028. callback = _ref.callback;
  35029. if (!callback) {
  35030. throw new Error('getProgramTime: callback must be provided');
  35031. }
  35032. if (!playlist || time === undefined) {
  35033. return callback({
  35034. message: 'getProgramTime: playlist and time must be provided'
  35035. });
  35036. }
  35037. var matchedSegment = findSegmentForPlayerTime(time, playlist);
  35038. if (!matchedSegment) {
  35039. return callback({
  35040. message: 'valid programTime was not found'
  35041. });
  35042. }
  35043. if (matchedSegment.type === 'estimate') {
  35044. return callback({
  35045. message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
  35046. seekTime: matchedSegment.estimatedStart
  35047. });
  35048. }
  35049. var programTimeObject = {
  35050. mediaSeconds: time
  35051. };
  35052. var programTime = playerTimeToProgramTime(time, matchedSegment.segment);
  35053. if (programTime) {
  35054. programTimeObject.programDateTime = programTime.toISOString();
  35055. }
  35056. return callback(null, programTimeObject);
  35057. };
  35058. /**
  35059. * Seeks in the player to a time that matches the given programTime ISO-8601 string.
  35060. *
  35061. * @param {Object} args
  35062. * @param {String} args.programTime A programTime to seek to as an ISO-8601 String
  35063. * @param {Object} args.playlist A playlist to look within
  35064. * @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2.
  35065. * @param {Function} args.seekTo A method to perform a seek
  35066. * @param {Boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
  35067. * @param {Object} args.tech The tech to seek on
  35068. * @param {Function} args.callback(err, newTime) A callback to return the new time to
  35069. * @returns {String} err.message A detailed error message
  35070. * @returns {Number} newTime The exact time that was seeked to in seconds
  35071. */
  35072. var seekToProgramTime = function seekToProgramTime(_ref2) {
  35073. var programTime = _ref2.programTime,
  35074. playlist = _ref2.playlist,
  35075. _ref2$retryCount = _ref2.retryCount,
  35076. retryCount = _ref2$retryCount === undefined ? 2 : _ref2$retryCount,
  35077. seekTo = _ref2.seekTo,
  35078. _ref2$pauseAfterSeek = _ref2.pauseAfterSeek,
  35079. pauseAfterSeek = _ref2$pauseAfterSeek === undefined ? true : _ref2$pauseAfterSeek,
  35080. tech = _ref2.tech,
  35081. callback = _ref2.callback;
  35082. if (!callback) {
  35083. throw new Error('seekToProgramTime: callback must be provided');
  35084. }
  35085. if (typeof programTime === 'undefined' || !playlist || !seekTo) {
  35086. return callback({
  35087. message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
  35088. });
  35089. }
  35090. if (!playlist.endList && !tech.hasStarted_) {
  35091. return callback({
  35092. message: 'player must be playing a live stream to start buffering'
  35093. });
  35094. }
  35095. if (!verifyProgramDateTimeTags(playlist)) {
  35096. return callback({
  35097. message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
  35098. });
  35099. }
  35100. var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
  35101. if (!matchedSegment) {
  35102. return callback({
  35103. message: programTime + ' was not found in the stream'
  35104. });
  35105. }
  35106. var segment = matchedSegment.segment;
  35107. var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
  35108. if (matchedSegment.type === 'estimate') {
  35109. // we've run out of retries
  35110. if (retryCount === 0) {
  35111. return callback({
  35112. message: programTime + ' is not buffered yet. Try again'
  35113. });
  35114. }
  35115. seekTo(matchedSegment.estimatedStart + mediaOffset);
  35116. tech.one('seeked', function () {
  35117. seekToProgramTime({
  35118. programTime: programTime,
  35119. playlist: playlist,
  35120. retryCount: retryCount - 1,
  35121. seekTo: seekTo,
  35122. pauseAfterSeek: pauseAfterSeek,
  35123. tech: tech,
  35124. callback: callback
  35125. });
  35126. });
  35127. return;
  35128. } // Since the segment.start value is determined from the buffered end or ending time
  35129. // of the prior segment, the seekToTime doesn't need to account for any transmuxer
  35130. // modifications.
  35131. var seekToTime = segment.start + mediaOffset;
  35132. var seekedCallback = function seekedCallback() {
  35133. return callback(null, tech.currentTime());
  35134. }; // listen for seeked event
  35135. tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
  35136. if (pauseAfterSeek) {
  35137. tech.pause();
  35138. }
  35139. seekTo(seekToTime);
  35140. };
  35141. /**
  35142. * ranges
  35143. *
  35144. * Utilities for working with TimeRanges.
  35145. *
  35146. */
  35147. // Fudge factor to account for TimeRanges rounding
  35148. var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
  35149. // can be misleading because of precision differences or when the current media has poorly
  35150. // aligned audio and video, which can cause values to be slightly off from what you would
  35151. // expect. This value is what we consider to be safe to use in such comparisons to account
  35152. // for these scenarios.
  35153. var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
  35154. var filterRanges = function filterRanges(timeRanges, predicate) {
  35155. var results = [];
  35156. var i = void 0;
  35157. if (timeRanges && timeRanges.length) {
  35158. // Search for ranges that match the predicate
  35159. for (i = 0; i < timeRanges.length; i++) {
  35160. if (predicate(timeRanges.start(i), timeRanges.end(i))) {
  35161. results.push([timeRanges.start(i), timeRanges.end(i)]);
  35162. }
  35163. }
  35164. }
  35165. return videojs$1.createTimeRanges(results);
  35166. };
  35167. /**
  35168. * Attempts to find the buffered TimeRange that contains the specified
  35169. * time.
  35170. * @param {TimeRanges} buffered - the TimeRanges object to query
  35171. * @param {number} time - the time to filter on.
  35172. * @returns {TimeRanges} a new TimeRanges object
  35173. */
  35174. var findRange = function findRange(buffered, time) {
  35175. return filterRanges(buffered, function (start, end) {
  35176. return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
  35177. });
  35178. };
  35179. /**
  35180. * Returns the TimeRanges that begin later than the specified time.
  35181. * @param {TimeRanges} timeRanges - the TimeRanges object to query
  35182. * @param {number} time - the time to filter on.
  35183. * @returns {TimeRanges} a new TimeRanges object.
  35184. */
  35185. var findNextRange = function findNextRange(timeRanges, time) {
  35186. return filterRanges(timeRanges, function (start) {
  35187. return start - TIME_FUDGE_FACTOR >= time;
  35188. });
  35189. };
  35190. /**
  35191. * Returns gaps within a list of TimeRanges
  35192. * @param {TimeRanges} buffered - the TimeRanges object
  35193. * @return {TimeRanges} a TimeRanges object of gaps
  35194. */
  35195. var findGaps = function findGaps(buffered) {
  35196. if (buffered.length < 2) {
  35197. return videojs$1.createTimeRanges();
  35198. }
  35199. var ranges = [];
  35200. for (var i = 1; i < buffered.length; i++) {
  35201. var start = buffered.end(i - 1);
  35202. var end = buffered.start(i);
  35203. ranges.push([start, end]);
  35204. }
  35205. return videojs$1.createTimeRanges(ranges);
  35206. };
  35207. /**
  35208. * Gets a human readable string for a TimeRange
  35209. *
  35210. * @param {TimeRange} range
  35211. * @returns {String} a human readable string
  35212. */
  35213. var printableRange = function printableRange(range) {
  35214. var strArr = [];
  35215. if (!range || !range.length) {
  35216. return '';
  35217. }
  35218. for (var i = 0; i < range.length; i++) {
  35219. strArr.push(range.start(i) + ' => ' + range.end(i));
  35220. }
  35221. return strArr.join(', ');
  35222. };
  35223. /**
  35224. * Calculates the amount of time left in seconds until the player hits the end of the
  35225. * buffer and causes a rebuffer
  35226. *
  35227. * @param {TimeRange} buffered
  35228. * The state of the buffer
  35229. * @param {Numnber} currentTime
  35230. * The current time of the player
  35231. * @param {Number} playbackRate
  35232. * The current playback rate of the player. Defaults to 1.
  35233. * @return {Number}
  35234. * Time until the player has to start rebuffering in seconds.
  35235. * @function timeUntilRebuffer
  35236. */
  35237. var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) {
  35238. var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
  35239. var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
  35240. return (bufferedEnd - currentTime) / playbackRate;
  35241. };
  35242. /**
  35243. * Converts a TimeRanges object into an array representation
  35244. * @param {TimeRanges} timeRanges
  35245. * @returns {Array}
  35246. */
  35247. var timeRangesToArray = function timeRangesToArray(timeRanges) {
  35248. var timeRangesList = [];
  35249. for (var i = 0; i < timeRanges.length; i++) {
  35250. timeRangesList.push({
  35251. start: timeRanges.start(i),
  35252. end: timeRanges.end(i)
  35253. });
  35254. }
  35255. return timeRangesList;
  35256. };
  35257. /**
  35258. * @file create-text-tracks-if-necessary.js
  35259. */
  35260. /**
  35261. * Create text tracks on video.js if they exist on a segment.
  35262. *
  35263. * @param {Object} sourceBuffer the VSB or FSB
  35264. * @param {Object} mediaSource the HTML media source
  35265. * @param {Object} segment the segment that may contain the text track
  35266. * @private
  35267. */
  35268. var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) {
  35269. var player = mediaSource.player_; // create an in-band caption track if one is present in the segment
  35270. if (segment.captions && segment.captions.length) {
  35271. if (!sourceBuffer.inbandTextTracks_) {
  35272. sourceBuffer.inbandTextTracks_ = {};
  35273. }
  35274. for (var trackId in segment.captionStreams) {
  35275. if (!sourceBuffer.inbandTextTracks_[trackId]) {
  35276. player.tech_.trigger({
  35277. type: 'usage',
  35278. name: 'hls-608'
  35279. });
  35280. var track = player.textTracks().getTrackById(trackId);
  35281. if (track) {
  35282. // Resuse an existing track with a CC# id because this was
  35283. // very likely created by videojs-contrib-hls from information
  35284. // in the m3u8 for us to use
  35285. sourceBuffer.inbandTextTracks_[trackId] = track;
  35286. } else {
  35287. // Otherwise, create a track with the default `CC#` label and
  35288. // without a language
  35289. sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({
  35290. kind: 'captions',
  35291. id: trackId,
  35292. label: trackId
  35293. }, false).track;
  35294. }
  35295. }
  35296. }
  35297. }
  35298. if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) {
  35299. sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({
  35300. kind: 'metadata',
  35301. label: 'Timed Metadata'
  35302. }, false).track;
  35303. sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType;
  35304. }
  35305. };
  35306. /**
  35307. * @file remove-cues-from-track.js
  35308. */
  35309. /**
  35310. * Remove cues from a track on video.js.
  35311. *
  35312. * @param {Double} start start of where we should remove the cue
  35313. * @param {Double} end end of where the we should remove the cue
  35314. * @param {Object} track the text track to remove the cues from
  35315. * @private
  35316. */
  35317. var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
  35318. var i = void 0;
  35319. var cue = void 0;
  35320. if (!track) {
  35321. return;
  35322. }
  35323. if (!track.cues) {
  35324. return;
  35325. }
  35326. i = track.cues.length;
  35327. while (i--) {
  35328. cue = track.cues[i]; // Remove any overlapping cue
  35329. if (cue.startTime <= end && cue.endTime >= start) {
  35330. track.removeCue(cue);
  35331. }
  35332. }
  35333. };
  35334. /**
  35335. * @file add-text-track-data.js
  35336. */
  35337. /**
  35338. * Define properties on a cue for backwards compatability,
  35339. * but warn the user that the way that they are using it
  35340. * is depricated and will be removed at a later date.
  35341. *
  35342. * @param {Cue} cue the cue to add the properties on
  35343. * @private
  35344. */
  35345. var deprecateOldCue = function deprecateOldCue(cue) {
  35346. Object.defineProperties(cue.frame, {
  35347. id: {
  35348. get: function get() {
  35349. videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
  35350. return cue.value.key;
  35351. }
  35352. },
  35353. value: {
  35354. get: function get() {
  35355. videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
  35356. return cue.value.data;
  35357. }
  35358. },
  35359. privateData: {
  35360. get: function get() {
  35361. videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
  35362. return cue.value.data;
  35363. }
  35364. }
  35365. });
  35366. };
  35367. var durationOfVideo = function durationOfVideo(duration) {
  35368. var dur = void 0;
  35369. if (isNaN(duration) || Math.abs(duration) === Infinity) {
  35370. dur = Number.MAX_VALUE;
  35371. } else {
  35372. dur = duration;
  35373. }
  35374. return dur;
  35375. };
  35376. /**
  35377. * Add text track data to a source handler given the captions and
  35378. * metadata from the buffer.
  35379. *
  35380. * @param {Object} sourceHandler the virtual source buffer
  35381. * @param {Array} captionArray an array of caption data
  35382. * @param {Array} metadataArray an array of meta data
  35383. * @private
  35384. */
  35385. var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) {
  35386. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  35387. if (captionArray) {
  35388. captionArray.forEach(function (caption) {
  35389. var track = caption.stream;
  35390. this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text));
  35391. }, sourceHandler);
  35392. }
  35393. if (metadataArray) {
  35394. var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration);
  35395. metadataArray.forEach(function (metadata) {
  35396. var time = metadata.cueTime + this.timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
  35397. // ignore this bit of metadata.
  35398. // This likely occurs when you have an non-timed ID3 tag like TIT2,
  35399. // which is the "Title/Songname/Content description" frame
  35400. if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) {
  35401. return;
  35402. }
  35403. metadata.frames.forEach(function (frame) {
  35404. var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
  35405. cue.frame = frame;
  35406. cue.value = frame;
  35407. deprecateOldCue(cue);
  35408. this.metadataTrack_.addCue(cue);
  35409. }, this);
  35410. }, sourceHandler); // Updating the metadeta cues so that
  35411. // the endTime of each cue is the startTime of the next cue
  35412. // the endTime of last cue is the duration of the video
  35413. if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) {
  35414. var cues = sourceHandler.metadataTrack_.cues;
  35415. var cuesArray = []; // Create a copy of the TextTrackCueList...
  35416. // ...disregarding cues with a falsey value
  35417. for (var i = 0; i < cues.length; i++) {
  35418. if (cues[i]) {
  35419. cuesArray.push(cues[i]);
  35420. }
  35421. } // Group cues by their startTime value
  35422. var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
  35423. var timeSlot = obj[cue.startTime] || [];
  35424. timeSlot.push(cue);
  35425. obj[cue.startTime] = timeSlot;
  35426. return obj;
  35427. }, {}); // Sort startTimes by ascending order
  35428. var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
  35429. return Number(a) - Number(b);
  35430. }); // Map each cue group's endTime to the next group's startTime
  35431. sortedStartTimes.forEach(function (startTime, idx) {
  35432. var cueGroup = cuesGroupedByStartTime[startTime];
  35433. var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime
  35434. cueGroup.forEach(function (cue) {
  35435. cue.endTime = nextTime;
  35436. });
  35437. });
  35438. }
  35439. }
  35440. };
  35441. var win = typeof window !== 'undefined' ? window : {},
  35442. TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(),
  35443. SCRIPT_TYPE = 'application/javascript',
  35444. BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder,
  35445. URL = win.URL || win.webkitURL || URL && URL.msURL,
  35446. Worker = win.Worker;
  35447. /**
  35448. * Returns a wrapper around Web Worker code that is constructible.
  35449. *
  35450. * @function shimWorker
  35451. *
  35452. * @param { String } filename The name of the file
  35453. * @param { Function } fn Function wrapping the code of the worker
  35454. */
  35455. function shimWorker(filename, fn) {
  35456. return function ShimWorker(forceFallback) {
  35457. var o = this;
  35458. if (!fn) {
  35459. return new Worker(filename);
  35460. } else if (Worker && !forceFallback) {
  35461. // Convert the function's inner code to a string to construct the worker
  35462. var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1),
  35463. objURL = createSourceObject(source);
  35464. this[TARGET] = new Worker(objURL);
  35465. wrapTerminate(this[TARGET], objURL);
  35466. return this[TARGET];
  35467. } else {
  35468. var selfShim = {
  35469. postMessage: function postMessage(m) {
  35470. if (o.onmessage) {
  35471. setTimeout(function () {
  35472. o.onmessage({
  35473. data: m,
  35474. target: selfShim
  35475. });
  35476. });
  35477. }
  35478. }
  35479. };
  35480. fn.call(selfShim);
  35481. this.postMessage = function (m) {
  35482. setTimeout(function () {
  35483. selfShim.onmessage({
  35484. data: m,
  35485. target: o
  35486. });
  35487. });
  35488. };
  35489. this.isThisThread = true;
  35490. }
  35491. };
  35492. } // Test Worker capabilities
  35493. if (Worker) {
  35494. var testWorker,
  35495. objURL = createSourceObject('self.onmessage = function () {}'),
  35496. testArray = new Uint8Array(1);
  35497. try {
  35498. testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it
  35499. testWorker.postMessage(testArray, [testArray.buffer]);
  35500. } catch (e) {
  35501. Worker = null;
  35502. } finally {
  35503. URL.revokeObjectURL(objURL);
  35504. if (testWorker) {
  35505. testWorker.terminate();
  35506. }
  35507. }
  35508. }
  35509. function createSourceObject(str) {
  35510. try {
  35511. return URL.createObjectURL(new Blob([str], {
  35512. type: SCRIPT_TYPE
  35513. }));
  35514. } catch (e) {
  35515. var blob = new BlobBuilder();
  35516. blob.append(str);
  35517. return URL.createObjectURL(blob.getBlob(type));
  35518. }
  35519. }
  35520. function wrapTerminate(worker, objURL) {
  35521. if (!worker || !objURL) return;
  35522. var term = worker.terminate;
  35523. worker.objURL = objURL;
  35524. worker.terminate = function () {
  35525. if (worker.objURL) URL.revokeObjectURL(worker.objURL);
  35526. term.call(worker);
  35527. };
  35528. }
  35529. var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) {
  35530. var self = this;
  35531. var transmuxerWorker = function () {
  35532. /**
  35533. * mux.js
  35534. *
  35535. * Copyright (c) 2015 Brightcove
  35536. * All rights reserved.
  35537. *
  35538. * Functions that generate fragmented MP4s suitable for use with Media
  35539. * Source Extensions.
  35540. */
  35541. var UINT32_MAX = Math.pow(2, 32) - 1;
  35542. 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
  35543. (function () {
  35544. var i;
  35545. types = {
  35546. avc1: [],
  35547. // codingname
  35548. avcC: [],
  35549. btrt: [],
  35550. dinf: [],
  35551. dref: [],
  35552. esds: [],
  35553. ftyp: [],
  35554. hdlr: [],
  35555. mdat: [],
  35556. mdhd: [],
  35557. mdia: [],
  35558. mfhd: [],
  35559. minf: [],
  35560. moof: [],
  35561. moov: [],
  35562. mp4a: [],
  35563. // codingname
  35564. mvex: [],
  35565. mvhd: [],
  35566. sdtp: [],
  35567. smhd: [],
  35568. stbl: [],
  35569. stco: [],
  35570. stsc: [],
  35571. stsd: [],
  35572. stsz: [],
  35573. stts: [],
  35574. styp: [],
  35575. tfdt: [],
  35576. tfhd: [],
  35577. traf: [],
  35578. trak: [],
  35579. trun: [],
  35580. trex: [],
  35581. tkhd: [],
  35582. vmhd: []
  35583. }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
  35584. // don't throw an error
  35585. if (typeof Uint8Array === 'undefined') {
  35586. return;
  35587. }
  35588. for (i in types) {
  35589. if (types.hasOwnProperty(i)) {
  35590. types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
  35591. }
  35592. }
  35593. MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
  35594. AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
  35595. MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
  35596. VIDEO_HDLR = new Uint8Array([0x00, // version 0
  35597. 0x00, 0x00, 0x00, // flags
  35598. 0x00, 0x00, 0x00, 0x00, // pre_defined
  35599. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  35600. 0x00, 0x00, 0x00, 0x00, // reserved
  35601. 0x00, 0x00, 0x00, 0x00, // reserved
  35602. 0x00, 0x00, 0x00, 0x00, // reserved
  35603. 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  35604. ]);
  35605. AUDIO_HDLR = new Uint8Array([0x00, // version 0
  35606. 0x00, 0x00, 0x00, // flags
  35607. 0x00, 0x00, 0x00, 0x00, // pre_defined
  35608. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  35609. 0x00, 0x00, 0x00, 0x00, // reserved
  35610. 0x00, 0x00, 0x00, 0x00, // reserved
  35611. 0x00, 0x00, 0x00, 0x00, // reserved
  35612. 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  35613. ]);
  35614. HDLR_TYPES = {
  35615. video: VIDEO_HDLR,
  35616. audio: AUDIO_HDLR
  35617. };
  35618. DREF = new Uint8Array([0x00, // version 0
  35619. 0x00, 0x00, 0x00, // flags
  35620. 0x00, 0x00, 0x00, 0x01, // entry_count
  35621. 0x00, 0x00, 0x00, 0x0c, // entry_size
  35622. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  35623. 0x00, // version 0
  35624. 0x00, 0x00, 0x01 // entry_flags
  35625. ]);
  35626. SMHD = new Uint8Array([0x00, // version
  35627. 0x00, 0x00, 0x00, // flags
  35628. 0x00, 0x00, // balance, 0 means centered
  35629. 0x00, 0x00 // reserved
  35630. ]);
  35631. STCO = new Uint8Array([0x00, // version
  35632. 0x00, 0x00, 0x00, // flags
  35633. 0x00, 0x00, 0x00, 0x00 // entry_count
  35634. ]);
  35635. STSC = STCO;
  35636. STSZ = new Uint8Array([0x00, // version
  35637. 0x00, 0x00, 0x00, // flags
  35638. 0x00, 0x00, 0x00, 0x00, // sample_size
  35639. 0x00, 0x00, 0x00, 0x00 // sample_count
  35640. ]);
  35641. STTS = STCO;
  35642. VMHD = new Uint8Array([0x00, // version
  35643. 0x00, 0x00, 0x01, // flags
  35644. 0x00, 0x00, // graphicsmode
  35645. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
  35646. ]);
  35647. })();
  35648. box = function box(type) {
  35649. var payload = [],
  35650. size = 0,
  35651. i,
  35652. result,
  35653. view;
  35654. for (i = 1; i < arguments.length; i++) {
  35655. payload.push(arguments[i]);
  35656. }
  35657. i = payload.length; // calculate the total size we need to allocate
  35658. while (i--) {
  35659. size += payload[i].byteLength;
  35660. }
  35661. result = new Uint8Array(size + 8);
  35662. view = new DataView(result.buffer, result.byteOffset, result.byteLength);
  35663. view.setUint32(0, result.byteLength);
  35664. result.set(type, 4); // copy the payload into the result
  35665. for (i = 0, size = 8; i < payload.length; i++) {
  35666. result.set(payload[i], size);
  35667. size += payload[i].byteLength;
  35668. }
  35669. return result;
  35670. };
  35671. dinf = function dinf() {
  35672. return box(types.dinf, box(types.dref, DREF));
  35673. };
  35674. esds = function esds(track) {
  35675. return box(types.esds, new Uint8Array([0x00, // version
  35676. 0x00, 0x00, 0x00, // flags
  35677. // ES_Descriptor
  35678. 0x03, // tag, ES_DescrTag
  35679. 0x19, // length
  35680. 0x00, 0x00, // ES_ID
  35681. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  35682. // DecoderConfigDescriptor
  35683. 0x04, // tag, DecoderConfigDescrTag
  35684. 0x11, // length
  35685. 0x40, // object type
  35686. 0x15, // streamType
  35687. 0x00, 0x06, 0x00, // bufferSizeDB
  35688. 0x00, 0x00, 0xda, 0xc0, // maxBitrate
  35689. 0x00, 0x00, 0xda, 0xc0, // avgBitrate
  35690. // DecoderSpecificInfo
  35691. 0x05, // tag, DecoderSpecificInfoTag
  35692. 0x02, // length
  35693. // ISO/IEC 14496-3, AudioSpecificConfig
  35694. // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  35695. track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
  35696. ]));
  35697. };
  35698. ftyp = function ftyp() {
  35699. return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
  35700. };
  35701. hdlr = function hdlr(type) {
  35702. return box(types.hdlr, HDLR_TYPES[type]);
  35703. };
  35704. mdat = function mdat(data) {
  35705. return box(types.mdat, data);
  35706. };
  35707. mdhd = function mdhd(track) {
  35708. var result = new Uint8Array([0x00, // version 0
  35709. 0x00, 0x00, 0x00, // flags
  35710. 0x00, 0x00, 0x00, 0x02, // creation_time
  35711. 0x00, 0x00, 0x00, 0x03, // modification_time
  35712. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  35713. track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
  35714. 0x55, 0xc4, // 'und' language (undetermined)
  35715. 0x00, 0x00]); // Use the sample rate from the track metadata, when it is
  35716. // defined. The sample rate can be parsed out of an ADTS header, for
  35717. // instance.
  35718. if (track.samplerate) {
  35719. result[12] = track.samplerate >>> 24 & 0xFF;
  35720. result[13] = track.samplerate >>> 16 & 0xFF;
  35721. result[14] = track.samplerate >>> 8 & 0xFF;
  35722. result[15] = track.samplerate & 0xFF;
  35723. }
  35724. return box(types.mdhd, result);
  35725. };
  35726. mdia = function mdia(track) {
  35727. return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
  35728. };
  35729. mfhd = function mfhd(sequenceNumber) {
  35730. return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
  35731. (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
  35732. ]));
  35733. };
  35734. minf = function minf(track) {
  35735. return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
  35736. };
  35737. moof = function moof(sequenceNumber, tracks) {
  35738. var trackFragments = [],
  35739. i = tracks.length; // build traf boxes for each track fragment
  35740. while (i--) {
  35741. trackFragments[i] = traf(tracks[i]);
  35742. }
  35743. return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
  35744. };
  35745. /**
  35746. * Returns a movie box.
  35747. * @param tracks {array} the tracks associated with this movie
  35748. * @see ISO/IEC 14496-12:2012(E), section 8.2.1
  35749. */
  35750. moov = function moov(tracks) {
  35751. var i = tracks.length,
  35752. boxes = [];
  35753. while (i--) {
  35754. boxes[i] = trak(tracks[i]);
  35755. }
  35756. return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
  35757. };
  35758. mvex = function mvex(tracks) {
  35759. var i = tracks.length,
  35760. boxes = [];
  35761. while (i--) {
  35762. boxes[i] = trex(tracks[i]);
  35763. }
  35764. return box.apply(null, [types.mvex].concat(boxes));
  35765. };
  35766. mvhd = function mvhd(duration) {
  35767. var bytes = new Uint8Array([0x00, // version 0
  35768. 0x00, 0x00, 0x00, // flags
  35769. 0x00, 0x00, 0x00, 0x01, // creation_time
  35770. 0x00, 0x00, 0x00, 0x02, // modification_time
  35771. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  35772. (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
  35773. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  35774. 0x01, 0x00, // 1.0 volume
  35775. 0x00, 0x00, // reserved
  35776. 0x00, 0x00, 0x00, 0x00, // reserved
  35777. 0x00, 0x00, 0x00, 0x00, // reserved
  35778. 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
  35779. 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
  35780. 0xff, 0xff, 0xff, 0xff // next_track_ID
  35781. ]);
  35782. return box(types.mvhd, bytes);
  35783. };
  35784. sdtp = function sdtp(track) {
  35785. var samples = track.samples || [],
  35786. bytes = new Uint8Array(4 + samples.length),
  35787. flags,
  35788. i; // leave the full box header (4 bytes) all zero
  35789. // write the sample table
  35790. for (i = 0; i < samples.length; i++) {
  35791. flags = samples[i].flags;
  35792. bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
  35793. }
  35794. return box(types.sdtp, bytes);
  35795. };
  35796. stbl = function stbl(track) {
  35797. return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
  35798. };
  35799. (function () {
  35800. var videoSample, audioSample;
  35801. stsd = function stsd(track) {
  35802. return box(types.stsd, new Uint8Array([0x00, // version 0
  35803. 0x00, 0x00, 0x00, // flags
  35804. 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
  35805. };
  35806. videoSample = function videoSample(track) {
  35807. var sps = track.sps || [],
  35808. pps = track.pps || [],
  35809. sequenceParameterSets = [],
  35810. pictureParameterSets = [],
  35811. i; // assemble the SPSs
  35812. for (i = 0; i < sps.length; i++) {
  35813. sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
  35814. sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
  35815. sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
  35816. } // assemble the PPSs
  35817. for (i = 0; i < pps.length; i++) {
  35818. pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
  35819. pictureParameterSets.push(pps[i].byteLength & 0xFF);
  35820. pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
  35821. }
  35822. return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  35823. 0x00, 0x01, // data_reference_index
  35824. 0x00, 0x00, // pre_defined
  35825. 0x00, 0x00, // reserved
  35826. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
  35827. (track.width & 0xff00) >> 8, track.width & 0xff, // width
  35828. (track.height & 0xff00) >> 8, track.height & 0xff, // height
  35829. 0x00, 0x48, 0x00, 0x00, // horizresolution
  35830. 0x00, 0x48, 0x00, 0x00, // vertresolution
  35831. 0x00, 0x00, 0x00, 0x00, // reserved
  35832. 0x00, 0x01, // frame_count
  35833. 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
  35834. 0x00, 0x18, // depth = 24
  35835. 0x11, 0x11 // pre_defined = -1
  35836. ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
  35837. track.profileIdc, // AVCProfileIndication
  35838. track.profileCompatibility, // profile_compatibility
  35839. track.levelIdc, // AVCLevelIndication
  35840. 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
  35841. ].concat([sps.length // numOfSequenceParameterSets
  35842. ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
  35843. ]).concat(pictureParameterSets))), // "PPS"
  35844. box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  35845. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  35846. 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
  35847. );
  35848. };
  35849. audioSample = function audioSample(track) {
  35850. return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
  35851. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  35852. 0x00, 0x01, // data_reference_index
  35853. // AudioSampleEntry, ISO/IEC 14496-12
  35854. 0x00, 0x00, 0x00, 0x00, // reserved
  35855. 0x00, 0x00, 0x00, 0x00, // reserved
  35856. (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
  35857. (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
  35858. 0x00, 0x00, // pre_defined
  35859. 0x00, 0x00, // reserved
  35860. (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
  35861. // MP4AudioSampleEntry, ISO/IEC 14496-14
  35862. ]), esds(track));
  35863. };
  35864. })();
  35865. tkhd = function tkhd(track) {
  35866. var result = new Uint8Array([0x00, // version 0
  35867. 0x00, 0x00, 0x07, // flags
  35868. 0x00, 0x00, 0x00, 0x00, // creation_time
  35869. 0x00, 0x00, 0x00, 0x00, // modification_time
  35870. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  35871. 0x00, 0x00, 0x00, 0x00, // reserved
  35872. (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
  35873. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
  35874. 0x00, 0x00, // layer
  35875. 0x00, 0x00, // alternate_group
  35876. 0x01, 0x00, // non-audio track volume
  35877. 0x00, 0x00, // reserved
  35878. 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
  35879. (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
  35880. (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
  35881. ]);
  35882. return box(types.tkhd, result);
  35883. };
  35884. /**
  35885. * Generate a track fragment (traf) box. A traf box collects metadata
  35886. * about tracks in a movie fragment (moof) box.
  35887. */
  35888. traf = function traf(track) {
  35889. var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
  35890. trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
  35891. 0x00, 0x00, 0x3a, // flags
  35892. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  35893. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  35894. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  35895. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  35896. 0x00, 0x00, 0x00, 0x00 // default_sample_flags
  35897. ]));
  35898. upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
  35899. lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
  35900. trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
  35901. 0x00, 0x00, 0x00, // flags
  35902. // baseMediaDecodeTime
  35903. 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
  35904. // the containing moof to the first payload byte of the associated
  35905. // mdat
  35906. dataOffset = 32 + // tfhd
  35907. 20 + // tfdt
  35908. 8 + // traf header
  35909. 16 + // mfhd
  35910. 8 + // moof header
  35911. 8; // mdat header
  35912. // audio tracks require less metadata
  35913. if (track.type === 'audio') {
  35914. trackFragmentRun = trun(track, dataOffset);
  35915. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
  35916. } // video tracks should contain an independent and disposable samples
  35917. // box (sdtp)
  35918. // generate one and adjust offsets to match
  35919. sampleDependencyTable = sdtp(track);
  35920. trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
  35921. return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
  35922. };
  35923. /**
  35924. * Generate a track box.
  35925. * @param track {object} a track definition
  35926. * @return {Uint8Array} the track box
  35927. */
  35928. trak = function trak(track) {
  35929. track.duration = track.duration || 0xffffffff;
  35930. return box(types.trak, tkhd(track), mdia(track));
  35931. };
  35932. trex = function trex(track) {
  35933. var result = new Uint8Array([0x00, // version 0
  35934. 0x00, 0x00, 0x00, // flags
  35935. (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
  35936. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  35937. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  35938. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  35939. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  35940. ]); // the last two bytes of default_sample_flags is the sample
  35941. // degradation priority, a hint about the importance of this sample
  35942. // relative to others. Lower the degradation priority for all sample
  35943. // types other than video.
  35944. if (track.type !== 'video') {
  35945. result[result.length - 1] = 0x00;
  35946. }
  35947. return box(types.trex, result);
  35948. };
  35949. (function () {
  35950. var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
  35951. // duration is present for the first sample, it will be present for
  35952. // all subsequent samples.
  35953. // see ISO/IEC 14496-12:2012, Section 8.8.8.1
  35954. trunHeader = function trunHeader(samples, offset) {
  35955. var durationPresent = 0,
  35956. sizePresent = 0,
  35957. flagsPresent = 0,
  35958. compositionTimeOffset = 0; // trun flag constants
  35959. if (samples.length) {
  35960. if (samples[0].duration !== undefined) {
  35961. durationPresent = 0x1;
  35962. }
  35963. if (samples[0].size !== undefined) {
  35964. sizePresent = 0x2;
  35965. }
  35966. if (samples[0].flags !== undefined) {
  35967. flagsPresent = 0x4;
  35968. }
  35969. if (samples[0].compositionTimeOffset !== undefined) {
  35970. compositionTimeOffset = 0x8;
  35971. }
  35972. }
  35973. return [0x00, // version 0
  35974. 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
  35975. (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
  35976. (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
  35977. ];
  35978. };
  35979. videoTrun = function videoTrun(track, offset) {
  35980. var bytes, samples, sample, i;
  35981. samples = track.samples || [];
  35982. offset += 8 + 12 + 16 * samples.length;
  35983. bytes = trunHeader(samples, offset);
  35984. for (i = 0; i < samples.length; i++) {
  35985. sample = samples[i];
  35986. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  35987. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
  35988. 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
  35989. (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
  35990. ]);
  35991. }
  35992. return box(types.trun, new Uint8Array(bytes));
  35993. };
  35994. audioTrun = function audioTrun(track, offset) {
  35995. var bytes, samples, sample, i;
  35996. samples = track.samples || [];
  35997. offset += 8 + 12 + 8 * samples.length;
  35998. bytes = trunHeader(samples, offset);
  35999. for (i = 0; i < samples.length; i++) {
  36000. sample = samples[i];
  36001. bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
  36002. (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
  36003. }
  36004. return box(types.trun, new Uint8Array(bytes));
  36005. };
  36006. trun = function trun(track, offset) {
  36007. if (track.type === 'audio') {
  36008. return audioTrun(track, offset);
  36009. }
  36010. return videoTrun(track, offset);
  36011. };
  36012. })();
  36013. var mp4Generator = {
  36014. ftyp: ftyp,
  36015. mdat: mdat,
  36016. moof: moof,
  36017. moov: moov,
  36018. initSegment: function initSegment(tracks) {
  36019. var fileType = ftyp(),
  36020. movie = moov(tracks),
  36021. result;
  36022. result = new Uint8Array(fileType.byteLength + movie.byteLength);
  36023. result.set(fileType);
  36024. result.set(movie, fileType.byteLength);
  36025. return result;
  36026. }
  36027. };
  36028. var toUnsigned = function toUnsigned(value) {
  36029. return value >>> 0;
  36030. };
  36031. var bin = {
  36032. toUnsigned: toUnsigned
  36033. };
  36034. var toUnsigned$1 = bin.toUnsigned;
  36035. var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path
  36036. _findBox = function findBox(data, path) {
  36037. var results = [],
  36038. i,
  36039. size,
  36040. type,
  36041. end,
  36042. subresults;
  36043. if (!path.length) {
  36044. // short-circuit the search for empty paths
  36045. return null;
  36046. }
  36047. for (i = 0; i < data.byteLength;) {
  36048. size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
  36049. type = parseType(data.subarray(i + 4, i + 8));
  36050. end = size > 1 ? i + size : data.byteLength;
  36051. if (type === path[0]) {
  36052. if (path.length === 1) {
  36053. // this is the end of the path and we've found the box we were
  36054. // looking for
  36055. results.push(data.subarray(i + 8, end));
  36056. } else {
  36057. // recursively search for the next box along the path
  36058. subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
  36059. if (subresults.length) {
  36060. results = results.concat(subresults);
  36061. }
  36062. }
  36063. }
  36064. i = end;
  36065. } // we've finished searching all of data
  36066. return results;
  36067. };
  36068. /**
  36069. * Returns the string representation of an ASCII encoded four byte buffer.
  36070. * @param buffer {Uint8Array} a four-byte buffer to translate
  36071. * @return {string} the corresponding string
  36072. */
  36073. parseType = function parseType(buffer) {
  36074. var result = '';
  36075. result += String.fromCharCode(buffer[0]);
  36076. result += String.fromCharCode(buffer[1]);
  36077. result += String.fromCharCode(buffer[2]);
  36078. result += String.fromCharCode(buffer[3]);
  36079. return result;
  36080. };
  36081. /**
  36082. * Parses an MP4 initialization segment and extracts the timescale
  36083. * values for any declared tracks. Timescale values indicate the
  36084. * number of clock ticks per second to assume for time-based values
  36085. * elsewhere in the MP4.
  36086. *
  36087. * To determine the start time of an MP4, you need two pieces of
  36088. * information: the timescale unit and the earliest base media decode
  36089. * time. Multiple timescales can be specified within an MP4 but the
  36090. * base media decode time is always expressed in the timescale from
  36091. * the media header box for the track:
  36092. * ```
  36093. * moov > trak > mdia > mdhd.timescale
  36094. * ```
  36095. * @param init {Uint8Array} the bytes of the init segment
  36096. * @return {object} a hash of track ids to timescale values or null if
  36097. * the init segment is malformed.
  36098. */
  36099. timescale = function timescale(init) {
  36100. var result = {},
  36101. traks = _findBox(init, ['moov', 'trak']); // mdhd timescale
  36102. return traks.reduce(function (result, trak) {
  36103. var tkhd, version, index, id, mdhd;
  36104. tkhd = _findBox(trak, ['tkhd'])[0];
  36105. if (!tkhd) {
  36106. return null;
  36107. }
  36108. version = tkhd[0];
  36109. index = version === 0 ? 12 : 20;
  36110. id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
  36111. mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
  36112. if (!mdhd) {
  36113. return null;
  36114. }
  36115. version = mdhd[0];
  36116. index = version === 0 ? 12 : 20;
  36117. result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
  36118. return result;
  36119. }, result);
  36120. };
  36121. /**
  36122. * Determine the base media decode start time, in seconds, for an MP4
  36123. * fragment. If multiple fragments are specified, the earliest time is
  36124. * returned.
  36125. *
  36126. * The base media decode time can be parsed from track fragment
  36127. * metadata:
  36128. * ```
  36129. * moof > traf > tfdt.baseMediaDecodeTime
  36130. * ```
  36131. * It requires the timescale value from the mdhd to interpret.
  36132. *
  36133. * @param timescale {object} a hash of track ids to timescale values.
  36134. * @return {number} the earliest base media decode start time for the
  36135. * fragment, in seconds
  36136. */
  36137. startTime = function startTime(timescale, fragment) {
  36138. var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
  36139. trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track
  36140. baseTimes = [].concat.apply([], trafs.map(function (traf) {
  36141. return _findBox(traf, ['tfhd']).map(function (tfhd) {
  36142. var id, scale, baseTime; // get the track id from the tfhd
  36143. id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
  36144. scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
  36145. baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
  36146. var version, result;
  36147. version = tfdt[0];
  36148. result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
  36149. if (version === 1) {
  36150. result *= Math.pow(2, 32);
  36151. result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
  36152. }
  36153. return result;
  36154. })[0];
  36155. baseTime = baseTime || Infinity; // convert base time to seconds
  36156. return baseTime / scale;
  36157. });
  36158. })); // return the minimum
  36159. result = Math.min.apply(null, baseTimes);
  36160. return isFinite(result) ? result : 0;
  36161. };
  36162. /**
  36163. * Find the trackIds of the video tracks in this source.
  36164. * Found by parsing the Handler Reference and Track Header Boxes:
  36165. * moov > trak > mdia > hdlr
  36166. * moov > trak > tkhd
  36167. *
  36168. * @param {Uint8Array} init - The bytes of the init segment for this source
  36169. * @return {Number[]} A list of trackIds
  36170. *
  36171. * @see ISO-BMFF-12/2015, Section 8.4.3
  36172. **/
  36173. getVideoTrackIds = function getVideoTrackIds(init) {
  36174. var traks = _findBox(init, ['moov', 'trak']);
  36175. var videoTrackIds = [];
  36176. traks.forEach(function (trak) {
  36177. var hdlrs = _findBox(trak, ['mdia', 'hdlr']);
  36178. var tkhds = _findBox(trak, ['tkhd']);
  36179. hdlrs.forEach(function (hdlr, index) {
  36180. var handlerType = parseType(hdlr.subarray(8, 12));
  36181. var tkhd = tkhds[index];
  36182. var view;
  36183. var version;
  36184. var trackId;
  36185. if (handlerType === 'vide') {
  36186. view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
  36187. version = view.getUint8(0);
  36188. trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
  36189. videoTrackIds.push(trackId);
  36190. }
  36191. });
  36192. });
  36193. return videoTrackIds;
  36194. };
  36195. var probe$$1 = {
  36196. findBox: _findBox,
  36197. parseType: parseType,
  36198. timescale: timescale,
  36199. startTime: startTime,
  36200. videoTrackIds: getVideoTrackIds
  36201. };
  36202. /**
  36203. * mux.js
  36204. *
  36205. * Copyright (c) 2014 Brightcove
  36206. * All rights reserved.
  36207. *
  36208. * A lightweight readable stream implemention that handles event dispatching.
  36209. * Objects that inherit from streams should call init in their constructors.
  36210. */
  36211. var Stream = function Stream() {
  36212. this.init = function () {
  36213. var listeners = {};
  36214. /**
  36215. * Add a listener for a specified event type.
  36216. * @param type {string} the event name
  36217. * @param listener {function} the callback to be invoked when an event of
  36218. * the specified type occurs
  36219. */
  36220. this.on = function (type, listener) {
  36221. if (!listeners[type]) {
  36222. listeners[type] = [];
  36223. }
  36224. listeners[type] = listeners[type].concat(listener);
  36225. };
  36226. /**
  36227. * Remove a listener for a specified event type.
  36228. * @param type {string} the event name
  36229. * @param listener {function} a function previously registered for this
  36230. * type of event through `on`
  36231. */
  36232. this.off = function (type, listener) {
  36233. var index;
  36234. if (!listeners[type]) {
  36235. return false;
  36236. }
  36237. index = listeners[type].indexOf(listener);
  36238. listeners[type] = listeners[type].slice();
  36239. listeners[type].splice(index, 1);
  36240. return index > -1;
  36241. };
  36242. /**
  36243. * Trigger an event of the specified type on this stream. Any additional
  36244. * arguments to this function are passed as parameters to event listeners.
  36245. * @param type {string} the event name
  36246. */
  36247. this.trigger = function (type) {
  36248. var callbacks, i, length, args;
  36249. callbacks = listeners[type];
  36250. if (!callbacks) {
  36251. return;
  36252. } // Slicing the arguments on every invocation of this method
  36253. // can add a significant amount of overhead. Avoid the
  36254. // intermediate object creation for the common case of a
  36255. // single callback argument
  36256. if (arguments.length === 2) {
  36257. length = callbacks.length;
  36258. for (i = 0; i < length; ++i) {
  36259. callbacks[i].call(this, arguments[1]);
  36260. }
  36261. } else {
  36262. args = [];
  36263. i = arguments.length;
  36264. for (i = 1; i < arguments.length; ++i) {
  36265. args.push(arguments[i]);
  36266. }
  36267. length = callbacks.length;
  36268. for (i = 0; i < length; ++i) {
  36269. callbacks[i].apply(this, args);
  36270. }
  36271. }
  36272. };
  36273. /**
  36274. * Destroys the stream and cleans up.
  36275. */
  36276. this.dispose = function () {
  36277. listeners = {};
  36278. };
  36279. };
  36280. };
  36281. /**
  36282. * Forwards all `data` events on this stream to the destination stream. The
  36283. * destination stream should provide a method `push` to receive the data
  36284. * events as they arrive.
  36285. * @param destination {stream} the stream that will receive all `data` events
  36286. * @param autoFlush {boolean} if false, we will not call `flush` on the destination
  36287. * when the current stream emits a 'done' event
  36288. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  36289. */
  36290. Stream.prototype.pipe = function (destination) {
  36291. this.on('data', function (data) {
  36292. destination.push(data);
  36293. });
  36294. this.on('done', function (flushSource) {
  36295. destination.flush(flushSource);
  36296. });
  36297. return destination;
  36298. }; // Default stream functions that are expected to be overridden to perform
  36299. // actual work. These are provided by the prototype as a sort of no-op
  36300. // implementation so that we don't have to check for their existence in the
  36301. // `pipe` function above.
  36302. Stream.prototype.push = function (data) {
  36303. this.trigger('data', data);
  36304. };
  36305. Stream.prototype.flush = function (flushSource) {
  36306. this.trigger('done', flushSource);
  36307. };
  36308. var stream = Stream; // Convert an array of nal units into an array of frames with each frame being
  36309. // composed of the nal units that make up that frame
  36310. // Also keep track of cummulative data about the frame from the nal units such
  36311. // as the frame duration, starting pts, etc.
  36312. var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
  36313. var i,
  36314. currentNal,
  36315. currentFrame = [],
  36316. frames = [];
  36317. currentFrame.byteLength = 0;
  36318. for (i = 0; i < nalUnits.length; i++) {
  36319. currentNal = nalUnits[i]; // Split on 'aud'-type nal units
  36320. if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  36321. // Since the very first nal unit is expected to be an AUD
  36322. // only push to the frames array when currentFrame is not empty
  36323. if (currentFrame.length) {
  36324. currentFrame.duration = currentNal.dts - currentFrame.dts;
  36325. frames.push(currentFrame);
  36326. }
  36327. currentFrame = [currentNal];
  36328. currentFrame.byteLength = currentNal.data.byteLength;
  36329. currentFrame.pts = currentNal.pts;
  36330. currentFrame.dts = currentNal.dts;
  36331. } else {
  36332. // Specifically flag key frames for ease of use later
  36333. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  36334. currentFrame.keyFrame = true;
  36335. }
  36336. currentFrame.duration = currentNal.dts - currentFrame.dts;
  36337. currentFrame.byteLength += currentNal.data.byteLength;
  36338. currentFrame.push(currentNal);
  36339. }
  36340. } // For the last frame, use the duration of the previous frame if we
  36341. // have nothing better to go on
  36342. if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
  36343. currentFrame.duration = frames[frames.length - 1].duration;
  36344. } // Push the final frame
  36345. frames.push(currentFrame);
  36346. return frames;
  36347. }; // Convert an array of frames into an array of Gop with each Gop being composed
  36348. // of the frames that make up that Gop
  36349. // Also keep track of cummulative data about the Gop from the frames such as the
  36350. // Gop duration, starting pts, etc.
  36351. var groupFramesIntoGops = function groupFramesIntoGops(frames) {
  36352. var i,
  36353. currentFrame,
  36354. currentGop = [],
  36355. gops = []; // We must pre-set some of the values on the Gop since we
  36356. // keep running totals of these values
  36357. currentGop.byteLength = 0;
  36358. currentGop.nalCount = 0;
  36359. currentGop.duration = 0;
  36360. currentGop.pts = frames[0].pts;
  36361. currentGop.dts = frames[0].dts; // store some metadata about all the Gops
  36362. gops.byteLength = 0;
  36363. gops.nalCount = 0;
  36364. gops.duration = 0;
  36365. gops.pts = frames[0].pts;
  36366. gops.dts = frames[0].dts;
  36367. for (i = 0; i < frames.length; i++) {
  36368. currentFrame = frames[i];
  36369. if (currentFrame.keyFrame) {
  36370. // Since the very first frame is expected to be an keyframe
  36371. // only push to the gops array when currentGop is not empty
  36372. if (currentGop.length) {
  36373. gops.push(currentGop);
  36374. gops.byteLength += currentGop.byteLength;
  36375. gops.nalCount += currentGop.nalCount;
  36376. gops.duration += currentGop.duration;
  36377. }
  36378. currentGop = [currentFrame];
  36379. currentGop.nalCount = currentFrame.length;
  36380. currentGop.byteLength = currentFrame.byteLength;
  36381. currentGop.pts = currentFrame.pts;
  36382. currentGop.dts = currentFrame.dts;
  36383. currentGop.duration = currentFrame.duration;
  36384. } else {
  36385. currentGop.duration += currentFrame.duration;
  36386. currentGop.nalCount += currentFrame.length;
  36387. currentGop.byteLength += currentFrame.byteLength;
  36388. currentGop.push(currentFrame);
  36389. }
  36390. }
  36391. if (gops.length && currentGop.duration <= 0) {
  36392. currentGop.duration = gops[gops.length - 1].duration;
  36393. }
  36394. gops.byteLength += currentGop.byteLength;
  36395. gops.nalCount += currentGop.nalCount;
  36396. gops.duration += currentGop.duration; // push the final Gop
  36397. gops.push(currentGop);
  36398. return gops;
  36399. };
  36400. /*
  36401. * Search for the first keyframe in the GOPs and throw away all frames
  36402. * until that keyframe. Then extend the duration of the pulled keyframe
  36403. * and pull the PTS and DTS of the keyframe so that it covers the time
  36404. * range of the frames that were disposed.
  36405. *
  36406. * @param {Array} gops video GOPs
  36407. * @returns {Array} modified video GOPs
  36408. */
  36409. var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
  36410. var currentGop;
  36411. if (!gops[0][0].keyFrame && gops.length > 1) {
  36412. // Remove the first GOP
  36413. currentGop = gops.shift();
  36414. gops.byteLength -= currentGop.byteLength;
  36415. gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
  36416. // first gop to cover the time period of the
  36417. // frames we just removed
  36418. gops[0][0].dts = currentGop.dts;
  36419. gops[0][0].pts = currentGop.pts;
  36420. gops[0][0].duration += currentGop.duration;
  36421. }
  36422. return gops;
  36423. };
  36424. /**
  36425. * Default sample object
  36426. * see ISO/IEC 14496-12:2012, section 8.6.4.3
  36427. */
  36428. var createDefaultSample = function createDefaultSample() {
  36429. return {
  36430. size: 0,
  36431. flags: {
  36432. isLeading: 0,
  36433. dependsOn: 1,
  36434. isDependedOn: 0,
  36435. hasRedundancy: 0,
  36436. degradationPriority: 0,
  36437. isNonSyncSample: 1
  36438. }
  36439. };
  36440. };
  36441. /*
  36442. * Collates information from a video frame into an object for eventual
  36443. * entry into an MP4 sample table.
  36444. *
  36445. * @param {Object} frame the video frame
  36446. * @param {Number} dataOffset the byte offset to position the sample
  36447. * @return {Object} object containing sample table info for a frame
  36448. */
  36449. var sampleForFrame = function sampleForFrame(frame, dataOffset) {
  36450. var sample = createDefaultSample();
  36451. sample.dataOffset = dataOffset;
  36452. sample.compositionTimeOffset = frame.pts - frame.dts;
  36453. sample.duration = frame.duration;
  36454. sample.size = 4 * frame.length; // Space for nal unit size
  36455. sample.size += frame.byteLength;
  36456. if (frame.keyFrame) {
  36457. sample.flags.dependsOn = 2;
  36458. sample.flags.isNonSyncSample = 0;
  36459. }
  36460. return sample;
  36461. }; // generate the track's sample table from an array of gops
  36462. var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
  36463. var h,
  36464. i,
  36465. sample,
  36466. currentGop,
  36467. currentFrame,
  36468. dataOffset = baseDataOffset || 0,
  36469. samples = [];
  36470. for (h = 0; h < gops.length; h++) {
  36471. currentGop = gops[h];
  36472. for (i = 0; i < currentGop.length; i++) {
  36473. currentFrame = currentGop[i];
  36474. sample = sampleForFrame(currentFrame, dataOffset);
  36475. dataOffset += sample.size;
  36476. samples.push(sample);
  36477. }
  36478. }
  36479. return samples;
  36480. }; // generate the track's raw mdat data from an array of gops
  36481. var concatenateNalData = function concatenateNalData(gops) {
  36482. var h,
  36483. i,
  36484. j,
  36485. currentGop,
  36486. currentFrame,
  36487. currentNal,
  36488. dataOffset = 0,
  36489. nalsByteLength = gops.byteLength,
  36490. numberOfNals = gops.nalCount,
  36491. totalByteLength = nalsByteLength + 4 * numberOfNals,
  36492. data = new Uint8Array(totalByteLength),
  36493. view = new DataView(data.buffer); // For each Gop..
  36494. for (h = 0; h < gops.length; h++) {
  36495. currentGop = gops[h]; // For each Frame..
  36496. for (i = 0; i < currentGop.length; i++) {
  36497. currentFrame = currentGop[i]; // For each NAL..
  36498. for (j = 0; j < currentFrame.length; j++) {
  36499. currentNal = currentFrame[j];
  36500. view.setUint32(dataOffset, currentNal.data.byteLength);
  36501. dataOffset += 4;
  36502. data.set(currentNal.data, dataOffset);
  36503. dataOffset += currentNal.data.byteLength;
  36504. }
  36505. }
  36506. }
  36507. return data;
  36508. };
  36509. var frameUtils = {
  36510. groupNalsIntoFrames: groupNalsIntoFrames,
  36511. groupFramesIntoGops: groupFramesIntoGops,
  36512. extendFirstKeyFrame: extendFirstKeyFrame,
  36513. generateSampleTable: generateSampleTable,
  36514. concatenateNalData: concatenateNalData
  36515. };
  36516. var highPrefix = [33, 16, 5, 32, 164, 27];
  36517. var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
  36518. var zeroFill = function zeroFill(count) {
  36519. var a = [];
  36520. while (count--) {
  36521. a.push(0);
  36522. }
  36523. return a;
  36524. };
  36525. var makeTable = function makeTable(metaTable) {
  36526. return Object.keys(metaTable).reduce(function (obj, key) {
  36527. obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
  36528. return arr.concat(part);
  36529. }, []));
  36530. return obj;
  36531. }, {});
  36532. }; // Frames-of-silence to use for filling in missing AAC frames
  36533. var coneOfSilence = {
  36534. 96000: [highPrefix, [227, 64], zeroFill(154), [56]],
  36535. 88200: [highPrefix, [231], zeroFill(170), [56]],
  36536. 64000: [highPrefix, [248, 192], zeroFill(240), [56]],
  36537. 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
  36538. 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
  36539. 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
  36540. 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
  36541. 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
  36542. 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]],
  36543. 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]],
  36544. 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
  36545. };
  36546. var silence = makeTable(coneOfSilence);
  36547. var ONE_SECOND_IN_TS = 90000,
  36548. // 90kHz clock
  36549. secondsToVideoTs,
  36550. secondsToAudioTs,
  36551. videoTsToSeconds,
  36552. audioTsToSeconds,
  36553. audioTsToVideoTs,
  36554. videoTsToAudioTs;
  36555. secondsToVideoTs = function secondsToVideoTs(seconds) {
  36556. return seconds * ONE_SECOND_IN_TS;
  36557. };
  36558. secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
  36559. return seconds * sampleRate;
  36560. };
  36561. videoTsToSeconds = function videoTsToSeconds(timestamp) {
  36562. return timestamp / ONE_SECOND_IN_TS;
  36563. };
  36564. audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
  36565. return timestamp / sampleRate;
  36566. };
  36567. audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
  36568. return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
  36569. };
  36570. videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
  36571. return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
  36572. };
  36573. var clock = {
  36574. secondsToVideoTs: secondsToVideoTs,
  36575. secondsToAudioTs: secondsToAudioTs,
  36576. videoTsToSeconds: videoTsToSeconds,
  36577. audioTsToSeconds: audioTsToSeconds,
  36578. audioTsToVideoTs: audioTsToVideoTs,
  36579. videoTsToAudioTs: videoTsToAudioTs
  36580. };
  36581. var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
  36582. /**
  36583. * Sum the `byteLength` properties of the data in each AAC frame
  36584. */
  36585. var sumFrameByteLengths = function sumFrameByteLengths(array) {
  36586. var i,
  36587. currentObj,
  36588. sum = 0; // sum the byteLength's all each nal unit in the frame
  36589. for (i = 0; i < array.length; i++) {
  36590. currentObj = array[i];
  36591. sum += currentObj.data.byteLength;
  36592. }
  36593. return sum;
  36594. }; // Possibly pad (prefix) the audio track with silence if appending this track
  36595. // would lead to the introduction of a gap in the audio buffer
  36596. var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
  36597. var baseMediaDecodeTimeTs,
  36598. frameDuration = 0,
  36599. audioGapDuration = 0,
  36600. audioFillFrameCount = 0,
  36601. audioFillDuration = 0,
  36602. silentFrame,
  36603. i;
  36604. if (!frames.length) {
  36605. return;
  36606. }
  36607. baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
  36608. frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
  36609. if (audioAppendStartTs && videoBaseMediaDecodeTime) {
  36610. // insert the shortest possible amount (audio gap or audio to video gap)
  36611. audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
  36612. audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
  36613. audioFillDuration = audioFillFrameCount * frameDuration;
  36614. } // don't attempt to fill gaps smaller than a single frame or larger
  36615. // than a half second
  36616. if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
  36617. return;
  36618. }
  36619. silentFrame = silence[track.samplerate];
  36620. if (!silentFrame) {
  36621. // we don't have a silent frame pregenerated for the sample rate, so use a frame
  36622. // from the content instead
  36623. silentFrame = frames[0].data;
  36624. }
  36625. for (i = 0; i < audioFillFrameCount; i++) {
  36626. frames.splice(i, 0, {
  36627. data: silentFrame
  36628. });
  36629. }
  36630. track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
  36631. }; // If the audio segment extends before the earliest allowed dts
  36632. // value, remove AAC frames until starts at or after the earliest
  36633. // allowed DTS so that we don't end up with a negative baseMedia-
  36634. // DecodeTime for the audio track
  36635. var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
  36636. if (track.minSegmentDts >= earliestAllowedDts) {
  36637. return adtsFrames;
  36638. } // We will need to recalculate the earliest segment Dts
  36639. track.minSegmentDts = Infinity;
  36640. return adtsFrames.filter(function (currentFrame) {
  36641. // If this is an allowed frame, keep it and record it's Dts
  36642. if (currentFrame.dts >= earliestAllowedDts) {
  36643. track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
  36644. track.minSegmentPts = track.minSegmentDts;
  36645. return true;
  36646. } // Otherwise, discard it
  36647. return false;
  36648. });
  36649. }; // generate the track's raw mdat data from an array of frames
  36650. var generateSampleTable$1 = function generateSampleTable(frames) {
  36651. var i,
  36652. currentFrame,
  36653. samples = [];
  36654. for (i = 0; i < frames.length; i++) {
  36655. currentFrame = frames[i];
  36656. samples.push({
  36657. size: currentFrame.data.byteLength,
  36658. duration: 1024 // For AAC audio, all samples contain 1024 samples
  36659. });
  36660. }
  36661. return samples;
  36662. }; // generate the track's sample table from an array of frames
  36663. var concatenateFrameData = function concatenateFrameData(frames) {
  36664. var i,
  36665. currentFrame,
  36666. dataOffset = 0,
  36667. data = new Uint8Array(sumFrameByteLengths(frames));
  36668. for (i = 0; i < frames.length; i++) {
  36669. currentFrame = frames[i];
  36670. data.set(currentFrame.data, dataOffset);
  36671. dataOffset += currentFrame.data.byteLength;
  36672. }
  36673. return data;
  36674. };
  36675. var audioFrameUtils = {
  36676. prefixWithSilence: prefixWithSilence,
  36677. trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
  36678. generateSampleTable: generateSampleTable$1,
  36679. concatenateFrameData: concatenateFrameData
  36680. };
  36681. var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock
  36682. /**
  36683. * Store information about the start and end of the track and the
  36684. * duration for each frame/sample we process in order to calculate
  36685. * the baseMediaDecodeTime
  36686. */
  36687. var collectDtsInfo = function collectDtsInfo(track, data) {
  36688. if (typeof data.pts === 'number') {
  36689. if (track.timelineStartInfo.pts === undefined) {
  36690. track.timelineStartInfo.pts = data.pts;
  36691. }
  36692. if (track.minSegmentPts === undefined) {
  36693. track.minSegmentPts = data.pts;
  36694. } else {
  36695. track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
  36696. }
  36697. if (track.maxSegmentPts === undefined) {
  36698. track.maxSegmentPts = data.pts;
  36699. } else {
  36700. track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
  36701. }
  36702. }
  36703. if (typeof data.dts === 'number') {
  36704. if (track.timelineStartInfo.dts === undefined) {
  36705. track.timelineStartInfo.dts = data.dts;
  36706. }
  36707. if (track.minSegmentDts === undefined) {
  36708. track.minSegmentDts = data.dts;
  36709. } else {
  36710. track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
  36711. }
  36712. if (track.maxSegmentDts === undefined) {
  36713. track.maxSegmentDts = data.dts;
  36714. } else {
  36715. track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
  36716. }
  36717. }
  36718. };
  36719. /**
  36720. * Clear values used to calculate the baseMediaDecodeTime between
  36721. * tracks
  36722. */
  36723. var clearDtsInfo = function clearDtsInfo(track) {
  36724. delete track.minSegmentDts;
  36725. delete track.maxSegmentDts;
  36726. delete track.minSegmentPts;
  36727. delete track.maxSegmentPts;
  36728. };
  36729. /**
  36730. * Calculate the track's baseMediaDecodeTime based on the earliest
  36731. * DTS the transmuxer has ever seen and the minimum DTS for the
  36732. * current track
  36733. * @param track {object} track metadata configuration
  36734. * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
  36735. * in the source; false to adjust the first segment to start at 0.
  36736. */
  36737. var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
  36738. var baseMediaDecodeTime,
  36739. scale,
  36740. minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
  36741. if (!keepOriginalTimestamps) {
  36742. minSegmentDts -= track.timelineStartInfo.dts;
  36743. } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
  36744. // we want the start of the first segment to be placed
  36745. baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
  36746. baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
  36747. baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
  36748. if (track.type === 'audio') {
  36749. // Audio has a different clock equal to the sampling_rate so we need to
  36750. // scale the PTS values into the clock rate of the track
  36751. scale = track.samplerate / ONE_SECOND_IN_TS$2;
  36752. baseMediaDecodeTime *= scale;
  36753. baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
  36754. }
  36755. return baseMediaDecodeTime;
  36756. };
  36757. var trackDecodeInfo = {
  36758. clearDtsInfo: clearDtsInfo,
  36759. calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
  36760. collectDtsInfo: collectDtsInfo
  36761. };
  36762. /**
  36763. * mux.js
  36764. *
  36765. * Copyright (c) 2015 Brightcove
  36766. * All rights reserved.
  36767. *
  36768. * Reads in-band caption information from a video elementary
  36769. * stream. Captions must follow the CEA-708 standard for injection
  36770. * into an MPEG-2 transport streams.
  36771. * @see https://en.wikipedia.org/wiki/CEA-708
  36772. * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
  36773. */
  36774. // Supplemental enhancement information (SEI) NAL units have a
  36775. // payload type field to indicate how they are to be
  36776. // interpreted. CEAS-708 caption content is always transmitted with
  36777. // payload type 0x04.
  36778. var USER_DATA_REGISTERED_ITU_T_T35 = 4,
  36779. RBSP_TRAILING_BITS = 128;
  36780. /**
  36781. * Parse a supplemental enhancement information (SEI) NAL unit.
  36782. * Stops parsing once a message of type ITU T T35 has been found.
  36783. *
  36784. * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  36785. * @return {object} the parsed SEI payload
  36786. * @see Rec. ITU-T H.264, 7.3.2.3.1
  36787. */
  36788. var parseSei = function parseSei(bytes) {
  36789. var i = 0,
  36790. result = {
  36791. payloadType: -1,
  36792. payloadSize: 0
  36793. },
  36794. payloadType = 0,
  36795. payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
  36796. while (i < bytes.byteLength) {
  36797. // stop once we have hit the end of the sei_rbsp
  36798. if (bytes[i] === RBSP_TRAILING_BITS) {
  36799. break;
  36800. } // Parse payload type
  36801. while (bytes[i] === 0xFF) {
  36802. payloadType += 255;
  36803. i++;
  36804. }
  36805. payloadType += bytes[i++]; // Parse payload size
  36806. while (bytes[i] === 0xFF) {
  36807. payloadSize += 255;
  36808. i++;
  36809. }
  36810. payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
  36811. // there can only ever be one caption message in a frame's sei
  36812. if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
  36813. result.payloadType = payloadType;
  36814. result.payloadSize = payloadSize;
  36815. result.payload = bytes.subarray(i, i + payloadSize);
  36816. break;
  36817. } // skip the payload and parse the next message
  36818. i += payloadSize;
  36819. payloadType = 0;
  36820. payloadSize = 0;
  36821. }
  36822. return result;
  36823. }; // see ANSI/SCTE 128-1 (2013), section 8.1
  36824. var parseUserData = function parseUserData(sei) {
  36825. // itu_t_t35_contry_code must be 181 (United States) for
  36826. // captions
  36827. if (sei.payload[0] !== 181) {
  36828. return null;
  36829. } // itu_t_t35_provider_code should be 49 (ATSC) for captions
  36830. if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
  36831. return null;
  36832. } // the user_identifier should be "GA94" to indicate ATSC1 data
  36833. if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
  36834. return null;
  36835. } // finally, user_data_type_code should be 0x03 for caption data
  36836. if (sei.payload[7] !== 0x03) {
  36837. return null;
  36838. } // return the user_data_type_structure and strip the trailing
  36839. // marker bits
  36840. return sei.payload.subarray(8, sei.payload.length - 1);
  36841. }; // see CEA-708-D, section 4.4
  36842. var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  36843. var results = [],
  36844. i,
  36845. count,
  36846. offset,
  36847. data; // if this is just filler, return immediately
  36848. if (!(userData[0] & 0x40)) {
  36849. return results;
  36850. } // parse out the cc_data_1 and cc_data_2 fields
  36851. count = userData[0] & 0x1f;
  36852. for (i = 0; i < count; i++) {
  36853. offset = i * 3;
  36854. data = {
  36855. type: userData[offset + 2] & 0x03,
  36856. pts: pts
  36857. }; // capture cc data when cc_valid is 1
  36858. if (userData[offset + 2] & 0x04) {
  36859. data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
  36860. results.push(data);
  36861. }
  36862. }
  36863. return results;
  36864. };
  36865. var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  36866. var length = data.byteLength,
  36867. emulationPreventionBytesPositions = [],
  36868. i = 1,
  36869. newLength,
  36870. newData; // Find all `Emulation Prevention Bytes`
  36871. while (i < length - 2) {
  36872. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  36873. emulationPreventionBytesPositions.push(i + 2);
  36874. i += 2;
  36875. } else {
  36876. i++;
  36877. }
  36878. } // If no Emulation Prevention Bytes were found just return the original
  36879. // array
  36880. if (emulationPreventionBytesPositions.length === 0) {
  36881. return data;
  36882. } // Create a new array to hold the NAL unit data
  36883. newLength = length - emulationPreventionBytesPositions.length;
  36884. newData = new Uint8Array(newLength);
  36885. var sourceIndex = 0;
  36886. for (i = 0; i < newLength; sourceIndex++, i++) {
  36887. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  36888. // Skip this byte
  36889. sourceIndex++; // Remove this position index
  36890. emulationPreventionBytesPositions.shift();
  36891. }
  36892. newData[i] = data[sourceIndex];
  36893. }
  36894. return newData;
  36895. }; // exports
  36896. var captionPacketParser = {
  36897. parseSei: parseSei,
  36898. parseUserData: parseUserData,
  36899. parseCaptionPackets: parseCaptionPackets,
  36900. discardEmulationPreventionBytes: discardEmulationPreventionBytes,
  36901. USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
  36902. }; // -----------------
  36903. // Link To Transport
  36904. // -----------------
  36905. var CaptionStream = function CaptionStream() {
  36906. CaptionStream.prototype.init.call(this);
  36907. this.captionPackets_ = [];
  36908. this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
  36909. new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
  36910. new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
  36911. new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
  36912. ];
  36913. this.reset(); // forward data and done events from CCs to this CaptionStream
  36914. this.ccStreams_.forEach(function (cc) {
  36915. cc.on('data', this.trigger.bind(this, 'data'));
  36916. cc.on('done', this.trigger.bind(this, 'done'));
  36917. }, this);
  36918. };
  36919. CaptionStream.prototype = new stream();
  36920. CaptionStream.prototype.push = function (event) {
  36921. var sei, userData, newCaptionPackets; // only examine SEI NALs
  36922. if (event.nalUnitType !== 'sei_rbsp') {
  36923. return;
  36924. } // parse the sei
  36925. sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
  36926. if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
  36927. return;
  36928. } // parse out the user data payload
  36929. userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
  36930. if (!userData) {
  36931. return;
  36932. } // Sometimes, the same segment # will be downloaded twice. To stop the
  36933. // caption data from being processed twice, we track the latest dts we've
  36934. // received and ignore everything with a dts before that. However, since
  36935. // data for a specific dts can be split across packets on either side of
  36936. // a segment boundary, we need to make sure we *don't* ignore the packets
  36937. // from the *next* segment that have dts === this.latestDts_. By constantly
  36938. // tracking the number of packets received with dts === this.latestDts_, we
  36939. // know how many should be ignored once we start receiving duplicates.
  36940. if (event.dts < this.latestDts_) {
  36941. // We've started getting older data, so set the flag.
  36942. this.ignoreNextEqualDts_ = true;
  36943. return;
  36944. } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
  36945. this.numSameDts_--;
  36946. if (!this.numSameDts_) {
  36947. // We've received the last duplicate packet, time to start processing again
  36948. this.ignoreNextEqualDts_ = false;
  36949. }
  36950. return;
  36951. } // parse out CC data packets and save them for later
  36952. newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
  36953. this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
  36954. if (this.latestDts_ !== event.dts) {
  36955. this.numSameDts_ = 0;
  36956. }
  36957. this.numSameDts_++;
  36958. this.latestDts_ = event.dts;
  36959. };
  36960. CaptionStream.prototype.flush = function () {
  36961. // make sure we actually parsed captions before proceeding
  36962. if (!this.captionPackets_.length) {
  36963. this.ccStreams_.forEach(function (cc) {
  36964. cc.flush();
  36965. }, this);
  36966. return;
  36967. } // In Chrome, the Array#sort function is not stable so add a
  36968. // presortIndex that we can use to ensure we get a stable-sort
  36969. this.captionPackets_.forEach(function (elem, idx) {
  36970. elem.presortIndex = idx;
  36971. }); // sort caption byte-pairs based on their PTS values
  36972. this.captionPackets_.sort(function (a, b) {
  36973. if (a.pts === b.pts) {
  36974. return a.presortIndex - b.presortIndex;
  36975. }
  36976. return a.pts - b.pts;
  36977. });
  36978. this.captionPackets_.forEach(function (packet) {
  36979. if (packet.type < 2) {
  36980. // Dispatch packet to the right Cea608Stream
  36981. this.dispatchCea608Packet(packet);
  36982. } // this is where an 'else' would go for a dispatching packets
  36983. // to a theoretical Cea708Stream that handles SERVICEn data
  36984. }, this);
  36985. this.captionPackets_.length = 0;
  36986. this.ccStreams_.forEach(function (cc) {
  36987. cc.flush();
  36988. }, this);
  36989. return;
  36990. };
  36991. CaptionStream.prototype.reset = function () {
  36992. this.latestDts_ = null;
  36993. this.ignoreNextEqualDts_ = false;
  36994. this.numSameDts_ = 0;
  36995. this.activeCea608Channel_ = [null, null];
  36996. this.ccStreams_.forEach(function (ccStream) {
  36997. ccStream.reset();
  36998. });
  36999. };
  37000. CaptionStream.prototype.dispatchCea608Packet = function (packet) {
  37001. // NOTE: packet.type is the CEA608 field
  37002. if (this.setsChannel1Active(packet)) {
  37003. this.activeCea608Channel_[packet.type] = 0;
  37004. } else if (this.setsChannel2Active(packet)) {
  37005. this.activeCea608Channel_[packet.type] = 1;
  37006. }
  37007. if (this.activeCea608Channel_[packet.type] === null) {
  37008. // If we haven't received anything to set the active channel, discard the
  37009. // data; we don't want jumbled captions
  37010. return;
  37011. }
  37012. this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
  37013. };
  37014. CaptionStream.prototype.setsChannel1Active = function (packet) {
  37015. return (packet.ccData & 0x7800) === 0x1000;
  37016. };
  37017. CaptionStream.prototype.setsChannel2Active = function (packet) {
  37018. return (packet.ccData & 0x7800) === 0x1800;
  37019. }; // ----------------------
  37020. // Session to Application
  37021. // ----------------------
  37022. // This hash maps non-ASCII, special, and extended character codes to their
  37023. // proper Unicode equivalent. The first keys that are only a single byte
  37024. // are the non-standard ASCII characters, which simply map the CEA608 byte
  37025. // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
  37026. // character codes, but have their MSB bitmasked with 0x03 so that a lookup
  37027. // can be performed regardless of the field and data channel on which the
  37028. // character code was received.
  37029. var CHARACTER_TRANSLATION = {
  37030. 0x2a: 0xe1,
  37031. // á
  37032. 0x5c: 0xe9,
  37033. // é
  37034. 0x5e: 0xed,
  37035. // í
  37036. 0x5f: 0xf3,
  37037. // ó
  37038. 0x60: 0xfa,
  37039. // ú
  37040. 0x7b: 0xe7,
  37041. // ç
  37042. 0x7c: 0xf7,
  37043. // ÷
  37044. 0x7d: 0xd1,
  37045. // Ñ
  37046. 0x7e: 0xf1,
  37047. // ñ
  37048. 0x7f: 0x2588,
  37049. // █
  37050. 0x0130: 0xae,
  37051. // ®
  37052. 0x0131: 0xb0,
  37053. // °
  37054. 0x0132: 0xbd,
  37055. // ½
  37056. 0x0133: 0xbf,
  37057. // ¿
  37058. 0x0134: 0x2122,
  37059. // ™
  37060. 0x0135: 0xa2,
  37061. // ¢
  37062. 0x0136: 0xa3,
  37063. // £
  37064. 0x0137: 0x266a,
  37065. // ♪
  37066. 0x0138: 0xe0,
  37067. // à
  37068. 0x0139: 0xa0,
  37069. //
  37070. 0x013a: 0xe8,
  37071. // è
  37072. 0x013b: 0xe2,
  37073. // â
  37074. 0x013c: 0xea,
  37075. // ê
  37076. 0x013d: 0xee,
  37077. // î
  37078. 0x013e: 0xf4,
  37079. // ô
  37080. 0x013f: 0xfb,
  37081. // û
  37082. 0x0220: 0xc1,
  37083. // Á
  37084. 0x0221: 0xc9,
  37085. // É
  37086. 0x0222: 0xd3,
  37087. // Ó
  37088. 0x0223: 0xda,
  37089. // Ú
  37090. 0x0224: 0xdc,
  37091. // Ü
  37092. 0x0225: 0xfc,
  37093. // ü
  37094. 0x0226: 0x2018,
  37095. // ‘
  37096. 0x0227: 0xa1,
  37097. // ¡
  37098. 0x0228: 0x2a,
  37099. // *
  37100. 0x0229: 0x27,
  37101. // '
  37102. 0x022a: 0x2014,
  37103. // —
  37104. 0x022b: 0xa9,
  37105. // ©
  37106. 0x022c: 0x2120,
  37107. // ℠
  37108. 0x022d: 0x2022,
  37109. // •
  37110. 0x022e: 0x201c,
  37111. // “
  37112. 0x022f: 0x201d,
  37113. // ”
  37114. 0x0230: 0xc0,
  37115. // À
  37116. 0x0231: 0xc2,
  37117. // Â
  37118. 0x0232: 0xc7,
  37119. // Ç
  37120. 0x0233: 0xc8,
  37121. // È
  37122. 0x0234: 0xca,
  37123. // Ê
  37124. 0x0235: 0xcb,
  37125. // Ë
  37126. 0x0236: 0xeb,
  37127. // ë
  37128. 0x0237: 0xce,
  37129. // Î
  37130. 0x0238: 0xcf,
  37131. // Ï
  37132. 0x0239: 0xef,
  37133. // ï
  37134. 0x023a: 0xd4,
  37135. // Ô
  37136. 0x023b: 0xd9,
  37137. // Ù
  37138. 0x023c: 0xf9,
  37139. // ù
  37140. 0x023d: 0xdb,
  37141. // Û
  37142. 0x023e: 0xab,
  37143. // «
  37144. 0x023f: 0xbb,
  37145. // »
  37146. 0x0320: 0xc3,
  37147. // Ã
  37148. 0x0321: 0xe3,
  37149. // ã
  37150. 0x0322: 0xcd,
  37151. // Í
  37152. 0x0323: 0xcc,
  37153. // Ì
  37154. 0x0324: 0xec,
  37155. // ì
  37156. 0x0325: 0xd2,
  37157. // Ò
  37158. 0x0326: 0xf2,
  37159. // ò
  37160. 0x0327: 0xd5,
  37161. // Õ
  37162. 0x0328: 0xf5,
  37163. // õ
  37164. 0x0329: 0x7b,
  37165. // {
  37166. 0x032a: 0x7d,
  37167. // }
  37168. 0x032b: 0x5c,
  37169. // \
  37170. 0x032c: 0x5e,
  37171. // ^
  37172. 0x032d: 0x5f,
  37173. // _
  37174. 0x032e: 0x7c,
  37175. // |
  37176. 0x032f: 0x7e,
  37177. // ~
  37178. 0x0330: 0xc4,
  37179. // Ä
  37180. 0x0331: 0xe4,
  37181. // ä
  37182. 0x0332: 0xd6,
  37183. // Ö
  37184. 0x0333: 0xf6,
  37185. // ö
  37186. 0x0334: 0xdf,
  37187. // ß
  37188. 0x0335: 0xa5,
  37189. // ¥
  37190. 0x0336: 0xa4,
  37191. // ¤
  37192. 0x0337: 0x2502,
  37193. // │
  37194. 0x0338: 0xc5,
  37195. // Å
  37196. 0x0339: 0xe5,
  37197. // å
  37198. 0x033a: 0xd8,
  37199. // Ø
  37200. 0x033b: 0xf8,
  37201. // ø
  37202. 0x033c: 0x250c,
  37203. // ┌
  37204. 0x033d: 0x2510,
  37205. // ┐
  37206. 0x033e: 0x2514,
  37207. // └
  37208. 0x033f: 0x2518 // ┘
  37209. };
  37210. var getCharFromCode = function getCharFromCode(code) {
  37211. if (code === null) {
  37212. return '';
  37213. }
  37214. code = CHARACTER_TRANSLATION[code] || code;
  37215. return String.fromCharCode(code);
  37216. }; // the index of the last row in a CEA-608 display buffer
  37217. var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
  37218. // getting it through bit logic.
  37219. 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
  37220. // cells. The "bottom" row is the last element in the outer array.
  37221. var createDisplayBuffer = function createDisplayBuffer() {
  37222. var result = [],
  37223. i = BOTTOM_ROW + 1;
  37224. while (i--) {
  37225. result.push('');
  37226. }
  37227. return result;
  37228. };
  37229. var Cea608Stream = function Cea608Stream(field, dataChannel) {
  37230. Cea608Stream.prototype.init.call(this);
  37231. this.field_ = field || 0;
  37232. this.dataChannel_ = dataChannel || 0;
  37233. this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
  37234. this.setConstants();
  37235. this.reset();
  37236. this.push = function (packet) {
  37237. var data, swap, char0, char1, text; // remove the parity bits
  37238. data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
  37239. if (data === this.lastControlCode_) {
  37240. this.lastControlCode_ = null;
  37241. return;
  37242. } // Store control codes
  37243. if ((data & 0xf000) === 0x1000) {
  37244. this.lastControlCode_ = data;
  37245. } else if (data !== this.PADDING_) {
  37246. this.lastControlCode_ = null;
  37247. }
  37248. char0 = data >>> 8;
  37249. char1 = data & 0xff;
  37250. if (data === this.PADDING_) {
  37251. return;
  37252. } else if (data === this.RESUME_CAPTION_LOADING_) {
  37253. this.mode_ = 'popOn';
  37254. } else if (data === this.END_OF_CAPTION_) {
  37255. // If an EOC is received while in paint-on mode, the displayed caption
  37256. // text should be swapped to non-displayed memory as if it was a pop-on
  37257. // caption. Because of that, we should explicitly switch back to pop-on
  37258. // mode
  37259. this.mode_ = 'popOn';
  37260. this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
  37261. this.flushDisplayed(packet.pts); // flip memory
  37262. swap = this.displayed_;
  37263. this.displayed_ = this.nonDisplayed_;
  37264. this.nonDisplayed_ = swap; // start measuring the time to display the caption
  37265. this.startPts_ = packet.pts;
  37266. } else if (data === this.ROLL_UP_2_ROWS_) {
  37267. this.rollUpRows_ = 2;
  37268. this.setRollUp(packet.pts);
  37269. } else if (data === this.ROLL_UP_3_ROWS_) {
  37270. this.rollUpRows_ = 3;
  37271. this.setRollUp(packet.pts);
  37272. } else if (data === this.ROLL_UP_4_ROWS_) {
  37273. this.rollUpRows_ = 4;
  37274. this.setRollUp(packet.pts);
  37275. } else if (data === this.CARRIAGE_RETURN_) {
  37276. this.clearFormatting(packet.pts);
  37277. this.flushDisplayed(packet.pts);
  37278. this.shiftRowsUp_();
  37279. this.startPts_ = packet.pts;
  37280. } else if (data === this.BACKSPACE_) {
  37281. if (this.mode_ === 'popOn') {
  37282. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  37283. } else {
  37284. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  37285. }
  37286. } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
  37287. this.flushDisplayed(packet.pts);
  37288. this.displayed_ = createDisplayBuffer();
  37289. } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
  37290. this.nonDisplayed_ = createDisplayBuffer();
  37291. } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
  37292. if (this.mode_ !== 'paintOn') {
  37293. // NOTE: This should be removed when proper caption positioning is
  37294. // implemented
  37295. this.flushDisplayed(packet.pts);
  37296. this.displayed_ = createDisplayBuffer();
  37297. }
  37298. this.mode_ = 'paintOn';
  37299. this.startPts_ = packet.pts; // Append special characters to caption text
  37300. } else if (this.isSpecialCharacter(char0, char1)) {
  37301. // Bitmask char0 so that we can apply character transformations
  37302. // regardless of field and data channel.
  37303. // Then byte-shift to the left and OR with char1 so we can pass the
  37304. // entire character code to `getCharFromCode`.
  37305. char0 = (char0 & 0x03) << 8;
  37306. text = getCharFromCode(char0 | char1);
  37307. this[this.mode_](packet.pts, text);
  37308. this.column_++; // Append extended characters to caption text
  37309. } else if (this.isExtCharacter(char0, char1)) {
  37310. // Extended characters always follow their "non-extended" equivalents.
  37311. // IE if a "è" is desired, you'll always receive "eè"; non-compliant
  37312. // decoders are supposed to drop the "è", while compliant decoders
  37313. // backspace the "e" and insert "è".
  37314. // Delete the previous character
  37315. if (this.mode_ === 'popOn') {
  37316. this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
  37317. } else {
  37318. this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
  37319. } // Bitmask char0 so that we can apply character transformations
  37320. // regardless of field and data channel.
  37321. // Then byte-shift to the left and OR with char1 so we can pass the
  37322. // entire character code to `getCharFromCode`.
  37323. char0 = (char0 & 0x03) << 8;
  37324. text = getCharFromCode(char0 | char1);
  37325. this[this.mode_](packet.pts, text);
  37326. this.column_++; // Process mid-row codes
  37327. } else if (this.isMidRowCode(char0, char1)) {
  37328. // Attributes are not additive, so clear all formatting
  37329. this.clearFormatting(packet.pts); // According to the standard, mid-row codes
  37330. // should be replaced with spaces, so add one now
  37331. this[this.mode_](packet.pts, ' ');
  37332. this.column_++;
  37333. if ((char1 & 0xe) === 0xe) {
  37334. this.addFormatting(packet.pts, ['i']);
  37335. }
  37336. if ((char1 & 0x1) === 0x1) {
  37337. this.addFormatting(packet.pts, ['u']);
  37338. } // Detect offset control codes and adjust cursor
  37339. } else if (this.isOffsetControlCode(char0, char1)) {
  37340. // Cursor position is set by indent PAC (see below) in 4-column
  37341. // increments, with an additional offset code of 1-3 to reach any
  37342. // of the 32 columns specified by CEA-608. So all we need to do
  37343. // here is increment the column cursor by the given offset.
  37344. this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
  37345. } else if (this.isPAC(char0, char1)) {
  37346. // There's no logic for PAC -> row mapping, so we have to just
  37347. // find the row code in an array and use its index :(
  37348. var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
  37349. if (this.mode_ === 'rollUp') {
  37350. // This implies that the base row is incorrectly set.
  37351. // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
  37352. // of roll-up rows set.
  37353. if (row - this.rollUpRows_ + 1 < 0) {
  37354. row = this.rollUpRows_ - 1;
  37355. }
  37356. this.setRollUp(packet.pts, row);
  37357. }
  37358. if (row !== this.row_) {
  37359. // formatting is only persistent for current row
  37360. this.clearFormatting(packet.pts);
  37361. this.row_ = row;
  37362. } // All PACs can apply underline, so detect and apply
  37363. // (All odd-numbered second bytes set underline)
  37364. if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
  37365. this.addFormatting(packet.pts, ['u']);
  37366. }
  37367. if ((data & 0x10) === 0x10) {
  37368. // We've got an indent level code. Each successive even number
  37369. // increments the column cursor by 4, so we can get the desired
  37370. // column position by bit-shifting to the right (to get n/2)
  37371. // and multiplying by 4.
  37372. this.column_ = ((data & 0xe) >> 1) * 4;
  37373. }
  37374. if (this.isColorPAC(char1)) {
  37375. // it's a color code, though we only support white, which
  37376. // can be either normal or italicized. white italics can be
  37377. // either 0x4e or 0x6e depending on the row, so we just
  37378. // bitwise-and with 0xe to see if italics should be turned on
  37379. if ((char1 & 0xe) === 0xe) {
  37380. this.addFormatting(packet.pts, ['i']);
  37381. }
  37382. } // We have a normal character in char0, and possibly one in char1
  37383. } else if (this.isNormalChar(char0)) {
  37384. if (char1 === 0x00) {
  37385. char1 = null;
  37386. }
  37387. text = getCharFromCode(char0);
  37388. text += getCharFromCode(char1);
  37389. this[this.mode_](packet.pts, text);
  37390. this.column_ += text.length;
  37391. } // finish data processing
  37392. };
  37393. };
  37394. Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
  37395. // display buffer
  37396. Cea608Stream.prototype.flushDisplayed = function (pts) {
  37397. var content = this.displayed_ // remove spaces from the start and end of the string
  37398. .map(function (row) {
  37399. try {
  37400. return row.trim();
  37401. } catch (e) {
  37402. // Ordinarily, this shouldn't happen. However, caption
  37403. // parsing errors should not throw exceptions and
  37404. // break playback.
  37405. // eslint-disable-next-line no-console
  37406. console.error('Skipping malformed caption.');
  37407. return '';
  37408. }
  37409. }) // combine all text rows to display in one cue
  37410. .join('\n') // and remove blank rows from the start and end, but not the middle
  37411. .replace(/^\n+|\n+$/g, '');
  37412. if (content.length) {
  37413. this.trigger('data', {
  37414. startPts: this.startPts_,
  37415. endPts: pts,
  37416. text: content,
  37417. stream: this.name_
  37418. });
  37419. }
  37420. };
  37421. /**
  37422. * Zero out the data, used for startup and on seek
  37423. */
  37424. Cea608Stream.prototype.reset = function () {
  37425. this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
  37426. // actually display captions. If a caption is shifted to a row
  37427. // with a lower index than this, it is cleared from the display
  37428. // buffer
  37429. this.topRow_ = 0;
  37430. this.startPts_ = 0;
  37431. this.displayed_ = createDisplayBuffer();
  37432. this.nonDisplayed_ = createDisplayBuffer();
  37433. this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
  37434. this.column_ = 0;
  37435. this.row_ = BOTTOM_ROW;
  37436. this.rollUpRows_ = 2; // This variable holds currently-applied formatting
  37437. this.formatting_ = [];
  37438. };
  37439. /**
  37440. * Sets up control code and related constants for this instance
  37441. */
  37442. Cea608Stream.prototype.setConstants = function () {
  37443. // The following attributes have these uses:
  37444. // ext_ : char0 for mid-row codes, and the base for extended
  37445. // chars (ext_+0, ext_+1, and ext_+2 are char0s for
  37446. // extended codes)
  37447. // control_: char0 for control codes, except byte-shifted to the
  37448. // left so that we can do this.control_ | CONTROL_CODE
  37449. // offset_: char0 for tab offset codes
  37450. //
  37451. // It's also worth noting that control codes, and _only_ control codes,
  37452. // differ between field 1 and field2. Field 2 control codes are always
  37453. // their field 1 value plus 1. That's why there's the "| field" on the
  37454. // control value.
  37455. if (this.dataChannel_ === 0) {
  37456. this.BASE_ = 0x10;
  37457. this.EXT_ = 0x11;
  37458. this.CONTROL_ = (0x14 | this.field_) << 8;
  37459. this.OFFSET_ = 0x17;
  37460. } else if (this.dataChannel_ === 1) {
  37461. this.BASE_ = 0x18;
  37462. this.EXT_ = 0x19;
  37463. this.CONTROL_ = (0x1c | this.field_) << 8;
  37464. this.OFFSET_ = 0x1f;
  37465. } // Constants for the LSByte command codes recognized by Cea608Stream. This
  37466. // list is not exhaustive. For a more comprehensive listing and semantics see
  37467. // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
  37468. // Padding
  37469. this.PADDING_ = 0x0000; // Pop-on Mode
  37470. this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
  37471. this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
  37472. this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
  37473. this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
  37474. this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
  37475. this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
  37476. this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
  37477. this.BACKSPACE_ = this.CONTROL_ | 0x21;
  37478. this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
  37479. this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
  37480. };
  37481. /**
  37482. * Detects if the 2-byte packet data is a special character
  37483. *
  37484. * Special characters have a second byte in the range 0x30 to 0x3f,
  37485. * with the first byte being 0x11 (for data channel 1) or 0x19 (for
  37486. * data channel 2).
  37487. *
  37488. * @param {Integer} char0 The first byte
  37489. * @param {Integer} char1 The second byte
  37490. * @return {Boolean} Whether the 2 bytes are an special character
  37491. */
  37492. Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
  37493. return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
  37494. };
  37495. /**
  37496. * Detects if the 2-byte packet data is an extended character
  37497. *
  37498. * Extended characters have a second byte in the range 0x20 to 0x3f,
  37499. * with the first byte being 0x12 or 0x13 (for data channel 1) or
  37500. * 0x1a or 0x1b (for data channel 2).
  37501. *
  37502. * @param {Integer} char0 The first byte
  37503. * @param {Integer} char1 The second byte
  37504. * @return {Boolean} Whether the 2 bytes are an extended character
  37505. */
  37506. Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
  37507. return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
  37508. };
  37509. /**
  37510. * Detects if the 2-byte packet is a mid-row code
  37511. *
  37512. * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
  37513. * the first byte being 0x11 (for data channel 1) or 0x19 (for data
  37514. * channel 2).
  37515. *
  37516. * @param {Integer} char0 The first byte
  37517. * @param {Integer} char1 The second byte
  37518. * @return {Boolean} Whether the 2 bytes are a mid-row code
  37519. */
  37520. Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
  37521. return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
  37522. };
  37523. /**
  37524. * Detects if the 2-byte packet is an offset control code
  37525. *
  37526. * Offset control codes have a second byte in the range 0x21 to 0x23,
  37527. * with the first byte being 0x17 (for data channel 1) or 0x1f (for
  37528. * data channel 2).
  37529. *
  37530. * @param {Integer} char0 The first byte
  37531. * @param {Integer} char1 The second byte
  37532. * @return {Boolean} Whether the 2 bytes are an offset control code
  37533. */
  37534. Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
  37535. return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
  37536. };
  37537. /**
  37538. * Detects if the 2-byte packet is a Preamble Address Code
  37539. *
  37540. * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
  37541. * or 0x18 to 0x1f (for data channel 2), with the second byte in the
  37542. * range 0x40 to 0x7f.
  37543. *
  37544. * @param {Integer} char0 The first byte
  37545. * @param {Integer} char1 The second byte
  37546. * @return {Boolean} Whether the 2 bytes are a PAC
  37547. */
  37548. Cea608Stream.prototype.isPAC = function (char0, char1) {
  37549. return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
  37550. };
  37551. /**
  37552. * Detects if a packet's second byte is in the range of a PAC color code
  37553. *
  37554. * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
  37555. * 0x60 to 0x6f.
  37556. *
  37557. * @param {Integer} char1 The second byte
  37558. * @return {Boolean} Whether the byte is a color PAC
  37559. */
  37560. Cea608Stream.prototype.isColorPAC = function (char1) {
  37561. return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
  37562. };
  37563. /**
  37564. * Detects if a single byte is in the range of a normal character
  37565. *
  37566. * Normal text bytes are in the range 0x20 to 0x7f.
  37567. *
  37568. * @param {Integer} char The byte
  37569. * @return {Boolean} Whether the byte is a normal character
  37570. */
  37571. Cea608Stream.prototype.isNormalChar = function (char) {
  37572. return char >= 0x20 && char <= 0x7f;
  37573. };
  37574. /**
  37575. * Configures roll-up
  37576. *
  37577. * @param {Integer} pts Current PTS
  37578. * @param {Integer} newBaseRow Used by PACs to slide the current window to
  37579. * a new position
  37580. */
  37581. Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
  37582. // Reset the base row to the bottom row when switching modes
  37583. if (this.mode_ !== 'rollUp') {
  37584. this.row_ = BOTTOM_ROW;
  37585. this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
  37586. this.flushDisplayed(pts);
  37587. this.nonDisplayed_ = createDisplayBuffer();
  37588. this.displayed_ = createDisplayBuffer();
  37589. }
  37590. if (newBaseRow !== undefined && newBaseRow !== this.row_) {
  37591. // move currently displayed captions (up or down) to the new base row
  37592. for (var i = 0; i < this.rollUpRows_; i++) {
  37593. this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
  37594. this.displayed_[this.row_ - i] = '';
  37595. }
  37596. }
  37597. if (newBaseRow === undefined) {
  37598. newBaseRow = this.row_;
  37599. }
  37600. this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
  37601. }; // Adds the opening HTML tag for the passed character to the caption text,
  37602. // and keeps track of it for later closing
  37603. Cea608Stream.prototype.addFormatting = function (pts, format) {
  37604. this.formatting_ = this.formatting_.concat(format);
  37605. var text = format.reduce(function (text, format) {
  37606. return text + '<' + format + '>';
  37607. }, '');
  37608. this[this.mode_](pts, text);
  37609. }; // Adds HTML closing tags for current formatting to caption text and
  37610. // clears remembered formatting
  37611. Cea608Stream.prototype.clearFormatting = function (pts) {
  37612. if (!this.formatting_.length) {
  37613. return;
  37614. }
  37615. var text = this.formatting_.reverse().reduce(function (text, format) {
  37616. return text + '</' + format + '>';
  37617. }, '');
  37618. this.formatting_ = [];
  37619. this[this.mode_](pts, text);
  37620. }; // Mode Implementations
  37621. Cea608Stream.prototype.popOn = function (pts, text) {
  37622. var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
  37623. baseRow += text;
  37624. this.nonDisplayed_[this.row_] = baseRow;
  37625. };
  37626. Cea608Stream.prototype.rollUp = function (pts, text) {
  37627. var baseRow = this.displayed_[this.row_];
  37628. baseRow += text;
  37629. this.displayed_[this.row_] = baseRow;
  37630. };
  37631. Cea608Stream.prototype.shiftRowsUp_ = function () {
  37632. var i; // clear out inactive rows
  37633. for (i = 0; i < this.topRow_; i++) {
  37634. this.displayed_[i] = '';
  37635. }
  37636. for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
  37637. this.displayed_[i] = '';
  37638. } // shift displayed rows up
  37639. for (i = this.topRow_; i < this.row_; i++) {
  37640. this.displayed_[i] = this.displayed_[i + 1];
  37641. } // clear out the bottom row
  37642. this.displayed_[this.row_] = '';
  37643. };
  37644. Cea608Stream.prototype.paintOn = function (pts, text) {
  37645. var baseRow = this.displayed_[this.row_];
  37646. baseRow += text;
  37647. this.displayed_[this.row_] = baseRow;
  37648. }; // exports
  37649. var captionStream = {
  37650. CaptionStream: CaptionStream,
  37651. Cea608Stream: Cea608Stream
  37652. };
  37653. var streamTypes = {
  37654. H264_STREAM_TYPE: 0x1B,
  37655. ADTS_STREAM_TYPE: 0x0F,
  37656. METADATA_STREAM_TYPE: 0x15
  37657. };
  37658. var MAX_TS = 8589934592;
  37659. var RO_THRESH = 4294967296;
  37660. var handleRollover = function handleRollover(value, reference) {
  37661. var direction = 1;
  37662. if (value > reference) {
  37663. // If the current timestamp value is greater than our reference timestamp and we detect a
  37664. // timestamp rollover, this means the roll over is happening in the opposite direction.
  37665. // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
  37666. // point will be set to a small number, e.g. 1. The user then seeks backwards over the
  37667. // rollover point. In loading this segment, the timestamp values will be very large,
  37668. // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
  37669. // the time stamp to be `value - 2^33`.
  37670. direction = -1;
  37671. } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
  37672. // cause an incorrect adjustment.
  37673. while (Math.abs(reference - value) > RO_THRESH) {
  37674. value += direction * MAX_TS;
  37675. }
  37676. return value;
  37677. };
  37678. var TimestampRolloverStream = function TimestampRolloverStream(type) {
  37679. var lastDTS, referenceDTS;
  37680. TimestampRolloverStream.prototype.init.call(this);
  37681. this.type_ = type;
  37682. this.push = function (data) {
  37683. if (data.type !== this.type_) {
  37684. return;
  37685. }
  37686. if (referenceDTS === undefined) {
  37687. referenceDTS = data.dts;
  37688. }
  37689. data.dts = handleRollover(data.dts, referenceDTS);
  37690. data.pts = handleRollover(data.pts, referenceDTS);
  37691. lastDTS = data.dts;
  37692. this.trigger('data', data);
  37693. };
  37694. this.flush = function () {
  37695. referenceDTS = lastDTS;
  37696. this.trigger('done');
  37697. };
  37698. this.discontinuity = function () {
  37699. referenceDTS = void 0;
  37700. lastDTS = void 0;
  37701. };
  37702. };
  37703. TimestampRolloverStream.prototype = new stream();
  37704. var timestampRolloverStream = {
  37705. TimestampRolloverStream: TimestampRolloverStream,
  37706. handleRollover: handleRollover
  37707. };
  37708. var percentEncode = function percentEncode(bytes, start, end) {
  37709. var i,
  37710. result = '';
  37711. for (i = start; i < end; i++) {
  37712. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  37713. }
  37714. return result;
  37715. },
  37716. // return the string representation of the specified byte range,
  37717. // interpreted as UTf-8.
  37718. parseUtf8 = function parseUtf8(bytes, start, end) {
  37719. return decodeURIComponent(percentEncode(bytes, start, end));
  37720. },
  37721. // return the string representation of the specified byte range,
  37722. // interpreted as ISO-8859-1.
  37723. parseIso88591 = function parseIso88591(bytes, start, end) {
  37724. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  37725. },
  37726. parseSyncSafeInteger = function parseSyncSafeInteger(data) {
  37727. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  37728. },
  37729. tagParsers = {
  37730. TXXX: function TXXX(tag) {
  37731. var i;
  37732. if (tag.data[0] !== 3) {
  37733. // ignore frames with unrecognized character encodings
  37734. return;
  37735. }
  37736. for (i = 1; i < tag.data.length; i++) {
  37737. if (tag.data[i] === 0) {
  37738. // parse the text fields
  37739. tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
  37740. tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
  37741. break;
  37742. }
  37743. }
  37744. tag.data = tag.value;
  37745. },
  37746. WXXX: function WXXX(tag) {
  37747. var i;
  37748. if (tag.data[0] !== 3) {
  37749. // ignore frames with unrecognized character encodings
  37750. return;
  37751. }
  37752. for (i = 1; i < tag.data.length; i++) {
  37753. if (tag.data[i] === 0) {
  37754. // parse the description and URL fields
  37755. tag.description = parseUtf8(tag.data, 1, i);
  37756. tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
  37757. break;
  37758. }
  37759. }
  37760. },
  37761. PRIV: function PRIV(tag) {
  37762. var i;
  37763. for (i = 0; i < tag.data.length; i++) {
  37764. if (tag.data[i] === 0) {
  37765. // parse the description and URL fields
  37766. tag.owner = parseIso88591(tag.data, 0, i);
  37767. break;
  37768. }
  37769. }
  37770. tag.privateData = tag.data.subarray(i + 1);
  37771. tag.data = tag.privateData;
  37772. }
  37773. },
  37774. _MetadataStream;
  37775. _MetadataStream = function MetadataStream(options) {
  37776. var settings = {
  37777. debug: !!(options && options.debug),
  37778. // the bytes of the program-level descriptor field in MP2T
  37779. // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
  37780. // program element descriptors"
  37781. descriptor: options && options.descriptor
  37782. },
  37783. // the total size in bytes of the ID3 tag being parsed
  37784. tagSize = 0,
  37785. // tag data that is not complete enough to be parsed
  37786. buffer = [],
  37787. // the total number of bytes currently in the buffer
  37788. bufferSize = 0,
  37789. i;
  37790. _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
  37791. // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
  37792. this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
  37793. if (settings.descriptor) {
  37794. for (i = 0; i < settings.descriptor.length; i++) {
  37795. this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
  37796. }
  37797. }
  37798. this.push = function (chunk) {
  37799. var tag, frameStart, frameSize, frame, i, frameHeader;
  37800. if (chunk.type !== 'timed-metadata') {
  37801. return;
  37802. } // if data_alignment_indicator is set in the PES header,
  37803. // we must have the start of a new ID3 tag. Assume anything
  37804. // remaining in the buffer was malformed and throw it out
  37805. if (chunk.dataAlignmentIndicator) {
  37806. bufferSize = 0;
  37807. buffer.length = 0;
  37808. } // ignore events that don't look like ID3 data
  37809. 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))) {
  37810. if (settings.debug) {
  37811. // eslint-disable-next-line no-console
  37812. console.log('Skipping unrecognized metadata packet');
  37813. }
  37814. return;
  37815. } // add this chunk to the data we've collected so far
  37816. buffer.push(chunk);
  37817. bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
  37818. if (buffer.length === 1) {
  37819. // the frame size is transmitted as a 28-bit integer in the
  37820. // last four bytes of the ID3 header.
  37821. // The most significant bit of each byte is dropped and the
  37822. // results concatenated to recover the actual value.
  37823. tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
  37824. // convenient for our comparisons to include it
  37825. tagSize += 10;
  37826. } // if the entire frame has not arrived, wait for more data
  37827. if (bufferSize < tagSize) {
  37828. return;
  37829. } // collect the entire frame so it can be parsed
  37830. tag = {
  37831. data: new Uint8Array(tagSize),
  37832. frames: [],
  37833. pts: buffer[0].pts,
  37834. dts: buffer[0].dts
  37835. };
  37836. for (i = 0; i < tagSize;) {
  37837. tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
  37838. i += buffer[0].data.byteLength;
  37839. bufferSize -= buffer[0].data.byteLength;
  37840. buffer.shift();
  37841. } // find the start of the first frame and the end of the tag
  37842. frameStart = 10;
  37843. if (tag.data[5] & 0x40) {
  37844. // advance the frame start past the extended header
  37845. frameStart += 4; // header size field
  37846. frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
  37847. tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
  37848. } // parse one or more ID3 frames
  37849. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  37850. do {
  37851. // determine the number of bytes in this frame
  37852. frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
  37853. if (frameSize < 1) {
  37854. // eslint-disable-next-line no-console
  37855. return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
  37856. }
  37857. frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
  37858. frame = {
  37859. id: frameHeader,
  37860. data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
  37861. };
  37862. frame.key = frame.id;
  37863. if (tagParsers[frame.id]) {
  37864. tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
  37865. // time for raw AAC data
  37866. if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
  37867. var d = frame.data,
  37868. size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  37869. size *= 4;
  37870. size += d[7] & 0x03;
  37871. frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
  37872. // on the value of this frame
  37873. // we couldn't have known the appropriate pts and dts before
  37874. // parsing this ID3 tag so set those values now
  37875. if (tag.pts === undefined && tag.dts === undefined) {
  37876. tag.pts = frame.timeStamp;
  37877. tag.dts = frame.timeStamp;
  37878. }
  37879. this.trigger('timestamp', frame);
  37880. }
  37881. }
  37882. tag.frames.push(frame);
  37883. frameStart += 10; // advance past the frame header
  37884. frameStart += frameSize; // advance past the frame body
  37885. } while (frameStart < tagSize);
  37886. this.trigger('data', tag);
  37887. };
  37888. };
  37889. _MetadataStream.prototype = new stream();
  37890. var metadataStream = _MetadataStream;
  37891. var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
  37892. var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
  37893. var MP2T_PACKET_LENGTH = 188,
  37894. // bytes
  37895. SYNC_BYTE = 0x47;
  37896. /**
  37897. * Splits an incoming stream of binary data into MPEG-2 Transport
  37898. * Stream packets.
  37899. */
  37900. _TransportPacketStream = function TransportPacketStream() {
  37901. var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
  37902. bytesInBuffer = 0;
  37903. _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
  37904. /**
  37905. * Split a stream of data into M2TS packets
  37906. **/
  37907. this.push = function (bytes) {
  37908. var startIndex = 0,
  37909. endIndex = MP2T_PACKET_LENGTH,
  37910. everything; // If there are bytes remaining from the last segment, prepend them to the
  37911. // bytes that were pushed in
  37912. if (bytesInBuffer) {
  37913. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  37914. everything.set(buffer.subarray(0, bytesInBuffer));
  37915. everything.set(bytes, bytesInBuffer);
  37916. bytesInBuffer = 0;
  37917. } else {
  37918. everything = bytes;
  37919. } // While we have enough data for a packet
  37920. while (endIndex < everything.byteLength) {
  37921. // Look for a pair of start and end sync bytes in the data..
  37922. if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
  37923. // We found a packet so emit it and jump one whole packet forward in
  37924. // the stream
  37925. this.trigger('data', everything.subarray(startIndex, endIndex));
  37926. startIndex += MP2T_PACKET_LENGTH;
  37927. endIndex += MP2T_PACKET_LENGTH;
  37928. continue;
  37929. } // If we get here, we have somehow become de-synchronized and we need to step
  37930. // forward one byte at a time until we find a pair of sync bytes that denote
  37931. // a packet
  37932. startIndex++;
  37933. endIndex++;
  37934. } // If there was some data left over at the end of the segment that couldn't
  37935. // possibly be a whole packet, keep it because it might be the start of a packet
  37936. // that continues in the next segment
  37937. if (startIndex < everything.byteLength) {
  37938. buffer.set(everything.subarray(startIndex), 0);
  37939. bytesInBuffer = everything.byteLength - startIndex;
  37940. }
  37941. };
  37942. /**
  37943. * Passes identified M2TS packets to the TransportParseStream to be parsed
  37944. **/
  37945. this.flush = function () {
  37946. // If the buffer contains a whole packet when we are being flushed, emit it
  37947. // and empty the buffer. Otherwise hold onto the data because it may be
  37948. // important for decoding the next segment
  37949. if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
  37950. this.trigger('data', buffer);
  37951. bytesInBuffer = 0;
  37952. }
  37953. this.trigger('done');
  37954. };
  37955. };
  37956. _TransportPacketStream.prototype = new stream();
  37957. /**
  37958. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  37959. * forms of the individual transport stream packets.
  37960. */
  37961. _TransportParseStream = function TransportParseStream() {
  37962. var parsePsi, parsePat, parsePmt, self;
  37963. _TransportParseStream.prototype.init.call(this);
  37964. self = this;
  37965. this.packetsWaitingForPmt = [];
  37966. this.programMapTable = undefined;
  37967. parsePsi = function parsePsi(payload, psi) {
  37968. var offset = 0; // PSI packets may be split into multiple sections and those
  37969. // sections may be split into multiple packets. If a PSI
  37970. // section starts in this packet, the payload_unit_start_indicator
  37971. // will be true and the first byte of the payload will indicate
  37972. // the offset from the current position to the start of the
  37973. // section.
  37974. if (psi.payloadUnitStartIndicator) {
  37975. offset += payload[offset] + 1;
  37976. }
  37977. if (psi.type === 'pat') {
  37978. parsePat(payload.subarray(offset), psi);
  37979. } else {
  37980. parsePmt(payload.subarray(offset), psi);
  37981. }
  37982. };
  37983. parsePat = function parsePat(payload, pat) {
  37984. pat.section_number = payload[7]; // eslint-disable-line camelcase
  37985. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  37986. // skip the PSI header and parse the first PMT entry
  37987. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  37988. pat.pmtPid = self.pmtPid;
  37989. };
  37990. /**
  37991. * Parse out the relevant fields of a Program Map Table (PMT).
  37992. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  37993. * packet. The first byte in this array should be the table_id
  37994. * field.
  37995. * @param pmt {object} the object that should be decorated with
  37996. * fields parsed from the PMT.
  37997. */
  37998. parsePmt = function parsePmt(payload, pmt) {
  37999. var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
  38000. // take effect. We don't believe this should ever be the case
  38001. // for HLS but we'll ignore "forward" PMT declarations if we see
  38002. // them. Future PMT declarations have the current_next_indicator
  38003. // set to zero.
  38004. if (!(payload[5] & 0x01)) {
  38005. return;
  38006. } // overwrite any existing program map table
  38007. self.programMapTable = {
  38008. video: null,
  38009. audio: null,
  38010. 'timed-metadata': {}
  38011. }; // the mapping table ends at the end of the current section
  38012. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  38013. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  38014. // long the program info descriptors are
  38015. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
  38016. offset = 12 + programInfoLength;
  38017. while (offset < tableEnd) {
  38018. var streamType = payload[offset];
  38019. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
  38020. // TODO: should this be done for metadata too? for now maintain behavior of
  38021. // multiple metadata streams
  38022. if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
  38023. self.programMapTable.video = pid;
  38024. } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
  38025. self.programMapTable.audio = pid;
  38026. } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
  38027. // map pid to stream type for metadata streams
  38028. self.programMapTable['timed-metadata'][pid] = streamType;
  38029. } // move to the next table entry
  38030. // skip past the elementary stream descriptors, if present
  38031. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  38032. } // record the map on the packet as well
  38033. pmt.programMapTable = self.programMapTable;
  38034. };
  38035. /**
  38036. * Deliver a new MP2T packet to the next stream in the pipeline.
  38037. */
  38038. this.push = function (packet) {
  38039. var result = {},
  38040. offset = 4;
  38041. result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
  38042. result.pid = packet[1] & 0x1f;
  38043. result.pid <<= 8;
  38044. result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
  38045. // fifth byte of the TS packet header. The adaptation field is
  38046. // used to add stuffing to PES packets that don't fill a complete
  38047. // TS packet, and to specify some forms of timing and control data
  38048. // that we do not currently use.
  38049. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  38050. offset += packet[offset] + 1;
  38051. } // parse the rest of the packet based on the type
  38052. if (result.pid === 0) {
  38053. result.type = 'pat';
  38054. parsePsi(packet.subarray(offset), result);
  38055. this.trigger('data', result);
  38056. } else if (result.pid === this.pmtPid) {
  38057. result.type = 'pmt';
  38058. parsePsi(packet.subarray(offset), result);
  38059. this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
  38060. while (this.packetsWaitingForPmt.length) {
  38061. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  38062. }
  38063. } else if (this.programMapTable === undefined) {
  38064. // When we have not seen a PMT yet, defer further processing of
  38065. // PES packets until one has been parsed
  38066. this.packetsWaitingForPmt.push([packet, offset, result]);
  38067. } else {
  38068. this.processPes_(packet, offset, result);
  38069. }
  38070. };
  38071. this.processPes_ = function (packet, offset, result) {
  38072. // set the appropriate stream type
  38073. if (result.pid === this.programMapTable.video) {
  38074. result.streamType = streamTypes.H264_STREAM_TYPE;
  38075. } else if (result.pid === this.programMapTable.audio) {
  38076. result.streamType = streamTypes.ADTS_STREAM_TYPE;
  38077. } else {
  38078. // if not video or audio, it is timed-metadata or unknown
  38079. // if unknown, streamType will be undefined
  38080. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  38081. }
  38082. result.type = 'pes';
  38083. result.data = packet.subarray(offset);
  38084. this.trigger('data', result);
  38085. };
  38086. };
  38087. _TransportParseStream.prototype = new stream();
  38088. _TransportParseStream.STREAM_TYPES = {
  38089. h264: 0x1b,
  38090. adts: 0x0f
  38091. };
  38092. /**
  38093. * Reconsistutes program elementary stream (PES) packets from parsed
  38094. * transport stream packets. That is, if you pipe an
  38095. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  38096. * events will be events which capture the bytes for individual PES
  38097. * packets plus relevant metadata that has been extracted from the
  38098. * container.
  38099. */
  38100. _ElementaryStream = function ElementaryStream() {
  38101. var self = this,
  38102. // PES packet fragments
  38103. video = {
  38104. data: [],
  38105. size: 0
  38106. },
  38107. audio = {
  38108. data: [],
  38109. size: 0
  38110. },
  38111. timedMetadata = {
  38112. data: [],
  38113. size: 0
  38114. },
  38115. parsePes = function parsePes(payload, pes) {
  38116. var ptsDtsFlags; // get the packet length, this will be 0 for video
  38117. pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
  38118. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
  38119. // and a DTS value. Determine what combination of values is
  38120. // available to work with.
  38121. ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  38122. // performs all bitwise operations on 32-bit integers but javascript
  38123. // supports a much greater range (52-bits) of integer using standard
  38124. // mathematical operations.
  38125. // We construct a 31-bit value using bitwise operators over the 31
  38126. // most significant bits and then multiply by 4 (equal to a left-shift
  38127. // of 2) before we add the final 2 least significant bits of the
  38128. // timestamp (equal to an OR.)
  38129. if (ptsDtsFlags & 0xC0) {
  38130. // the PTS and DTS are not written out directly. For information
  38131. // on how they are encoded, see
  38132. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  38133. pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
  38134. pes.pts *= 4; // Left shift by 2
  38135. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  38136. pes.dts = pes.pts;
  38137. if (ptsDtsFlags & 0x40) {
  38138. pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
  38139. pes.dts *= 4; // Left shift by 2
  38140. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  38141. }
  38142. } // the data section starts immediately after the PES header.
  38143. // pes_header_data_length specifies the number of header bytes
  38144. // that follow the last byte of the field.
  38145. pes.data = payload.subarray(9 + payload[8]);
  38146. },
  38147. /**
  38148. * Pass completely parsed PES packets to the next stream in the pipeline
  38149. **/
  38150. flushStream = function flushStream(stream$$1, type, forceFlush) {
  38151. var packetData = new Uint8Array(stream$$1.size),
  38152. event = {
  38153. type: type
  38154. },
  38155. i = 0,
  38156. offset = 0,
  38157. packetFlushable = false,
  38158. fragment; // do nothing if there is not enough buffered data for a complete
  38159. // PES header
  38160. if (!stream$$1.data.length || stream$$1.size < 9) {
  38161. return;
  38162. }
  38163. event.trackId = stream$$1.data[0].pid; // reassemble the packet
  38164. for (i = 0; i < stream$$1.data.length; i++) {
  38165. fragment = stream$$1.data[i];
  38166. packetData.set(fragment.data, offset);
  38167. offset += fragment.data.byteLength;
  38168. } // parse assembled packet's PES header
  38169. parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
  38170. // check that there is enough stream data to fill the packet
  38171. packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right
  38172. if (forceFlush || packetFlushable) {
  38173. stream$$1.size = 0;
  38174. stream$$1.data.length = 0;
  38175. } // only emit packets that are complete. this is to avoid assembling
  38176. // incomplete PES packets due to poor segmentation
  38177. if (packetFlushable) {
  38178. self.trigger('data', event);
  38179. }
  38180. };
  38181. _ElementaryStream.prototype.init.call(this);
  38182. /**
  38183. * Identifies M2TS packet types and parses PES packets using metadata
  38184. * parsed from the PMT
  38185. **/
  38186. this.push = function (data) {
  38187. ({
  38188. pat: function pat() {// we have to wait for the PMT to arrive as well before we
  38189. // have any meaningful metadata
  38190. },
  38191. pes: function pes() {
  38192. var stream$$1, streamType;
  38193. switch (data.streamType) {
  38194. case streamTypes.H264_STREAM_TYPE:
  38195. case streamTypes.H264_STREAM_TYPE:
  38196. stream$$1 = video;
  38197. streamType = 'video';
  38198. break;
  38199. case streamTypes.ADTS_STREAM_TYPE:
  38200. stream$$1 = audio;
  38201. streamType = 'audio';
  38202. break;
  38203. case streamTypes.METADATA_STREAM_TYPE:
  38204. stream$$1 = timedMetadata;
  38205. streamType = 'timed-metadata';
  38206. break;
  38207. default:
  38208. // ignore unknown stream types
  38209. return;
  38210. } // if a new packet is starting, we can flush the completed
  38211. // packet
  38212. if (data.payloadUnitStartIndicator) {
  38213. flushStream(stream$$1, streamType, true);
  38214. } // buffer this fragment until we are sure we've received the
  38215. // complete payload
  38216. stream$$1.data.push(data);
  38217. stream$$1.size += data.data.byteLength;
  38218. },
  38219. pmt: function pmt() {
  38220. var event = {
  38221. type: 'metadata',
  38222. tracks: []
  38223. },
  38224. programMapTable = data.programMapTable; // translate audio and video streams to tracks
  38225. if (programMapTable.video !== null) {
  38226. event.tracks.push({
  38227. timelineStartInfo: {
  38228. baseMediaDecodeTime: 0
  38229. },
  38230. id: +programMapTable.video,
  38231. codec: 'avc',
  38232. type: 'video'
  38233. });
  38234. }
  38235. if (programMapTable.audio !== null) {
  38236. event.tracks.push({
  38237. timelineStartInfo: {
  38238. baseMediaDecodeTime: 0
  38239. },
  38240. id: +programMapTable.audio,
  38241. codec: 'adts',
  38242. type: 'audio'
  38243. });
  38244. }
  38245. self.trigger('data', event);
  38246. }
  38247. })[data.type]();
  38248. };
  38249. /**
  38250. * Flush any remaining input. Video PES packets may be of variable
  38251. * length. Normally, the start of a new video packet can trigger the
  38252. * finalization of the previous packet. That is not possible if no
  38253. * more video is forthcoming, however. In that case, some other
  38254. * mechanism (like the end of the file) has to be employed. When it is
  38255. * clear that no additional data is forthcoming, calling this method
  38256. * will flush the buffered packets.
  38257. */
  38258. this.flush = function () {
  38259. // !!THIS ORDER IS IMPORTANT!!
  38260. // video first then audio
  38261. flushStream(video, 'video');
  38262. flushStream(audio, 'audio');
  38263. flushStream(timedMetadata, 'timed-metadata');
  38264. this.trigger('done');
  38265. };
  38266. };
  38267. _ElementaryStream.prototype = new stream();
  38268. var m2ts = {
  38269. PAT_PID: 0x0000,
  38270. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
  38271. TransportPacketStream: _TransportPacketStream,
  38272. TransportParseStream: _TransportParseStream,
  38273. ElementaryStream: _ElementaryStream,
  38274. TimestampRolloverStream: TimestampRolloverStream$1,
  38275. CaptionStream: captionStream.CaptionStream,
  38276. Cea608Stream: captionStream.Cea608Stream,
  38277. MetadataStream: metadataStream
  38278. };
  38279. for (var type in streamTypes) {
  38280. if (streamTypes.hasOwnProperty(type)) {
  38281. m2ts[type] = streamTypes[type];
  38282. }
  38283. }
  38284. var m2ts_1 = m2ts;
  38285. var _AdtsStream;
  38286. var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  38287. /*
  38288. * Accepts a ElementaryStream and emits data events with parsed
  38289. * AAC Audio Frames of the individual packets. Input audio in ADTS
  38290. * format is unpacked and re-emitted as AAC frames.
  38291. *
  38292. * @see http://wiki.multimedia.cx/index.php?title=ADTS
  38293. * @see http://wiki.multimedia.cx/?title=Understanding_AAC
  38294. */
  38295. _AdtsStream = function AdtsStream() {
  38296. var buffer;
  38297. _AdtsStream.prototype.init.call(this);
  38298. this.push = function (packet) {
  38299. var i = 0,
  38300. frameNum = 0,
  38301. frameLength,
  38302. protectionSkipBytes,
  38303. frameEnd,
  38304. oldBuffer,
  38305. sampleCount,
  38306. adtsFrameDuration;
  38307. if (packet.type !== 'audio') {
  38308. // ignore non-audio data
  38309. return;
  38310. } // Prepend any data in the buffer to the input data so that we can parse
  38311. // aac frames the cross a PES packet boundary
  38312. if (buffer) {
  38313. oldBuffer = buffer;
  38314. buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
  38315. buffer.set(oldBuffer);
  38316. buffer.set(packet.data, oldBuffer.byteLength);
  38317. } else {
  38318. buffer = packet.data;
  38319. } // unpack any ADTS frames which have been fully received
  38320. // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
  38321. while (i + 5 < buffer.length) {
  38322. // Loook for the start of an ADTS header..
  38323. if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
  38324. // If a valid header was not found, jump one forward and attempt to
  38325. // find a valid ADTS header starting at the next byte
  38326. i++;
  38327. continue;
  38328. } // The protection skip bit tells us if we have 2 bytes of CRC data at the
  38329. // end of the ADTS header
  38330. protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
  38331. // end of the sync sequence
  38332. frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
  38333. sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
  38334. adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
  38335. frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
  38336. // and wait for more data
  38337. if (buffer.byteLength < frameEnd) {
  38338. return;
  38339. } // Otherwise, deliver the complete AAC frame
  38340. this.trigger('data', {
  38341. pts: packet.pts + frameNum * adtsFrameDuration,
  38342. dts: packet.dts + frameNum * adtsFrameDuration,
  38343. sampleCount: sampleCount,
  38344. audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
  38345. channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
  38346. samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
  38347. samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
  38348. // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
  38349. samplesize: 16,
  38350. data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
  38351. }); // If the buffer is empty, clear it and return
  38352. if (buffer.byteLength === frameEnd) {
  38353. buffer = undefined;
  38354. return;
  38355. }
  38356. frameNum++; // Remove the finished frame from the buffer and start the process again
  38357. buffer = buffer.subarray(frameEnd);
  38358. }
  38359. };
  38360. this.flush = function () {
  38361. this.trigger('done');
  38362. };
  38363. };
  38364. _AdtsStream.prototype = new stream();
  38365. var adts = _AdtsStream;
  38366. var ExpGolomb;
  38367. /**
  38368. * Parser for exponential Golomb codes, a variable-bitwidth number encoding
  38369. * scheme used by h264.
  38370. */
  38371. ExpGolomb = function ExpGolomb(workingData) {
  38372. var // the number of bytes left to examine in workingData
  38373. workingBytesAvailable = workingData.byteLength,
  38374. // the current word being examined
  38375. workingWord = 0,
  38376. // :uint
  38377. // the number of bits left to examine in the current word
  38378. workingBitsAvailable = 0; // :uint;
  38379. // ():uint
  38380. this.length = function () {
  38381. return 8 * workingBytesAvailable;
  38382. }; // ():uint
  38383. this.bitsAvailable = function () {
  38384. return 8 * workingBytesAvailable + workingBitsAvailable;
  38385. }; // ():void
  38386. this.loadWord = function () {
  38387. var position = workingData.byteLength - workingBytesAvailable,
  38388. workingBytes = new Uint8Array(4),
  38389. availableBytes = Math.min(4, workingBytesAvailable);
  38390. if (availableBytes === 0) {
  38391. throw new Error('no bytes available');
  38392. }
  38393. workingBytes.set(workingData.subarray(position, position + availableBytes));
  38394. workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
  38395. workingBitsAvailable = availableBytes * 8;
  38396. workingBytesAvailable -= availableBytes;
  38397. }; // (count:int):void
  38398. this.skipBits = function (count) {
  38399. var skipBytes; // :int
  38400. if (workingBitsAvailable > count) {
  38401. workingWord <<= count;
  38402. workingBitsAvailable -= count;
  38403. } else {
  38404. count -= workingBitsAvailable;
  38405. skipBytes = Math.floor(count / 8);
  38406. count -= skipBytes * 8;
  38407. workingBytesAvailable -= skipBytes;
  38408. this.loadWord();
  38409. workingWord <<= count;
  38410. workingBitsAvailable -= count;
  38411. }
  38412. }; // (size:int):uint
  38413. this.readBits = function (size) {
  38414. var bits = Math.min(workingBitsAvailable, size),
  38415. // :uint
  38416. valu = workingWord >>> 32 - bits; // :uint
  38417. // if size > 31, handle error
  38418. workingBitsAvailable -= bits;
  38419. if (workingBitsAvailable > 0) {
  38420. workingWord <<= bits;
  38421. } else if (workingBytesAvailable > 0) {
  38422. this.loadWord();
  38423. }
  38424. bits = size - bits;
  38425. if (bits > 0) {
  38426. return valu << bits | this.readBits(bits);
  38427. }
  38428. return valu;
  38429. }; // ():uint
  38430. this.skipLeadingZeros = function () {
  38431. var leadingZeroCount; // :uint
  38432. for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
  38433. if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
  38434. // the first bit of working word is 1
  38435. workingWord <<= leadingZeroCount;
  38436. workingBitsAvailable -= leadingZeroCount;
  38437. return leadingZeroCount;
  38438. }
  38439. } // we exhausted workingWord and still have not found a 1
  38440. this.loadWord();
  38441. return leadingZeroCount + this.skipLeadingZeros();
  38442. }; // ():void
  38443. this.skipUnsignedExpGolomb = function () {
  38444. this.skipBits(1 + this.skipLeadingZeros());
  38445. }; // ():void
  38446. this.skipExpGolomb = function () {
  38447. this.skipBits(1 + this.skipLeadingZeros());
  38448. }; // ():uint
  38449. this.readUnsignedExpGolomb = function () {
  38450. var clz = this.skipLeadingZeros(); // :uint
  38451. return this.readBits(clz + 1) - 1;
  38452. }; // ():int
  38453. this.readExpGolomb = function () {
  38454. var valu = this.readUnsignedExpGolomb(); // :int
  38455. if (0x01 & valu) {
  38456. // the number is odd if the low order bit is set
  38457. return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
  38458. }
  38459. return -1 * (valu >>> 1); // divide by two then make it negative
  38460. }; // Some convenience functions
  38461. // :Boolean
  38462. this.readBoolean = function () {
  38463. return this.readBits(1) === 1;
  38464. }; // ():int
  38465. this.readUnsignedByte = function () {
  38466. return this.readBits(8);
  38467. };
  38468. this.loadWord();
  38469. };
  38470. var expGolomb = ExpGolomb;
  38471. var _H264Stream, _NalByteStream;
  38472. var PROFILES_WITH_OPTIONAL_SPS_DATA;
  38473. /**
  38474. * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
  38475. */
  38476. _NalByteStream = function NalByteStream() {
  38477. var syncPoint = 0,
  38478. i,
  38479. buffer;
  38480. _NalByteStream.prototype.init.call(this);
  38481. /*
  38482. * Scans a byte stream and triggers a data event with the NAL units found.
  38483. * @param {Object} data Event received from H264Stream
  38484. * @param {Uint8Array} data.data The h264 byte stream to be scanned
  38485. *
  38486. * @see H264Stream.push
  38487. */
  38488. this.push = function (data) {
  38489. var swapBuffer;
  38490. if (!buffer) {
  38491. buffer = data.data;
  38492. } else {
  38493. swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
  38494. swapBuffer.set(buffer);
  38495. swapBuffer.set(data.data, buffer.byteLength);
  38496. buffer = swapBuffer;
  38497. } // Rec. ITU-T H.264, Annex B
  38498. // scan for NAL unit boundaries
  38499. // a match looks like this:
  38500. // 0 0 1 .. NAL .. 0 0 1
  38501. // ^ sync point ^ i
  38502. // or this:
  38503. // 0 0 1 .. NAL .. 0 0 0
  38504. // ^ sync point ^ i
  38505. // advance the sync point to a NAL start, if necessary
  38506. for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
  38507. if (buffer[syncPoint + 2] === 1) {
  38508. // the sync point is properly aligned
  38509. i = syncPoint + 5;
  38510. break;
  38511. }
  38512. }
  38513. while (i < buffer.byteLength) {
  38514. // look at the current byte to determine if we've hit the end of
  38515. // a NAL unit boundary
  38516. switch (buffer[i]) {
  38517. case 0:
  38518. // skip past non-sync sequences
  38519. if (buffer[i - 1] !== 0) {
  38520. i += 2;
  38521. break;
  38522. } else if (buffer[i - 2] !== 0) {
  38523. i++;
  38524. break;
  38525. } // deliver the NAL unit if it isn't empty
  38526. if (syncPoint + 3 !== i - 2) {
  38527. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  38528. } // drop trailing zeroes
  38529. do {
  38530. i++;
  38531. } while (buffer[i] !== 1 && i < buffer.length);
  38532. syncPoint = i - 2;
  38533. i += 3;
  38534. break;
  38535. case 1:
  38536. // skip past non-sync sequences
  38537. if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
  38538. i += 3;
  38539. break;
  38540. } // deliver the NAL unit
  38541. this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
  38542. syncPoint = i - 2;
  38543. i += 3;
  38544. break;
  38545. default:
  38546. // the current byte isn't a one or zero, so it cannot be part
  38547. // of a sync sequence
  38548. i += 3;
  38549. break;
  38550. }
  38551. } // filter out the NAL units that were delivered
  38552. buffer = buffer.subarray(syncPoint);
  38553. i -= syncPoint;
  38554. syncPoint = 0;
  38555. };
  38556. this.flush = function () {
  38557. // deliver the last buffered NAL unit
  38558. if (buffer && buffer.byteLength > 3) {
  38559. this.trigger('data', buffer.subarray(syncPoint + 3));
  38560. } // reset the stream state
  38561. buffer = null;
  38562. syncPoint = 0;
  38563. this.trigger('done');
  38564. };
  38565. };
  38566. _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
  38567. // see Recommendation ITU-T H.264 (4/2013),
  38568. // 7.3.2.1.1 Sequence parameter set data syntax
  38569. PROFILES_WITH_OPTIONAL_SPS_DATA = {
  38570. 100: true,
  38571. 110: true,
  38572. 122: true,
  38573. 244: true,
  38574. 44: true,
  38575. 83: true,
  38576. 86: true,
  38577. 118: true,
  38578. 128: true,
  38579. 138: true,
  38580. 139: true,
  38581. 134: true
  38582. };
  38583. /**
  38584. * Accepts input from a ElementaryStream and produces H.264 NAL unit data
  38585. * events.
  38586. */
  38587. _H264Stream = function H264Stream() {
  38588. var nalByteStream = new _NalByteStream(),
  38589. self,
  38590. trackId,
  38591. currentPts,
  38592. currentDts,
  38593. discardEmulationPreventionBytes,
  38594. readSequenceParameterSet,
  38595. skipScalingList;
  38596. _H264Stream.prototype.init.call(this);
  38597. self = this;
  38598. /*
  38599. * Pushes a packet from a stream onto the NalByteStream
  38600. *
  38601. * @param {Object} packet - A packet received from a stream
  38602. * @param {Uint8Array} packet.data - The raw bytes of the packet
  38603. * @param {Number} packet.dts - Decode timestamp of the packet
  38604. * @param {Number} packet.pts - Presentation timestamp of the packet
  38605. * @param {Number} packet.trackId - The id of the h264 track this packet came from
  38606. * @param {('video'|'audio')} packet.type - The type of packet
  38607. *
  38608. */
  38609. this.push = function (packet) {
  38610. if (packet.type !== 'video') {
  38611. return;
  38612. }
  38613. trackId = packet.trackId;
  38614. currentPts = packet.pts;
  38615. currentDts = packet.dts;
  38616. nalByteStream.push(packet);
  38617. };
  38618. /*
  38619. * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
  38620. * for the NALUs to the next stream component.
  38621. * Also, preprocess caption and sequence parameter NALUs.
  38622. *
  38623. * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
  38624. * @see NalByteStream.push
  38625. */
  38626. nalByteStream.on('data', function (data) {
  38627. var event = {
  38628. trackId: trackId,
  38629. pts: currentPts,
  38630. dts: currentDts,
  38631. data: data
  38632. };
  38633. switch (data[0] & 0x1f) {
  38634. case 0x05:
  38635. event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
  38636. break;
  38637. case 0x06:
  38638. event.nalUnitType = 'sei_rbsp';
  38639. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  38640. break;
  38641. case 0x07:
  38642. event.nalUnitType = 'seq_parameter_set_rbsp';
  38643. event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
  38644. event.config = readSequenceParameterSet(event.escapedRBSP);
  38645. break;
  38646. case 0x08:
  38647. event.nalUnitType = 'pic_parameter_set_rbsp';
  38648. break;
  38649. case 0x09:
  38650. event.nalUnitType = 'access_unit_delimiter_rbsp';
  38651. break;
  38652. default:
  38653. break;
  38654. } // This triggers data on the H264Stream
  38655. self.trigger('data', event);
  38656. });
  38657. nalByteStream.on('done', function () {
  38658. self.trigger('done');
  38659. });
  38660. this.flush = function () {
  38661. nalByteStream.flush();
  38662. };
  38663. /**
  38664. * Advance the ExpGolomb decoder past a scaling list. The scaling
  38665. * list is optionally transmitted as part of a sequence parameter
  38666. * set and is not relevant to transmuxing.
  38667. * @param count {number} the number of entries in this scaling list
  38668. * @param expGolombDecoder {object} an ExpGolomb pointed to the
  38669. * start of a scaling list
  38670. * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  38671. */
  38672. skipScalingList = function skipScalingList(count, expGolombDecoder) {
  38673. var lastScale = 8,
  38674. nextScale = 8,
  38675. j,
  38676. deltaScale;
  38677. for (j = 0; j < count; j++) {
  38678. if (nextScale !== 0) {
  38679. deltaScale = expGolombDecoder.readExpGolomb();
  38680. nextScale = (lastScale + deltaScale + 256) % 256;
  38681. }
  38682. lastScale = nextScale === 0 ? lastScale : nextScale;
  38683. }
  38684. };
  38685. /**
  38686. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  38687. * Sequence Payload"
  38688. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  38689. * unit
  38690. * @return {Uint8Array} the RBSP without any Emulation
  38691. * Prevention Bytes
  38692. */
  38693. discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  38694. var length = data.byteLength,
  38695. emulationPreventionBytesPositions = [],
  38696. i = 1,
  38697. newLength,
  38698. newData; // Find all `Emulation Prevention Bytes`
  38699. while (i < length - 2) {
  38700. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  38701. emulationPreventionBytesPositions.push(i + 2);
  38702. i += 2;
  38703. } else {
  38704. i++;
  38705. }
  38706. } // If no Emulation Prevention Bytes were found just return the original
  38707. // array
  38708. if (emulationPreventionBytesPositions.length === 0) {
  38709. return data;
  38710. } // Create a new array to hold the NAL unit data
  38711. newLength = length - emulationPreventionBytesPositions.length;
  38712. newData = new Uint8Array(newLength);
  38713. var sourceIndex = 0;
  38714. for (i = 0; i < newLength; sourceIndex++, i++) {
  38715. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  38716. // Skip this byte
  38717. sourceIndex++; // Remove this position index
  38718. emulationPreventionBytesPositions.shift();
  38719. }
  38720. newData[i] = data[sourceIndex];
  38721. }
  38722. return newData;
  38723. };
  38724. /**
  38725. * Read a sequence parameter set and return some interesting video
  38726. * properties. A sequence parameter set is the H264 metadata that
  38727. * describes the properties of upcoming video frames.
  38728. * @param data {Uint8Array} the bytes of a sequence parameter set
  38729. * @return {object} an object with configuration parsed from the
  38730. * sequence parameter set, including the dimensions of the
  38731. * associated video frames.
  38732. */
  38733. readSequenceParameterSet = function readSequenceParameterSet(data) {
  38734. var frameCropLeftOffset = 0,
  38735. frameCropRightOffset = 0,
  38736. frameCropTopOffset = 0,
  38737. frameCropBottomOffset = 0,
  38738. sarScale = 1,
  38739. expGolombDecoder,
  38740. profileIdc,
  38741. levelIdc,
  38742. profileCompatibility,
  38743. chromaFormatIdc,
  38744. picOrderCntType,
  38745. numRefFramesInPicOrderCntCycle,
  38746. picWidthInMbsMinus1,
  38747. picHeightInMapUnitsMinus1,
  38748. frameMbsOnlyFlag,
  38749. scalingListCount,
  38750. sarRatio,
  38751. aspectRatioIdc,
  38752. i;
  38753. expGolombDecoder = new expGolomb(data);
  38754. profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
  38755. profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
  38756. levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
  38757. expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
  38758. // some profiles have more optional data we don't need
  38759. if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
  38760. chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  38761. if (chromaFormatIdc === 3) {
  38762. expGolombDecoder.skipBits(1); // separate_colour_plane_flag
  38763. }
  38764. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
  38765. expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
  38766. expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  38767. if (expGolombDecoder.readBoolean()) {
  38768. // seq_scaling_matrix_present_flag
  38769. scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
  38770. for (i = 0; i < scalingListCount; i++) {
  38771. if (expGolombDecoder.readBoolean()) {
  38772. // seq_scaling_list_present_flag[ i ]
  38773. if (i < 6) {
  38774. skipScalingList(16, expGolombDecoder);
  38775. } else {
  38776. skipScalingList(64, expGolombDecoder);
  38777. }
  38778. }
  38779. }
  38780. }
  38781. }
  38782. expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
  38783. picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  38784. if (picOrderCntType === 0) {
  38785. expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
  38786. } else if (picOrderCntType === 1) {
  38787. expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
  38788. expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
  38789. expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
  38790. numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
  38791. for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  38792. expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
  38793. }
  38794. }
  38795. expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
  38796. expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  38797. picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  38798. picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
  38799. frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  38800. if (frameMbsOnlyFlag === 0) {
  38801. expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
  38802. }
  38803. expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
  38804. if (expGolombDecoder.readBoolean()) {
  38805. // frame_cropping_flag
  38806. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  38807. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  38808. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  38809. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  38810. }
  38811. if (expGolombDecoder.readBoolean()) {
  38812. // vui_parameters_present_flag
  38813. if (expGolombDecoder.readBoolean()) {
  38814. // aspect_ratio_info_present_flag
  38815. aspectRatioIdc = expGolombDecoder.readUnsignedByte();
  38816. switch (aspectRatioIdc) {
  38817. case 1:
  38818. sarRatio = [1, 1];
  38819. break;
  38820. case 2:
  38821. sarRatio = [12, 11];
  38822. break;
  38823. case 3:
  38824. sarRatio = [10, 11];
  38825. break;
  38826. case 4:
  38827. sarRatio = [16, 11];
  38828. break;
  38829. case 5:
  38830. sarRatio = [40, 33];
  38831. break;
  38832. case 6:
  38833. sarRatio = [24, 11];
  38834. break;
  38835. case 7:
  38836. sarRatio = [20, 11];
  38837. break;
  38838. case 8:
  38839. sarRatio = [32, 11];
  38840. break;
  38841. case 9:
  38842. sarRatio = [80, 33];
  38843. break;
  38844. case 10:
  38845. sarRatio = [18, 11];
  38846. break;
  38847. case 11:
  38848. sarRatio = [15, 11];
  38849. break;
  38850. case 12:
  38851. sarRatio = [64, 33];
  38852. break;
  38853. case 13:
  38854. sarRatio = [160, 99];
  38855. break;
  38856. case 14:
  38857. sarRatio = [4, 3];
  38858. break;
  38859. case 15:
  38860. sarRatio = [3, 2];
  38861. break;
  38862. case 16:
  38863. sarRatio = [2, 1];
  38864. break;
  38865. case 255:
  38866. {
  38867. sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
  38868. break;
  38869. }
  38870. }
  38871. if (sarRatio) {
  38872. sarScale = sarRatio[0] / sarRatio[1];
  38873. }
  38874. }
  38875. }
  38876. return {
  38877. profileIdc: profileIdc,
  38878. levelIdc: levelIdc,
  38879. profileCompatibility: profileCompatibility,
  38880. width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
  38881. height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
  38882. };
  38883. };
  38884. };
  38885. _H264Stream.prototype = new stream();
  38886. var h264 = {
  38887. H264Stream: _H264Stream,
  38888. NalByteStream: _NalByteStream
  38889. };
  38890. /**
  38891. * mux.js
  38892. *
  38893. * Copyright (c) 2016 Brightcove
  38894. * All rights reserved.
  38895. *
  38896. * Utilities to detect basic properties and metadata about Aac data.
  38897. */
  38898. var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
  38899. var isLikelyAacData = function isLikelyAacData(data) {
  38900. if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
  38901. return true;
  38902. }
  38903. return false;
  38904. };
  38905. var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
  38906. return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
  38907. }; // return a percent-encoded representation of the specified byte range
  38908. // @see http://en.wikipedia.org/wiki/Percent-encoding
  38909. var percentEncode$1 = function percentEncode(bytes, start, end) {
  38910. var i,
  38911. result = '';
  38912. for (i = start; i < end; i++) {
  38913. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  38914. }
  38915. return result;
  38916. }; // return the string representation of the specified byte range,
  38917. // interpreted as ISO-8859-1.
  38918. var parseIso88591$1 = function parseIso88591(bytes, start, end) {
  38919. return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
  38920. };
  38921. var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
  38922. var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
  38923. flags = header[byteIndex + 5],
  38924. footerPresent = (flags & 16) >> 4;
  38925. if (footerPresent) {
  38926. return returnSize + 20;
  38927. }
  38928. return returnSize + 10;
  38929. };
  38930. var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
  38931. var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
  38932. middle = header[byteIndex + 4] << 3,
  38933. highTwo = header[byteIndex + 3] & 0x3 << 11;
  38934. return highTwo | middle | lowThree;
  38935. };
  38936. var parseType$1 = function parseType(header, byteIndex) {
  38937. if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
  38938. return 'timed-metadata';
  38939. } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
  38940. return 'audio';
  38941. }
  38942. return null;
  38943. };
  38944. var parseSampleRate = function parseSampleRate(packet) {
  38945. var i = 0;
  38946. while (i + 5 < packet.length) {
  38947. if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
  38948. // If a valid header was not found, jump one forward and attempt to
  38949. // find a valid ADTS header starting at the next byte
  38950. i++;
  38951. continue;
  38952. }
  38953. return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
  38954. }
  38955. return null;
  38956. };
  38957. var parseAacTimestamp = function parseAacTimestamp(packet) {
  38958. var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
  38959. frameStart = 10;
  38960. if (packet[5] & 0x40) {
  38961. // advance the frame start past the extended header
  38962. frameStart += 4; // header size field
  38963. frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
  38964. } // parse one or more ID3 frames
  38965. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  38966. do {
  38967. // determine the number of bytes in this frame
  38968. frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
  38969. if (frameSize < 1) {
  38970. return null;
  38971. }
  38972. frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
  38973. if (frameHeader === 'PRIV') {
  38974. frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
  38975. for (var i = 0; i < frame.byteLength; i++) {
  38976. if (frame[i] === 0) {
  38977. var owner = parseIso88591$1(frame, 0, i);
  38978. if (owner === 'com.apple.streaming.transportStreamTimestamp') {
  38979. var d = frame.subarray(i + 1);
  38980. var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
  38981. size *= 4;
  38982. size += d[7] & 0x03;
  38983. return size;
  38984. }
  38985. break;
  38986. }
  38987. }
  38988. }
  38989. frameStart += 10; // advance past the frame header
  38990. frameStart += frameSize; // advance past the frame body
  38991. } while (frameStart < packet.byteLength);
  38992. return null;
  38993. };
  38994. var utils = {
  38995. isLikelyAacData: isLikelyAacData,
  38996. parseId3TagSize: parseId3TagSize,
  38997. parseAdtsSize: parseAdtsSize,
  38998. parseType: parseType$1,
  38999. parseSampleRate: parseSampleRate,
  39000. parseAacTimestamp: parseAacTimestamp
  39001. }; // Constants
  39002. var _AacStream;
  39003. /**
  39004. * Splits an incoming stream of binary data into ADTS and ID3 Frames.
  39005. */
  39006. _AacStream = function AacStream() {
  39007. var everything = new Uint8Array(),
  39008. timeStamp = 0;
  39009. _AacStream.prototype.init.call(this);
  39010. this.setTimestamp = function (timestamp) {
  39011. timeStamp = timestamp;
  39012. };
  39013. this.push = function (bytes) {
  39014. var frameSize = 0,
  39015. byteIndex = 0,
  39016. bytesLeft,
  39017. chunk,
  39018. packet,
  39019. tempLength; // If there are bytes remaining from the last segment, prepend them to the
  39020. // bytes that were pushed in
  39021. if (everything.length) {
  39022. tempLength = everything.length;
  39023. everything = new Uint8Array(bytes.byteLength + tempLength);
  39024. everything.set(everything.subarray(0, tempLength));
  39025. everything.set(bytes, tempLength);
  39026. } else {
  39027. everything = bytes;
  39028. }
  39029. while (everything.length - byteIndex >= 3) {
  39030. if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
  39031. // Exit early because we don't have enough to parse
  39032. // the ID3 tag header
  39033. if (everything.length - byteIndex < 10) {
  39034. break;
  39035. } // check framesize
  39036. frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  39037. // to emit a full packet
  39038. // Add to byteIndex to support multiple ID3 tags in sequence
  39039. if (byteIndex + frameSize > everything.length) {
  39040. break;
  39041. }
  39042. chunk = {
  39043. type: 'timed-metadata',
  39044. data: everything.subarray(byteIndex, byteIndex + frameSize)
  39045. };
  39046. this.trigger('data', chunk);
  39047. byteIndex += frameSize;
  39048. continue;
  39049. } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
  39050. // Exit early because we don't have enough to parse
  39051. // the ADTS frame header
  39052. if (everything.length - byteIndex < 7) {
  39053. break;
  39054. }
  39055. frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
  39056. // to emit a full packet
  39057. if (byteIndex + frameSize > everything.length) {
  39058. break;
  39059. }
  39060. packet = {
  39061. type: 'audio',
  39062. data: everything.subarray(byteIndex, byteIndex + frameSize),
  39063. pts: timeStamp,
  39064. dts: timeStamp
  39065. };
  39066. this.trigger('data', packet);
  39067. byteIndex += frameSize;
  39068. continue;
  39069. }
  39070. byteIndex++;
  39071. }
  39072. bytesLeft = everything.length - byteIndex;
  39073. if (bytesLeft > 0) {
  39074. everything = everything.subarray(byteIndex);
  39075. } else {
  39076. everything = new Uint8Array();
  39077. }
  39078. };
  39079. };
  39080. _AacStream.prototype = new stream();
  39081. var aac = _AacStream;
  39082. var H264Stream = h264.H264Stream;
  39083. var isLikelyAacData$1 = utils.isLikelyAacData; // constants
  39084. var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
  39085. var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types
  39086. var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
  39087. /**
  39088. * Compare two arrays (even typed) for same-ness
  39089. */
  39090. var arrayEquals = function arrayEquals(a, b) {
  39091. var i;
  39092. if (a.length !== b.length) {
  39093. return false;
  39094. } // compare the value of each element in the array
  39095. for (i = 0; i < a.length; i++) {
  39096. if (a[i] !== b[i]) {
  39097. return false;
  39098. }
  39099. }
  39100. return true;
  39101. };
  39102. var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
  39103. var ptsOffsetFromDts = startPts - startDts,
  39104. decodeDuration = endDts - startDts,
  39105. presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
  39106. // however, the player time values will reflect a start from the baseMediaDecodeTime.
  39107. // In order to provide relevant values for the player times, base timing info on the
  39108. // baseMediaDecodeTime and the DTS and PTS durations of the segment.
  39109. return {
  39110. start: {
  39111. dts: baseMediaDecodeTime,
  39112. pts: baseMediaDecodeTime + ptsOffsetFromDts
  39113. },
  39114. end: {
  39115. dts: baseMediaDecodeTime + decodeDuration,
  39116. pts: baseMediaDecodeTime + presentationDuration
  39117. },
  39118. prependedContentDuration: prependedContentDuration,
  39119. baseMediaDecodeTime: baseMediaDecodeTime
  39120. };
  39121. };
  39122. /**
  39123. * Constructs a single-track, ISO BMFF media segment from AAC data
  39124. * events. The output of this stream can be fed to a SourceBuffer
  39125. * configured with a suitable initialization segment.
  39126. * @param track {object} track metadata configuration
  39127. * @param options {object} transmuxer options object
  39128. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  39129. * in the source; false to adjust the first segment to start at 0.
  39130. */
  39131. _AudioSegmentStream = function AudioSegmentStream(track, options) {
  39132. var adtsFrames = [],
  39133. sequenceNumber = 0,
  39134. earliestAllowedDts = 0,
  39135. audioAppendStartTs = 0,
  39136. videoBaseMediaDecodeTime = Infinity;
  39137. options = options || {};
  39138. _AudioSegmentStream.prototype.init.call(this);
  39139. this.push = function (data) {
  39140. trackDecodeInfo.collectDtsInfo(track, data);
  39141. if (track) {
  39142. AUDIO_PROPERTIES.forEach(function (prop) {
  39143. track[prop] = data[prop];
  39144. });
  39145. } // buffer audio data until end() is called
  39146. adtsFrames.push(data);
  39147. };
  39148. this.setEarliestDts = function (earliestDts) {
  39149. earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
  39150. };
  39151. this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  39152. videoBaseMediaDecodeTime = baseMediaDecodeTime;
  39153. };
  39154. this.setAudioAppendStart = function (timestamp) {
  39155. audioAppendStartTs = timestamp;
  39156. };
  39157. this.flush = function () {
  39158. var frames, moof, mdat, boxes; // return early if no audio data has been observed
  39159. if (adtsFrames.length === 0) {
  39160. this.trigger('done', 'AudioSegmentStream');
  39161. return;
  39162. }
  39163. frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
  39164. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  39165. audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
  39166. // samples (that is, adts frames) in the audio data
  39167. track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
  39168. mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
  39169. adtsFrames = [];
  39170. moof = mp4Generator.moof(sequenceNumber, [track]);
  39171. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
  39172. sequenceNumber++;
  39173. boxes.set(moof);
  39174. boxes.set(mdat, moof.byteLength);
  39175. trackDecodeInfo.clearDtsInfo(track);
  39176. this.trigger('data', {
  39177. track: track,
  39178. boxes: boxes
  39179. });
  39180. this.trigger('done', 'AudioSegmentStream');
  39181. };
  39182. };
  39183. _AudioSegmentStream.prototype = new stream();
  39184. /**
  39185. * Constructs a single-track, ISO BMFF media segment from H264 data
  39186. * events. The output of this stream can be fed to a SourceBuffer
  39187. * configured with a suitable initialization segment.
  39188. * @param track {object} track metadata configuration
  39189. * @param options {object} transmuxer options object
  39190. * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
  39191. * gopsToAlignWith list when attempting to align gop pts
  39192. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  39193. * in the source; false to adjust the first segment to start at 0.
  39194. */
  39195. _VideoSegmentStream = function VideoSegmentStream(track, options) {
  39196. var sequenceNumber = 0,
  39197. nalUnits = [],
  39198. gopsToAlignWith = [],
  39199. config,
  39200. pps;
  39201. options = options || {};
  39202. _VideoSegmentStream.prototype.init.call(this);
  39203. delete track.minPTS;
  39204. this.gopCache_ = [];
  39205. /**
  39206. * Constructs a ISO BMFF segment given H264 nalUnits
  39207. * @param {Object} nalUnit A data event representing a nalUnit
  39208. * @param {String} nalUnit.nalUnitType
  39209. * @param {Object} nalUnit.config Properties for a mp4 track
  39210. * @param {Uint8Array} nalUnit.data The nalUnit bytes
  39211. * @see lib/codecs/h264.js
  39212. **/
  39213. this.push = function (nalUnit) {
  39214. trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
  39215. if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
  39216. config = nalUnit.config;
  39217. track.sps = [nalUnit.data];
  39218. VIDEO_PROPERTIES.forEach(function (prop) {
  39219. track[prop] = config[prop];
  39220. }, this);
  39221. }
  39222. if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
  39223. pps = nalUnit.data;
  39224. track.pps = [nalUnit.data];
  39225. } // buffer video until flush() is called
  39226. nalUnits.push(nalUnit);
  39227. };
  39228. /**
  39229. * Pass constructed ISO BMFF track and boxes on to the
  39230. * next stream in the pipeline
  39231. **/
  39232. this.flush = function () {
  39233. var frames,
  39234. gopForFusion,
  39235. gops,
  39236. moof,
  39237. mdat,
  39238. boxes,
  39239. prependedContentDuration = 0,
  39240. firstGop,
  39241. lastGop; // Throw away nalUnits at the start of the byte stream until
  39242. // we find the first AUD
  39243. while (nalUnits.length) {
  39244. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  39245. break;
  39246. }
  39247. nalUnits.shift();
  39248. } // Return early if no video data has been observed
  39249. if (nalUnits.length === 0) {
  39250. this.resetStream_();
  39251. this.trigger('done', 'VideoSegmentStream');
  39252. return;
  39253. } // Organize the raw nal-units into arrays that represent
  39254. // higher-level constructs such as frames and gops
  39255. // (group-of-pictures)
  39256. frames = frameUtils.groupNalsIntoFrames(nalUnits);
  39257. gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
  39258. // a problem since MSE (on Chrome) requires a leading keyframe.
  39259. //
  39260. // We have two approaches to repairing this situation:
  39261. // 1) GOP-FUSION:
  39262. // This is where we keep track of the GOPS (group-of-pictures)
  39263. // from previous fragments and attempt to find one that we can
  39264. // prepend to the current fragment in order to create a valid
  39265. // fragment.
  39266. // 2) KEYFRAME-PULLING:
  39267. // Here we search for the first keyframe in the fragment and
  39268. // throw away all the frames between the start of the fragment
  39269. // and that keyframe. We then extend the duration and pull the
  39270. // PTS of the keyframe forward so that it covers the time range
  39271. // of the frames that were disposed of.
  39272. //
  39273. // #1 is far prefereable over #2 which can cause "stuttering" but
  39274. // requires more things to be just right.
  39275. if (!gops[0][0].keyFrame) {
  39276. // Search for a gop for fusion from our gopCache
  39277. gopForFusion = this.getGopForFusion_(nalUnits[0], track);
  39278. if (gopForFusion) {
  39279. // in order to provide more accurate timing information about the segment, save
  39280. // the number of seconds prepended to the original segment due to GOP fusion
  39281. prependedContentDuration = gopForFusion.duration;
  39282. gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
  39283. // new gop at the beginning
  39284. gops.byteLength += gopForFusion.byteLength;
  39285. gops.nalCount += gopForFusion.nalCount;
  39286. gops.pts = gopForFusion.pts;
  39287. gops.dts = gopForFusion.dts;
  39288. gops.duration += gopForFusion.duration;
  39289. } else {
  39290. // If we didn't find a candidate gop fall back to keyframe-pulling
  39291. gops = frameUtils.extendFirstKeyFrame(gops);
  39292. }
  39293. } // Trim gops to align with gopsToAlignWith
  39294. if (gopsToAlignWith.length) {
  39295. var alignedGops;
  39296. if (options.alignGopsAtEnd) {
  39297. alignedGops = this.alignGopsAtEnd_(gops);
  39298. } else {
  39299. alignedGops = this.alignGopsAtStart_(gops);
  39300. }
  39301. if (!alignedGops) {
  39302. // save all the nals in the last GOP into the gop cache
  39303. this.gopCache_.unshift({
  39304. gop: gops.pop(),
  39305. pps: track.pps,
  39306. sps: track.sps
  39307. }); // Keep a maximum of 6 GOPs in the cache
  39308. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  39309. nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
  39310. this.resetStream_();
  39311. this.trigger('done', 'VideoSegmentStream');
  39312. return;
  39313. } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
  39314. // when recalculated before sending off to CoalesceStream
  39315. trackDecodeInfo.clearDtsInfo(track);
  39316. gops = alignedGops;
  39317. }
  39318. trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
  39319. // samples (that is, frames) in the video data
  39320. track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
  39321. mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
  39322. track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  39323. this.trigger('processedGopsInfo', gops.map(function (gop) {
  39324. return {
  39325. pts: gop.pts,
  39326. dts: gop.dts,
  39327. byteLength: gop.byteLength
  39328. };
  39329. }));
  39330. firstGop = gops[0];
  39331. lastGop = gops[gops.length - 1];
  39332. 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
  39333. this.gopCache_.unshift({
  39334. gop: gops.pop(),
  39335. pps: track.pps,
  39336. sps: track.sps
  39337. }); // Keep a maximum of 6 GOPs in the cache
  39338. this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
  39339. nalUnits = [];
  39340. this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
  39341. this.trigger('timelineStartInfo', track.timelineStartInfo);
  39342. moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
  39343. // throwing away hundreds of media segment fragments
  39344. boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
  39345. sequenceNumber++;
  39346. boxes.set(moof);
  39347. boxes.set(mdat, moof.byteLength);
  39348. this.trigger('data', {
  39349. track: track,
  39350. boxes: boxes
  39351. });
  39352. this.resetStream_(); // Continue with the flush process now
  39353. this.trigger('done', 'VideoSegmentStream');
  39354. };
  39355. this.resetStream_ = function () {
  39356. trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
  39357. // for instance, when we are rendition switching
  39358. config = undefined;
  39359. pps = undefined;
  39360. }; // Search for a candidate Gop for gop-fusion from the gop cache and
  39361. // return it or return null if no good candidate was found
  39362. this.getGopForFusion_ = function (nalUnit) {
  39363. var halfSecond = 45000,
  39364. // Half-a-second in a 90khz clock
  39365. allowableOverlap = 10000,
  39366. // About 3 frames @ 30fps
  39367. nearestDistance = Infinity,
  39368. dtsDistance,
  39369. nearestGopObj,
  39370. currentGop,
  39371. currentGopObj,
  39372. i; // Search for the GOP nearest to the beginning of this nal unit
  39373. for (i = 0; i < this.gopCache_.length; i++) {
  39374. currentGopObj = this.gopCache_[i];
  39375. currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
  39376. if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
  39377. continue;
  39378. } // Reject Gops that would require a negative baseMediaDecodeTime
  39379. if (currentGop.dts < track.timelineStartInfo.dts) {
  39380. continue;
  39381. } // The distance between the end of the gop and the start of the nalUnit
  39382. dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
  39383. // a half-second of the nal unit
  39384. if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
  39385. // Always use the closest GOP we found if there is more than
  39386. // one candidate
  39387. if (!nearestGopObj || nearestDistance > dtsDistance) {
  39388. nearestGopObj = currentGopObj;
  39389. nearestDistance = dtsDistance;
  39390. }
  39391. }
  39392. }
  39393. if (nearestGopObj) {
  39394. return nearestGopObj.gop;
  39395. }
  39396. return null;
  39397. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  39398. // of gopsToAlignWith starting from the START of the list
  39399. this.alignGopsAtStart_ = function (gops) {
  39400. var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
  39401. byteLength = gops.byteLength;
  39402. nalCount = gops.nalCount;
  39403. duration = gops.duration;
  39404. alignIndex = gopIndex = 0;
  39405. while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
  39406. align = gopsToAlignWith[alignIndex];
  39407. gop = gops[gopIndex];
  39408. if (align.pts === gop.pts) {
  39409. break;
  39410. }
  39411. if (gop.pts > align.pts) {
  39412. // this current gop starts after the current gop we want to align on, so increment
  39413. // align index
  39414. alignIndex++;
  39415. continue;
  39416. } // current gop starts before the current gop we want to align on. so increment gop
  39417. // index
  39418. gopIndex++;
  39419. byteLength -= gop.byteLength;
  39420. nalCount -= gop.nalCount;
  39421. duration -= gop.duration;
  39422. }
  39423. if (gopIndex === 0) {
  39424. // no gops to trim
  39425. return gops;
  39426. }
  39427. if (gopIndex === gops.length) {
  39428. // all gops trimmed, skip appending all gops
  39429. return null;
  39430. }
  39431. alignedGops = gops.slice(gopIndex);
  39432. alignedGops.byteLength = byteLength;
  39433. alignedGops.duration = duration;
  39434. alignedGops.nalCount = nalCount;
  39435. alignedGops.pts = alignedGops[0].pts;
  39436. alignedGops.dts = alignedGops[0].dts;
  39437. return alignedGops;
  39438. }; // trim gop list to the first gop found that has a matching pts with a gop in the list
  39439. // of gopsToAlignWith starting from the END of the list
  39440. this.alignGopsAtEnd_ = function (gops) {
  39441. var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
  39442. alignIndex = gopsToAlignWith.length - 1;
  39443. gopIndex = gops.length - 1;
  39444. alignEndIndex = null;
  39445. matchFound = false;
  39446. while (alignIndex >= 0 && gopIndex >= 0) {
  39447. align = gopsToAlignWith[alignIndex];
  39448. gop = gops[gopIndex];
  39449. if (align.pts === gop.pts) {
  39450. matchFound = true;
  39451. break;
  39452. }
  39453. if (align.pts > gop.pts) {
  39454. alignIndex--;
  39455. continue;
  39456. }
  39457. if (alignIndex === gopsToAlignWith.length - 1) {
  39458. // gop.pts is greater than the last alignment candidate. If no match is found
  39459. // by the end of this loop, we still want to append gops that come after this
  39460. // point
  39461. alignEndIndex = gopIndex;
  39462. }
  39463. gopIndex--;
  39464. }
  39465. if (!matchFound && alignEndIndex === null) {
  39466. return null;
  39467. }
  39468. var trimIndex;
  39469. if (matchFound) {
  39470. trimIndex = gopIndex;
  39471. } else {
  39472. trimIndex = alignEndIndex;
  39473. }
  39474. if (trimIndex === 0) {
  39475. return gops;
  39476. }
  39477. var alignedGops = gops.slice(trimIndex);
  39478. var metadata = alignedGops.reduce(function (total, gop) {
  39479. total.byteLength += gop.byteLength;
  39480. total.duration += gop.duration;
  39481. total.nalCount += gop.nalCount;
  39482. return total;
  39483. }, {
  39484. byteLength: 0,
  39485. duration: 0,
  39486. nalCount: 0
  39487. });
  39488. alignedGops.byteLength = metadata.byteLength;
  39489. alignedGops.duration = metadata.duration;
  39490. alignedGops.nalCount = metadata.nalCount;
  39491. alignedGops.pts = alignedGops[0].pts;
  39492. alignedGops.dts = alignedGops[0].dts;
  39493. return alignedGops;
  39494. };
  39495. this.alignGopsWith = function (newGopsToAlignWith) {
  39496. gopsToAlignWith = newGopsToAlignWith;
  39497. };
  39498. };
  39499. _VideoSegmentStream.prototype = new stream();
  39500. /**
  39501. * A Stream that can combine multiple streams (ie. audio & video)
  39502. * into a single output segment for MSE. Also supports audio-only
  39503. * and video-only streams.
  39504. * @param options {object} transmuxer options object
  39505. * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
  39506. * in the source; false to adjust the first segment to start at media timeline start.
  39507. */
  39508. _CoalesceStream = function CoalesceStream(options, metadataStream) {
  39509. // Number of Tracks per output segment
  39510. // If greater than 1, we combine multiple
  39511. // tracks into a single segment
  39512. this.numberOfTracks = 0;
  39513. this.metadataStream = metadataStream;
  39514. options = options || {};
  39515. if (typeof options.remux !== 'undefined') {
  39516. this.remuxTracks = !!options.remux;
  39517. } else {
  39518. this.remuxTracks = true;
  39519. }
  39520. if (typeof options.keepOriginalTimestamps === 'boolean') {
  39521. this.keepOriginalTimestamps = options.keepOriginalTimestamps;
  39522. }
  39523. this.pendingTracks = [];
  39524. this.videoTrack = null;
  39525. this.pendingBoxes = [];
  39526. this.pendingCaptions = [];
  39527. this.pendingMetadata = [];
  39528. this.pendingBytes = 0;
  39529. this.emittedTracks = 0;
  39530. _CoalesceStream.prototype.init.call(this); // Take output from multiple
  39531. this.push = function (output) {
  39532. // buffer incoming captions until the associated video segment
  39533. // finishes
  39534. if (output.text) {
  39535. return this.pendingCaptions.push(output);
  39536. } // buffer incoming id3 tags until the final flush
  39537. if (output.frames) {
  39538. return this.pendingMetadata.push(output);
  39539. } // Add this track to the list of pending tracks and store
  39540. // important information required for the construction of
  39541. // the final segment
  39542. this.pendingTracks.push(output.track);
  39543. this.pendingBoxes.push(output.boxes);
  39544. this.pendingBytes += output.boxes.byteLength;
  39545. if (output.track.type === 'video') {
  39546. this.videoTrack = output.track;
  39547. }
  39548. if (output.track.type === 'audio') {
  39549. this.audioTrack = output.track;
  39550. }
  39551. };
  39552. };
  39553. _CoalesceStream.prototype = new stream();
  39554. _CoalesceStream.prototype.flush = function (flushSource) {
  39555. var offset = 0,
  39556. event = {
  39557. captions: [],
  39558. captionStreams: {},
  39559. metadata: [],
  39560. info: {}
  39561. },
  39562. caption,
  39563. id3,
  39564. initSegment,
  39565. timelineStartPts = 0,
  39566. i;
  39567. if (this.pendingTracks.length < this.numberOfTracks) {
  39568. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  39569. // Return because we haven't received a flush from a data-generating
  39570. // portion of the segment (meaning that we have only recieved meta-data
  39571. // or captions.)
  39572. return;
  39573. } else if (this.remuxTracks) {
  39574. // Return until we have enough tracks from the pipeline to remux (if we
  39575. // are remuxing audio and video into a single MP4)
  39576. return;
  39577. } else if (this.pendingTracks.length === 0) {
  39578. // In the case where we receive a flush without any data having been
  39579. // received we consider it an emitted track for the purposes of coalescing
  39580. // `done` events.
  39581. // We do this for the case where there is an audio and video track in the
  39582. // segment but no audio data. (seen in several playlists with alternate
  39583. // audio tracks and no audio present in the main TS segments.)
  39584. this.emittedTracks++;
  39585. if (this.emittedTracks >= this.numberOfTracks) {
  39586. this.trigger('done');
  39587. this.emittedTracks = 0;
  39588. }
  39589. return;
  39590. }
  39591. }
  39592. if (this.videoTrack) {
  39593. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  39594. VIDEO_PROPERTIES.forEach(function (prop) {
  39595. event.info[prop] = this.videoTrack[prop];
  39596. }, this);
  39597. } else if (this.audioTrack) {
  39598. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  39599. AUDIO_PROPERTIES.forEach(function (prop) {
  39600. event.info[prop] = this.audioTrack[prop];
  39601. }, this);
  39602. }
  39603. if (this.pendingTracks.length === 1) {
  39604. event.type = this.pendingTracks[0].type;
  39605. } else {
  39606. event.type = 'combined';
  39607. }
  39608. this.emittedTracks += this.pendingTracks.length;
  39609. initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
  39610. event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
  39611. // and track definitions
  39612. event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
  39613. event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
  39614. for (i = 0; i < this.pendingBoxes.length; i++) {
  39615. event.data.set(this.pendingBoxes[i], offset);
  39616. offset += this.pendingBoxes[i].byteLength;
  39617. } // Translate caption PTS times into second offsets to match the
  39618. // video timeline for the segment, and add track info
  39619. for (i = 0; i < this.pendingCaptions.length; i++) {
  39620. caption = this.pendingCaptions[i];
  39621. caption.startTime = caption.startPts;
  39622. if (!this.keepOriginalTimestamps) {
  39623. caption.startTime -= timelineStartPts;
  39624. }
  39625. caption.startTime /= 90e3;
  39626. caption.endTime = caption.endPts;
  39627. if (!this.keepOriginalTimestamps) {
  39628. caption.endTime -= timelineStartPts;
  39629. }
  39630. caption.endTime /= 90e3;
  39631. event.captionStreams[caption.stream] = true;
  39632. event.captions.push(caption);
  39633. } // Translate ID3 frame PTS times into second offsets to match the
  39634. // video timeline for the segment
  39635. for (i = 0; i < this.pendingMetadata.length; i++) {
  39636. id3 = this.pendingMetadata[i];
  39637. id3.cueTime = id3.pts;
  39638. if (!this.keepOriginalTimestamps) {
  39639. id3.cueTime -= timelineStartPts;
  39640. }
  39641. id3.cueTime /= 90e3;
  39642. event.metadata.push(id3);
  39643. } // We add this to every single emitted segment even though we only need
  39644. // it for the first
  39645. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  39646. this.pendingTracks.length = 0;
  39647. this.videoTrack = null;
  39648. this.pendingBoxes.length = 0;
  39649. this.pendingCaptions.length = 0;
  39650. this.pendingBytes = 0;
  39651. this.pendingMetadata.length = 0; // Emit the built segment
  39652. this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted
  39653. if (this.emittedTracks >= this.numberOfTracks) {
  39654. this.trigger('done');
  39655. this.emittedTracks = 0;
  39656. }
  39657. };
  39658. /**
  39659. * A Stream that expects MP2T binary data as input and produces
  39660. * corresponding media segments, suitable for use with Media Source
  39661. * Extension (MSE) implementations that support the ISO BMFF byte
  39662. * stream format, like Chrome.
  39663. */
  39664. _Transmuxer = function Transmuxer(options) {
  39665. var self = this,
  39666. hasFlushed = true,
  39667. videoTrack,
  39668. audioTrack;
  39669. _Transmuxer.prototype.init.call(this);
  39670. options = options || {};
  39671. this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
  39672. this.transmuxPipeline_ = {};
  39673. this.setupAacPipeline = function () {
  39674. var pipeline = {};
  39675. this.transmuxPipeline_ = pipeline;
  39676. pipeline.type = 'aac';
  39677. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  39678. pipeline.aacStream = new aac();
  39679. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  39680. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  39681. pipeline.adtsStream = new adts();
  39682. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  39683. pipeline.headOfPipeline = pipeline.aacStream;
  39684. pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  39685. pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
  39686. pipeline.metadataStream.on('timestamp', function (frame) {
  39687. pipeline.aacStream.setTimestamp(frame.timeStamp);
  39688. });
  39689. pipeline.aacStream.on('data', function (data) {
  39690. if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
  39691. audioTrack = audioTrack || {
  39692. timelineStartInfo: {
  39693. baseMediaDecodeTime: self.baseMediaDecodeTime
  39694. },
  39695. codec: 'adts',
  39696. type: 'audio'
  39697. }; // hook up the audio segment stream to the first track with aac data
  39698. pipeline.coalesceStream.numberOfTracks++;
  39699. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  39700. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  39701. }
  39702. }); // Re-emit any data coming from the coalesce stream to the outside world
  39703. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  39704. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  39705. };
  39706. this.setupTsPipeline = function () {
  39707. var pipeline = {};
  39708. this.transmuxPipeline_ = pipeline;
  39709. pipeline.type = 'ts';
  39710. pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
  39711. pipeline.packetStream = new m2ts_1.TransportPacketStream();
  39712. pipeline.parseStream = new m2ts_1.TransportParseStream();
  39713. pipeline.elementaryStream = new m2ts_1.ElementaryStream();
  39714. pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
  39715. pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
  39716. pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
  39717. pipeline.adtsStream = new adts();
  39718. pipeline.h264Stream = new H264Stream();
  39719. pipeline.captionStream = new m2ts_1.CaptionStream();
  39720. pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
  39721. pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
  39722. pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!!
  39723. // demux the streams
  39724. pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
  39725. pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
  39726. pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
  39727. pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
  39728. pipeline.elementaryStream.on('data', function (data) {
  39729. var i;
  39730. if (data.type === 'metadata') {
  39731. i = data.tracks.length; // scan the tracks listed in the metadata
  39732. while (i--) {
  39733. if (!videoTrack && data.tracks[i].type === 'video') {
  39734. videoTrack = data.tracks[i];
  39735. videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  39736. } else if (!audioTrack && data.tracks[i].type === 'audio') {
  39737. audioTrack = data.tracks[i];
  39738. audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
  39739. }
  39740. } // hook up the video segment stream to the first track with h264 data
  39741. if (videoTrack && !pipeline.videoSegmentStream) {
  39742. pipeline.coalesceStream.numberOfTracks++;
  39743. pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
  39744. pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
  39745. // When video emits timelineStartInfo data after a flush, we forward that
  39746. // info to the AudioSegmentStream, if it exists, because video timeline
  39747. // data takes precedence.
  39748. if (audioTrack) {
  39749. audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
  39750. // very earliest DTS we have seen in video because Chrome will
  39751. // interpret any video track with a baseMediaDecodeTime that is
  39752. // non-zero as a gap.
  39753. pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
  39754. }
  39755. });
  39756. pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
  39757. pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
  39758. pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
  39759. if (audioTrack) {
  39760. pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
  39761. }
  39762. }); // Set up the final part of the video pipeline
  39763. pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
  39764. }
  39765. if (audioTrack && !pipeline.audioSegmentStream) {
  39766. // hook up the audio segment stream to the first track with aac data
  39767. pipeline.coalesceStream.numberOfTracks++;
  39768. pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
  39769. pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
  39770. }
  39771. }
  39772. }); // Re-emit any data coming from the coalesce stream to the outside world
  39773. pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
  39774. pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
  39775. }; // hook up the segment streams once track metadata is delivered
  39776. this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
  39777. var pipeline = this.transmuxPipeline_;
  39778. if (!options.keepOriginalTimestamps) {
  39779. this.baseMediaDecodeTime = baseMediaDecodeTime;
  39780. }
  39781. if (audioTrack) {
  39782. audioTrack.timelineStartInfo.dts = undefined;
  39783. audioTrack.timelineStartInfo.pts = undefined;
  39784. trackDecodeInfo.clearDtsInfo(audioTrack);
  39785. if (!options.keepOriginalTimestamps) {
  39786. audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  39787. }
  39788. if (pipeline.audioTimestampRolloverStream) {
  39789. pipeline.audioTimestampRolloverStream.discontinuity();
  39790. }
  39791. }
  39792. if (videoTrack) {
  39793. if (pipeline.videoSegmentStream) {
  39794. pipeline.videoSegmentStream.gopCache_ = [];
  39795. pipeline.videoTimestampRolloverStream.discontinuity();
  39796. }
  39797. videoTrack.timelineStartInfo.dts = undefined;
  39798. videoTrack.timelineStartInfo.pts = undefined;
  39799. trackDecodeInfo.clearDtsInfo(videoTrack);
  39800. pipeline.captionStream.reset();
  39801. if (!options.keepOriginalTimestamps) {
  39802. videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
  39803. }
  39804. }
  39805. if (pipeline.timedMetadataTimestampRolloverStream) {
  39806. pipeline.timedMetadataTimestampRolloverStream.discontinuity();
  39807. }
  39808. };
  39809. this.setAudioAppendStart = function (timestamp) {
  39810. if (audioTrack) {
  39811. this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
  39812. }
  39813. };
  39814. this.alignGopsWith = function (gopsToAlignWith) {
  39815. if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
  39816. this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
  39817. }
  39818. }; // feed incoming data to the front of the parsing pipeline
  39819. this.push = function (data) {
  39820. if (hasFlushed) {
  39821. var isAac = isLikelyAacData$1(data);
  39822. if (isAac && this.transmuxPipeline_.type !== 'aac') {
  39823. this.setupAacPipeline();
  39824. } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
  39825. this.setupTsPipeline();
  39826. }
  39827. hasFlushed = false;
  39828. }
  39829. this.transmuxPipeline_.headOfPipeline.push(data);
  39830. }; // flush any buffered data
  39831. this.flush = function () {
  39832. hasFlushed = true; // Start at the top of the pipeline and flush all pending work
  39833. this.transmuxPipeline_.headOfPipeline.flush();
  39834. }; // Caption data has to be reset when seeking outside buffered range
  39835. this.resetCaptions = function () {
  39836. if (this.transmuxPipeline_.captionStream) {
  39837. this.transmuxPipeline_.captionStream.reset();
  39838. }
  39839. };
  39840. };
  39841. _Transmuxer.prototype = new stream();
  39842. var transmuxer = {
  39843. Transmuxer: _Transmuxer,
  39844. VideoSegmentStream: _VideoSegmentStream,
  39845. AudioSegmentStream: _AudioSegmentStream,
  39846. AUDIO_PROPERTIES: AUDIO_PROPERTIES,
  39847. VIDEO_PROPERTIES: VIDEO_PROPERTIES,
  39848. // exported for testing
  39849. generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
  39850. };
  39851. var inspectMp4,
  39852. _textifyMp,
  39853. parseType$2 = probe$$1.parseType,
  39854. parseMp4Date = function parseMp4Date(seconds) {
  39855. return new Date(seconds * 1000 - 2082844800000);
  39856. },
  39857. parseSampleFlags = function parseSampleFlags(flags) {
  39858. return {
  39859. isLeading: (flags[0] & 0x0c) >>> 2,
  39860. dependsOn: flags[0] & 0x03,
  39861. isDependedOn: (flags[1] & 0xc0) >>> 6,
  39862. hasRedundancy: (flags[1] & 0x30) >>> 4,
  39863. paddingValue: (flags[1] & 0x0e) >>> 1,
  39864. isNonSyncSample: flags[1] & 0x01,
  39865. degradationPriority: flags[2] << 8 | flags[3]
  39866. };
  39867. },
  39868. nalParse = function nalParse(avcStream) {
  39869. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  39870. result = [],
  39871. i,
  39872. length;
  39873. for (i = 0; i + 4 < avcStream.length; i += length) {
  39874. length = avcView.getUint32(i);
  39875. i += 4; // bail if this doesn't appear to be an H264 stream
  39876. if (length <= 0) {
  39877. result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
  39878. continue;
  39879. }
  39880. switch (avcStream[i] & 0x1F) {
  39881. case 0x01:
  39882. result.push('slice_layer_without_partitioning_rbsp');
  39883. break;
  39884. case 0x05:
  39885. result.push('slice_layer_without_partitioning_rbsp_idr');
  39886. break;
  39887. case 0x06:
  39888. result.push('sei_rbsp');
  39889. break;
  39890. case 0x07:
  39891. result.push('seq_parameter_set_rbsp');
  39892. break;
  39893. case 0x08:
  39894. result.push('pic_parameter_set_rbsp');
  39895. break;
  39896. case 0x09:
  39897. result.push('access_unit_delimiter_rbsp');
  39898. break;
  39899. default:
  39900. result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
  39901. break;
  39902. }
  39903. }
  39904. return result;
  39905. },
  39906. // registry of handlers for individual mp4 box types
  39907. parse$$1 = {
  39908. // codingname, not a first-class box type. stsd entries share the
  39909. // same format as real boxes so the parsing infrastructure can be
  39910. // shared
  39911. avc1: function avc1(data) {
  39912. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  39913. return {
  39914. dataReferenceIndex: view.getUint16(6),
  39915. width: view.getUint16(24),
  39916. height: view.getUint16(26),
  39917. horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
  39918. vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
  39919. frameCount: view.getUint16(40),
  39920. depth: view.getUint16(74),
  39921. config: inspectMp4(data.subarray(78, data.byteLength))
  39922. };
  39923. },
  39924. avcC: function avcC(data) {
  39925. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  39926. result = {
  39927. configurationVersion: data[0],
  39928. avcProfileIndication: data[1],
  39929. profileCompatibility: data[2],
  39930. avcLevelIndication: data[3],
  39931. lengthSizeMinusOne: data[4] & 0x03,
  39932. sps: [],
  39933. pps: []
  39934. },
  39935. numOfSequenceParameterSets = data[5] & 0x1f,
  39936. numOfPictureParameterSets,
  39937. nalSize,
  39938. offset,
  39939. i; // iterate past any SPSs
  39940. offset = 6;
  39941. for (i = 0; i < numOfSequenceParameterSets; i++) {
  39942. nalSize = view.getUint16(offset);
  39943. offset += 2;
  39944. result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  39945. offset += nalSize;
  39946. } // iterate past any PPSs
  39947. numOfPictureParameterSets = data[offset];
  39948. offset++;
  39949. for (i = 0; i < numOfPictureParameterSets; i++) {
  39950. nalSize = view.getUint16(offset);
  39951. offset += 2;
  39952. result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
  39953. offset += nalSize;
  39954. }
  39955. return result;
  39956. },
  39957. btrt: function btrt(data) {
  39958. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  39959. return {
  39960. bufferSizeDB: view.getUint32(0),
  39961. maxBitrate: view.getUint32(4),
  39962. avgBitrate: view.getUint32(8)
  39963. };
  39964. },
  39965. esds: function esds(data) {
  39966. return {
  39967. version: data[0],
  39968. flags: new Uint8Array(data.subarray(1, 4)),
  39969. esId: data[6] << 8 | data[7],
  39970. streamPriority: data[8] & 0x1f,
  39971. decoderConfig: {
  39972. objectProfileIndication: data[11],
  39973. streamType: data[12] >>> 2 & 0x3f,
  39974. bufferSize: data[13] << 16 | data[14] << 8 | data[15],
  39975. maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
  39976. avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
  39977. decoderConfigDescriptor: {
  39978. tag: data[24],
  39979. length: data[25],
  39980. audioObjectType: data[26] >>> 3 & 0x1f,
  39981. samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
  39982. channelConfiguration: data[27] >>> 3 & 0x0f
  39983. }
  39984. }
  39985. };
  39986. },
  39987. ftyp: function ftyp(data) {
  39988. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  39989. result = {
  39990. majorBrand: parseType$2(data.subarray(0, 4)),
  39991. minorVersion: view.getUint32(4),
  39992. compatibleBrands: []
  39993. },
  39994. i = 8;
  39995. while (i < data.byteLength) {
  39996. result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4)));
  39997. i += 4;
  39998. }
  39999. return result;
  40000. },
  40001. dinf: function dinf(data) {
  40002. return {
  40003. boxes: inspectMp4(data)
  40004. };
  40005. },
  40006. dref: function dref(data) {
  40007. return {
  40008. version: data[0],
  40009. flags: new Uint8Array(data.subarray(1, 4)),
  40010. dataReferences: inspectMp4(data.subarray(8))
  40011. };
  40012. },
  40013. hdlr: function hdlr(data) {
  40014. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40015. result = {
  40016. version: view.getUint8(0),
  40017. flags: new Uint8Array(data.subarray(1, 4)),
  40018. handlerType: parseType$2(data.subarray(8, 12)),
  40019. name: ''
  40020. },
  40021. i = 8; // parse out the name field
  40022. for (i = 24; i < data.byteLength; i++) {
  40023. if (data[i] === 0x00) {
  40024. // the name field is null-terminated
  40025. i++;
  40026. break;
  40027. }
  40028. result.name += String.fromCharCode(data[i]);
  40029. } // decode UTF-8 to javascript's internal representation
  40030. // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
  40031. result.name = decodeURIComponent(escape(result.name));
  40032. return result;
  40033. },
  40034. mdat: function mdat(data) {
  40035. return {
  40036. byteLength: data.byteLength,
  40037. nals: nalParse(data)
  40038. };
  40039. },
  40040. mdhd: function mdhd(data) {
  40041. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40042. i = 4,
  40043. language,
  40044. result = {
  40045. version: view.getUint8(0),
  40046. flags: new Uint8Array(data.subarray(1, 4)),
  40047. language: ''
  40048. };
  40049. if (result.version === 1) {
  40050. i += 4;
  40051. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40052. i += 8;
  40053. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40054. i += 4;
  40055. result.timescale = view.getUint32(i);
  40056. i += 8;
  40057. result.duration = view.getUint32(i); // truncating top 4 bytes
  40058. } else {
  40059. result.creationTime = parseMp4Date(view.getUint32(i));
  40060. i += 4;
  40061. result.modificationTime = parseMp4Date(view.getUint32(i));
  40062. i += 4;
  40063. result.timescale = view.getUint32(i);
  40064. i += 4;
  40065. result.duration = view.getUint32(i);
  40066. }
  40067. i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
  40068. // each field is the packed difference between its ASCII value and 0x60
  40069. language = view.getUint16(i);
  40070. result.language += String.fromCharCode((language >> 10) + 0x60);
  40071. result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
  40072. result.language += String.fromCharCode((language & 0x1f) + 0x60);
  40073. return result;
  40074. },
  40075. mdia: function mdia(data) {
  40076. return {
  40077. boxes: inspectMp4(data)
  40078. };
  40079. },
  40080. mfhd: function mfhd(data) {
  40081. return {
  40082. version: data[0],
  40083. flags: new Uint8Array(data.subarray(1, 4)),
  40084. sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  40085. };
  40086. },
  40087. minf: function minf(data) {
  40088. return {
  40089. boxes: inspectMp4(data)
  40090. };
  40091. },
  40092. // codingname, not a first-class box type. stsd entries share the
  40093. // same format as real boxes so the parsing infrastructure can be
  40094. // shared
  40095. mp4a: function mp4a(data) {
  40096. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40097. result = {
  40098. // 6 bytes reserved
  40099. dataReferenceIndex: view.getUint16(6),
  40100. // 4 + 4 bytes reserved
  40101. channelcount: view.getUint16(16),
  40102. samplesize: view.getUint16(18),
  40103. // 2 bytes pre_defined
  40104. // 2 bytes reserved
  40105. samplerate: view.getUint16(24) + view.getUint16(26) / 65536
  40106. }; // if there are more bytes to process, assume this is an ISO/IEC
  40107. // 14496-14 MP4AudioSampleEntry and parse the ESDBox
  40108. if (data.byteLength > 28) {
  40109. result.streamDescriptor = inspectMp4(data.subarray(28))[0];
  40110. }
  40111. return result;
  40112. },
  40113. moof: function moof(data) {
  40114. return {
  40115. boxes: inspectMp4(data)
  40116. };
  40117. },
  40118. moov: function moov(data) {
  40119. return {
  40120. boxes: inspectMp4(data)
  40121. };
  40122. },
  40123. mvex: function mvex(data) {
  40124. return {
  40125. boxes: inspectMp4(data)
  40126. };
  40127. },
  40128. mvhd: function mvhd(data) {
  40129. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40130. i = 4,
  40131. result = {
  40132. version: view.getUint8(0),
  40133. flags: new Uint8Array(data.subarray(1, 4))
  40134. };
  40135. if (result.version === 1) {
  40136. i += 4;
  40137. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40138. i += 8;
  40139. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40140. i += 4;
  40141. result.timescale = view.getUint32(i);
  40142. i += 8;
  40143. result.duration = view.getUint32(i); // truncating top 4 bytes
  40144. } else {
  40145. result.creationTime = parseMp4Date(view.getUint32(i));
  40146. i += 4;
  40147. result.modificationTime = parseMp4Date(view.getUint32(i));
  40148. i += 4;
  40149. result.timescale = view.getUint32(i);
  40150. i += 4;
  40151. result.duration = view.getUint32(i);
  40152. }
  40153. i += 4; // convert fixed-point, base 16 back to a number
  40154. result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
  40155. i += 4;
  40156. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  40157. i += 2;
  40158. i += 2;
  40159. i += 2 * 4;
  40160. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  40161. i += 9 * 4;
  40162. i += 6 * 4;
  40163. result.nextTrackId = view.getUint32(i);
  40164. return result;
  40165. },
  40166. pdin: function pdin(data) {
  40167. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  40168. return {
  40169. version: view.getUint8(0),
  40170. flags: new Uint8Array(data.subarray(1, 4)),
  40171. rate: view.getUint32(4),
  40172. initialDelay: view.getUint32(8)
  40173. };
  40174. },
  40175. sdtp: function sdtp(data) {
  40176. var result = {
  40177. version: data[0],
  40178. flags: new Uint8Array(data.subarray(1, 4)),
  40179. samples: []
  40180. },
  40181. i;
  40182. for (i = 4; i < data.byteLength; i++) {
  40183. result.samples.push({
  40184. dependsOn: (data[i] & 0x30) >> 4,
  40185. isDependedOn: (data[i] & 0x0c) >> 2,
  40186. hasRedundancy: data[i] & 0x03
  40187. });
  40188. }
  40189. return result;
  40190. },
  40191. sidx: function sidx(data) {
  40192. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40193. result = {
  40194. version: data[0],
  40195. flags: new Uint8Array(data.subarray(1, 4)),
  40196. references: [],
  40197. referenceId: view.getUint32(4),
  40198. timescale: view.getUint32(8),
  40199. earliestPresentationTime: view.getUint32(12),
  40200. firstOffset: view.getUint32(16)
  40201. },
  40202. referenceCount = view.getUint16(22),
  40203. i;
  40204. for (i = 24; referenceCount; i += 12, referenceCount--) {
  40205. result.references.push({
  40206. referenceType: (data[i] & 0x80) >>> 7,
  40207. referencedSize: view.getUint32(i) & 0x7FFFFFFF,
  40208. subsegmentDuration: view.getUint32(i + 4),
  40209. startsWithSap: !!(data[i + 8] & 0x80),
  40210. sapType: (data[i + 8] & 0x70) >>> 4,
  40211. sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
  40212. });
  40213. }
  40214. return result;
  40215. },
  40216. smhd: function smhd(data) {
  40217. return {
  40218. version: data[0],
  40219. flags: new Uint8Array(data.subarray(1, 4)),
  40220. balance: data[4] + data[5] / 256
  40221. };
  40222. },
  40223. stbl: function stbl(data) {
  40224. return {
  40225. boxes: inspectMp4(data)
  40226. };
  40227. },
  40228. stco: function stco(data) {
  40229. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40230. result = {
  40231. version: data[0],
  40232. flags: new Uint8Array(data.subarray(1, 4)),
  40233. chunkOffsets: []
  40234. },
  40235. entryCount = view.getUint32(4),
  40236. i;
  40237. for (i = 8; entryCount; i += 4, entryCount--) {
  40238. result.chunkOffsets.push(view.getUint32(i));
  40239. }
  40240. return result;
  40241. },
  40242. stsc: function stsc(data) {
  40243. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40244. entryCount = view.getUint32(4),
  40245. result = {
  40246. version: data[0],
  40247. flags: new Uint8Array(data.subarray(1, 4)),
  40248. sampleToChunks: []
  40249. },
  40250. i;
  40251. for (i = 8; entryCount; i += 12, entryCount--) {
  40252. result.sampleToChunks.push({
  40253. firstChunk: view.getUint32(i),
  40254. samplesPerChunk: view.getUint32(i + 4),
  40255. sampleDescriptionIndex: view.getUint32(i + 8)
  40256. });
  40257. }
  40258. return result;
  40259. },
  40260. stsd: function stsd(data) {
  40261. return {
  40262. version: data[0],
  40263. flags: new Uint8Array(data.subarray(1, 4)),
  40264. sampleDescriptions: inspectMp4(data.subarray(8))
  40265. };
  40266. },
  40267. stsz: function stsz(data) {
  40268. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40269. result = {
  40270. version: data[0],
  40271. flags: new Uint8Array(data.subarray(1, 4)),
  40272. sampleSize: view.getUint32(4),
  40273. entries: []
  40274. },
  40275. i;
  40276. for (i = 12; i < data.byteLength; i += 4) {
  40277. result.entries.push(view.getUint32(i));
  40278. }
  40279. return result;
  40280. },
  40281. stts: function stts(data) {
  40282. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40283. result = {
  40284. version: data[0],
  40285. flags: new Uint8Array(data.subarray(1, 4)),
  40286. timeToSamples: []
  40287. },
  40288. entryCount = view.getUint32(4),
  40289. i;
  40290. for (i = 8; entryCount; i += 8, entryCount--) {
  40291. result.timeToSamples.push({
  40292. sampleCount: view.getUint32(i),
  40293. sampleDelta: view.getUint32(i + 4)
  40294. });
  40295. }
  40296. return result;
  40297. },
  40298. styp: function styp(data) {
  40299. return parse$$1.ftyp(data);
  40300. },
  40301. tfdt: function tfdt(data) {
  40302. var result = {
  40303. version: data[0],
  40304. flags: new Uint8Array(data.subarray(1, 4)),
  40305. baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  40306. };
  40307. if (result.version === 1) {
  40308. result.baseMediaDecodeTime *= Math.pow(2, 32);
  40309. result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11];
  40310. }
  40311. return result;
  40312. },
  40313. tfhd: function tfhd(data) {
  40314. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40315. result = {
  40316. version: data[0],
  40317. flags: new Uint8Array(data.subarray(1, 4)),
  40318. trackId: view.getUint32(4)
  40319. },
  40320. baseDataOffsetPresent = result.flags[2] & 0x01,
  40321. sampleDescriptionIndexPresent = result.flags[2] & 0x02,
  40322. defaultSampleDurationPresent = result.flags[2] & 0x08,
  40323. defaultSampleSizePresent = result.flags[2] & 0x10,
  40324. defaultSampleFlagsPresent = result.flags[2] & 0x20,
  40325. durationIsEmpty = result.flags[0] & 0x010000,
  40326. defaultBaseIsMoof = result.flags[0] & 0x020000,
  40327. i;
  40328. i = 8;
  40329. if (baseDataOffsetPresent) {
  40330. i += 4; // truncate top 4 bytes
  40331. // FIXME: should we read the full 64 bits?
  40332. result.baseDataOffset = view.getUint32(12);
  40333. i += 4;
  40334. }
  40335. if (sampleDescriptionIndexPresent) {
  40336. result.sampleDescriptionIndex = view.getUint32(i);
  40337. i += 4;
  40338. }
  40339. if (defaultSampleDurationPresent) {
  40340. result.defaultSampleDuration = view.getUint32(i);
  40341. i += 4;
  40342. }
  40343. if (defaultSampleSizePresent) {
  40344. result.defaultSampleSize = view.getUint32(i);
  40345. i += 4;
  40346. }
  40347. if (defaultSampleFlagsPresent) {
  40348. result.defaultSampleFlags = view.getUint32(i);
  40349. }
  40350. if (durationIsEmpty) {
  40351. result.durationIsEmpty = true;
  40352. }
  40353. if (!baseDataOffsetPresent && defaultBaseIsMoof) {
  40354. result.baseDataOffsetIsMoof = true;
  40355. }
  40356. return result;
  40357. },
  40358. tkhd: function tkhd(data) {
  40359. var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40360. i = 4,
  40361. result = {
  40362. version: view.getUint8(0),
  40363. flags: new Uint8Array(data.subarray(1, 4))
  40364. };
  40365. if (result.version === 1) {
  40366. i += 4;
  40367. result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40368. i += 8;
  40369. result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
  40370. i += 4;
  40371. result.trackId = view.getUint32(i);
  40372. i += 4;
  40373. i += 8;
  40374. result.duration = view.getUint32(i); // truncating top 4 bytes
  40375. } else {
  40376. result.creationTime = parseMp4Date(view.getUint32(i));
  40377. i += 4;
  40378. result.modificationTime = parseMp4Date(view.getUint32(i));
  40379. i += 4;
  40380. result.trackId = view.getUint32(i);
  40381. i += 4;
  40382. i += 4;
  40383. result.duration = view.getUint32(i);
  40384. }
  40385. i += 4;
  40386. i += 2 * 4;
  40387. result.layer = view.getUint16(i);
  40388. i += 2;
  40389. result.alternateGroup = view.getUint16(i);
  40390. i += 2; // convert fixed-point, base 16 back to a number
  40391. result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
  40392. i += 2;
  40393. i += 2;
  40394. result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
  40395. i += 9 * 4;
  40396. result.width = view.getUint16(i) + view.getUint16(i + 2) / 16;
  40397. i += 4;
  40398. result.height = view.getUint16(i) + view.getUint16(i + 2) / 16;
  40399. return result;
  40400. },
  40401. traf: function traf(data) {
  40402. return {
  40403. boxes: inspectMp4(data)
  40404. };
  40405. },
  40406. trak: function trak(data) {
  40407. return {
  40408. boxes: inspectMp4(data)
  40409. };
  40410. },
  40411. trex: function trex(data) {
  40412. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  40413. return {
  40414. version: data[0],
  40415. flags: new Uint8Array(data.subarray(1, 4)),
  40416. trackId: view.getUint32(4),
  40417. defaultSampleDescriptionIndex: view.getUint32(8),
  40418. defaultSampleDuration: view.getUint32(12),
  40419. defaultSampleSize: view.getUint32(16),
  40420. sampleDependsOn: data[20] & 0x03,
  40421. sampleIsDependedOn: (data[21] & 0xc0) >> 6,
  40422. sampleHasRedundancy: (data[21] & 0x30) >> 4,
  40423. samplePaddingValue: (data[21] & 0x0e) >> 1,
  40424. sampleIsDifferenceSample: !!(data[21] & 0x01),
  40425. sampleDegradationPriority: view.getUint16(22)
  40426. };
  40427. },
  40428. trun: function trun(data) {
  40429. var result = {
  40430. version: data[0],
  40431. flags: new Uint8Array(data.subarray(1, 4)),
  40432. samples: []
  40433. },
  40434. view = new DataView(data.buffer, data.byteOffset, data.byteLength),
  40435. // Flag interpretation
  40436. dataOffsetPresent = result.flags[2] & 0x01,
  40437. // compare with 2nd byte of 0x1
  40438. firstSampleFlagsPresent = result.flags[2] & 0x04,
  40439. // compare with 2nd byte of 0x4
  40440. sampleDurationPresent = result.flags[1] & 0x01,
  40441. // compare with 2nd byte of 0x100
  40442. sampleSizePresent = result.flags[1] & 0x02,
  40443. // compare with 2nd byte of 0x200
  40444. sampleFlagsPresent = result.flags[1] & 0x04,
  40445. // compare with 2nd byte of 0x400
  40446. sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
  40447. // compare with 2nd byte of 0x800
  40448. sampleCount = view.getUint32(4),
  40449. offset = 8,
  40450. sample;
  40451. if (dataOffsetPresent) {
  40452. // 32 bit signed integer
  40453. result.dataOffset = view.getInt32(offset);
  40454. offset += 4;
  40455. } // Overrides the flags for the first sample only. The order of
  40456. // optional values will be: duration, size, compositionTimeOffset
  40457. if (firstSampleFlagsPresent && sampleCount) {
  40458. sample = {
  40459. flags: parseSampleFlags(data.subarray(offset, offset + 4))
  40460. };
  40461. offset += 4;
  40462. if (sampleDurationPresent) {
  40463. sample.duration = view.getUint32(offset);
  40464. offset += 4;
  40465. }
  40466. if (sampleSizePresent) {
  40467. sample.size = view.getUint32(offset);
  40468. offset += 4;
  40469. }
  40470. if (sampleCompositionTimeOffsetPresent) {
  40471. // Note: this should be a signed int if version is 1
  40472. sample.compositionTimeOffset = view.getUint32(offset);
  40473. offset += 4;
  40474. }
  40475. result.samples.push(sample);
  40476. sampleCount--;
  40477. }
  40478. while (sampleCount--) {
  40479. sample = {};
  40480. if (sampleDurationPresent) {
  40481. sample.duration = view.getUint32(offset);
  40482. offset += 4;
  40483. }
  40484. if (sampleSizePresent) {
  40485. sample.size = view.getUint32(offset);
  40486. offset += 4;
  40487. }
  40488. if (sampleFlagsPresent) {
  40489. sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
  40490. offset += 4;
  40491. }
  40492. if (sampleCompositionTimeOffsetPresent) {
  40493. // Note: this should be a signed int if version is 1
  40494. sample.compositionTimeOffset = view.getUint32(offset);
  40495. offset += 4;
  40496. }
  40497. result.samples.push(sample);
  40498. }
  40499. return result;
  40500. },
  40501. 'url ': function url(data) {
  40502. return {
  40503. version: data[0],
  40504. flags: new Uint8Array(data.subarray(1, 4))
  40505. };
  40506. },
  40507. vmhd: function vmhd(data) {
  40508. var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
  40509. return {
  40510. version: data[0],
  40511. flags: new Uint8Array(data.subarray(1, 4)),
  40512. graphicsmode: view.getUint16(4),
  40513. opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
  40514. };
  40515. }
  40516. };
  40517. /**
  40518. * Return a javascript array of box objects parsed from an ISO base
  40519. * media file.
  40520. * @param data {Uint8Array} the binary data of the media to be inspected
  40521. * @return {array} a javascript array of potentially nested box objects
  40522. */
  40523. inspectMp4 = function inspectMp4(data) {
  40524. var i = 0,
  40525. result = [],
  40526. view,
  40527. size,
  40528. type,
  40529. end,
  40530. box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
  40531. var ab = new ArrayBuffer(data.length);
  40532. var v = new Uint8Array(ab);
  40533. for (var z = 0; z < data.length; ++z) {
  40534. v[z] = data[z];
  40535. }
  40536. view = new DataView(ab);
  40537. while (i < data.byteLength) {
  40538. // parse box data
  40539. size = view.getUint32(i);
  40540. type = parseType$2(data.subarray(i + 4, i + 8));
  40541. end = size > 1 ? i + size : data.byteLength; // parse type-specific data
  40542. box = (parse$$1[type] || function (data) {
  40543. return {
  40544. data: data
  40545. };
  40546. })(data.subarray(i + 8, end));
  40547. box.size = size;
  40548. box.type = type; // store this box and move to the next
  40549. result.push(box);
  40550. i = end;
  40551. }
  40552. return result;
  40553. };
  40554. /**
  40555. * Returns a textual representation of the javascript represtentation
  40556. * of an MP4 file. You can use it as an alternative to
  40557. * JSON.stringify() to compare inspected MP4s.
  40558. * @param inspectedMp4 {array} the parsed array of boxes in an MP4
  40559. * file
  40560. * @param depth {number} (optional) the number of ancestor boxes of
  40561. * the elements of inspectedMp4. Assumed to be zero if unspecified.
  40562. * @return {string} a text representation of the parsed MP4
  40563. */
  40564. _textifyMp = function textifyMp4(inspectedMp4, depth) {
  40565. var indent;
  40566. depth = depth || 0;
  40567. indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
  40568. return inspectedMp4.map(function (box, index) {
  40569. // list the box type first at the current indentation level
  40570. return indent + box.type + '\n' + // the type is already included and handle child boxes separately
  40571. Object.keys(box).filter(function (key) {
  40572. return key !== 'type' && key !== 'boxes'; // output all the box properties
  40573. }).map(function (key) {
  40574. var prefix = indent + ' ' + key + ': ',
  40575. value = box[key]; // print out raw bytes as hexademical
  40576. if (value instanceof Uint8Array || value instanceof Uint32Array) {
  40577. var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) {
  40578. return ' ' + ('00' + byte.toString(16)).slice(-2);
  40579. }).join('').match(/.{1,24}/g);
  40580. if (!bytes) {
  40581. return prefix + '<>';
  40582. }
  40583. if (bytes.length === 1) {
  40584. return prefix + '<' + bytes.join('').slice(1) + '>';
  40585. }
  40586. return prefix + '<\n' + bytes.map(function (line) {
  40587. return indent + ' ' + line;
  40588. }).join('\n') + '\n' + indent + ' >';
  40589. } // stringify generic objects
  40590. return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
  40591. if (index === 0) {
  40592. return line;
  40593. }
  40594. return indent + ' ' + line;
  40595. }).join('\n');
  40596. }).join('\n') + ( // recursively textify the child boxes
  40597. box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
  40598. }).join('\n');
  40599. };
  40600. var mp4Inspector = {
  40601. inspect: inspectMp4,
  40602. textify: _textifyMp,
  40603. parseTfdt: parse$$1.tfdt,
  40604. parseHdlr: parse$$1.hdlr,
  40605. parseTfhd: parse$$1.tfhd,
  40606. parseTrun: parse$$1.trun,
  40607. parseSidx: parse$$1.sidx
  40608. };
  40609. var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
  40610. var CaptionStream$1 = captionStream.CaptionStream;
  40611. /**
  40612. * Maps an offset in the mdat to a sample based on the the size of the samples.
  40613. * Assumes that `parseSamples` has been called first.
  40614. *
  40615. * @param {Number} offset - The offset into the mdat
  40616. * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
  40617. * @return {?Object} The matching sample, or null if no match was found.
  40618. *
  40619. * @see ISO-BMFF-12/2015, Section 8.8.8
  40620. **/
  40621. var mapToSample = function mapToSample(offset, samples) {
  40622. var approximateOffset = offset;
  40623. for (var i = 0; i < samples.length; i++) {
  40624. var sample = samples[i];
  40625. if (approximateOffset < sample.size) {
  40626. return sample;
  40627. }
  40628. approximateOffset -= sample.size;
  40629. }
  40630. return null;
  40631. };
  40632. /**
  40633. * Finds SEI nal units contained in a Media Data Box.
  40634. * Assumes that `parseSamples` has been called first.
  40635. *
  40636. * @param {Uint8Array} avcStream - The bytes of the mdat
  40637. * @param {Object[]} samples - The samples parsed out by `parseSamples`
  40638. * @param {Number} trackId - The trackId of this video track
  40639. * @return {Object[]} seiNals - the parsed SEI NALUs found.
  40640. * The contents of the seiNal should match what is expected by
  40641. * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
  40642. *
  40643. * @see ISO-BMFF-12/2015, Section 8.1.1
  40644. * @see Rec. ITU-T H.264, 7.3.2.3.1
  40645. **/
  40646. var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
  40647. var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
  40648. result = [],
  40649. seiNal,
  40650. i,
  40651. length,
  40652. lastMatchedSample;
  40653. for (i = 0; i + 4 < avcStream.length; i += length) {
  40654. length = avcView.getUint32(i);
  40655. i += 4; // Bail if this doesn't appear to be an H264 stream
  40656. if (length <= 0) {
  40657. continue;
  40658. }
  40659. switch (avcStream[i] & 0x1F) {
  40660. case 0x06:
  40661. var data = avcStream.subarray(i + 1, i + 1 + length);
  40662. var matchingSample = mapToSample(i, samples);
  40663. seiNal = {
  40664. nalUnitType: 'sei_rbsp',
  40665. size: length,
  40666. data: data,
  40667. escapedRBSP: discardEmulationPreventionBytes$1(data),
  40668. trackId: trackId
  40669. };
  40670. if (matchingSample) {
  40671. seiNal.pts = matchingSample.pts;
  40672. seiNal.dts = matchingSample.dts;
  40673. lastMatchedSample = matchingSample;
  40674. } else {
  40675. // If a matching sample cannot be found, use the last
  40676. // sample's values as they should be as close as possible
  40677. seiNal.pts = lastMatchedSample.pts;
  40678. seiNal.dts = lastMatchedSample.dts;
  40679. }
  40680. result.push(seiNal);
  40681. break;
  40682. default:
  40683. break;
  40684. }
  40685. }
  40686. return result;
  40687. };
  40688. /**
  40689. * Parses sample information out of Track Run Boxes and calculates
  40690. * the absolute presentation and decode timestamps of each sample.
  40691. *
  40692. * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
  40693. * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
  40694. @see ISO-BMFF-12/2015, Section 8.8.12
  40695. * @param {Object} tfhd - The parsed Track Fragment Header
  40696. * @see inspect.parseTfhd
  40697. * @return {Object[]} the parsed samples
  40698. *
  40699. * @see ISO-BMFF-12/2015, Section 8.8.8
  40700. **/
  40701. var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
  40702. var currentDts = baseMediaDecodeTime;
  40703. var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
  40704. var defaultSampleSize = tfhd.defaultSampleSize || 0;
  40705. var trackId = tfhd.trackId;
  40706. var allSamples = [];
  40707. truns.forEach(function (trun) {
  40708. // Note: We currently do not parse the sample table as well
  40709. // as the trun. It's possible some sources will require this.
  40710. // moov > trak > mdia > minf > stbl
  40711. var trackRun = mp4Inspector.parseTrun(trun);
  40712. var samples = trackRun.samples;
  40713. samples.forEach(function (sample) {
  40714. if (sample.duration === undefined) {
  40715. sample.duration = defaultSampleDuration;
  40716. }
  40717. if (sample.size === undefined) {
  40718. sample.size = defaultSampleSize;
  40719. }
  40720. sample.trackId = trackId;
  40721. sample.dts = currentDts;
  40722. if (sample.compositionTimeOffset === undefined) {
  40723. sample.compositionTimeOffset = 0;
  40724. }
  40725. sample.pts = currentDts + sample.compositionTimeOffset;
  40726. currentDts += sample.duration;
  40727. });
  40728. allSamples = allSamples.concat(samples);
  40729. });
  40730. return allSamples;
  40731. };
  40732. /**
  40733. * Parses out caption nals from an FMP4 segment's video tracks.
  40734. *
  40735. * @param {Uint8Array} segment - The bytes of a single segment
  40736. * @param {Number} videoTrackId - The trackId of a video track in the segment
  40737. * @return {Object.<Number, Object[]>} A mapping of video trackId to
  40738. * a list of seiNals found in that track
  40739. **/
  40740. var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
  40741. // To get the samples
  40742. var trafs = probe$$1.findBox(segment, ['moof', 'traf']); // To get SEI NAL units
  40743. var mdats = probe$$1.findBox(segment, ['mdat']);
  40744. var captionNals = {};
  40745. var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
  40746. mdats.forEach(function (mdat, index) {
  40747. var matchingTraf = trafs[index];
  40748. mdatTrafPairs.push({
  40749. mdat: mdat,
  40750. traf: matchingTraf
  40751. });
  40752. });
  40753. mdatTrafPairs.forEach(function (pair) {
  40754. var mdat = pair.mdat;
  40755. var traf = pair.traf;
  40756. var tfhd = probe$$1.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
  40757. var headerInfo = mp4Inspector.parseTfhd(tfhd[0]);
  40758. var trackId = headerInfo.trackId;
  40759. var tfdt = probe$$1.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
  40760. var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
  40761. var truns = probe$$1.findBox(traf, ['trun']);
  40762. var samples;
  40763. var seiNals; // Only parse video data for the chosen video track
  40764. if (videoTrackId === trackId && truns.length > 0) {
  40765. samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
  40766. seiNals = findSeiNals(mdat, samples, trackId);
  40767. if (!captionNals[trackId]) {
  40768. captionNals[trackId] = [];
  40769. }
  40770. captionNals[trackId] = captionNals[trackId].concat(seiNals);
  40771. }
  40772. });
  40773. return captionNals;
  40774. };
  40775. /**
  40776. * Parses out inband captions from an MP4 container and returns
  40777. * caption objects that can be used by WebVTT and the TextTrack API.
  40778. * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
  40779. * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
  40780. * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
  40781. *
  40782. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  40783. * @param {Number} trackId - The id of the video track to parse
  40784. * @param {Number} timescale - The timescale for the video track from the init segment
  40785. *
  40786. * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
  40787. * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
  40788. * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
  40789. * @return {String} parsedCaptions[].text - The visible content of the caption
  40790. **/
  40791. var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
  40792. var seiNals;
  40793. if (!trackId) {
  40794. return null;
  40795. }
  40796. seiNals = parseCaptionNals(segment, trackId);
  40797. return {
  40798. seiNals: seiNals[trackId],
  40799. timescale: timescale
  40800. };
  40801. };
  40802. /**
  40803. * Converts SEI NALUs into captions that can be used by video.js
  40804. **/
  40805. var CaptionParser$$1 = function CaptionParser$$1() {
  40806. var isInitialized = false;
  40807. var captionStream$$1; // Stores segments seen before trackId and timescale are set
  40808. var segmentCache; // Stores video track ID of the track being parsed
  40809. var trackId; // Stores the timescale of the track being parsed
  40810. var timescale; // Stores captions parsed so far
  40811. var parsedCaptions;
  40812. /**
  40813. * A method to indicate whether a CaptionParser has been initalized
  40814. * @returns {Boolean}
  40815. **/
  40816. this.isInitialized = function () {
  40817. return isInitialized;
  40818. };
  40819. /**
  40820. * Initializes the underlying CaptionStream, SEI NAL parsing
  40821. * and management, and caption collection
  40822. **/
  40823. this.init = function () {
  40824. captionStream$$1 = new CaptionStream$1();
  40825. isInitialized = true; // Collect dispatched captions
  40826. captionStream$$1.on('data', function (event) {
  40827. // Convert to seconds in the source's timescale
  40828. event.startTime = event.startPts / timescale;
  40829. event.endTime = event.endPts / timescale;
  40830. parsedCaptions.captions.push(event);
  40831. parsedCaptions.captionStreams[event.stream] = true;
  40832. });
  40833. };
  40834. /**
  40835. * Determines if a new video track will be selected
  40836. * or if the timescale changed
  40837. * @return {Boolean}
  40838. **/
  40839. this.isNewInit = function (videoTrackIds, timescales) {
  40840. if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
  40841. return false;
  40842. }
  40843. return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
  40844. };
  40845. /**
  40846. * Parses out SEI captions and interacts with underlying
  40847. * CaptionStream to return dispatched captions
  40848. *
  40849. * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
  40850. * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
  40851. * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
  40852. * @see parseEmbeddedCaptions
  40853. * @see m2ts/caption-stream.js
  40854. **/
  40855. this.parse = function (segment, videoTrackIds, timescales) {
  40856. var parsedData;
  40857. if (!this.isInitialized()) {
  40858. return null; // This is not likely to be a video segment
  40859. } else if (!videoTrackIds || !timescales) {
  40860. return null;
  40861. } else if (this.isNewInit(videoTrackIds, timescales)) {
  40862. // Use the first video track only as there is no
  40863. // mechanism to switch to other video tracks
  40864. trackId = videoTrackIds[0];
  40865. timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
  40866. // data until we have one
  40867. } else if (!trackId || !timescale) {
  40868. segmentCache.push(segment);
  40869. return null;
  40870. } // Now that a timescale and trackId is set, parse cached segments
  40871. while (segmentCache.length > 0) {
  40872. var cachedSegment = segmentCache.shift();
  40873. this.parse(cachedSegment, videoTrackIds, timescales);
  40874. }
  40875. parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
  40876. if (parsedData === null || !parsedData.seiNals) {
  40877. return null;
  40878. }
  40879. this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
  40880. this.flushStream();
  40881. return parsedCaptions;
  40882. };
  40883. /**
  40884. * Pushes SEI NALUs onto CaptionStream
  40885. * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
  40886. * Assumes that `parseCaptionNals` has been called first
  40887. * @see m2ts/caption-stream.js
  40888. **/
  40889. this.pushNals = function (nals) {
  40890. if (!this.isInitialized() || !nals || nals.length === 0) {
  40891. return null;
  40892. }
  40893. nals.forEach(function (nal) {
  40894. captionStream$$1.push(nal);
  40895. });
  40896. };
  40897. /**
  40898. * Flushes underlying CaptionStream to dispatch processed, displayable captions
  40899. * @see m2ts/caption-stream.js
  40900. **/
  40901. this.flushStream = function () {
  40902. if (!this.isInitialized()) {
  40903. return null;
  40904. }
  40905. captionStream$$1.flush();
  40906. };
  40907. /**
  40908. * Reset caption buckets for new data
  40909. **/
  40910. this.clearParsedCaptions = function () {
  40911. parsedCaptions.captions = [];
  40912. parsedCaptions.captionStreams = {};
  40913. };
  40914. /**
  40915. * Resets underlying CaptionStream
  40916. * @see m2ts/caption-stream.js
  40917. **/
  40918. this.resetCaptionStream = function () {
  40919. if (!this.isInitialized()) {
  40920. return null;
  40921. }
  40922. captionStream$$1.reset();
  40923. };
  40924. /**
  40925. * Convenience method to clear all captions flushed from the
  40926. * CaptionStream and still being parsed
  40927. * @see m2ts/caption-stream.js
  40928. **/
  40929. this.clearAllCaptions = function () {
  40930. this.clearParsedCaptions();
  40931. this.resetCaptionStream();
  40932. };
  40933. /**
  40934. * Reset caption parser
  40935. **/
  40936. this.reset = function () {
  40937. segmentCache = [];
  40938. trackId = null;
  40939. timescale = null;
  40940. if (!parsedCaptions) {
  40941. parsedCaptions = {
  40942. captions: [],
  40943. // CC1, CC2, CC3, CC4
  40944. captionStreams: {}
  40945. };
  40946. } else {
  40947. this.clearParsedCaptions();
  40948. }
  40949. this.resetCaptionStream();
  40950. };
  40951. this.reset();
  40952. };
  40953. var captionParser = CaptionParser$$1;
  40954. var mp4$$1 = {
  40955. generator: mp4Generator,
  40956. probe: probe$$1,
  40957. Transmuxer: transmuxer.Transmuxer,
  40958. AudioSegmentStream: transmuxer.AudioSegmentStream,
  40959. VideoSegmentStream: transmuxer.VideoSegmentStream,
  40960. CaptionParser: captionParser
  40961. };
  40962. var classCallCheck = function classCallCheck(instance, Constructor) {
  40963. if (!(instance instanceof Constructor)) {
  40964. throw new TypeError("Cannot call a class as a function");
  40965. }
  40966. };
  40967. var createClass = function () {
  40968. function defineProperties(target, props) {
  40969. for (var i = 0; i < props.length; i++) {
  40970. var descriptor = props[i];
  40971. descriptor.enumerable = descriptor.enumerable || false;
  40972. descriptor.configurable = true;
  40973. if ("value" in descriptor) descriptor.writable = true;
  40974. Object.defineProperty(target, descriptor.key, descriptor);
  40975. }
  40976. }
  40977. return function (Constructor, protoProps, staticProps) {
  40978. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  40979. if (staticProps) defineProperties(Constructor, staticProps);
  40980. return Constructor;
  40981. };
  40982. }();
  40983. /**
  40984. * @file transmuxer-worker.js
  40985. */
  40986. /**
  40987. * Re-emits transmuxer events by converting them into messages to the
  40988. * world outside the worker.
  40989. *
  40990. * @param {Object} transmuxer the transmuxer to wire events on
  40991. * @private
  40992. */
  40993. var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer) {
  40994. transmuxer.on('data', function (segment) {
  40995. // transfer ownership of the underlying ArrayBuffer
  40996. // instead of doing a copy to save memory
  40997. // ArrayBuffers are transferable but generic TypedArrays are not
  40998. // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
  40999. var initArray = segment.initSegment;
  41000. segment.initSegment = {
  41001. data: initArray.buffer,
  41002. byteOffset: initArray.byteOffset,
  41003. byteLength: initArray.byteLength
  41004. };
  41005. var typedArray = segment.data;
  41006. segment.data = typedArray.buffer;
  41007. self.postMessage({
  41008. action: 'data',
  41009. segment: segment,
  41010. byteOffset: typedArray.byteOffset,
  41011. byteLength: typedArray.byteLength
  41012. }, [segment.data]);
  41013. });
  41014. if (transmuxer.captionStream) {
  41015. transmuxer.captionStream.on('data', function (caption) {
  41016. self.postMessage({
  41017. action: 'caption',
  41018. data: caption
  41019. });
  41020. });
  41021. }
  41022. transmuxer.on('done', function (data) {
  41023. self.postMessage({
  41024. action: 'done'
  41025. });
  41026. });
  41027. transmuxer.on('gopInfo', function (gopInfo) {
  41028. self.postMessage({
  41029. action: 'gopInfo',
  41030. gopInfo: gopInfo
  41031. });
  41032. });
  41033. transmuxer.on('videoSegmentTimingInfo', function (videoSegmentTimingInfo) {
  41034. self.postMessage({
  41035. action: 'videoSegmentTimingInfo',
  41036. videoSegmentTimingInfo: videoSegmentTimingInfo
  41037. });
  41038. });
  41039. };
  41040. /**
  41041. * All incoming messages route through this hash. If no function exists
  41042. * to handle an incoming message, then we ignore the message.
  41043. *
  41044. * @class MessageHandlers
  41045. * @param {Object} options the options to initialize with
  41046. */
  41047. var MessageHandlers = function () {
  41048. function MessageHandlers(self, options) {
  41049. classCallCheck(this, MessageHandlers);
  41050. this.options = options || {};
  41051. this.self = self;
  41052. this.init();
  41053. }
  41054. /**
  41055. * initialize our web worker and wire all the events.
  41056. */
  41057. createClass(MessageHandlers, [{
  41058. key: 'init',
  41059. value: function init() {
  41060. if (this.transmuxer) {
  41061. this.transmuxer.dispose();
  41062. }
  41063. this.transmuxer = new mp4$$1.Transmuxer(this.options);
  41064. wireTransmuxerEvents(this.self, this.transmuxer);
  41065. }
  41066. /**
  41067. * Adds data (a ts segment) to the start of the transmuxer pipeline for
  41068. * processing.
  41069. *
  41070. * @param {ArrayBuffer} data data to push into the muxer
  41071. */
  41072. }, {
  41073. key: 'push',
  41074. value: function push(data) {
  41075. // Cast array buffer to correct type for transmuxer
  41076. var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
  41077. this.transmuxer.push(segment);
  41078. }
  41079. /**
  41080. * Recreate the transmuxer so that the next segment added via `push`
  41081. * start with a fresh transmuxer.
  41082. */
  41083. }, {
  41084. key: 'reset',
  41085. value: function reset() {
  41086. this.init();
  41087. }
  41088. /**
  41089. * Set the value that will be used as the `baseMediaDecodeTime` time for the
  41090. * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
  41091. * set relative to the first based on the PTS values.
  41092. *
  41093. * @param {Object} data used to set the timestamp offset in the muxer
  41094. */
  41095. }, {
  41096. key: 'setTimestampOffset',
  41097. value: function setTimestampOffset(data) {
  41098. var timestampOffset = data.timestampOffset || 0;
  41099. this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000));
  41100. }
  41101. }, {
  41102. key: 'setAudioAppendStart',
  41103. value: function setAudioAppendStart(data) {
  41104. this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000));
  41105. }
  41106. /**
  41107. * Forces the pipeline to finish processing the last segment and emit it's
  41108. * results.
  41109. *
  41110. * @param {Object} data event data, not really used
  41111. */
  41112. }, {
  41113. key: 'flush',
  41114. value: function flush(data) {
  41115. this.transmuxer.flush();
  41116. }
  41117. }, {
  41118. key: 'resetCaptions',
  41119. value: function resetCaptions() {
  41120. this.transmuxer.resetCaptions();
  41121. }
  41122. }, {
  41123. key: 'alignGopsWith',
  41124. value: function alignGopsWith(data) {
  41125. this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
  41126. }
  41127. }]);
  41128. return MessageHandlers;
  41129. }();
  41130. /**
  41131. * Our web wroker interface so that things can talk to mux.js
  41132. * that will be running in a web worker. the scope is passed to this by
  41133. * webworkify.
  41134. *
  41135. * @param {Object} self the scope for the web worker
  41136. */
  41137. var TransmuxerWorker = function TransmuxerWorker(self) {
  41138. self.onmessage = function (event) {
  41139. if (event.data.action === 'init' && event.data.options) {
  41140. this.messageHandlers = new MessageHandlers(self, event.data.options);
  41141. return;
  41142. }
  41143. if (!this.messageHandlers) {
  41144. this.messageHandlers = new MessageHandlers(self);
  41145. }
  41146. if (event.data && event.data.action && event.data.action !== 'init') {
  41147. if (this.messageHandlers[event.data.action]) {
  41148. this.messageHandlers[event.data.action](event.data);
  41149. }
  41150. }
  41151. };
  41152. };
  41153. var transmuxerWorker = new TransmuxerWorker(self);
  41154. return transmuxerWorker;
  41155. }();
  41156. });
  41157. /**
  41158. * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
  41159. * codec strings, or translating codec strings into objects that can be examined.
  41160. */
  41161. // Default codec parameters if none were provided for video and/or audio
  41162. var defaultCodecs = {
  41163. videoCodec: 'avc1',
  41164. videoObjectTypeIndicator: '.4d400d',
  41165. // AAC-LC
  41166. audioProfile: '2'
  41167. };
  41168. /**
  41169. * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
  41170. * `avc1.<hhhhhh>`
  41171. *
  41172. * @param {Array} codecs an array of codec strings to fix
  41173. * @return {Array} the translated codec array
  41174. * @private
  41175. */
  41176. var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
  41177. return codecs.map(function (codec) {
  41178. return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
  41179. var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
  41180. var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
  41181. return 'avc1.' + profileHex + '00' + avcLevelHex;
  41182. });
  41183. });
  41184. };
  41185. /**
  41186. * Parses a codec string to retrieve the number of codecs specified,
  41187. * the video codec and object type indicator, and the audio profile.
  41188. */
  41189. var parseCodecs = function parseCodecs() {
  41190. var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
  41191. var result = {
  41192. codecCount: 0
  41193. };
  41194. var parsed = void 0;
  41195. result.codecCount = codecs.split(',').length;
  41196. result.codecCount = result.codecCount || 2; // parse the video codec
  41197. parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs);
  41198. if (parsed) {
  41199. result.videoCodec = parsed[2];
  41200. result.videoObjectTypeIndicator = parsed[3];
  41201. } // parse the last field of the audio codec
  41202. result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs);
  41203. result.audioProfile = result.audioProfile && result.audioProfile[2];
  41204. return result;
  41205. };
  41206. /**
  41207. * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
  41208. * standard `avc1.<hhhhhh>`.
  41209. *
  41210. * @param codecString {String} the codec string
  41211. * @return {String} the codec string with old apple-style codecs replaced
  41212. *
  41213. * @private
  41214. */
  41215. var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
  41216. return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
  41217. return translateLegacyCodecs([match])[0];
  41218. });
  41219. };
  41220. /**
  41221. * Build a media mime-type string from a set of parameters
  41222. * @param {String} type either 'audio' or 'video'
  41223. * @param {String} container either 'mp2t' or 'mp4'
  41224. * @param {Array} codecs an array of codec strings to add
  41225. * @return {String} a valid media mime-type
  41226. */
  41227. var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) {
  41228. // The codecs array is filtered so that falsey values are
  41229. // dropped and don't cause Array#join to create spurious
  41230. // commas
  41231. return type + '/' + container + '; codecs="' + codecs.filter(function (c) {
  41232. return !!c;
  41233. }).join(', ') + '"';
  41234. };
  41235. /**
  41236. * Returns the type container based on information in the playlist
  41237. * @param {Playlist} media the current media playlist
  41238. * @return {String} a valid media container type
  41239. */
  41240. var getContainerType = function getContainerType(media) {
  41241. // An initialization segment means the media playlist is an iframe
  41242. // playlist or is using the mp4 container. We don't currently
  41243. // support iframe playlists, so assume this is signalling mp4
  41244. // fragments.
  41245. if (media.segments && media.segments.length && media.segments[0].map) {
  41246. return 'mp4';
  41247. }
  41248. return 'mp2t';
  41249. };
  41250. /**
  41251. * Returns a set of codec strings parsed from the playlist or the default
  41252. * codec strings if no codecs were specified in the playlist
  41253. * @param {Playlist} media the current media playlist
  41254. * @return {Object} an object with the video and audio codecs
  41255. */
  41256. var getCodecs = function getCodecs(media) {
  41257. // if the codecs were explicitly specified, use them instead of the
  41258. // defaults
  41259. var mediaAttributes = media.attributes || {};
  41260. if (mediaAttributes.CODECS) {
  41261. return parseCodecs(mediaAttributes.CODECS);
  41262. }
  41263. return defaultCodecs;
  41264. };
  41265. var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) {
  41266. if (!master.mediaGroups.AUDIO || !audioGroupId) {
  41267. return null;
  41268. }
  41269. var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
  41270. if (!audioGroup) {
  41271. return null;
  41272. }
  41273. for (var name in audioGroup) {
  41274. var audioType = audioGroup[name];
  41275. if (audioType.default && audioType.playlists) {
  41276. // codec should be the same for all playlists within the audio type
  41277. return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
  41278. }
  41279. }
  41280. return null;
  41281. };
  41282. /**
  41283. * Calculates the MIME type strings for a working configuration of
  41284. * SourceBuffers to play variant streams in a master playlist. If
  41285. * there is no possible working configuration, an empty array will be
  41286. * returned.
  41287. *
  41288. * @param master {Object} the m3u8 object for the master playlist
  41289. * @param media {Object} the m3u8 object for the variant playlist
  41290. * @return {Array} the MIME type strings. If the array has more than
  41291. * one entry, the first element should be applied to the video
  41292. * SourceBuffer and the second to the audio SourceBuffer.
  41293. *
  41294. * @private
  41295. */
  41296. var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) {
  41297. var containerType = getContainerType(media);
  41298. var codecInfo = getCodecs(media);
  41299. var mediaAttributes = media.attributes || {}; // Default condition for a traditional HLS (no demuxed audio/video)
  41300. var isMuxed = true;
  41301. var isMaat = false;
  41302. if (!media) {
  41303. // Not enough information
  41304. return [];
  41305. }
  41306. if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) {
  41307. var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; // Handle the case where we are in a multiple-audio track scenario
  41308. if (audioGroup) {
  41309. isMaat = true; // Start with the everything demuxed then...
  41310. isMuxed = false; // ...check to see if any audio group tracks are muxed (ie. lacking a uri)
  41311. for (var groupId in audioGroup) {
  41312. // either a uri is present (if the case of HLS and an external playlist), or
  41313. // playlists is present (in the case of DASH where we don't have external audio
  41314. // playlists)
  41315. if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
  41316. isMuxed = true;
  41317. break;
  41318. }
  41319. }
  41320. }
  41321. } // HLS with multiple-audio tracks must always get an audio codec.
  41322. // Put another way, there is no way to have a video-only multiple-audio HLS!
  41323. if (isMaat && !codecInfo.audioProfile) {
  41324. if (!isMuxed) {
  41325. // It is possible for codecs to be specified on the audio media group playlist but
  41326. // not on the rendition playlist. This is mostly the case for DASH, where audio and
  41327. // video are always separate (and separately specified).
  41328. codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO);
  41329. }
  41330. if (!codecInfo.audioProfile) {
  41331. 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)');
  41332. codecInfo.audioProfile = defaultCodecs.audioProfile;
  41333. }
  41334. } // Generate the final codec strings from the codec object generated above
  41335. var codecStrings = {};
  41336. if (codecInfo.videoCodec) {
  41337. codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator;
  41338. }
  41339. if (codecInfo.audioProfile) {
  41340. codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile;
  41341. } // Finally, make and return an array with proper mime-types depending on
  41342. // the configuration
  41343. var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]);
  41344. var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]);
  41345. var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]);
  41346. if (isMaat) {
  41347. if (!isMuxed && codecStrings.video) {
  41348. return [justVideo, justAudio];
  41349. }
  41350. if (!isMuxed && !codecStrings.video) {
  41351. // There is no muxed content and no video codec string, so this is an audio only
  41352. // stream with alternate audio.
  41353. return [justAudio, justAudio];
  41354. } // There exists the possiblity that this will return a `video/container`
  41355. // mime-type for the first entry in the array even when there is only audio.
  41356. // This doesn't appear to be a problem and simplifies the code.
  41357. return [bothVideoAudio, justAudio];
  41358. } // If there is no video codec at all, always just return a single
  41359. // audio/<container> mime-type
  41360. if (!codecStrings.video) {
  41361. return [justAudio];
  41362. } // When not using separate audio media groups, audio and video is
  41363. // *always* muxed
  41364. return [bothVideoAudio];
  41365. };
  41366. /**
  41367. * Parse a content type header into a type and parameters
  41368. * object
  41369. *
  41370. * @param {String} type the content type header
  41371. * @return {Object} the parsed content-type
  41372. * @private
  41373. */
  41374. var parseContentType = function parseContentType(type) {
  41375. var object = {
  41376. type: '',
  41377. parameters: {}
  41378. };
  41379. var parameters = type.trim().split(';'); // first parameter should always be content-type
  41380. object.type = parameters.shift().trim();
  41381. parameters.forEach(function (parameter) {
  41382. var pair = parameter.trim().split('=');
  41383. if (pair.length > 1) {
  41384. var name = pair[0].replace(/"/g, '').trim();
  41385. var value = pair[1].replace(/"/g, '').trim();
  41386. object.parameters[name] = value;
  41387. }
  41388. });
  41389. return object;
  41390. };
  41391. /**
  41392. * Check if a codec string refers to an audio codec.
  41393. *
  41394. * @param {String} codec codec string to check
  41395. * @return {Boolean} if this is an audio codec
  41396. * @private
  41397. */
  41398. var isAudioCodec = function isAudioCodec(codec) {
  41399. return /mp4a\.\d+.\d+/i.test(codec);
  41400. };
  41401. /**
  41402. * Check if a codec string refers to a video codec.
  41403. *
  41404. * @param {String} codec codec string to check
  41405. * @return {Boolean} if this is a video codec
  41406. * @private
  41407. */
  41408. var isVideoCodec = function isVideoCodec(codec) {
  41409. return /avc1\.[\da-f]+/i.test(codec);
  41410. };
  41411. /**
  41412. * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
  41413. * front of current time.
  41414. *
  41415. * @param {Array} buffer
  41416. * The current buffer of gop information
  41417. * @param {Number} currentTime
  41418. * The current time
  41419. * @param {Double} mapping
  41420. * Offset to map display time to stream presentation time
  41421. * @return {Array}
  41422. * List of gops considered safe to append over
  41423. */
  41424. var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) {
  41425. if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
  41426. return [];
  41427. } // pts value for current time + 3 seconds to give a bit more wiggle room
  41428. var currentTimePts = Math.ceil((currentTime - mapping + 3) * 90000);
  41429. var i = void 0;
  41430. for (i = 0; i < buffer.length; i++) {
  41431. if (buffer[i].pts > currentTimePts) {
  41432. break;
  41433. }
  41434. }
  41435. return buffer.slice(i);
  41436. };
  41437. /**
  41438. * Appends gop information (timing and byteLength) received by the transmuxer for the
  41439. * gops appended in the last call to appendBuffer
  41440. *
  41441. * @param {Array} buffer
  41442. * The current buffer of gop information
  41443. * @param {Array} gops
  41444. * List of new gop information
  41445. * @param {boolean} replace
  41446. * If true, replace the buffer with the new gop information. If false, append the
  41447. * new gop information to the buffer in the right location of time.
  41448. * @return {Array}
  41449. * Updated list of gop information
  41450. */
  41451. var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
  41452. if (!gops.length) {
  41453. return buffer;
  41454. }
  41455. if (replace) {
  41456. // If we are in safe append mode, then completely overwrite the gop buffer
  41457. // with the most recent appeneded data. This will make sure that when appending
  41458. // future segments, we only try to align with gops that are both ahead of current
  41459. // time and in the last segment appended.
  41460. return gops.slice();
  41461. }
  41462. var start = gops[0].pts;
  41463. var i = 0;
  41464. for (i; i < buffer.length; i++) {
  41465. if (buffer[i].pts >= start) {
  41466. break;
  41467. }
  41468. }
  41469. return buffer.slice(0, i).concat(gops);
  41470. };
  41471. /**
  41472. * Removes gop information in buffer that overlaps with provided start and end
  41473. *
  41474. * @param {Array} buffer
  41475. * The current buffer of gop information
  41476. * @param {Double} start
  41477. * position to start the remove at
  41478. * @param {Double} end
  41479. * position to end the remove at
  41480. * @param {Double} mapping
  41481. * Offset to map display time to stream presentation time
  41482. */
  41483. var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
  41484. var startPts = Math.ceil((start - mapping) * 90000);
  41485. var endPts = Math.ceil((end - mapping) * 90000);
  41486. var updatedBuffer = buffer.slice();
  41487. var i = buffer.length;
  41488. while (i--) {
  41489. if (buffer[i].pts <= endPts) {
  41490. break;
  41491. }
  41492. }
  41493. if (i === -1) {
  41494. // no removal because end of remove range is before start of buffer
  41495. return updatedBuffer;
  41496. }
  41497. var j = i + 1;
  41498. while (j--) {
  41499. if (buffer[j].pts <= startPts) {
  41500. break;
  41501. }
  41502. } // clamp remove range start to 0 index
  41503. j = Math.max(j, 0);
  41504. updatedBuffer.splice(j, i - j + 1);
  41505. return updatedBuffer;
  41506. };
  41507. var buffered = function buffered(videoBuffer, audioBuffer, audioDisabled) {
  41508. var start = null;
  41509. var end = null;
  41510. var arity = 0;
  41511. var extents = [];
  41512. var ranges = []; // neither buffer has been created yet
  41513. if (!videoBuffer && !audioBuffer) {
  41514. return videojs$1.createTimeRange();
  41515. } // only one buffer is configured
  41516. if (!videoBuffer) {
  41517. return audioBuffer.buffered;
  41518. }
  41519. if (!audioBuffer) {
  41520. return videoBuffer.buffered;
  41521. } // both buffers are configured
  41522. if (audioDisabled) {
  41523. return videoBuffer.buffered;
  41524. } // both buffers are empty
  41525. if (videoBuffer.buffered.length === 0 && audioBuffer.buffered.length === 0) {
  41526. return videojs$1.createTimeRange();
  41527. } // Handle the case where we have both buffers and create an
  41528. // intersection of the two
  41529. var videoBuffered = videoBuffer.buffered;
  41530. var audioBuffered = audioBuffer.buffered;
  41531. var count = videoBuffered.length; // A) Gather up all start and end times
  41532. while (count--) {
  41533. extents.push({
  41534. time: videoBuffered.start(count),
  41535. type: 'start'
  41536. });
  41537. extents.push({
  41538. time: videoBuffered.end(count),
  41539. type: 'end'
  41540. });
  41541. }
  41542. count = audioBuffered.length;
  41543. while (count--) {
  41544. extents.push({
  41545. time: audioBuffered.start(count),
  41546. type: 'start'
  41547. });
  41548. extents.push({
  41549. time: audioBuffered.end(count),
  41550. type: 'end'
  41551. });
  41552. } // B) Sort them by time
  41553. extents.sort(function (a, b) {
  41554. return a.time - b.time;
  41555. }); // C) Go along one by one incrementing arity for start and decrementing
  41556. // arity for ends
  41557. for (count = 0; count < extents.length; count++) {
  41558. if (extents[count].type === 'start') {
  41559. arity++; // D) If arity is ever incremented to 2 we are entering an
  41560. // overlapping range
  41561. if (arity === 2) {
  41562. start = extents[count].time;
  41563. }
  41564. } else if (extents[count].type === 'end') {
  41565. arity--; // E) If arity is ever decremented to 1 we leaving an
  41566. // overlapping range
  41567. if (arity === 1) {
  41568. end = extents[count].time;
  41569. }
  41570. } // F) Record overlapping ranges
  41571. if (start !== null && end !== null) {
  41572. ranges.push([start, end]);
  41573. start = null;
  41574. end = null;
  41575. }
  41576. }
  41577. return videojs$1.createTimeRanges(ranges);
  41578. };
  41579. /**
  41580. * @file virtual-source-buffer.js
  41581. */
  41582. var ONE_SECOND_IN_TS$3 = 90000; // We create a wrapper around the SourceBuffer so that we can manage the
  41583. // state of the `updating` property manually. We have to do this because
  41584. // Firefox changes `updating` to false long before triggering `updateend`
  41585. // events and that was causing strange problems in videojs-contrib-hls
  41586. var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) {
  41587. var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
  41588. var wrapper = Object.create(null);
  41589. wrapper.updating = false;
  41590. wrapper.realBuffer_ = sourceBuffer;
  41591. var _loop = function _loop(key) {
  41592. if (typeof sourceBuffer[key] === 'function') {
  41593. wrapper[key] = function () {
  41594. return sourceBuffer[key].apply(sourceBuffer, arguments);
  41595. };
  41596. } else if (typeof wrapper[key] === 'undefined') {
  41597. Object.defineProperty(wrapper, key, {
  41598. get: function get$$1() {
  41599. return sourceBuffer[key];
  41600. },
  41601. set: function set$$1(v) {
  41602. return sourceBuffer[key] = v;
  41603. }
  41604. });
  41605. }
  41606. };
  41607. for (var key in sourceBuffer) {
  41608. _loop(key);
  41609. }
  41610. return wrapper;
  41611. };
  41612. /**
  41613. * VirtualSourceBuffers exist so that we can transmux non native formats
  41614. * into a native format, but keep the same api as a native source buffer.
  41615. * It creates a transmuxer, that works in its own thread (a web worker) and
  41616. * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
  41617. * then send all of that data to the naive sourcebuffer so that it is
  41618. * indestinguishable from a natively supported format.
  41619. *
  41620. * @param {HtmlMediaSource} mediaSource the parent mediaSource
  41621. * @param {Array} codecs array of codecs that we will be dealing with
  41622. * @class VirtualSourceBuffer
  41623. * @extends video.js.EventTarget
  41624. */
  41625. var VirtualSourceBuffer = function (_videojs$EventTarget) {
  41626. inherits$1(VirtualSourceBuffer, _videojs$EventTarget);
  41627. function VirtualSourceBuffer(mediaSource, codecs) {
  41628. classCallCheck$1(this, VirtualSourceBuffer);
  41629. var _this = possibleConstructorReturn$1(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget));
  41630. _this.timestampOffset_ = 0;
  41631. _this.pendingBuffers_ = [];
  41632. _this.bufferUpdating_ = false;
  41633. _this.mediaSource_ = mediaSource;
  41634. _this.codecs_ = codecs;
  41635. _this.audioCodec_ = null;
  41636. _this.videoCodec_ = null;
  41637. _this.audioDisabled_ = false;
  41638. _this.appendAudioInitSegment_ = true;
  41639. _this.gopBuffer_ = [];
  41640. _this.timeMapping_ = 0;
  41641. _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11;
  41642. var options = {
  41643. remux: false,
  41644. alignGopsAtEnd: _this.safeAppend_
  41645. };
  41646. _this.codecs_.forEach(function (codec) {
  41647. if (isAudioCodec(codec)) {
  41648. _this.audioCodec_ = codec;
  41649. } else if (isVideoCodec(codec)) {
  41650. _this.videoCodec_ = codec;
  41651. }
  41652. }); // append muxed segments to their respective native buffers as
  41653. // soon as they are available
  41654. _this.transmuxer_ = new TransmuxWorker();
  41655. _this.transmuxer_.postMessage({
  41656. action: 'init',
  41657. options: options
  41658. });
  41659. _this.transmuxer_.onmessage = function (event) {
  41660. if (event.data.action === 'data') {
  41661. return _this.data_(event);
  41662. }
  41663. if (event.data.action === 'done') {
  41664. return _this.done_(event);
  41665. }
  41666. if (event.data.action === 'gopInfo') {
  41667. return _this.appendGopInfo_(event);
  41668. }
  41669. if (event.data.action === 'videoSegmentTimingInfo') {
  41670. return _this.videoSegmentTimingInfo_(event.data.videoSegmentTimingInfo);
  41671. }
  41672. }; // this timestampOffset is a property with the side-effect of resetting
  41673. // baseMediaDecodeTime in the transmuxer on the setter
  41674. Object.defineProperty(_this, 'timestampOffset', {
  41675. get: function get$$1() {
  41676. return this.timestampOffset_;
  41677. },
  41678. set: function set$$1(val) {
  41679. if (typeof val === 'number' && val >= 0) {
  41680. this.timestampOffset_ = val;
  41681. this.appendAudioInitSegment_ = true; // reset gop buffer on timestampoffset as this signals a change in timeline
  41682. this.gopBuffer_.length = 0;
  41683. this.timeMapping_ = 0; // We have to tell the transmuxer to set the baseMediaDecodeTime to
  41684. // the desired timestampOffset for the next segment
  41685. this.transmuxer_.postMessage({
  41686. action: 'setTimestampOffset',
  41687. timestampOffset: val
  41688. });
  41689. }
  41690. }
  41691. }); // setting the append window affects both source buffers
  41692. Object.defineProperty(_this, 'appendWindowStart', {
  41693. get: function get$$1() {
  41694. return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
  41695. },
  41696. set: function set$$1(start) {
  41697. if (this.videoBuffer_) {
  41698. this.videoBuffer_.appendWindowStart = start;
  41699. }
  41700. if (this.audioBuffer_) {
  41701. this.audioBuffer_.appendWindowStart = start;
  41702. }
  41703. }
  41704. }); // this buffer is "updating" if either of its native buffers are
  41705. Object.defineProperty(_this, 'updating', {
  41706. get: function get$$1() {
  41707. return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating);
  41708. }
  41709. }); // the buffered property is the intersection of the buffered
  41710. // ranges of the native source buffers
  41711. Object.defineProperty(_this, 'buffered', {
  41712. get: function get$$1() {
  41713. return buffered(this.videoBuffer_, this.audioBuffer_, this.audioDisabled_);
  41714. }
  41715. });
  41716. return _this;
  41717. }
  41718. /**
  41719. * When we get a data event from the transmuxer
  41720. * we call this function and handle the data that
  41721. * was sent to us
  41722. *
  41723. * @private
  41724. * @param {Event} event the data event from the transmuxer
  41725. */
  41726. createClass$1(VirtualSourceBuffer, [{
  41727. key: 'data_',
  41728. value: function data_(event) {
  41729. var segment = event.data.segment; // Cast ArrayBuffer to TypedArray
  41730. segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength);
  41731. segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength);
  41732. createTextTracksIfNecessary(this, this.mediaSource_, segment); // Add the segments to the pendingBuffers array
  41733. this.pendingBuffers_.push(segment);
  41734. return;
  41735. }
  41736. /**
  41737. * When we get a done event from the transmuxer
  41738. * we call this function and we process all
  41739. * of the pending data that we have been saving in the
  41740. * data_ function
  41741. *
  41742. * @private
  41743. * @param {Event} event the done event from the transmuxer
  41744. */
  41745. }, {
  41746. key: 'done_',
  41747. value: function done_(event) {
  41748. // Don't process and append data if the mediaSource is closed
  41749. if (this.mediaSource_.readyState === 'closed') {
  41750. this.pendingBuffers_.length = 0;
  41751. return;
  41752. } // All buffers should have been flushed from the muxer
  41753. // start processing anything we have received
  41754. this.processPendingSegments_();
  41755. return;
  41756. }
  41757. }, {
  41758. key: 'videoSegmentTimingInfo_',
  41759. value: function videoSegmentTimingInfo_(timingInfo) {
  41760. var timingInfoInSeconds = {
  41761. start: {
  41762. decode: timingInfo.start.dts / ONE_SECOND_IN_TS$3,
  41763. presentation: timingInfo.start.pts / ONE_SECOND_IN_TS$3
  41764. },
  41765. end: {
  41766. decode: timingInfo.end.dts / ONE_SECOND_IN_TS$3,
  41767. presentation: timingInfo.end.pts / ONE_SECOND_IN_TS$3
  41768. },
  41769. baseMediaDecodeTime: timingInfo.baseMediaDecodeTime / ONE_SECOND_IN_TS$3
  41770. };
  41771. if (timingInfo.prependedContentDuration) {
  41772. timingInfoInSeconds.prependedContentDuration = timingInfo.prependedContentDuration / ONE_SECOND_IN_TS$3;
  41773. }
  41774. this.trigger({
  41775. type: 'videoSegmentTimingInfo',
  41776. videoSegmentTimingInfo: timingInfoInSeconds
  41777. });
  41778. }
  41779. /**
  41780. * Create our internal native audio/video source buffers and add
  41781. * event handlers to them with the following conditions:
  41782. * 1. they do not already exist on the mediaSource
  41783. * 2. this VSB has a codec for them
  41784. *
  41785. * @private
  41786. */
  41787. }, {
  41788. key: 'createRealSourceBuffers_',
  41789. value: function createRealSourceBuffers_() {
  41790. var _this2 = this;
  41791. var types = ['audio', 'video'];
  41792. types.forEach(function (type) {
  41793. // Don't create a SourceBuffer of this type if we don't have a
  41794. // codec for it
  41795. if (!_this2[type + 'Codec_']) {
  41796. return;
  41797. } // Do nothing if a SourceBuffer of this type already exists
  41798. if (_this2[type + 'Buffer_']) {
  41799. return;
  41800. }
  41801. var buffer = null; // If the mediasource already has a SourceBuffer for the codec
  41802. // use that
  41803. if (_this2.mediaSource_[type + 'Buffer_']) {
  41804. buffer = _this2.mediaSource_[type + 'Buffer_']; // In multiple audio track cases, the audio source buffer is disabled
  41805. // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier
  41806. // than createRealSourceBuffers_ is called to create the second
  41807. // VirtualSourceBuffer because that happens as a side-effect of
  41808. // videojs-contrib-hls starting the audioSegmentLoader. As a result,
  41809. // the audioBuffer is essentially "ownerless" and no one will toggle
  41810. // the `updating` state back to false once the `updateend` event is received
  41811. //
  41812. // Setting `updating` to false manually will work around this
  41813. // situation and allow work to continue
  41814. buffer.updating = false;
  41815. } else {
  41816. var codecProperty = type + 'Codec_';
  41817. var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"';
  41818. buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType);
  41819. _this2.mediaSource_[type + 'Buffer_'] = buffer;
  41820. }
  41821. _this2[type + 'Buffer_'] = buffer; // Wire up the events to the SourceBuffer
  41822. ['update', 'updatestart', 'updateend'].forEach(function (event) {
  41823. buffer.addEventListener(event, function () {
  41824. // if audio is disabled
  41825. if (type === 'audio' && _this2.audioDisabled_) {
  41826. return;
  41827. }
  41828. if (event === 'updateend') {
  41829. _this2[type + 'Buffer_'].updating = false;
  41830. }
  41831. var shouldTrigger = types.every(function (t) {
  41832. // skip checking audio's updating status if audio
  41833. // is not enabled
  41834. if (t === 'audio' && _this2.audioDisabled_) {
  41835. return true;
  41836. } // if the other type if updating we don't trigger
  41837. if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) {
  41838. return false;
  41839. }
  41840. return true;
  41841. });
  41842. if (shouldTrigger) {
  41843. return _this2.trigger(event);
  41844. }
  41845. });
  41846. });
  41847. });
  41848. }
  41849. /**
  41850. * Emulate the native mediasource function, but our function will
  41851. * send all of the proposed segments to the transmuxer so that we
  41852. * can transmux them before we append them to our internal
  41853. * native source buffers in the correct format.
  41854. *
  41855. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
  41856. * @param {Uint8Array} segment the segment to append to the buffer
  41857. */
  41858. }, {
  41859. key: 'appendBuffer',
  41860. value: function appendBuffer(segment) {
  41861. // Start the internal "updating" state
  41862. this.bufferUpdating_ = true;
  41863. if (this.audioBuffer_ && this.audioBuffer_.buffered.length) {
  41864. var audioBuffered = this.audioBuffer_.buffered;
  41865. this.transmuxer_.postMessage({
  41866. action: 'setAudioAppendStart',
  41867. appendStart: audioBuffered.end(audioBuffered.length - 1)
  41868. });
  41869. }
  41870. if (this.videoBuffer_) {
  41871. this.transmuxer_.postMessage({
  41872. action: 'alignGopsWith',
  41873. gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_ ? this.mediaSource_.player_.currentTime() : null, this.timeMapping_)
  41874. });
  41875. }
  41876. this.transmuxer_.postMessage({
  41877. action: 'push',
  41878. // Send the typed-array of data as an ArrayBuffer so that
  41879. // it can be sent as a "Transferable" and avoid the costly
  41880. // memory copy
  41881. data: segment.buffer,
  41882. // To recreate the original typed-array, we need information
  41883. // about what portion of the ArrayBuffer it was a view into
  41884. byteOffset: segment.byteOffset,
  41885. byteLength: segment.byteLength
  41886. }, [segment.buffer]);
  41887. this.transmuxer_.postMessage({
  41888. action: 'flush'
  41889. });
  41890. }
  41891. /**
  41892. * Appends gop information (timing and byteLength) received by the transmuxer for the
  41893. * gops appended in the last call to appendBuffer
  41894. *
  41895. * @param {Event} event
  41896. * The gopInfo event from the transmuxer
  41897. * @param {Array} event.data.gopInfo
  41898. * List of gop info to append
  41899. */
  41900. }, {
  41901. key: 'appendGopInfo_',
  41902. value: function appendGopInfo_(event) {
  41903. this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_);
  41904. }
  41905. /**
  41906. * Emulate the native mediasource function and remove parts
  41907. * of the buffer from any of our internal buffers that exist
  41908. *
  41909. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
  41910. * @param {Double} start position to start the remove at
  41911. * @param {Double} end position to end the remove at
  41912. */
  41913. }, {
  41914. key: 'remove',
  41915. value: function remove(start, end) {
  41916. if (this.videoBuffer_) {
  41917. this.videoBuffer_.updating = true;
  41918. this.videoBuffer_.remove(start, end);
  41919. this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
  41920. }
  41921. if (!this.audioDisabled_ && this.audioBuffer_) {
  41922. this.audioBuffer_.updating = true;
  41923. this.audioBuffer_.remove(start, end);
  41924. } // Remove Metadata Cues (id3)
  41925. removeCuesFromTrack(start, end, this.metadataTrack_); // Remove Any Captions
  41926. if (this.inbandTextTracks_) {
  41927. for (var track in this.inbandTextTracks_) {
  41928. removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
  41929. }
  41930. }
  41931. }
  41932. /**
  41933. * Process any segments that the muxer has output
  41934. * Concatenate segments together based on type and append them into
  41935. * their respective sourceBuffers
  41936. *
  41937. * @private
  41938. */
  41939. }, {
  41940. key: 'processPendingSegments_',
  41941. value: function processPendingSegments_() {
  41942. var sortedSegments = {
  41943. video: {
  41944. segments: [],
  41945. bytes: 0
  41946. },
  41947. audio: {
  41948. segments: [],
  41949. bytes: 0
  41950. },
  41951. captions: [],
  41952. metadata: []
  41953. }; // Sort segments into separate video/audio arrays and
  41954. // keep track of their total byte lengths
  41955. sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) {
  41956. var type = segment.type;
  41957. var data = segment.data;
  41958. var initSegment = segment.initSegment;
  41959. segmentObj[type].segments.push(data);
  41960. segmentObj[type].bytes += data.byteLength;
  41961. segmentObj[type].initSegment = initSegment; // Gather any captions into a single array
  41962. if (segment.captions) {
  41963. segmentObj.captions = segmentObj.captions.concat(segment.captions);
  41964. }
  41965. if (segment.info) {
  41966. segmentObj[type].info = segment.info;
  41967. } // Gather any metadata into a single array
  41968. if (segment.metadata) {
  41969. segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
  41970. }
  41971. return segmentObj;
  41972. }, sortedSegments); // Create the real source buffers if they don't exist by now since we
  41973. // finally are sure what tracks are contained in the source
  41974. if (!this.videoBuffer_ && !this.audioBuffer_) {
  41975. // Remove any codecs that may have been specified by default but
  41976. // are no longer applicable now
  41977. if (sortedSegments.video.bytes === 0) {
  41978. this.videoCodec_ = null;
  41979. }
  41980. if (sortedSegments.audio.bytes === 0) {
  41981. this.audioCodec_ = null;
  41982. }
  41983. this.createRealSourceBuffers_();
  41984. }
  41985. if (sortedSegments.audio.info) {
  41986. this.mediaSource_.trigger({
  41987. type: 'audioinfo',
  41988. info: sortedSegments.audio.info
  41989. });
  41990. }
  41991. if (sortedSegments.video.info) {
  41992. this.mediaSource_.trigger({
  41993. type: 'videoinfo',
  41994. info: sortedSegments.video.info
  41995. });
  41996. }
  41997. if (this.appendAudioInitSegment_) {
  41998. if (!this.audioDisabled_ && this.audioBuffer_) {
  41999. sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment);
  42000. sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength;
  42001. }
  42002. this.appendAudioInitSegment_ = false;
  42003. }
  42004. var triggerUpdateend = false; // Merge multiple video and audio segments into one and append
  42005. if (this.videoBuffer_ && sortedSegments.video.bytes) {
  42006. sortedSegments.video.segments.unshift(sortedSegments.video.initSegment);
  42007. sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength;
  42008. this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
  42009. } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) {
  42010. // The transmuxer did not return any bytes of video, meaning it was all trimmed
  42011. // for gop alignment. Since we have a video buffer and audio is disabled, updateend
  42012. // will never be triggered by this source buffer, which will cause contrib-hls
  42013. // to be stuck forever waiting for updateend. If audio is not disabled, updateend
  42014. // will be triggered by the audio buffer, which will be sent upwards since the video
  42015. // buffer will not be in an updating state.
  42016. triggerUpdateend = true;
  42017. } // Add text-track data for all
  42018. addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata);
  42019. if (!this.audioDisabled_ && this.audioBuffer_) {
  42020. this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
  42021. }
  42022. this.pendingBuffers_.length = 0;
  42023. if (triggerUpdateend) {
  42024. this.trigger('updateend');
  42025. } // We are no longer in the internal "updating" state
  42026. this.bufferUpdating_ = false;
  42027. }
  42028. /**
  42029. * Combine all segments into a single Uint8Array and then append them
  42030. * to the destination buffer
  42031. *
  42032. * @param {Object} segmentObj
  42033. * @param {SourceBuffer} destinationBuffer native source buffer to append data to
  42034. * @private
  42035. */
  42036. }, {
  42037. key: 'concatAndAppendSegments_',
  42038. value: function concatAndAppendSegments_(segmentObj, destinationBuffer) {
  42039. var offset = 0;
  42040. var tempBuffer = void 0;
  42041. if (segmentObj.bytes) {
  42042. tempBuffer = new Uint8Array(segmentObj.bytes); // Combine the individual segments into one large typed-array
  42043. segmentObj.segments.forEach(function (segment) {
  42044. tempBuffer.set(segment, offset);
  42045. offset += segment.byteLength;
  42046. });
  42047. try {
  42048. destinationBuffer.updating = true;
  42049. destinationBuffer.appendBuffer(tempBuffer);
  42050. } catch (error) {
  42051. if (this.mediaSource_.player_) {
  42052. this.mediaSource_.player_.error({
  42053. code: -3,
  42054. type: 'APPEND_BUFFER_ERR',
  42055. message: error.message,
  42056. originalError: error
  42057. });
  42058. }
  42059. }
  42060. }
  42061. }
  42062. /**
  42063. * Emulate the native mediasource function. abort any soureBuffer
  42064. * actions and throw out any un-appended data.
  42065. *
  42066. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
  42067. */
  42068. }, {
  42069. key: 'abort',
  42070. value: function abort() {
  42071. if (this.videoBuffer_) {
  42072. this.videoBuffer_.abort();
  42073. }
  42074. if (!this.audioDisabled_ && this.audioBuffer_) {
  42075. this.audioBuffer_.abort();
  42076. }
  42077. if (this.transmuxer_) {
  42078. this.transmuxer_.postMessage({
  42079. action: 'reset'
  42080. });
  42081. }
  42082. this.pendingBuffers_.length = 0;
  42083. this.bufferUpdating_ = false;
  42084. }
  42085. }]);
  42086. return VirtualSourceBuffer;
  42087. }(videojs$1.EventTarget);
  42088. /**
  42089. * @file html-media-source.js
  42090. */
  42091. /**
  42092. * Our MediaSource implementation in HTML, mimics native
  42093. * MediaSource where/if possible.
  42094. *
  42095. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
  42096. * @class HtmlMediaSource
  42097. * @extends videojs.EventTarget
  42098. */
  42099. var HtmlMediaSource = function (_videojs$EventTarget) {
  42100. inherits$1(HtmlMediaSource, _videojs$EventTarget);
  42101. function HtmlMediaSource() {
  42102. classCallCheck$1(this, HtmlMediaSource);
  42103. var _this = possibleConstructorReturn$1(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this));
  42104. var property = void 0;
  42105. _this.nativeMediaSource_ = new window$1.MediaSource(); // delegate to the native MediaSource's methods by default
  42106. for (property in _this.nativeMediaSource_) {
  42107. if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') {
  42108. _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_);
  42109. }
  42110. } // emulate `duration` and `seekable` until seeking can be
  42111. // handled uniformly for live streams
  42112. // see https://github.com/w3c/media-source/issues/5
  42113. _this.duration_ = NaN;
  42114. Object.defineProperty(_this, 'duration', {
  42115. get: function get$$1() {
  42116. if (this.duration_ === Infinity) {
  42117. return this.duration_;
  42118. }
  42119. return this.nativeMediaSource_.duration;
  42120. },
  42121. set: function set$$1(duration) {
  42122. this.duration_ = duration;
  42123. if (duration !== Infinity) {
  42124. this.nativeMediaSource_.duration = duration;
  42125. return;
  42126. }
  42127. }
  42128. });
  42129. Object.defineProperty(_this, 'seekable', {
  42130. get: function get$$1() {
  42131. if (this.duration_ === Infinity) {
  42132. return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]);
  42133. }
  42134. return this.nativeMediaSource_.seekable;
  42135. }
  42136. });
  42137. Object.defineProperty(_this, 'readyState', {
  42138. get: function get$$1() {
  42139. return this.nativeMediaSource_.readyState;
  42140. }
  42141. });
  42142. Object.defineProperty(_this, 'activeSourceBuffers', {
  42143. get: function get$$1() {
  42144. return this.activeSourceBuffers_;
  42145. }
  42146. }); // the list of virtual and native SourceBuffers created by this
  42147. // MediaSource
  42148. _this.sourceBuffers = [];
  42149. _this.activeSourceBuffers_ = [];
  42150. /**
  42151. * update the list of active source buffers based upon various
  42152. * imformation from HLS and video.js
  42153. *
  42154. * @private
  42155. */
  42156. _this.updateActiveSourceBuffers_ = function () {
  42157. // Retain the reference but empty the array
  42158. _this.activeSourceBuffers_.length = 0; // If there is only one source buffer, then it will always be active and audio will
  42159. // be disabled based on the codec of the source buffer
  42160. if (_this.sourceBuffers.length === 1) {
  42161. var sourceBuffer = _this.sourceBuffers[0];
  42162. sourceBuffer.appendAudioInitSegment_ = true;
  42163. sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_;
  42164. _this.activeSourceBuffers_.push(sourceBuffer);
  42165. return;
  42166. } // There are 2 source buffers, a combined (possibly video only) source buffer and
  42167. // and an audio only source buffer.
  42168. // By default, the audio in the combined virtual source buffer is enabled
  42169. // and the audio-only source buffer (if it exists) is disabled.
  42170. var disableCombined = false;
  42171. var disableAudioOnly = true; // TODO: maybe we can store the sourcebuffers on the track objects?
  42172. // safari may do something like this
  42173. for (var i = 0; i < _this.player_.audioTracks().length; i++) {
  42174. var track = _this.player_.audioTracks()[i];
  42175. if (track.enabled && track.kind !== 'main') {
  42176. // The enabled track is an alternate audio track so disable the audio in
  42177. // the combined source buffer and enable the audio-only source buffer.
  42178. disableCombined = true;
  42179. disableAudioOnly = false;
  42180. break;
  42181. }
  42182. }
  42183. _this.sourceBuffers.forEach(function (sourceBuffer, index) {
  42184. /* eslinst-disable */
  42185. // TODO once codecs are required, we can switch to using the codecs to determine
  42186. // what stream is the video stream, rather than relying on videoTracks
  42187. /* eslinst-enable */
  42188. sourceBuffer.appendAudioInitSegment_ = true;
  42189. if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
  42190. // combined
  42191. sourceBuffer.audioDisabled_ = disableCombined;
  42192. } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) {
  42193. // If the "combined" source buffer is video only, then we do not want
  42194. // disable the audio-only source buffer (this is mostly for demuxed
  42195. // audio and video hls)
  42196. sourceBuffer.audioDisabled_ = true;
  42197. disableAudioOnly = false;
  42198. } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
  42199. // audio only
  42200. // In the case of audio only with alternate audio and disableAudioOnly is true
  42201. // this means we want to disable the audio on the alternate audio sourcebuffer
  42202. // but not the main "combined" source buffer. The "combined" source buffer is
  42203. // always at index 0, so this ensures audio won't be disabled in both source
  42204. // buffers.
  42205. sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly;
  42206. if (sourceBuffer.audioDisabled_) {
  42207. return;
  42208. }
  42209. }
  42210. _this.activeSourceBuffers_.push(sourceBuffer);
  42211. });
  42212. };
  42213. _this.onPlayerMediachange_ = function () {
  42214. _this.sourceBuffers.forEach(function (sourceBuffer) {
  42215. sourceBuffer.appendAudioInitSegment_ = true;
  42216. });
  42217. };
  42218. _this.onHlsReset_ = function () {
  42219. _this.sourceBuffers.forEach(function (sourceBuffer) {
  42220. if (sourceBuffer.transmuxer_) {
  42221. sourceBuffer.transmuxer_.postMessage({
  42222. action: 'resetCaptions'
  42223. });
  42224. }
  42225. });
  42226. };
  42227. _this.onHlsSegmentTimeMapping_ = function (event) {
  42228. _this.sourceBuffers.forEach(function (buffer) {
  42229. return buffer.timeMapping_ = event.mapping;
  42230. });
  42231. }; // Re-emit MediaSource events on the polyfill
  42232. ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) {
  42233. this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
  42234. }, _this); // capture the associated player when the MediaSource is
  42235. // successfully attached
  42236. _this.on('sourceopen', function (event) {
  42237. // Get the player this MediaSource is attached to
  42238. var video = document.querySelector('[src="' + _this.url_ + '"]');
  42239. if (!video) {
  42240. return;
  42241. }
  42242. _this.player_ = videojs$1(video.parentNode);
  42243. if (!_this.player_) {
  42244. return;
  42245. } // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader
  42246. // resets its state and flushes the buffer
  42247. _this.player_.tech_.on('hls-reset', _this.onHlsReset_); // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main
  42248. // SegmentLoader inspects an MTS segment and has an accurate stream to display
  42249. // time mapping
  42250. _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_);
  42251. if (_this.player_.audioTracks && _this.player_.audioTracks()) {
  42252. _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_);
  42253. _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_);
  42254. _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_);
  42255. }
  42256. _this.player_.on('mediachange', _this.onPlayerMediachange_);
  42257. });
  42258. _this.on('sourceended', function (event) {
  42259. var duration = durationOfVideo(_this.duration);
  42260. for (var i = 0; i < _this.sourceBuffers.length; i++) {
  42261. var sourcebuffer = _this.sourceBuffers[i];
  42262. var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues;
  42263. if (cues && cues.length) {
  42264. cues[cues.length - 1].endTime = duration;
  42265. }
  42266. }
  42267. }); // explicitly terminate any WebWorkers that were created
  42268. // by SourceHandlers
  42269. _this.on('sourceclose', function (event) {
  42270. this.sourceBuffers.forEach(function (sourceBuffer) {
  42271. if (sourceBuffer.transmuxer_) {
  42272. sourceBuffer.transmuxer_.terminate();
  42273. }
  42274. });
  42275. this.sourceBuffers.length = 0;
  42276. if (!this.player_) {
  42277. return;
  42278. }
  42279. if (this.player_.audioTracks && this.player_.audioTracks()) {
  42280. this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
  42281. this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
  42282. this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
  42283. } // We can only change this if the player hasn't been disposed of yet
  42284. // because `off` eventually tries to use the el_ property. If it has
  42285. // been disposed of, then don't worry about it because there are no
  42286. // event handlers left to unbind anyway
  42287. if (this.player_.el_) {
  42288. this.player_.off('mediachange', this.onPlayerMediachange_);
  42289. }
  42290. if (this.player_.tech_ && this.player_.tech_.el_) {
  42291. this.player_.tech_.off('hls-reset', this.onHlsReset_);
  42292. this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_);
  42293. }
  42294. });
  42295. return _this;
  42296. }
  42297. /**
  42298. * Add a range that that can now be seeked to.
  42299. *
  42300. * @param {Double} start where to start the addition
  42301. * @param {Double} end where to end the addition
  42302. * @private
  42303. */
  42304. createClass$1(HtmlMediaSource, [{
  42305. key: 'addSeekableRange_',
  42306. value: function addSeekableRange_(start, end) {
  42307. var error = void 0;
  42308. if (this.duration !== Infinity) {
  42309. error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
  42310. error.name = 'InvalidStateError';
  42311. error.code = 11;
  42312. throw error;
  42313. }
  42314. if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) {
  42315. this.nativeMediaSource_.duration = end;
  42316. }
  42317. }
  42318. /**
  42319. * Add a source buffer to the media source.
  42320. *
  42321. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
  42322. * @param {String} type the content-type of the content
  42323. * @return {Object} the created source buffer
  42324. */
  42325. }, {
  42326. key: 'addSourceBuffer',
  42327. value: function addSourceBuffer(type) {
  42328. var buffer = void 0;
  42329. var parsedType = parseContentType(type); // Create a VirtualSourceBuffer to transmux MPEG-2 transport
  42330. // stream segments into fragmented MP4s
  42331. if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) {
  42332. var codecs = [];
  42333. if (parsedType.parameters && parsedType.parameters.codecs) {
  42334. codecs = parsedType.parameters.codecs.split(',');
  42335. codecs = translateLegacyCodecs(codecs);
  42336. codecs = codecs.filter(function (codec) {
  42337. return isAudioCodec(codec) || isVideoCodec(codec);
  42338. });
  42339. }
  42340. if (codecs.length === 0) {
  42341. codecs = ['avc1.4d400d', 'mp4a.40.2'];
  42342. }
  42343. buffer = new VirtualSourceBuffer(this, codecs);
  42344. if (this.sourceBuffers.length !== 0) {
  42345. // If another VirtualSourceBuffer already exists, then we are creating a
  42346. // SourceBuffer for an alternate audio track and therefore we know that
  42347. // the source has both an audio and video track.
  42348. // That means we should trigger the manual creation of the real
  42349. // SourceBuffers instead of waiting for the transmuxer to return data
  42350. this.sourceBuffers[0].createRealSourceBuffers_();
  42351. buffer.createRealSourceBuffers_(); // Automatically disable the audio on the first source buffer if
  42352. // a second source buffer is ever created
  42353. this.sourceBuffers[0].audioDisabled_ = true;
  42354. }
  42355. } else {
  42356. // delegate to the native implementation
  42357. buffer = this.nativeMediaSource_.addSourceBuffer(type);
  42358. }
  42359. this.sourceBuffers.push(buffer);
  42360. return buffer;
  42361. }
  42362. }]);
  42363. return HtmlMediaSource;
  42364. }(videojs$1.EventTarget);
  42365. /**
  42366. * @file videojs-contrib-media-sources.js
  42367. */
  42368. var urlCount = 0; // ------------
  42369. // Media Source
  42370. // ------------
  42371. // store references to the media sources so they can be connected
  42372. // to a video element (a swf object)
  42373. // TODO: can we store this somewhere local to this module?
  42374. videojs$1.mediaSources = {};
  42375. /**
  42376. * Provide a method for a swf object to notify JS that a
  42377. * media source is now open.
  42378. *
  42379. * @param {String} msObjectURL string referencing the MSE Object URL
  42380. * @param {String} swfId the swf id
  42381. */
  42382. var open = function open(msObjectURL, swfId) {
  42383. var mediaSource = videojs$1.mediaSources[msObjectURL];
  42384. if (mediaSource) {
  42385. mediaSource.trigger({
  42386. type: 'sourceopen',
  42387. swfId: swfId
  42388. });
  42389. } else {
  42390. throw new Error('Media Source not found (Video.js)');
  42391. }
  42392. };
  42393. /**
  42394. * Check to see if the native MediaSource object exists and supports
  42395. * an MP4 container with both H.264 video and AAC-LC audio.
  42396. *
  42397. * @return {Boolean} if native media sources are supported
  42398. */
  42399. var supportsNativeMediaSources = function supportsNativeMediaSources() {
  42400. return !!window$1.MediaSource && !!window$1.MediaSource.isTypeSupported && window$1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"');
  42401. };
  42402. /**
  42403. * An emulation of the MediaSource API so that we can support
  42404. * native and non-native functionality. returns an instance of
  42405. * HtmlMediaSource.
  42406. *
  42407. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource
  42408. */
  42409. var MediaSource = function MediaSource() {
  42410. this.MediaSource = {
  42411. open: open,
  42412. supportsNativeMediaSources: supportsNativeMediaSources
  42413. };
  42414. if (supportsNativeMediaSources()) {
  42415. return new HtmlMediaSource();
  42416. }
  42417. throw new Error('Cannot use create a virtual MediaSource for this video');
  42418. };
  42419. MediaSource.open = open;
  42420. MediaSource.supportsNativeMediaSources = supportsNativeMediaSources;
  42421. /**
  42422. * A wrapper around the native URL for our MSE object
  42423. * implementation, this object is exposed under videojs.URL
  42424. *
  42425. * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
  42426. */
  42427. var URL$1 = {
  42428. /**
  42429. * A wrapper around the native createObjectURL for our objects.
  42430. * This function maps a native or emulated mediaSource to a blob
  42431. * url so that it can be loaded into video.js
  42432. *
  42433. * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
  42434. * @param {MediaSource} object the object to create a blob url to
  42435. */
  42436. createObjectURL: function createObjectURL(object) {
  42437. var objectUrlPrefix = 'blob:vjs-media-source/';
  42438. var url = void 0; // use the native MediaSource to generate an object URL
  42439. if (object instanceof HtmlMediaSource) {
  42440. url = window$1.URL.createObjectURL(object.nativeMediaSource_);
  42441. object.url_ = url;
  42442. return url;
  42443. } // if the object isn't an emulated MediaSource, delegate to the
  42444. // native implementation
  42445. if (!(object instanceof HtmlMediaSource)) {
  42446. url = window$1.URL.createObjectURL(object);
  42447. object.url_ = url;
  42448. return url;
  42449. } // build a URL that can be used to map back to the emulated
  42450. // MediaSource
  42451. url = objectUrlPrefix + urlCount;
  42452. urlCount++; // setup the mapping back to object
  42453. videojs$1.mediaSources[url] = object;
  42454. return url;
  42455. }
  42456. };
  42457. videojs$1.MediaSource = MediaSource;
  42458. videojs$1.URL = URL$1;
  42459. var EventTarget$1$1 = videojs$1.EventTarget,
  42460. mergeOptions$2 = videojs$1.mergeOptions;
  42461. /**
  42462. * Returns a new master manifest that is the result of merging an updated master manifest
  42463. * into the original version.
  42464. *
  42465. * @param {Object} oldMaster
  42466. * The old parsed mpd object
  42467. * @param {Object} newMaster
  42468. * The updated parsed mpd object
  42469. * @return {Object}
  42470. * A new object representing the original master manifest with the updated media
  42471. * playlists merged in
  42472. */
  42473. var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) {
  42474. var noChanges = void 0;
  42475. var update = mergeOptions$2(oldMaster, {
  42476. // These are top level properties that can be updated
  42477. duration: newMaster.duration,
  42478. minimumUpdatePeriod: newMaster.minimumUpdatePeriod
  42479. }); // First update the playlists in playlist list
  42480. for (var i = 0; i < newMaster.playlists.length; i++) {
  42481. var playlistUpdate = updateMaster(update, newMaster.playlists[i]);
  42482. if (playlistUpdate) {
  42483. update = playlistUpdate;
  42484. } else {
  42485. noChanges = true;
  42486. }
  42487. } // Then update media group playlists
  42488. forEachMediaGroup(newMaster, function (properties, type, group, label) {
  42489. if (properties.playlists && properties.playlists.length) {
  42490. var uri = properties.playlists[0].uri;
  42491. var _playlistUpdate = updateMaster(update, properties.playlists[0]);
  42492. if (_playlistUpdate) {
  42493. update = _playlistUpdate; // update the playlist reference within media groups
  42494. update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri];
  42495. noChanges = false;
  42496. }
  42497. }
  42498. });
  42499. if (noChanges) {
  42500. return null;
  42501. }
  42502. return update;
  42503. };
  42504. var DashPlaylistLoader = function (_EventTarget) {
  42505. inherits$1(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent
  42506. // playlist loader setups from media groups will expect to be able to pass a playlist
  42507. // (since there aren't external URLs to media playlists with DASH)
  42508. function DashPlaylistLoader(srcUrlOrPlaylist, hls) {
  42509. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  42510. var masterPlaylistLoader = arguments[3];
  42511. classCallCheck$1(this, DashPlaylistLoader);
  42512. var _this = possibleConstructorReturn$1(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this));
  42513. var _options$withCredenti = options.withCredentials,
  42514. withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
  42515. _options$handleManife = options.handleManifestRedirects,
  42516. handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
  42517. _this.hls_ = hls;
  42518. _this.withCredentials = withCredentials;
  42519. _this.handleManifestRedirects = handleManifestRedirects;
  42520. if (!srcUrlOrPlaylist) {
  42521. throw new Error('A non-empty playlist URL or playlist is required');
  42522. } // event naming?
  42523. _this.on('minimumUpdatePeriod', function () {
  42524. _this.refreshXml_();
  42525. }); // live playlist staleness timeout
  42526. _this.on('mediaupdatetimeout', function () {
  42527. _this.refreshMedia_();
  42528. });
  42529. _this.state = 'HAVE_NOTHING';
  42530. _this.loadedPlaylists_ = {}; // initialize the loader state
  42531. // The masterPlaylistLoader will be created with a string
  42532. if (typeof srcUrlOrPlaylist === 'string') {
  42533. _this.srcUrl = srcUrlOrPlaylist;
  42534. return possibleConstructorReturn$1(_this);
  42535. }
  42536. _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist);
  42537. return _this;
  42538. }
  42539. createClass$1(DashPlaylistLoader, [{
  42540. key: 'setupChildLoader',
  42541. value: function setupChildLoader(masterPlaylistLoader, playlist) {
  42542. this.masterPlaylistLoader_ = masterPlaylistLoader;
  42543. this.childPlaylist_ = playlist;
  42544. }
  42545. }, {
  42546. key: 'dispose',
  42547. value: function dispose() {
  42548. this.stopRequest();
  42549. this.loadedPlaylists_ = {};
  42550. window$1.clearTimeout(this.mediaUpdateTimeout);
  42551. }
  42552. }, {
  42553. key: 'hasPendingRequest',
  42554. value: function hasPendingRequest() {
  42555. return this.request || this.mediaRequest_;
  42556. }
  42557. }, {
  42558. key: 'stopRequest',
  42559. value: function stopRequest() {
  42560. if (this.request) {
  42561. var oldRequest = this.request;
  42562. this.request = null;
  42563. oldRequest.onreadystatechange = null;
  42564. oldRequest.abort();
  42565. }
  42566. }
  42567. }, {
  42568. key: 'media',
  42569. value: function media(playlist) {
  42570. // getter
  42571. if (!playlist) {
  42572. return this.media_;
  42573. } // setter
  42574. if (this.state === 'HAVE_NOTHING') {
  42575. throw new Error('Cannot switch media playlist from ' + this.state);
  42576. }
  42577. var startingState = this.state; // find the playlist object if the target playlist has been specified by URI
  42578. if (typeof playlist === 'string') {
  42579. if (!this.master.playlists[playlist]) {
  42580. throw new Error('Unknown playlist URI: ' + playlist);
  42581. }
  42582. playlist = this.master.playlists[playlist];
  42583. }
  42584. var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to previously loaded playlists immediately
  42585. if (mediaChange && this.loadedPlaylists_[playlist.uri] && this.loadedPlaylists_[playlist.uri].endList) {
  42586. this.state = 'HAVE_METADATA';
  42587. this.media_ = playlist; // trigger media change if the active media has been updated
  42588. if (mediaChange) {
  42589. this.trigger('mediachanging');
  42590. this.trigger('mediachange');
  42591. }
  42592. return;
  42593. } // switching to the active playlist is a no-op
  42594. if (!mediaChange) {
  42595. return;
  42596. } // switching from an already loaded playlist
  42597. if (this.media_) {
  42598. this.trigger('mediachanging');
  42599. } // TODO: check for sidx here
  42600. // Continue asynchronously if there is no sidx
  42601. // wait one tick to allow haveMaster to run first on a child loader
  42602. this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, {
  42603. startingState: startingState,
  42604. playlist: playlist
  42605. }), 0);
  42606. }
  42607. }, {
  42608. key: 'haveMetadata',
  42609. value: function haveMetadata(_ref) {
  42610. var startingState = _ref.startingState,
  42611. playlist = _ref.playlist;
  42612. this.state = 'HAVE_METADATA';
  42613. this.media_ = playlist;
  42614. this.loadedPlaylists_[playlist.uri] = playlist;
  42615. this.mediaRequest_ = null; // This will trigger loadedplaylist
  42616. this.refreshMedia_(); // fire loadedmetadata the first time a media playlist is loaded
  42617. // to resolve setup of media groups
  42618. if (startingState === 'HAVE_MASTER') {
  42619. this.trigger('loadedmetadata');
  42620. } else {
  42621. // trigger media change if the active media has been updated
  42622. this.trigger('mediachange');
  42623. }
  42624. }
  42625. }, {
  42626. key: 'pause',
  42627. value: function pause() {
  42628. this.stopRequest();
  42629. window$1.clearTimeout(this.mediaUpdateTimeout);
  42630. if (this.state === 'HAVE_NOTHING') {
  42631. // If we pause the loader before any data has been retrieved, its as if we never
  42632. // started, so reset to an unstarted state.
  42633. this.started = false;
  42634. }
  42635. }
  42636. }, {
  42637. key: 'load',
  42638. value: function load(isFinalRendition) {
  42639. var _this2 = this;
  42640. window$1.clearTimeout(this.mediaUpdateTimeout);
  42641. var media = this.media();
  42642. if (isFinalRendition) {
  42643. var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
  42644. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  42645. return _this2.load();
  42646. }, delay);
  42647. return;
  42648. } // because the playlists are internal to the manifest, load should either load the
  42649. // main manifest, or do nothing but trigger an event
  42650. if (!this.started) {
  42651. this.start();
  42652. return;
  42653. }
  42654. this.trigger('loadedplaylist');
  42655. }
  42656. /**
  42657. * Parses the master xml string and updates playlist uri references
  42658. *
  42659. * @return {Object}
  42660. * The parsed mpd manifest object
  42661. */
  42662. }, {
  42663. key: 'parseMasterXml',
  42664. value: function parseMasterXml() {
  42665. var master = parse(this.masterXml_, {
  42666. manifestUri: this.srcUrl,
  42667. clientOffset: this.clientOffset_
  42668. });
  42669. master.uri = this.srcUrl; // Set up phony URIs for the playlists since we won't have external URIs for DASH
  42670. // but reference playlists by their URI throughout the project
  42671. // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes).
  42672. for (var i = 0; i < master.playlists.length; i++) {
  42673. var phonyUri = 'placeholder-uri-' + i;
  42674. master.playlists[i].uri = phonyUri; // set up by URI references
  42675. master.playlists[phonyUri] = master.playlists[i];
  42676. } // set up phony URIs for the media group playlists since we won't have external
  42677. // URIs for DASH but reference playlists by their URI throughout the project
  42678. forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
  42679. if (properties.playlists && properties.playlists.length) {
  42680. var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey;
  42681. properties.playlists[0].uri = _phonyUri; // setup URI references
  42682. master.playlists[_phonyUri] = properties.playlists[0];
  42683. }
  42684. });
  42685. setupMediaPlaylists(master);
  42686. resolveMediaGroupUris(master);
  42687. return master;
  42688. }
  42689. }, {
  42690. key: 'start',
  42691. value: function start() {
  42692. var _this3 = this;
  42693. this.started = true; // We don't need to request the master manifest again
  42694. // Call this asynchronously to match the xhr request behavior below
  42695. if (this.masterPlaylistLoader_) {
  42696. this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0);
  42697. return;
  42698. } // request the specified URL
  42699. this.request = this.hls_.xhr({
  42700. uri: this.srcUrl,
  42701. withCredentials: this.withCredentials
  42702. }, function (error, req) {
  42703. // disposed
  42704. if (!_this3.request) {
  42705. return;
  42706. } // clear the loader's request reference
  42707. _this3.request = null;
  42708. if (error) {
  42709. _this3.error = {
  42710. status: req.status,
  42711. message: 'DASH playlist request error at URL: ' + _this3.srcUrl,
  42712. responseText: req.responseText,
  42713. // MEDIA_ERR_NETWORK
  42714. code: 2
  42715. };
  42716. if (_this3.state === 'HAVE_NOTHING') {
  42717. _this3.started = false;
  42718. }
  42719. return _this3.trigger('error');
  42720. }
  42721. _this3.masterXml_ = req.responseText;
  42722. if (req.responseHeaders && req.responseHeaders.date) {
  42723. _this3.masterLoaded_ = Date.parse(req.responseHeaders.date);
  42724. } else {
  42725. _this3.masterLoaded_ = Date.now();
  42726. }
  42727. _this3.srcUrl = resolveManifestRedirect(_this3.handleManifestRedirects, _this3.srcUrl, req);
  42728. _this3.syncClientServerClock_(_this3.onClientServerClockSync_.bind(_this3));
  42729. });
  42730. }
  42731. /**
  42732. * Parses the master xml for UTCTiming node to sync the client clock to the server
  42733. * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
  42734. *
  42735. * @param {Function} done
  42736. * Function to call when clock sync has completed
  42737. */
  42738. }, {
  42739. key: 'syncClientServerClock_',
  42740. value: function syncClientServerClock_(done) {
  42741. var _this4 = this;
  42742. var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
  42743. // server clock
  42744. if (utcTiming === null) {
  42745. this.clientOffset_ = this.masterLoaded_ - Date.now();
  42746. return done();
  42747. }
  42748. if (utcTiming.method === 'DIRECT') {
  42749. this.clientOffset_ = utcTiming.value - Date.now();
  42750. return done();
  42751. }
  42752. this.request = this.hls_.xhr({
  42753. uri: resolveUrl$1(this.srcUrl, utcTiming.value),
  42754. method: utcTiming.method,
  42755. withCredentials: this.withCredentials
  42756. }, function (error, req) {
  42757. // disposed
  42758. if (!_this4.request) {
  42759. return;
  42760. }
  42761. if (error) {
  42762. // sync request failed, fall back to using date header from mpd
  42763. // TODO: log warning
  42764. _this4.clientOffset_ = _this4.masterLoaded_ - Date.now();
  42765. return done();
  42766. }
  42767. var serverTime = void 0;
  42768. if (utcTiming.method === 'HEAD') {
  42769. if (!req.responseHeaders || !req.responseHeaders.date) {
  42770. // expected date header not preset, fall back to using date header from mpd
  42771. // TODO: log warning
  42772. serverTime = _this4.masterLoaded_;
  42773. } else {
  42774. serverTime = Date.parse(req.responseHeaders.date);
  42775. }
  42776. } else {
  42777. serverTime = Date.parse(req.responseText);
  42778. }
  42779. _this4.clientOffset_ = serverTime - Date.now();
  42780. done();
  42781. });
  42782. }
  42783. }, {
  42784. key: 'haveMaster_',
  42785. value: function haveMaster_() {
  42786. this.state = 'HAVE_MASTER'; // clear media request
  42787. this.mediaRequest_ = null;
  42788. if (!this.masterPlaylistLoader_) {
  42789. this.master = this.parseMasterXml(); // We have the master playlist at this point, so
  42790. // trigger this to allow MasterPlaylistController
  42791. // to make an initial playlist selection
  42792. this.trigger('loadedplaylist');
  42793. } else if (!this.media_) {
  42794. // no media playlist was specifically selected so select
  42795. // the one the child playlist loader was created with
  42796. this.media(this.childPlaylist_);
  42797. }
  42798. }
  42799. /**
  42800. * Handler for after client/server clock synchronization has happened. Sets up
  42801. * xml refresh timer if specificed by the manifest.
  42802. */
  42803. }, {
  42804. key: 'onClientServerClockSync_',
  42805. value: function onClientServerClockSync_() {
  42806. var _this5 = this;
  42807. this.haveMaster_();
  42808. if (!this.hasPendingRequest() && !this.media_) {
  42809. this.media(this.master.playlists[0]);
  42810. } // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not
  42811. // be refreshed when this is the case. The inter-op guide says that when the
  42812. // minimumUpdatePeriod is 0, the manifest should outline all currently available
  42813. // segments, but future segments may require an update. I think a good solution
  42814. // would be to update the manifest at the same rate that the media playlists
  42815. // are "refreshed", i.e. every targetDuration.
  42816. if (this.master && this.master.minimumUpdatePeriod) {
  42817. window$1.setTimeout(function () {
  42818. _this5.trigger('minimumUpdatePeriod');
  42819. }, this.master.minimumUpdatePeriod);
  42820. }
  42821. }
  42822. /**
  42823. * Sends request to refresh the master xml and updates the parsed master manifest
  42824. * TODO: Does the client offset need to be recalculated when the xml is refreshed?
  42825. */
  42826. }, {
  42827. key: 'refreshXml_',
  42828. value: function refreshXml_() {
  42829. var _this6 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when
  42830. // sidx is implemented
  42831. this.request = this.hls_.xhr({
  42832. uri: this.srcUrl,
  42833. withCredentials: this.withCredentials
  42834. }, function (error, req) {
  42835. // disposed
  42836. if (!_this6.request) {
  42837. return;
  42838. } // clear the loader's request reference
  42839. _this6.request = null;
  42840. if (error) {
  42841. _this6.error = {
  42842. status: req.status,
  42843. message: 'DASH playlist request error at URL: ' + _this6.srcUrl,
  42844. responseText: req.responseText,
  42845. // MEDIA_ERR_NETWORK
  42846. code: 2
  42847. };
  42848. if (_this6.state === 'HAVE_NOTHING') {
  42849. _this6.started = false;
  42850. }
  42851. return _this6.trigger('error');
  42852. }
  42853. _this6.masterXml_ = req.responseText;
  42854. var newMaster = _this6.parseMasterXml();
  42855. var updatedMaster = updateMaster$1(_this6.master, newMaster);
  42856. if (updatedMaster) {
  42857. _this6.master = updatedMaster;
  42858. }
  42859. window$1.setTimeout(function () {
  42860. _this6.trigger('minimumUpdatePeriod');
  42861. }, _this6.master.minimumUpdatePeriod);
  42862. });
  42863. }
  42864. /**
  42865. * Refreshes the media playlist by re-parsing the master xml and updating playlist
  42866. * references. If this is an alternate loader, the updated parsed manifest is retrieved
  42867. * from the master loader.
  42868. */
  42869. }, {
  42870. key: 'refreshMedia_',
  42871. value: function refreshMedia_() {
  42872. var _this7 = this;
  42873. var oldMaster = void 0;
  42874. var newMaster = void 0;
  42875. if (this.masterPlaylistLoader_) {
  42876. oldMaster = this.masterPlaylistLoader_.master;
  42877. newMaster = this.masterPlaylistLoader_.parseMasterXml();
  42878. } else {
  42879. oldMaster = this.master;
  42880. newMaster = this.parseMasterXml();
  42881. }
  42882. var updatedMaster = updateMaster$1(oldMaster, newMaster);
  42883. if (updatedMaster) {
  42884. if (this.masterPlaylistLoader_) {
  42885. this.masterPlaylistLoader_.master = updatedMaster;
  42886. } else {
  42887. this.master = updatedMaster;
  42888. }
  42889. this.media_ = updatedMaster.playlists[this.media_.uri];
  42890. } else {
  42891. this.trigger('playlistunchanged');
  42892. }
  42893. if (!this.media().endList) {
  42894. this.mediaUpdateTimeout = window$1.setTimeout(function () {
  42895. _this7.trigger('mediaupdatetimeout');
  42896. }, refreshDelay(this.media(), !!updatedMaster));
  42897. }
  42898. this.trigger('loadedplaylist');
  42899. }
  42900. }]);
  42901. return DashPlaylistLoader;
  42902. }(EventTarget$1$1);
  42903. var logger = function logger(source) {
  42904. if (videojs$1.log.debug) {
  42905. return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >');
  42906. }
  42907. return function () {};
  42908. };
  42909. function noop$1() {}
  42910. /**
  42911. * @file source-updater.js
  42912. */
  42913. /**
  42914. * A queue of callbacks to be serialized and applied when a
  42915. * MediaSource and its associated SourceBuffers are not in the
  42916. * updating state. It is used by the segment loader to update the
  42917. * underlying SourceBuffers when new data is loaded, for instance.
  42918. *
  42919. * @class SourceUpdater
  42920. * @param {MediaSource} mediaSource the MediaSource to create the
  42921. * SourceBuffer from
  42922. * @param {String} mimeType the desired MIME type of the underlying
  42923. * SourceBuffer
  42924. * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is
  42925. * added to the media source
  42926. */
  42927. var SourceUpdater = function () {
  42928. function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) {
  42929. classCallCheck$1(this, SourceUpdater);
  42930. this.callbacks_ = [];
  42931. this.pendingCallback_ = null;
  42932. this.timestampOffset_ = 0;
  42933. this.mediaSource = mediaSource;
  42934. this.processedAppend_ = false;
  42935. this.type_ = type;
  42936. this.mimeType_ = mimeType;
  42937. this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']');
  42938. if (mediaSource.readyState === 'closed') {
  42939. mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter));
  42940. } else {
  42941. this.createSourceBuffer_(mimeType, sourceBufferEmitter);
  42942. }
  42943. }
  42944. createClass$1(SourceUpdater, [{
  42945. key: 'createSourceBuffer_',
  42946. value: function createSourceBuffer_(mimeType, sourceBufferEmitter) {
  42947. var _this = this;
  42948. this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType);
  42949. this.logger_('created SourceBuffer');
  42950. if (sourceBufferEmitter) {
  42951. sourceBufferEmitter.trigger('sourcebufferadded');
  42952. if (this.mediaSource.sourceBuffers.length < 2) {
  42953. // There's another source buffer we must wait for before we can start updating
  42954. // our own (or else we can get into a bad state, i.e., appending video/audio data
  42955. // before the other video/audio source buffer is available and leading to a video
  42956. // or audio only buffer).
  42957. sourceBufferEmitter.on('sourcebufferadded', function () {
  42958. _this.start_();
  42959. });
  42960. return;
  42961. }
  42962. }
  42963. this.start_();
  42964. }
  42965. }, {
  42966. key: 'start_',
  42967. value: function start_() {
  42968. var _this2 = this;
  42969. this.started_ = true; // run completion handlers and process callbacks as updateend
  42970. // events fire
  42971. this.onUpdateendCallback_ = function () {
  42972. var pendingCallback = _this2.pendingCallback_;
  42973. _this2.pendingCallback_ = null;
  42974. _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']');
  42975. if (pendingCallback) {
  42976. pendingCallback();
  42977. }
  42978. _this2.runCallback_();
  42979. };
  42980. this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_);
  42981. this.runCallback_();
  42982. }
  42983. /**
  42984. * Aborts the current segment and resets the segment parser.
  42985. *
  42986. * @param {Function} done function to call when done
  42987. * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
  42988. */
  42989. }, {
  42990. key: 'abort',
  42991. value: function abort(done) {
  42992. var _this3 = this;
  42993. if (this.processedAppend_) {
  42994. this.queueCallback_(function () {
  42995. _this3.sourceBuffer_.abort();
  42996. }, done);
  42997. }
  42998. }
  42999. /**
  43000. * Queue an update to append an ArrayBuffer.
  43001. *
  43002. * @param {ArrayBuffer} bytes
  43003. * @param {Function} done the function to call when done
  43004. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
  43005. */
  43006. }, {
  43007. key: 'appendBuffer',
  43008. value: function appendBuffer(config, done) {
  43009. var _this4 = this;
  43010. this.processedAppend_ = true;
  43011. this.queueCallback_(function () {
  43012. if (config.videoSegmentTimingInfoCallback) {
  43013. _this4.sourceBuffer_.addEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
  43014. }
  43015. _this4.sourceBuffer_.appendBuffer(config.bytes);
  43016. }, function () {
  43017. if (config.videoSegmentTimingInfoCallback) {
  43018. _this4.sourceBuffer_.removeEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
  43019. }
  43020. done();
  43021. });
  43022. }
  43023. /**
  43024. * Indicates what TimeRanges are buffered in the managed SourceBuffer.
  43025. *
  43026. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
  43027. */
  43028. }, {
  43029. key: 'buffered',
  43030. value: function buffered() {
  43031. if (!this.sourceBuffer_) {
  43032. return videojs$1.createTimeRanges();
  43033. }
  43034. return this.sourceBuffer_.buffered;
  43035. }
  43036. /**
  43037. * Queue an update to remove a time range from the buffer.
  43038. *
  43039. * @param {Number} start where to start the removal
  43040. * @param {Number} end where to end the removal
  43041. * @param {Function} [done=noop] optional callback to be executed when the remove
  43042. * operation is complete
  43043. * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
  43044. */
  43045. }, {
  43046. key: 'remove',
  43047. value: function remove(start, end) {
  43048. var _this5 = this;
  43049. var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop$1;
  43050. if (this.processedAppend_) {
  43051. this.queueCallback_(function () {
  43052. _this5.logger_('remove [' + start + ' => ' + end + ']');
  43053. _this5.sourceBuffer_.remove(start, end);
  43054. }, done);
  43055. }
  43056. }
  43057. /**
  43058. * Whether the underlying sourceBuffer is updating or not
  43059. *
  43060. * @return {Boolean} the updating status of the SourceBuffer
  43061. */
  43062. }, {
  43063. key: 'updating',
  43064. value: function updating() {
  43065. // we are updating if the sourcebuffer is updating or
  43066. return !this.sourceBuffer_ || this.sourceBuffer_.updating || // if we have a pending callback that is not our internal noop
  43067. !!this.pendingCallback_ && this.pendingCallback_ !== noop$1;
  43068. }
  43069. /**
  43070. * Set/get the timestampoffset on the SourceBuffer
  43071. *
  43072. * @return {Number} the timestamp offset
  43073. */
  43074. }, {
  43075. key: 'timestampOffset',
  43076. value: function timestampOffset(offset) {
  43077. var _this6 = this;
  43078. if (typeof offset !== 'undefined') {
  43079. this.queueCallback_(function () {
  43080. _this6.sourceBuffer_.timestampOffset = offset;
  43081. });
  43082. this.timestampOffset_ = offset;
  43083. }
  43084. return this.timestampOffset_;
  43085. }
  43086. /**
  43087. * Queue a callback to run
  43088. */
  43089. }, {
  43090. key: 'queueCallback_',
  43091. value: function queueCallback_(callback, done) {
  43092. this.callbacks_.push([callback.bind(this), done]);
  43093. this.runCallback_();
  43094. }
  43095. /**
  43096. * Run a queued callback
  43097. */
  43098. }, {
  43099. key: 'runCallback_',
  43100. value: function runCallback_() {
  43101. var callbacks = void 0;
  43102. if (!this.updating() && this.callbacks_.length && this.started_) {
  43103. callbacks = this.callbacks_.shift();
  43104. this.pendingCallback_ = callbacks[1];
  43105. callbacks[0]();
  43106. }
  43107. }
  43108. /**
  43109. * dispose of the source updater and the underlying sourceBuffer
  43110. */
  43111. }, {
  43112. key: 'dispose',
  43113. value: function dispose() {
  43114. this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
  43115. if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') {
  43116. this.sourceBuffer_.abort();
  43117. }
  43118. }
  43119. }]);
  43120. return SourceUpdater;
  43121. }();
  43122. var Config = {
  43123. GOAL_BUFFER_LENGTH: 30,
  43124. MAX_GOAL_BUFFER_LENGTH: 60,
  43125. GOAL_BUFFER_LENGTH_RATE: 1,
  43126. // 0.5 MB/s
  43127. INITIAL_BANDWIDTH: 4194304,
  43128. // A fudge factor to apply to advertised playlist bitrates to account for
  43129. // temporary flucations in client bandwidth
  43130. BANDWIDTH_VARIANCE: 1.2,
  43131. // How much of the buffer must be filled before we consider upswitching
  43132. BUFFER_LOW_WATER_LINE: 0,
  43133. MAX_BUFFER_LOW_WATER_LINE: 30,
  43134. BUFFER_LOW_WATER_LINE_RATE: 1
  43135. };
  43136. var REQUEST_ERRORS = {
  43137. FAILURE: 2,
  43138. TIMEOUT: -101,
  43139. ABORTED: -102
  43140. };
  43141. /**
  43142. * Turns segment byterange into a string suitable for use in
  43143. * HTTP Range requests
  43144. *
  43145. * @param {Object} byterange - an object with two values defining the start and end
  43146. * of a byte-range
  43147. */
  43148. var byterangeStr = function byterangeStr(byterange) {
  43149. var byterangeStart = void 0;
  43150. var byterangeEnd = void 0; // `byterangeEnd` is one less than `offset + length` because the HTTP range
  43151. // header uses inclusive ranges
  43152. byterangeEnd = byterange.offset + byterange.length - 1;
  43153. byterangeStart = byterange.offset;
  43154. return 'bytes=' + byterangeStart + '-' + byterangeEnd;
  43155. };
  43156. /**
  43157. * Defines headers for use in the xhr request for a particular segment.
  43158. *
  43159. * @param {Object} segment - a simplified copy of the segmentInfo object
  43160. * from SegmentLoader
  43161. */
  43162. var segmentXhrHeaders = function segmentXhrHeaders(segment) {
  43163. var headers = {};
  43164. if (segment.byterange) {
  43165. headers.Range = byterangeStr(segment.byterange);
  43166. }
  43167. return headers;
  43168. };
  43169. /**
  43170. * Abort all requests
  43171. *
  43172. * @param {Object} activeXhrs - an object that tracks all XHR requests
  43173. */
  43174. var abortAll = function abortAll(activeXhrs) {
  43175. activeXhrs.forEach(function (xhr) {
  43176. xhr.abort();
  43177. });
  43178. };
  43179. /**
  43180. * Gather important bandwidth stats once a request has completed
  43181. *
  43182. * @param {Object} request - the XHR request from which to gather stats
  43183. */
  43184. var getRequestStats = function getRequestStats(request) {
  43185. return {
  43186. bandwidth: request.bandwidth,
  43187. bytesReceived: request.bytesReceived || 0,
  43188. roundTripTime: request.roundTripTime || 0
  43189. };
  43190. };
  43191. /**
  43192. * If possible gather bandwidth stats as a request is in
  43193. * progress
  43194. *
  43195. * @param {Event} progressEvent - an event object from an XHR's progress event
  43196. */
  43197. var getProgressStats = function getProgressStats(progressEvent) {
  43198. var request = progressEvent.target;
  43199. var roundTripTime = Date.now() - request.requestTime;
  43200. var stats = {
  43201. bandwidth: Infinity,
  43202. bytesReceived: 0,
  43203. roundTripTime: roundTripTime || 0
  43204. };
  43205. stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
  43206. // because we should only use bandwidth stats on progress to determine when
  43207. // abort a request early due to insufficient bandwidth
  43208. stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
  43209. return stats;
  43210. };
  43211. /**
  43212. * Handle all error conditions in one place and return an object
  43213. * with all the information
  43214. *
  43215. * @param {Error|null} error - if non-null signals an error occured with the XHR
  43216. * @param {Object} request - the XHR request that possibly generated the error
  43217. */
  43218. var handleErrors = function handleErrors(error, request) {
  43219. if (request.timedout) {
  43220. return {
  43221. status: request.status,
  43222. message: 'HLS request timed-out at URL: ' + request.uri,
  43223. code: REQUEST_ERRORS.TIMEOUT,
  43224. xhr: request
  43225. };
  43226. }
  43227. if (request.aborted) {
  43228. return {
  43229. status: request.status,
  43230. message: 'HLS request aborted at URL: ' + request.uri,
  43231. code: REQUEST_ERRORS.ABORTED,
  43232. xhr: request
  43233. };
  43234. }
  43235. if (error) {
  43236. return {
  43237. status: request.status,
  43238. message: 'HLS request errored at URL: ' + request.uri,
  43239. code: REQUEST_ERRORS.FAILURE,
  43240. xhr: request
  43241. };
  43242. }
  43243. return null;
  43244. };
  43245. /**
  43246. * Handle responses for key data and convert the key data to the correct format
  43247. * for the decryption step later
  43248. *
  43249. * @param {Object} segment - a simplified copy of the segmentInfo object
  43250. * from SegmentLoader
  43251. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  43252. * this request
  43253. */
  43254. var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) {
  43255. return function (error, request) {
  43256. var response = request.response;
  43257. var errorObj = handleErrors(error, request);
  43258. if (errorObj) {
  43259. return finishProcessingFn(errorObj, segment);
  43260. }
  43261. if (response.byteLength !== 16) {
  43262. return finishProcessingFn({
  43263. status: request.status,
  43264. message: 'Invalid HLS key at URL: ' + request.uri,
  43265. code: REQUEST_ERRORS.FAILURE,
  43266. xhr: request
  43267. }, segment);
  43268. }
  43269. var view = new DataView(response);
  43270. segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
  43271. return finishProcessingFn(null, segment);
  43272. };
  43273. };
  43274. /**
  43275. * Handle init-segment responses
  43276. *
  43277. * @param {Object} segment - a simplified copy of the segmentInfo object
  43278. * from SegmentLoader
  43279. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  43280. * this request
  43281. */
  43282. var handleInitSegmentResponse = function handleInitSegmentResponse(segment, captionParser, finishProcessingFn) {
  43283. return function (error, request) {
  43284. var response = request.response;
  43285. var errorObj = handleErrors(error, request);
  43286. if (errorObj) {
  43287. return finishProcessingFn(errorObj, segment);
  43288. } // stop processing if received empty content
  43289. if (response.byteLength === 0) {
  43290. return finishProcessingFn({
  43291. status: request.status,
  43292. message: 'Empty HLS segment content at URL: ' + request.uri,
  43293. code: REQUEST_ERRORS.FAILURE,
  43294. xhr: request
  43295. }, segment);
  43296. }
  43297. segment.map.bytes = new Uint8Array(request.response); // Initialize CaptionParser if it hasn't been yet
  43298. if (!captionParser.isInitialized()) {
  43299. captionParser.init();
  43300. }
  43301. segment.map.timescales = probe.timescale(segment.map.bytes);
  43302. segment.map.videoTrackIds = probe.videoTrackIds(segment.map.bytes);
  43303. return finishProcessingFn(null, segment);
  43304. };
  43305. };
  43306. /**
  43307. * Response handler for segment-requests being sure to set the correct
  43308. * property depending on whether the segment is encryped or not
  43309. * Also records and keeps track of stats that are used for ABR purposes
  43310. *
  43311. * @param {Object} segment - a simplified copy of the segmentInfo object
  43312. * from SegmentLoader
  43313. * @param {Function} finishProcessingFn - a callback to execute to continue processing
  43314. * this request
  43315. */
  43316. var handleSegmentResponse = function handleSegmentResponse(segment, captionParser, finishProcessingFn) {
  43317. return function (error, request) {
  43318. var response = request.response;
  43319. var errorObj = handleErrors(error, request);
  43320. var parsed = void 0;
  43321. if (errorObj) {
  43322. return finishProcessingFn(errorObj, segment);
  43323. } // stop processing if received empty content
  43324. if (response.byteLength === 0) {
  43325. return finishProcessingFn({
  43326. status: request.status,
  43327. message: 'Empty HLS segment content at URL: ' + request.uri,
  43328. code: REQUEST_ERRORS.FAILURE,
  43329. xhr: request
  43330. }, segment);
  43331. }
  43332. segment.stats = getRequestStats(request);
  43333. if (segment.key) {
  43334. segment.encryptedBytes = new Uint8Array(request.response);
  43335. } else {
  43336. segment.bytes = new Uint8Array(request.response);
  43337. } // This is likely an FMP4 and has the init segment.
  43338. // Run through the CaptionParser in case there are captions.
  43339. if (segment.map && segment.map.bytes) {
  43340. // Initialize CaptionParser if it hasn't been yet
  43341. if (!captionParser.isInitialized()) {
  43342. captionParser.init();
  43343. }
  43344. parsed = captionParser.parse(segment.bytes, segment.map.videoTrackIds, segment.map.timescales);
  43345. if (parsed && parsed.captions) {
  43346. segment.captionStreams = parsed.captionStreams;
  43347. segment.fmp4Captions = parsed.captions;
  43348. }
  43349. }
  43350. return finishProcessingFn(null, segment);
  43351. };
  43352. };
  43353. /**
  43354. * Decrypt the segment via the decryption web worker
  43355. *
  43356. * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
  43357. * @param {Object} segment - a simplified copy of the segmentInfo object
  43358. * from SegmentLoader
  43359. * @param {Function} doneFn - a callback that is executed after decryption has completed
  43360. */
  43361. var decryptSegment = function decryptSegment(decrypter, segment, doneFn) {
  43362. var decryptionHandler = function decryptionHandler(event) {
  43363. if (event.data.source === segment.requestId) {
  43364. decrypter.removeEventListener('message', decryptionHandler);
  43365. var decrypted = event.data.decrypted;
  43366. segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength);
  43367. return doneFn(null, segment);
  43368. }
  43369. };
  43370. decrypter.addEventListener('message', decryptionHandler); // this is an encrypted segment
  43371. // incrementally decrypt the segment
  43372. decrypter.postMessage(createTransferableMessage({
  43373. source: segment.requestId,
  43374. encrypted: segment.encryptedBytes,
  43375. key: segment.key.bytes,
  43376. iv: segment.key.iv
  43377. }), [segment.encryptedBytes.buffer, segment.key.bytes.buffer]);
  43378. };
  43379. /**
  43380. * This function waits for all XHRs to finish (with either success or failure)
  43381. * before continueing processing via it's callback. The function gathers errors
  43382. * from each request into a single errors array so that the error status for
  43383. * each request can be examined later.
  43384. *
  43385. * @param {Object} activeXhrs - an object that tracks all XHR requests
  43386. * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
  43387. * @param {Function} doneFn - a callback that is executed after all resources have been
  43388. * downloaded and any decryption completed
  43389. */
  43390. var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) {
  43391. var count = 0;
  43392. var didError = false;
  43393. return function (error, segment) {
  43394. if (didError) {
  43395. return;
  43396. }
  43397. if (error) {
  43398. didError = true; // If there are errors, we have to abort any outstanding requests
  43399. abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
  43400. // handle the aborted events from those requests, there are some cases where we may
  43401. // never get an aborted event. For instance, if the network connection is lost and
  43402. // there were two requests, the first may have triggered an error immediately, while
  43403. // the second request remains unsent. In that case, the aborted algorithm will not
  43404. // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
  43405. //
  43406. // We also can't rely on the ready state of the XHR, since the request that
  43407. // triggered the connection error may also show as a ready state of 0 (unsent).
  43408. // Therefore, we have to finish this group of requests immediately after the first
  43409. // seen error.
  43410. return doneFn(error, segment);
  43411. }
  43412. count += 1;
  43413. if (count === activeXhrs.length) {
  43414. // Keep track of when *all* of the requests have completed
  43415. segment.endOfAllRequests = Date.now();
  43416. if (segment.encryptedBytes) {
  43417. return decryptSegment(decrypter, segment, doneFn);
  43418. } // Otherwise, everything is ready just continue
  43419. return doneFn(null, segment);
  43420. }
  43421. };
  43422. };
  43423. /**
  43424. * Simple progress event callback handler that gathers some stats before
  43425. * executing a provided callback with the `segment` object
  43426. *
  43427. * @param {Object} segment - a simplified copy of the segmentInfo object
  43428. * from SegmentLoader
  43429. * @param {Function} progressFn - a callback that is executed each time a progress event
  43430. * is received
  43431. * @param {Event} event - the progress event object from XMLHttpRequest
  43432. */
  43433. var handleProgress = function handleProgress(segment, progressFn) {
  43434. return function (event) {
  43435. segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
  43436. if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
  43437. segment.stats.firstBytesReceivedAt = Date.now();
  43438. }
  43439. return progressFn(event, segment);
  43440. };
  43441. };
  43442. /**
  43443. * Load all resources and does any processing necessary for a media-segment
  43444. *
  43445. * Features:
  43446. * decrypts the media-segment if it has a key uri and an iv
  43447. * aborts *all* requests if *any* one request fails
  43448. *
  43449. * The segment object, at minimum, has the following format:
  43450. * {
  43451. * resolvedUri: String,
  43452. * [byterange]: {
  43453. * offset: Number,
  43454. * length: Number
  43455. * },
  43456. * [key]: {
  43457. * resolvedUri: String
  43458. * [byterange]: {
  43459. * offset: Number,
  43460. * length: Number
  43461. * },
  43462. * iv: {
  43463. * bytes: Uint32Array
  43464. * }
  43465. * },
  43466. * [map]: {
  43467. * resolvedUri: String,
  43468. * [byterange]: {
  43469. * offset: Number,
  43470. * length: Number
  43471. * },
  43472. * [bytes]: Uint8Array
  43473. * }
  43474. * }
  43475. * ...where [name] denotes optional properties
  43476. *
  43477. * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
  43478. * @param {Object} xhrOptions - the base options to provide to all xhr requests
  43479. * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
  43480. * decryption routines
  43481. * @param {Object} segment - a simplified copy of the segmentInfo object
  43482. * from SegmentLoader
  43483. * @param {Function} progressFn - a callback that receives progress events from the main
  43484. * segment's xhr request
  43485. * @param {Function} doneFn - a callback that is executed only once all requests have
  43486. * succeeded or failed
  43487. * @returns {Function} a function that, when invoked, immediately aborts all
  43488. * outstanding requests
  43489. */
  43490. var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, captionParser, segment, progressFn, doneFn) {
  43491. var activeXhrs = [];
  43492. var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); // optionally, request the decryption key
  43493. if (segment.key) {
  43494. var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, {
  43495. uri: segment.key.resolvedUri,
  43496. responseType: 'arraybuffer'
  43497. });
  43498. var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn);
  43499. var keyXhr = xhr(keyRequestOptions, keyRequestCallback);
  43500. activeXhrs.push(keyXhr);
  43501. } // optionally, request the associated media init segment
  43502. if (segment.map && !segment.map.bytes) {
  43503. var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, {
  43504. uri: segment.map.resolvedUri,
  43505. responseType: 'arraybuffer',
  43506. headers: segmentXhrHeaders(segment.map)
  43507. });
  43508. var initSegmentRequestCallback = handleInitSegmentResponse(segment, captionParser, finishProcessingFn);
  43509. var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
  43510. activeXhrs.push(initSegmentXhr);
  43511. }
  43512. var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, {
  43513. uri: segment.resolvedUri,
  43514. responseType: 'arraybuffer',
  43515. headers: segmentXhrHeaders(segment)
  43516. });
  43517. var segmentRequestCallback = handleSegmentResponse(segment, captionParser, finishProcessingFn);
  43518. var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
  43519. segmentXhr.addEventListener('progress', handleProgress(segment, progressFn));
  43520. activeXhrs.push(segmentXhr);
  43521. return function () {
  43522. return abortAll(activeXhrs);
  43523. };
  43524. }; // Utilities
  43525. /**
  43526. * Returns the CSS value for the specified property on an element
  43527. * using `getComputedStyle`. Firefox has a long-standing issue where
  43528. * getComputedStyle() may return null when running in an iframe with
  43529. * `display: none`.
  43530. *
  43531. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
  43532. * @param {HTMLElement} el the htmlelement to work on
  43533. * @param {string} the proprety to get the style for
  43534. */
  43535. var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
  43536. var result = void 0;
  43537. if (!el) {
  43538. return '';
  43539. }
  43540. result = window$1.getComputedStyle(el);
  43541. if (!result) {
  43542. return '';
  43543. }
  43544. return result[property];
  43545. };
  43546. /**
  43547. * Resuable stable sort function
  43548. *
  43549. * @param {Playlists} array
  43550. * @param {Function} sortFn Different comparators
  43551. * @function stableSort
  43552. */
  43553. var stableSort = function stableSort(array, sortFn) {
  43554. var newArray = array.slice();
  43555. array.sort(function (left, right) {
  43556. var cmp = sortFn(left, right);
  43557. if (cmp === 0) {
  43558. return newArray.indexOf(left) - newArray.indexOf(right);
  43559. }
  43560. return cmp;
  43561. });
  43562. };
  43563. /**
  43564. * A comparator function to sort two playlist object by bandwidth.
  43565. *
  43566. * @param {Object} left a media playlist object
  43567. * @param {Object} right a media playlist object
  43568. * @return {Number} Greater than zero if the bandwidth attribute of
  43569. * left is greater than the corresponding attribute of right. Less
  43570. * than zero if the bandwidth of right is greater than left and
  43571. * exactly zero if the two are equal.
  43572. */
  43573. var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
  43574. var leftBandwidth = void 0;
  43575. var rightBandwidth = void 0;
  43576. if (left.attributes.BANDWIDTH) {
  43577. leftBandwidth = left.attributes.BANDWIDTH;
  43578. }
  43579. leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE;
  43580. if (right.attributes.BANDWIDTH) {
  43581. rightBandwidth = right.attributes.BANDWIDTH;
  43582. }
  43583. rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE;
  43584. return leftBandwidth - rightBandwidth;
  43585. };
  43586. /**
  43587. * A comparator function to sort two playlist object by resolution (width).
  43588. * @param {Object} left a media playlist object
  43589. * @param {Object} right a media playlist object
  43590. * @return {Number} Greater than zero if the resolution.width attribute of
  43591. * left is greater than the corresponding attribute of right. Less
  43592. * than zero if the resolution.width of right is greater than left and
  43593. * exactly zero if the two are equal.
  43594. */
  43595. var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
  43596. var leftWidth = void 0;
  43597. var rightWidth = void 0;
  43598. if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
  43599. leftWidth = left.attributes.RESOLUTION.width;
  43600. }
  43601. leftWidth = leftWidth || window$1.Number.MAX_VALUE;
  43602. if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
  43603. rightWidth = right.attributes.RESOLUTION.width;
  43604. }
  43605. rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
  43606. // have the same media dimensions/ resolution
  43607. if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
  43608. return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
  43609. }
  43610. return leftWidth - rightWidth;
  43611. };
  43612. /**
  43613. * Chooses the appropriate media playlist based on bandwidth and player size
  43614. *
  43615. * @param {Object} master
  43616. * Object representation of the master manifest
  43617. * @param {Number} playerBandwidth
  43618. * Current calculated bandwidth of the player
  43619. * @param {Number} playerWidth
  43620. * Current width of the player element
  43621. * @param {Number} playerHeight
  43622. * Current height of the player element
  43623. * @param {Boolean} limitRenditionByPlayerDimensions
  43624. * True if the player width and height should be used during the selection, false otherwise
  43625. * @return {Playlist} the highest bitrate playlist less than the
  43626. * currently detected bandwidth, accounting for some amount of
  43627. * bandwidth variance
  43628. */
  43629. var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) {
  43630. // convert the playlists to an intermediary representation to make comparisons easier
  43631. var sortedPlaylistReps = master.playlists.map(function (playlist) {
  43632. var width = void 0;
  43633. var height = void 0;
  43634. var bandwidth = void 0;
  43635. width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
  43636. height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
  43637. bandwidth = playlist.attributes.BANDWIDTH;
  43638. bandwidth = bandwidth || window$1.Number.MAX_VALUE;
  43639. return {
  43640. bandwidth: bandwidth,
  43641. width: width,
  43642. height: height,
  43643. playlist: playlist
  43644. };
  43645. });
  43646. stableSort(sortedPlaylistReps, function (left, right) {
  43647. return left.bandwidth - right.bandwidth;
  43648. }); // filter out any playlists that have been excluded due to
  43649. // incompatible configurations
  43650. sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  43651. return !Playlist.isIncompatible(rep.playlist);
  43652. }); // filter out any playlists that have been disabled manually through the representations
  43653. // api or blacklisted temporarily due to playback errors.
  43654. var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  43655. return Playlist.isEnabled(rep.playlist);
  43656. });
  43657. if (!enabledPlaylistReps.length) {
  43658. // if there are no enabled playlists, then they have all been blacklisted or disabled
  43659. // by the user through the representations api. In this case, ignore blacklisting and
  43660. // fallback to what the user wants by using playlists the user has not disabled.
  43661. enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
  43662. return !Playlist.isDisabled(rep.playlist);
  43663. });
  43664. } // filter out any variant that has greater effective bitrate
  43665. // than the current estimated bandwidth
  43666. var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
  43667. return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
  43668. });
  43669. var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
  43670. // and then taking the very first element
  43671. var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
  43672. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  43673. })[0]; // if we're not going to limit renditions by player size, make an early decision.
  43674. if (limitRenditionByPlayerDimensions === false) {
  43675. var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  43676. return _chosenRep ? _chosenRep.playlist : null;
  43677. } // filter out playlists without resolution information
  43678. var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
  43679. return rep.width && rep.height;
  43680. }); // sort variants by resolution
  43681. stableSort(haveResolution, function (left, right) {
  43682. return left.width - right.width;
  43683. }); // if we have the exact resolution as the player use it
  43684. var resolutionBestRepList = haveResolution.filter(function (rep) {
  43685. return rep.width === playerWidth && rep.height === playerHeight;
  43686. });
  43687. highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
  43688. var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
  43689. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  43690. })[0];
  43691. var resolutionPlusOneList = void 0;
  43692. var resolutionPlusOneSmallest = void 0;
  43693. var resolutionPlusOneRep = void 0; // find the smallest variant that is larger than the player
  43694. // if there is no match of exact resolution
  43695. if (!resolutionBestRep) {
  43696. resolutionPlusOneList = haveResolution.filter(function (rep) {
  43697. return rep.width > playerWidth || rep.height > playerHeight;
  43698. }); // find all the variants have the same smallest resolution
  43699. resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
  43700. return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
  43701. }); // ensure that we also pick the highest bandwidth variant that
  43702. // is just-larger-than the video player
  43703. highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
  43704. resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
  43705. return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
  43706. })[0];
  43707. } // fallback chain of variants
  43708. var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
  43709. return chosenRep ? chosenRep.playlist : null;
  43710. }; // Playlist Selectors
  43711. /**
  43712. * Chooses the appropriate media playlist based on the most recent
  43713. * bandwidth estimate and the player size.
  43714. *
  43715. * Expects to be called within the context of an instance of HlsHandler
  43716. *
  43717. * @return {Playlist} the highest bitrate playlist less than the
  43718. * currently detected bandwidth, accounting for some amount of
  43719. * bandwidth variance
  43720. */
  43721. var lastBandwidthSelector = function lastBandwidthSelector() {
  43722. return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), this.limitRenditionByPlayerDimensions);
  43723. };
  43724. /**
  43725. * Chooses the appropriate media playlist based on the potential to rebuffer
  43726. *
  43727. * @param {Object} settings
  43728. * Object of information required to use this selector
  43729. * @param {Object} settings.master
  43730. * Object representation of the master manifest
  43731. * @param {Number} settings.currentTime
  43732. * The current time of the player
  43733. * @param {Number} settings.bandwidth
  43734. * Current measured bandwidth
  43735. * @param {Number} settings.duration
  43736. * Duration of the media
  43737. * @param {Number} settings.segmentDuration
  43738. * Segment duration to be used in round trip time calculations
  43739. * @param {Number} settings.timeUntilRebuffer
  43740. * Time left in seconds until the player has to rebuffer
  43741. * @param {Number} settings.currentTimeline
  43742. * The current timeline segments are being loaded from
  43743. * @param {SyncController} settings.syncController
  43744. * SyncController for determining if we have a sync point for a given playlist
  43745. * @return {Object|null}
  43746. * {Object} return.playlist
  43747. * The highest bandwidth playlist with the least amount of rebuffering
  43748. * {Number} return.rebufferingImpact
  43749. * The amount of time in seconds switching to this playlist will rebuffer. A
  43750. * negative value means that switching will cause zero rebuffering.
  43751. */
  43752. var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
  43753. var master = settings.master,
  43754. currentTime = settings.currentTime,
  43755. bandwidth = settings.bandwidth,
  43756. duration$$1 = settings.duration,
  43757. segmentDuration = settings.segmentDuration,
  43758. timeUntilRebuffer = settings.timeUntilRebuffer,
  43759. currentTimeline = settings.currentTimeline,
  43760. syncController = settings.syncController; // filter out any playlists that have been excluded due to
  43761. // incompatible configurations
  43762. var compatiblePlaylists = master.playlists.filter(function (playlist) {
  43763. return !Playlist.isIncompatible(playlist);
  43764. }); // filter out any playlists that have been disabled manually through the representations
  43765. // api or blacklisted temporarily due to playback errors.
  43766. var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
  43767. if (!enabledPlaylists.length) {
  43768. // if there are no enabled playlists, then they have all been blacklisted or disabled
  43769. // by the user through the representations api. In this case, ignore blacklisting and
  43770. // fallback to what the user wants by using playlists the user has not disabled.
  43771. enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
  43772. return !Playlist.isDisabled(playlist);
  43773. });
  43774. }
  43775. var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
  43776. var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
  43777. var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
  43778. // sync request first. This will double the request time
  43779. var numRequests = syncPoint ? 1 : 2;
  43780. var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
  43781. var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
  43782. return {
  43783. playlist: playlist,
  43784. rebufferingImpact: rebufferingImpact
  43785. };
  43786. });
  43787. var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
  43788. return estimate.rebufferingImpact <= 0;
  43789. }); // Sort by bandwidth DESC
  43790. stableSort(noRebufferingPlaylists, function (a, b) {
  43791. return comparePlaylistBandwidth(b.playlist, a.playlist);
  43792. });
  43793. if (noRebufferingPlaylists.length) {
  43794. return noRebufferingPlaylists[0];
  43795. }
  43796. stableSort(rebufferingEstimates, function (a, b) {
  43797. return a.rebufferingImpact - b.rebufferingImpact;
  43798. });
  43799. return rebufferingEstimates[0] || null;
  43800. };
  43801. /**
  43802. * Chooses the appropriate media playlist, which in this case is the lowest bitrate
  43803. * one with video. If no renditions with video exist, return the lowest audio rendition.
  43804. *
  43805. * Expects to be called within the context of an instance of HlsHandler
  43806. *
  43807. * @return {Object|null}
  43808. * {Object} return.playlist
  43809. * The lowest bitrate playlist that contains a video codec. If no such rendition
  43810. * exists pick the lowest audio rendition.
  43811. */
  43812. var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
  43813. // filter out any playlists that have been excluded due to
  43814. // incompatible configurations or playback errors
  43815. var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
  43816. stableSort(playlists, function (a, b) {
  43817. return comparePlaylistBandwidth(a, b);
  43818. }); // Parse and assume that playlists with no video codec have no video
  43819. // (this is not necessarily true, although it is generally true).
  43820. //
  43821. // If an entire manifest has no valid videos everything will get filtered
  43822. // out.
  43823. var playlistsWithVideo = playlists.filter(function (playlist) {
  43824. return parseCodecs(playlist.attributes.CODECS).videoCodec;
  43825. });
  43826. return playlistsWithVideo[0] || null;
  43827. };
  43828. /**
  43829. * Create captions text tracks on video.js if they do not exist
  43830. *
  43831. * @param {Object} inbandTextTracks a reference to current inbandTextTracks
  43832. * @param {Object} tech the video.js tech
  43833. * @param {Object} captionStreams the caption streams to create
  43834. * @private
  43835. */
  43836. var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStreams) {
  43837. for (var trackId in captionStreams) {
  43838. if (!inbandTextTracks[trackId]) {
  43839. tech.trigger({
  43840. type: 'usage',
  43841. name: 'hls-608'
  43842. });
  43843. var track = tech.textTracks().getTrackById(trackId);
  43844. if (track) {
  43845. // Resuse an existing track with a CC# id because this was
  43846. // very likely created by videojs-contrib-hls from information
  43847. // in the m3u8 for us to use
  43848. inbandTextTracks[trackId] = track;
  43849. } else {
  43850. // Otherwise, create a track with the default `CC#` label and
  43851. // without a language
  43852. inbandTextTracks[trackId] = tech.addRemoteTextTrack({
  43853. kind: 'captions',
  43854. id: trackId,
  43855. label: trackId
  43856. }, false).track;
  43857. }
  43858. }
  43859. }
  43860. };
  43861. var addCaptionData = function addCaptionData(_ref) {
  43862. var inbandTextTracks = _ref.inbandTextTracks,
  43863. captionArray = _ref.captionArray,
  43864. timestampOffset = _ref.timestampOffset;
  43865. if (!captionArray) {
  43866. return;
  43867. }
  43868. var Cue = window.WebKitDataCue || window.VTTCue;
  43869. captionArray.forEach(function (caption) {
  43870. var track = caption.stream;
  43871. var startTime = caption.startTime;
  43872. var endTime = caption.endTime;
  43873. if (!inbandTextTracks[track]) {
  43874. return;
  43875. }
  43876. startTime += timestampOffset;
  43877. endTime += timestampOffset;
  43878. inbandTextTracks[track].addCue(new Cue(startTime, endTime, caption.text));
  43879. });
  43880. };
  43881. /**
  43882. * @file segment-loader.js
  43883. */
  43884. // in ms
  43885. var CHECK_BUFFER_DELAY = 500;
  43886. /**
  43887. * Determines if we should call endOfStream on the media source based
  43888. * on the state of the buffer or if appened segment was the final
  43889. * segment in the playlist.
  43890. *
  43891. * @param {Object} playlist a media playlist object
  43892. * @param {Object} mediaSource the MediaSource object
  43893. * @param {Number} segmentIndex the index of segment we last appended
  43894. * @returns {Boolean} do we need to call endOfStream on the MediaSource
  43895. */
  43896. var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) {
  43897. if (!playlist || !mediaSource) {
  43898. return false;
  43899. }
  43900. var segments = playlist.segments; // determine a few boolean values to help make the branch below easier
  43901. // to read
  43902. var appendedLastSegment = segmentIndex === segments.length; // if we've buffered to the end of the video, we need to call endOfStream
  43903. // so that MediaSources can trigger the `ended` event when it runs out of
  43904. // buffered data instead of waiting for me
  43905. return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment;
  43906. };
  43907. var finite = function finite(num) {
  43908. return typeof num === 'number' && isFinite(num);
  43909. };
  43910. var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) {
  43911. // Although these checks should most likely cover non 'main' types, for now it narrows
  43912. // the scope of our checks.
  43913. if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) {
  43914. return null;
  43915. }
  43916. if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) {
  43917. return 'Neither audio nor video found in segment.';
  43918. }
  43919. if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) {
  43920. 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.';
  43921. }
  43922. if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) {
  43923. 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.';
  43924. }
  43925. return null;
  43926. };
  43927. /**
  43928. * Calculates a time value that is safe to remove from the back buffer without interupting
  43929. * playback.
  43930. *
  43931. * @param {TimeRange} seekable
  43932. * The current seekable range
  43933. * @param {Number} currentTime
  43934. * The current time of the player
  43935. * @param {Number} targetDuration
  43936. * The target duration of the current playlist
  43937. * @return {Number}
  43938. * Time that is safe to remove from the back buffer without interupting playback
  43939. */
  43940. var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) {
  43941. var removeToTime = void 0;
  43942. if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) {
  43943. // If we have a seekable range use that as the limit for what can be removed safely
  43944. removeToTime = seekable$$1.start(0);
  43945. } else {
  43946. // otherwise remove anything older than 30 seconds before the current play head
  43947. removeToTime = currentTime - 30;
  43948. } // Don't allow removing from the buffer within target duration of current time
  43949. // to avoid the possibility of removing the GOP currently being played which could
  43950. // cause playback stalls.
  43951. return Math.min(removeToTime, currentTime - targetDuration);
  43952. };
  43953. var segmentInfoString = function segmentInfoString(segmentInfo) {
  43954. var _segmentInfo$segment = segmentInfo.segment,
  43955. start = _segmentInfo$segment.start,
  43956. end = _segmentInfo$segment.end,
  43957. _segmentInfo$playlist = segmentInfo.playlist,
  43958. seq = _segmentInfo$playlist.mediaSequence,
  43959. id = _segmentInfo$playlist.id,
  43960. _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
  43961. segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2,
  43962. index = segmentInfo.mediaIndex,
  43963. timeline = segmentInfo.timeline;
  43964. return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' ');
  43965. };
  43966. /**
  43967. * An object that manages segment loading and appending.
  43968. *
  43969. * @class SegmentLoader
  43970. * @param {Object} options required and optional options
  43971. * @extends videojs.EventTarget
  43972. */
  43973. var SegmentLoader = function (_videojs$EventTarget) {
  43974. inherits$1(SegmentLoader, _videojs$EventTarget);
  43975. function SegmentLoader(settings) {
  43976. classCallCheck$1(this, SegmentLoader); // check pre-conditions
  43977. var _this = possibleConstructorReturn$1(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this));
  43978. if (!settings) {
  43979. throw new TypeError('Initialization settings are required');
  43980. }
  43981. if (typeof settings.currentTime !== 'function') {
  43982. throw new TypeError('No currentTime getter specified');
  43983. }
  43984. if (!settings.mediaSource) {
  43985. throw new TypeError('No MediaSource specified');
  43986. } // public properties
  43987. _this.bandwidth = settings.bandwidth;
  43988. _this.throughput = {
  43989. rate: 0,
  43990. count: 0
  43991. };
  43992. _this.roundTrip = NaN;
  43993. _this.resetStats_();
  43994. _this.mediaIndex = null; // private settings
  43995. _this.hasPlayed_ = settings.hasPlayed;
  43996. _this.currentTime_ = settings.currentTime;
  43997. _this.seekable_ = settings.seekable;
  43998. _this.seeking_ = settings.seeking;
  43999. _this.duration_ = settings.duration;
  44000. _this.mediaSource_ = settings.mediaSource;
  44001. _this.hls_ = settings.hls;
  44002. _this.loaderType_ = settings.loaderType;
  44003. _this.startingMedia_ = void 0;
  44004. _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
  44005. _this.goalBufferLength_ = settings.goalBufferLength;
  44006. _this.sourceType_ = settings.sourceType;
  44007. _this.inbandTextTracks_ = settings.inbandTextTracks;
  44008. _this.state_ = 'INIT'; // private instance variables
  44009. _this.checkBufferTimeout_ = null;
  44010. _this.error_ = void 0;
  44011. _this.currentTimeline_ = -1;
  44012. _this.pendingSegment_ = null;
  44013. _this.mimeType_ = null;
  44014. _this.sourceUpdater_ = null;
  44015. _this.xhrOptions_ = null; // Fragmented mp4 playback
  44016. _this.activeInitSegmentId_ = null;
  44017. _this.initSegments_ = {}; // Fmp4 CaptionParser
  44018. _this.captionParser_ = new mp4_6();
  44019. _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
  44020. // between a time in the display time and a segment index within
  44021. // a playlist
  44022. _this.syncController_ = settings.syncController;
  44023. _this.syncPoint_ = {
  44024. segmentIndex: 0,
  44025. time: 0
  44026. };
  44027. _this.syncController_.on('syncinfoupdate', function () {
  44028. return _this.trigger('syncinfoupdate');
  44029. });
  44030. _this.mediaSource_.addEventListener('sourceopen', function () {
  44031. return _this.ended_ = false;
  44032. }); // ...for determining the fetch location
  44033. _this.fetchAtBuffer_ = false;
  44034. _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']');
  44035. Object.defineProperty(_this, 'state', {
  44036. get: function get$$1() {
  44037. return this.state_;
  44038. },
  44039. set: function set$$1(newState) {
  44040. if (newState !== this.state_) {
  44041. this.logger_(this.state_ + ' -> ' + newState);
  44042. this.state_ = newState;
  44043. }
  44044. }
  44045. });
  44046. return _this;
  44047. }
  44048. /**
  44049. * reset all of our media stats
  44050. *
  44051. * @private
  44052. */
  44053. createClass$1(SegmentLoader, [{
  44054. key: 'resetStats_',
  44055. value: function resetStats_() {
  44056. this.mediaBytesTransferred = 0;
  44057. this.mediaRequests = 0;
  44058. this.mediaRequestsAborted = 0;
  44059. this.mediaRequestsTimedout = 0;
  44060. this.mediaRequestsErrored = 0;
  44061. this.mediaTransferDuration = 0;
  44062. this.mediaSecondsLoaded = 0;
  44063. }
  44064. /**
  44065. * dispose of the SegmentLoader and reset to the default state
  44066. */
  44067. }, {
  44068. key: 'dispose',
  44069. value: function dispose() {
  44070. this.state = 'DISPOSED';
  44071. this.pause();
  44072. this.abort_();
  44073. if (this.sourceUpdater_) {
  44074. this.sourceUpdater_.dispose();
  44075. }
  44076. this.resetStats_();
  44077. this.captionParser_.reset();
  44078. }
  44079. /**
  44080. * abort anything that is currently doing on with the SegmentLoader
  44081. * and reset to a default state
  44082. */
  44083. }, {
  44084. key: 'abort',
  44085. value: function abort() {
  44086. if (this.state !== 'WAITING') {
  44087. if (this.pendingSegment_) {
  44088. this.pendingSegment_ = null;
  44089. }
  44090. return;
  44091. }
  44092. this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
  44093. // since we are no longer "waiting" on any requests. XHR callback is not always run
  44094. // when the request is aborted. This will prevent the loader from being stuck in the
  44095. // WAITING state indefinitely.
  44096. this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
  44097. // next segment
  44098. if (!this.paused()) {
  44099. this.monitorBuffer_();
  44100. }
  44101. }
  44102. /**
  44103. * abort all pending xhr requests and null any pending segements
  44104. *
  44105. * @private
  44106. */
  44107. }, {
  44108. key: 'abort_',
  44109. value: function abort_() {
  44110. if (this.pendingSegment_) {
  44111. this.pendingSegment_.abortRequests();
  44112. } // clear out the segment being processed
  44113. this.pendingSegment_ = null;
  44114. }
  44115. /**
  44116. * set an error on the segment loader and null out any pending segements
  44117. *
  44118. * @param {Error} error the error to set on the SegmentLoader
  44119. * @return {Error} the error that was set or that is currently set
  44120. */
  44121. }, {
  44122. key: 'error',
  44123. value: function error(_error) {
  44124. if (typeof _error !== 'undefined') {
  44125. this.error_ = _error;
  44126. }
  44127. this.pendingSegment_ = null;
  44128. return this.error_;
  44129. }
  44130. }, {
  44131. key: 'endOfStream',
  44132. value: function endOfStream() {
  44133. this.ended_ = true;
  44134. this.pause();
  44135. this.trigger('ended');
  44136. }
  44137. /**
  44138. * Indicates which time ranges are buffered
  44139. *
  44140. * @return {TimeRange}
  44141. * TimeRange object representing the current buffered ranges
  44142. */
  44143. }, {
  44144. key: 'buffered_',
  44145. value: function buffered_() {
  44146. if (!this.sourceUpdater_) {
  44147. return videojs$1.createTimeRanges();
  44148. }
  44149. return this.sourceUpdater_.buffered();
  44150. }
  44151. /**
  44152. * Gets and sets init segment for the provided map
  44153. *
  44154. * @param {Object} map
  44155. * The map object representing the init segment to get or set
  44156. * @param {Boolean=} set
  44157. * If true, the init segment for the provided map should be saved
  44158. * @return {Object}
  44159. * map object for desired init segment
  44160. */
  44161. }, {
  44162. key: 'initSegment',
  44163. value: function initSegment(map) {
  44164. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  44165. if (!map) {
  44166. return null;
  44167. }
  44168. var id = initSegmentId(map);
  44169. var storedMap = this.initSegments_[id];
  44170. if (set$$1 && !storedMap && map.bytes) {
  44171. this.initSegments_[id] = storedMap = {
  44172. resolvedUri: map.resolvedUri,
  44173. byterange: map.byterange,
  44174. bytes: map.bytes,
  44175. timescales: map.timescales,
  44176. videoTrackIds: map.videoTrackIds
  44177. };
  44178. }
  44179. return storedMap || map;
  44180. }
  44181. /**
  44182. * Returns true if all configuration required for loading is present, otherwise false.
  44183. *
  44184. * @return {Boolean} True if the all configuration is ready for loading
  44185. * @private
  44186. */
  44187. }, {
  44188. key: 'couldBeginLoading_',
  44189. value: function couldBeginLoading_() {
  44190. return this.playlist_ && ( // the source updater is created when init_ is called, so either having a
  44191. // source updater or being in the INIT state with a mimeType is enough
  44192. // to say we have all the needed configuration to start loading.
  44193. this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused();
  44194. }
  44195. /**
  44196. * load a playlist and start to fill the buffer
  44197. */
  44198. }, {
  44199. key: 'load',
  44200. value: function load() {
  44201. // un-pause
  44202. this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
  44203. // specified
  44204. if (!this.playlist_) {
  44205. return;
  44206. } // not sure if this is the best place for this
  44207. this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading
  44208. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  44209. return this.init_();
  44210. } // if we're in the middle of processing a segment already, don't
  44211. // kick off an additional segment request
  44212. if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
  44213. return;
  44214. }
  44215. this.state = 'READY';
  44216. }
  44217. /**
  44218. * Once all the starting parameters have been specified, begin
  44219. * operation. This method should only be invoked from the INIT
  44220. * state.
  44221. *
  44222. * @private
  44223. */
  44224. }, {
  44225. key: 'init_',
  44226. value: function init_() {
  44227. this.state = 'READY';
  44228. this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_);
  44229. this.resetEverything();
  44230. return this.monitorBuffer_();
  44231. }
  44232. /**
  44233. * set a playlist on the segment loader
  44234. *
  44235. * @param {PlaylistLoader} media the playlist to set on the segment loader
  44236. */
  44237. }, {
  44238. key: 'playlist',
  44239. value: function playlist(newPlaylist) {
  44240. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  44241. if (!newPlaylist) {
  44242. return;
  44243. }
  44244. var oldPlaylist = this.playlist_;
  44245. var segmentInfo = this.pendingSegment_;
  44246. this.playlist_ = newPlaylist;
  44247. this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
  44248. // is always our zero-time so force a sync update each time the playlist
  44249. // is refreshed from the server
  44250. if (!this.hasPlayed_()) {
  44251. newPlaylist.syncInfo = {
  44252. mediaSequence: newPlaylist.mediaSequence,
  44253. time: 0
  44254. };
  44255. }
  44256. var oldId = null;
  44257. if (oldPlaylist) {
  44258. if (oldPlaylist.id) {
  44259. oldId = oldPlaylist.id;
  44260. } else if (oldPlaylist.uri) {
  44261. oldId = oldPlaylist.uri;
  44262. }
  44263. }
  44264. this.logger_('playlist update [' + oldId + ' => ' + (newPlaylist.id || newPlaylist.uri) + ']'); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
  44265. // in LIVE, we always want to update with new playlists (including refreshes)
  44266. this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
  44267. // buffering now
  44268. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  44269. return this.init_();
  44270. }
  44271. if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
  44272. if (this.mediaIndex !== null) {
  44273. // we must "resync" the segment loader when we switch renditions and
  44274. // the segment loader is already synced to the previous rendition
  44275. this.resyncLoader();
  44276. } // the rest of this function depends on `oldPlaylist` being defined
  44277. return;
  44278. } // we reloaded the same playlist so we are in a live scenario
  44279. // and we will likely need to adjust the mediaIndex
  44280. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
  44281. this.logger_('live window shift [' + mediaSequenceDiff + ']'); // update the mediaIndex on the SegmentLoader
  44282. // this is important because we can abort a request and this value must be
  44283. // equal to the last appended mediaIndex
  44284. if (this.mediaIndex !== null) {
  44285. this.mediaIndex -= mediaSequenceDiff;
  44286. } // update the mediaIndex on the SegmentInfo object
  44287. // this is important because we will update this.mediaIndex with this value
  44288. // in `handleUpdateEnd_` after the segment has been successfully appended
  44289. if (segmentInfo) {
  44290. segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is
  44291. // saved for the new playlist's segment, however, if the segment fell off the
  44292. // playlist, we can leave the old reference and just lose the timing info
  44293. if (segmentInfo.mediaIndex >= 0) {
  44294. segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
  44295. }
  44296. }
  44297. this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
  44298. }
  44299. /**
  44300. * Prevent the loader from fetching additional segments. If there
  44301. * is a segment request outstanding, it will finish processing
  44302. * before the loader halts. A segment loader can be unpaused by
  44303. * calling load().
  44304. */
  44305. }, {
  44306. key: 'pause',
  44307. value: function pause() {
  44308. if (this.checkBufferTimeout_) {
  44309. window$1.clearTimeout(this.checkBufferTimeout_);
  44310. this.checkBufferTimeout_ = null;
  44311. }
  44312. }
  44313. /**
  44314. * Returns whether the segment loader is fetching additional
  44315. * segments when given the opportunity. This property can be
  44316. * modified through calls to pause() and load().
  44317. */
  44318. }, {
  44319. key: 'paused',
  44320. value: function paused() {
  44321. return this.checkBufferTimeout_ === null;
  44322. }
  44323. /**
  44324. * create/set the following mimetype on the SourceBuffer through a
  44325. * SourceUpdater
  44326. *
  44327. * @param {String} mimeType the mime type string to use
  44328. * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer
  44329. * is added to the media source
  44330. */
  44331. }, {
  44332. key: 'mimeType',
  44333. value: function mimeType(_mimeType, sourceBufferEmitter) {
  44334. if (this.mimeType_) {
  44335. return;
  44336. }
  44337. this.mimeType_ = _mimeType;
  44338. this.sourceBufferEmitter_ = sourceBufferEmitter; // if we were unpaused but waiting for a sourceUpdater, start
  44339. // buffering now
  44340. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  44341. this.init_();
  44342. }
  44343. }
  44344. /**
  44345. * Delete all the buffered data and reset the SegmentLoader
  44346. * @param {Function} [done] an optional callback to be executed when the remove
  44347. * operation is complete
  44348. */
  44349. }, {
  44350. key: 'resetEverything',
  44351. value: function resetEverything(done) {
  44352. this.ended_ = false;
  44353. this.resetLoader();
  44354. this.remove(0, this.duration_(), done); // clears fmp4 captions
  44355. this.captionParser_.clearAllCaptions();
  44356. this.trigger('reseteverything');
  44357. }
  44358. /**
  44359. * Force the SegmentLoader to resync and start loading around the currentTime instead
  44360. * of starting at the end of the buffer
  44361. *
  44362. * Useful for fast quality changes
  44363. */
  44364. }, {
  44365. key: 'resetLoader',
  44366. value: function resetLoader() {
  44367. this.fetchAtBuffer_ = false;
  44368. this.resyncLoader();
  44369. }
  44370. /**
  44371. * Force the SegmentLoader to restart synchronization and make a conservative guess
  44372. * before returning to the simple walk-forward method
  44373. */
  44374. }, {
  44375. key: 'resyncLoader',
  44376. value: function resyncLoader() {
  44377. this.mediaIndex = null;
  44378. this.syncPoint_ = null;
  44379. this.abort();
  44380. }
  44381. /**
  44382. * Remove any data in the source buffer between start and end times
  44383. * @param {Number} start - the start time of the region to remove from the buffer
  44384. * @param {Number} end - the end time of the region to remove from the buffer
  44385. * @param {Function} [done] - an optional callback to be executed when the remove
  44386. * operation is complete
  44387. */
  44388. }, {
  44389. key: 'remove',
  44390. value: function remove(start, end, done) {
  44391. if (this.sourceUpdater_) {
  44392. this.sourceUpdater_.remove(start, end, done);
  44393. }
  44394. removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
  44395. if (this.inbandTextTracks_) {
  44396. for (var id in this.inbandTextTracks_) {
  44397. removeCuesFromTrack(start, end, this.inbandTextTracks_[id]);
  44398. }
  44399. }
  44400. }
  44401. /**
  44402. * (re-)schedule monitorBufferTick_ to run as soon as possible
  44403. *
  44404. * @private
  44405. */
  44406. }, {
  44407. key: 'monitorBuffer_',
  44408. value: function monitorBuffer_() {
  44409. if (this.checkBufferTimeout_) {
  44410. window$1.clearTimeout(this.checkBufferTimeout_);
  44411. }
  44412. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1);
  44413. }
  44414. /**
  44415. * As long as the SegmentLoader is in the READY state, periodically
  44416. * invoke fillBuffer_().
  44417. *
  44418. * @private
  44419. */
  44420. }, {
  44421. key: 'monitorBufferTick_',
  44422. value: function monitorBufferTick_() {
  44423. if (this.state === 'READY') {
  44424. this.fillBuffer_();
  44425. }
  44426. if (this.checkBufferTimeout_) {
  44427. window$1.clearTimeout(this.checkBufferTimeout_);
  44428. }
  44429. this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
  44430. }
  44431. /**
  44432. * fill the buffer with segements unless the sourceBuffers are
  44433. * currently updating
  44434. *
  44435. * Note: this function should only ever be called by monitorBuffer_
  44436. * and never directly
  44437. *
  44438. * @private
  44439. */
  44440. }, {
  44441. key: 'fillBuffer_',
  44442. value: function fillBuffer_() {
  44443. if (this.sourceUpdater_.updating()) {
  44444. return;
  44445. }
  44446. if (!this.syncPoint_) {
  44447. this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  44448. } // see if we need to begin loading immediately
  44449. var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
  44450. if (!segmentInfo) {
  44451. return;
  44452. }
  44453. if (this.isEndOfStream_(segmentInfo.mediaIndex)) {
  44454. this.endOfStream();
  44455. return;
  44456. }
  44457. if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
  44458. return;
  44459. } // We will need to change timestampOffset of the sourceBuffer if either of
  44460. // the following conditions are true:
  44461. // - The segment.timeline !== this.currentTimeline
  44462. // (we are crossing a discontinuity somehow)
  44463. // - The "timestampOffset" for the start of this segment is less than
  44464. // the currently set timestampOffset
  44465. // Also, clear captions if we are crossing a discontinuity boundary
  44466. if (segmentInfo.timeline !== this.currentTimeline_ || segmentInfo.startOfSegment !== null && segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset()) {
  44467. this.syncController_.reset();
  44468. segmentInfo.timestampOffset = segmentInfo.startOfSegment;
  44469. this.captionParser_.clearAllCaptions();
  44470. }
  44471. this.loadSegment_(segmentInfo);
  44472. }
  44473. /**
  44474. * Determines if this segment loader is at the end of it's stream.
  44475. *
  44476. * @param {Number} mediaIndex the index of segment we last appended
  44477. * @param {Object} [playlist=this.playlist_] a media playlist object
  44478. * @returns {Boolean} true if at end of stream, false otherwise.
  44479. */
  44480. }, {
  44481. key: 'isEndOfStream_',
  44482. value: function isEndOfStream_(mediaIndex) {
  44483. var playlist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.playlist_;
  44484. return detectEndOfStream(playlist, this.mediaSource_, mediaIndex) && !this.sourceUpdater_.updating();
  44485. }
  44486. /**
  44487. * Determines what segment request should be made, given current playback
  44488. * state.
  44489. *
  44490. * @param {TimeRanges} buffered - the state of the buffer
  44491. * @param {Object} playlist - the playlist object to fetch segments from
  44492. * @param {Number} mediaIndex - the previous mediaIndex fetched or null
  44493. * @param {Boolean} hasPlayed - a flag indicating whether we have played or not
  44494. * @param {Number} currentTime - the playback position in seconds
  44495. * @param {Object} syncPoint - a segment info object that describes the
  44496. * @returns {Object} a segment request object that describes the segment to load
  44497. */
  44498. }, {
  44499. key: 'checkBuffer_',
  44500. value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) {
  44501. var lastBufferedEnd = 0;
  44502. var startOfSegment = void 0;
  44503. if (buffered.length) {
  44504. lastBufferedEnd = buffered.end(buffered.length - 1);
  44505. }
  44506. var bufferedTime = Math.max(0, lastBufferedEnd - currentTime);
  44507. if (!playlist.segments.length) {
  44508. return null;
  44509. } // if there is plenty of content buffered, and the video has
  44510. // been played before relax for awhile
  44511. if (bufferedTime >= this.goalBufferLength_()) {
  44512. return null;
  44513. } // if the video has not yet played once, and we already have
  44514. // one segment downloaded do nothing
  44515. if (!hasPlayed && bufferedTime >= 1) {
  44516. return null;
  44517. } // When the syncPoint is null, there is no way of determining a good
  44518. // conservative segment index to fetch from
  44519. // The best thing to do here is to get the kind of sync-point data by
  44520. // making a request
  44521. if (syncPoint === null) {
  44522. mediaIndex = this.getSyncSegmentCandidate_(playlist);
  44523. return this.generateSegmentInfo_(playlist, mediaIndex, null, true);
  44524. } // Under normal playback conditions fetching is a simple walk forward
  44525. if (mediaIndex !== null) {
  44526. var segment = playlist.segments[mediaIndex];
  44527. if (segment && segment.end) {
  44528. startOfSegment = segment.end;
  44529. } else {
  44530. startOfSegment = lastBufferedEnd;
  44531. }
  44532. return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false);
  44533. } // There is a sync-point but the lack of a mediaIndex indicates that
  44534. // we need to make a good conservative guess about which segment to
  44535. // fetch
  44536. if (this.fetchAtBuffer_) {
  44537. // Find the segment containing the end of the buffer
  44538. var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time);
  44539. mediaIndex = mediaSourceInfo.mediaIndex;
  44540. startOfSegment = mediaSourceInfo.startTime;
  44541. } else {
  44542. // Find the segment containing currentTime
  44543. var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time);
  44544. mediaIndex = _mediaSourceInfo.mediaIndex;
  44545. startOfSegment = _mediaSourceInfo.startTime;
  44546. }
  44547. return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false);
  44548. }
  44549. /**
  44550. * The segment loader has no recourse except to fetch a segment in the
  44551. * current playlist and use the internal timestamps in that segment to
  44552. * generate a syncPoint. This function returns a good candidate index
  44553. * for that process.
  44554. *
  44555. * @param {Object} playlist - the playlist object to look for a
  44556. * @returns {Number} An index of a segment from the playlist to load
  44557. */
  44558. }, {
  44559. key: 'getSyncSegmentCandidate_',
  44560. value: function getSyncSegmentCandidate_(playlist) {
  44561. var _this2 = this;
  44562. if (this.currentTimeline_ === -1) {
  44563. return 0;
  44564. }
  44565. var segmentIndexArray = playlist.segments.map(function (s, i) {
  44566. return {
  44567. timeline: s.timeline,
  44568. segmentIndex: i
  44569. };
  44570. }).filter(function (s) {
  44571. return s.timeline === _this2.currentTimeline_;
  44572. });
  44573. if (segmentIndexArray.length) {
  44574. return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex;
  44575. }
  44576. return Math.max(playlist.segments.length - 1, 0);
  44577. }
  44578. }, {
  44579. key: 'generateSegmentInfo_',
  44580. value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) {
  44581. if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) {
  44582. return null;
  44583. }
  44584. var segment = playlist.segments[mediaIndex];
  44585. return {
  44586. requestId: 'segment-loader-' + Math.random(),
  44587. // resolve the segment URL relative to the playlist
  44588. uri: segment.resolvedUri,
  44589. // the segment's mediaIndex at the time it was requested
  44590. mediaIndex: mediaIndex,
  44591. // whether or not to update the SegmentLoader's state with this
  44592. // segment's mediaIndex
  44593. isSyncRequest: isSyncRequest,
  44594. startOfSegment: startOfSegment,
  44595. // the segment's playlist
  44596. playlist: playlist,
  44597. // unencrypted bytes of the segment
  44598. bytes: null,
  44599. // when a key is defined for this segment, the encrypted bytes
  44600. encryptedBytes: null,
  44601. // The target timestampOffset for this segment when we append it
  44602. // to the source buffer
  44603. timestampOffset: null,
  44604. // The timeline that the segment is in
  44605. timeline: segment.timeline,
  44606. // The expected duration of the segment in seconds
  44607. duration: segment.duration,
  44608. // retain the segment in case the playlist updates while doing an async process
  44609. segment: segment
  44610. };
  44611. }
  44612. /**
  44613. * Determines if the network has enough bandwidth to complete the current segment
  44614. * request in a timely manner. If not, the request will be aborted early and bandwidth
  44615. * updated to trigger a playlist switch.
  44616. *
  44617. * @param {Object} stats
  44618. * Object containing stats about the request timing and size
  44619. * @return {Boolean} True if the request was aborted, false otherwise
  44620. * @private
  44621. */
  44622. }, {
  44623. key: 'abortRequestEarly_',
  44624. value: function abortRequestEarly_(stats) {
  44625. if (this.hls_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition
  44626. // TODO: Replace using timeout with a boolean indicating whether this playlist is
  44627. // the lowestEnabledRendition.
  44628. !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes
  44629. !this.playlist_.attributes.BANDWIDTH) {
  44630. return false;
  44631. } // Wait at least 1 second since the first byte of data has been received before
  44632. // using the calculated bandwidth from the progress event to allow the bitrate
  44633. // to stabilize
  44634. if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
  44635. return false;
  44636. }
  44637. var currentTime = this.currentTime_();
  44638. var measuredBandwidth = stats.bandwidth;
  44639. var segmentDuration = this.pendingSegment_.duration;
  44640. var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
  44641. // if we are only left with less than 1 second when the request completes.
  44642. // A negative timeUntilRebuffering indicates we are already rebuffering
  44643. var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
  44644. // is larger than the estimated time until the player runs out of forward buffer
  44645. if (requestTimeRemaining <= timeUntilRebuffer$$1) {
  44646. return false;
  44647. }
  44648. var switchCandidate = minRebufferMaxBandwidthSelector({
  44649. master: this.hls_.playlists.master,
  44650. currentTime: currentTime,
  44651. bandwidth: measuredBandwidth,
  44652. duration: this.duration_(),
  44653. segmentDuration: segmentDuration,
  44654. timeUntilRebuffer: timeUntilRebuffer$$1,
  44655. currentTimeline: this.currentTimeline_,
  44656. syncController: this.syncController_
  44657. });
  44658. if (!switchCandidate) {
  44659. return;
  44660. }
  44661. var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1;
  44662. var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
  44663. var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
  44664. // potential round trip time of the new request so that we are not too aggressive
  44665. // with switching to a playlist that might save us a fraction of a second.
  44666. if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) {
  44667. minimumTimeSaving = 1;
  44668. }
  44669. if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
  44670. return false;
  44671. } // set the bandwidth to that of the desired playlist being sure to scale by
  44672. // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
  44673. // don't trigger a bandwidthupdate as the bandwidth is artifial
  44674. this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
  44675. this.abort();
  44676. this.trigger('earlyabort');
  44677. return true;
  44678. }
  44679. /**
  44680. * XHR `progress` event handler
  44681. *
  44682. * @param {Event}
  44683. * The XHR `progress` event
  44684. * @param {Object} simpleSegment
  44685. * A simplified segment object copy
  44686. * @private
  44687. */
  44688. }, {
  44689. key: 'handleProgress_',
  44690. value: function handleProgress_(event, simpleSegment) {
  44691. if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) {
  44692. return;
  44693. }
  44694. this.trigger('progress');
  44695. }
  44696. /**
  44697. * load a specific segment from a request into the buffer
  44698. *
  44699. * @private
  44700. */
  44701. }, {
  44702. key: 'loadSegment_',
  44703. value: function loadSegment_(segmentInfo) {
  44704. this.state = 'WAITING';
  44705. this.pendingSegment_ = segmentInfo;
  44706. this.trimBackBuffer_(segmentInfo);
  44707. segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.captionParser_, this.createSimplifiedSegmentObj_(segmentInfo), // progress callback
  44708. this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this));
  44709. }
  44710. /**
  44711. * trim the back buffer so that we don't have too much data
  44712. * in the source buffer
  44713. *
  44714. * @private
  44715. *
  44716. * @param {Object} segmentInfo - the current segment
  44717. */
  44718. }, {
  44719. key: 'trimBackBuffer_',
  44720. value: function trimBackBuffer_(segmentInfo) {
  44721. var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
  44722. // buffer and a very conservative "garbage collector"
  44723. // We manually clear out the old buffer to ensure
  44724. // we don't trigger the QuotaExceeded error
  44725. // on the source buffer during subsequent appends
  44726. if (removeToTime > 0) {
  44727. this.remove(0, removeToTime);
  44728. }
  44729. }
  44730. /**
  44731. * created a simplified copy of the segment object with just the
  44732. * information necessary to perform the XHR and decryption
  44733. *
  44734. * @private
  44735. *
  44736. * @param {Object} segmentInfo - the current segment
  44737. * @returns {Object} a simplified segment object copy
  44738. */
  44739. }, {
  44740. key: 'createSimplifiedSegmentObj_',
  44741. value: function createSimplifiedSegmentObj_(segmentInfo) {
  44742. var segment = segmentInfo.segment;
  44743. var simpleSegment = {
  44744. resolvedUri: segment.resolvedUri,
  44745. byterange: segment.byterange,
  44746. requestId: segmentInfo.requestId
  44747. };
  44748. if (segment.key) {
  44749. // if the media sequence is greater than 2^32, the IV will be incorrect
  44750. // assuming 10s segments, that would be about 1300 years
  44751. var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
  44752. simpleSegment.key = {
  44753. resolvedUri: segment.key.resolvedUri,
  44754. iv: iv
  44755. };
  44756. }
  44757. if (segment.map) {
  44758. simpleSegment.map = this.initSegment(segment.map);
  44759. }
  44760. return simpleSegment;
  44761. }
  44762. /**
  44763. * Handle the callback from the segmentRequest function and set the
  44764. * associated SegmentLoader state and errors if necessary
  44765. *
  44766. * @private
  44767. */
  44768. }, {
  44769. key: 'segmentRequestFinished_',
  44770. value: function segmentRequestFinished_(error, simpleSegment) {
  44771. // every request counts as a media request even if it has been aborted
  44772. // or canceled due to a timeout
  44773. this.mediaRequests += 1;
  44774. if (simpleSegment.stats) {
  44775. this.mediaBytesTransferred += simpleSegment.stats.bytesReceived;
  44776. this.mediaTransferDuration += simpleSegment.stats.roundTripTime;
  44777. } // The request was aborted and the SegmentLoader has already been reset
  44778. if (!this.pendingSegment_) {
  44779. this.mediaRequestsAborted += 1;
  44780. return;
  44781. } // the request was aborted and the SegmentLoader has already started
  44782. // another request. this can happen when the timeout for an aborted
  44783. // request triggers due to a limitation in the XHR library
  44784. // do not count this as any sort of request or we risk double-counting
  44785. if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
  44786. return;
  44787. } // an error occurred from the active pendingSegment_ so reset everything
  44788. if (error) {
  44789. this.pendingSegment_ = null;
  44790. this.state = 'READY'; // the requests were aborted just record the aborted stat and exit
  44791. // this is not a true error condition and nothing corrective needs
  44792. // to be done
  44793. if (error.code === REQUEST_ERRORS.ABORTED) {
  44794. this.mediaRequestsAborted += 1;
  44795. return;
  44796. }
  44797. this.pause(); // the error is really just that at least one of the requests timed-out
  44798. // set the bandwidth to a very low value and trigger an ABR switch to
  44799. // take emergency action
  44800. if (error.code === REQUEST_ERRORS.TIMEOUT) {
  44801. this.mediaRequestsTimedout += 1;
  44802. this.bandwidth = 1;
  44803. this.roundTrip = NaN;
  44804. this.trigger('bandwidthupdate');
  44805. return;
  44806. } // if control-flow has arrived here, then the error is real
  44807. // emit an error event to blacklist the current playlist
  44808. this.mediaRequestsErrored += 1;
  44809. this.error(error);
  44810. this.trigger('error');
  44811. return;
  44812. } // the response was a success so set any bandwidth stats the request
  44813. // generated for ABR purposes
  44814. this.bandwidth = simpleSegment.stats.bandwidth;
  44815. this.roundTrip = simpleSegment.stats.roundTripTime; // if this request included an initialization segment, save that data
  44816. // to the initSegment cache
  44817. if (simpleSegment.map) {
  44818. simpleSegment.map = this.initSegment(simpleSegment.map, true);
  44819. }
  44820. this.processSegmentResponse_(simpleSegment);
  44821. }
  44822. /**
  44823. * Move any important data from the simplified segment object
  44824. * back to the real segment object for future phases
  44825. *
  44826. * @private
  44827. */
  44828. }, {
  44829. key: 'processSegmentResponse_',
  44830. value: function processSegmentResponse_(simpleSegment) {
  44831. var segmentInfo = this.pendingSegment_;
  44832. segmentInfo.bytes = simpleSegment.bytes;
  44833. if (simpleSegment.map) {
  44834. segmentInfo.segment.map.bytes = simpleSegment.map.bytes;
  44835. }
  44836. segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; // This has fmp4 captions, add them to text tracks
  44837. if (simpleSegment.fmp4Captions) {
  44838. createCaptionsTrackIfNotExists(this.inbandTextTracks_, this.hls_.tech_, simpleSegment.captionStreams);
  44839. addCaptionData({
  44840. inbandTextTracks: this.inbandTextTracks_,
  44841. captionArray: simpleSegment.fmp4Captions,
  44842. // fmp4s will not have a timestamp offset
  44843. timestampOffset: 0
  44844. }); // Reset stored captions since we added parsed
  44845. // captions to a text track at this point
  44846. this.captionParser_.clearParsedCaptions();
  44847. }
  44848. this.handleSegment_();
  44849. }
  44850. /**
  44851. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  44852. *
  44853. * @private
  44854. */
  44855. }, {
  44856. key: 'handleSegment_',
  44857. value: function handleSegment_() {
  44858. var _this3 = this;
  44859. if (!this.pendingSegment_) {
  44860. this.state = 'READY';
  44861. return;
  44862. }
  44863. var segmentInfo = this.pendingSegment_;
  44864. var segment = segmentInfo.segment;
  44865. var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); // When we have our first timing info, determine what media types this loader is
  44866. // dealing with. Although we're maintaining extra state, it helps to preserve the
  44867. // separation of segment loader from the actual source buffers.
  44868. if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( // Guard against cases where we're not getting timing info at all until we are
  44869. // certain that all streams will provide it.
  44870. timingInfo.containsAudio || timingInfo.containsVideo)) {
  44871. this.startingMedia_ = {
  44872. containsAudio: timingInfo.containsAudio,
  44873. containsVideo: timingInfo.containsVideo
  44874. };
  44875. }
  44876. var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo);
  44877. if (illegalMediaSwitchError) {
  44878. this.error({
  44879. message: illegalMediaSwitchError,
  44880. blacklistDuration: Infinity
  44881. });
  44882. this.trigger('error');
  44883. return;
  44884. }
  44885. if (segmentInfo.isSyncRequest) {
  44886. this.trigger('syncinfoupdate');
  44887. this.pendingSegment_ = null;
  44888. this.state = 'READY';
  44889. return;
  44890. }
  44891. if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
  44892. this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); // fired when a timestamp offset is set in HLS (can also identify discontinuities)
  44893. this.trigger('timestampoffset');
  44894. }
  44895. var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline);
  44896. if (timelineMapping !== null) {
  44897. this.trigger({
  44898. type: 'segmenttimemapping',
  44899. mapping: timelineMapping
  44900. });
  44901. }
  44902. this.state = 'APPENDING'; // if the media initialization segment is changing, append it
  44903. // before the content segment
  44904. if (segment.map) {
  44905. var initId = initSegmentId(segment.map);
  44906. if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) {
  44907. var initSegment = this.initSegment(segment.map);
  44908. this.sourceUpdater_.appendBuffer({
  44909. bytes: initSegment.bytes
  44910. }, function () {
  44911. _this3.activeInitSegmentId_ = initId;
  44912. });
  44913. }
  44914. }
  44915. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  44916. if (typeof segment.start === 'number' && typeof segment.end === 'number') {
  44917. this.mediaSecondsLoaded += segment.end - segment.start;
  44918. } else {
  44919. this.mediaSecondsLoaded += segment.duration;
  44920. }
  44921. this.logger_(segmentInfoString(segmentInfo));
  44922. this.sourceUpdater_.appendBuffer({
  44923. bytes: segmentInfo.bytes,
  44924. videoSegmentTimingInfoCallback: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId)
  44925. }, this.handleUpdateEnd_.bind(this));
  44926. }
  44927. }, {
  44928. key: 'handleVideoSegmentTimingInfo_',
  44929. value: function handleVideoSegmentTimingInfo_(requestId, event) {
  44930. if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
  44931. return;
  44932. }
  44933. var segment = this.pendingSegment_.segment;
  44934. if (!segment.videoTimingInfo) {
  44935. segment.videoTimingInfo = {};
  44936. }
  44937. segment.videoTimingInfo.transmuxerPrependedSeconds = event.videoSegmentTimingInfo.prependedContentDuration || 0;
  44938. segment.videoTimingInfo.transmuxedPresentationStart = event.videoSegmentTimingInfo.start.presentation;
  44939. segment.videoTimingInfo.transmuxedPresentationEnd = event.videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging
  44940. segment.videoTimingInfo.baseMediaDecodeTime = event.videoSegmentTimingInfo.baseMediaDecodeTime;
  44941. }
  44942. /**
  44943. * callback to run when appendBuffer is finished. detects if we are
  44944. * in a good state to do things with the data we got, or if we need
  44945. * to wait for more
  44946. *
  44947. * @private
  44948. */
  44949. }, {
  44950. key: 'handleUpdateEnd_',
  44951. value: function handleUpdateEnd_() {
  44952. if (!this.pendingSegment_) {
  44953. this.state = 'READY';
  44954. if (!this.paused()) {
  44955. this.monitorBuffer_();
  44956. }
  44957. return;
  44958. }
  44959. var segmentInfo = this.pendingSegment_;
  44960. var segment = segmentInfo.segment;
  44961. var isWalkingForward = this.mediaIndex !== null;
  44962. this.pendingSegment_ = null;
  44963. this.recordThroughput_(segmentInfo);
  44964. this.addSegmentMetadataCue_(segmentInfo);
  44965. this.state = 'READY';
  44966. this.mediaIndex = segmentInfo.mediaIndex;
  44967. this.fetchAtBuffer_ = true;
  44968. this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
  44969. // the following conditional otherwise it may consider this a bad "guess"
  44970. // and attempt to resync when the post-update seekable window and live
  44971. // point would mean that this was the perfect segment to fetch
  44972. this.trigger('syncinfoupdate'); // If we previously appended a segment that ends more than 3 targetDurations before
  44973. // the currentTime_ that means that our conservative guess was too conservative.
  44974. // In that case, reset the loader state so that we try to use any information gained
  44975. // from the previous request to create a new, more accurate, sync-point.
  44976. if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
  44977. this.resetEverything();
  44978. return;
  44979. } // Don't do a rendition switch unless we have enough time to get a sync segment
  44980. // and conservatively guess
  44981. if (isWalkingForward) {
  44982. this.trigger('bandwidthupdate');
  44983. }
  44984. this.trigger('progress'); // any time an update finishes and the last segment is in the
  44985. // buffer, end the stream. this ensures the "ended" event will
  44986. // fire if playback reaches that point.
  44987. if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) {
  44988. this.endOfStream();
  44989. }
  44990. if (!this.paused()) {
  44991. this.monitorBuffer_();
  44992. }
  44993. }
  44994. /**
  44995. * Records the current throughput of the decrypt, transmux, and append
  44996. * portion of the semgment pipeline. `throughput.rate` is a the cumulative
  44997. * moving average of the throughput. `throughput.count` is the number of
  44998. * data points in the average.
  44999. *
  45000. * @private
  45001. * @param {Object} segmentInfo the object returned by loadSegment
  45002. */
  45003. }, {
  45004. key: 'recordThroughput_',
  45005. value: function recordThroughput_(segmentInfo) {
  45006. var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
  45007. // by zero in the case where the throughput is ridiculously high
  45008. var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
  45009. var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
  45010. // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
  45011. this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
  45012. }
  45013. /**
  45014. * Adds a cue to the segment-metadata track with some metadata information about the
  45015. * segment
  45016. *
  45017. * @private
  45018. * @param {Object} segmentInfo
  45019. * the object returned by loadSegment
  45020. * @method addSegmentMetadataCue_
  45021. */
  45022. }, {
  45023. key: 'addSegmentMetadataCue_',
  45024. value: function addSegmentMetadataCue_(segmentInfo) {
  45025. if (!this.segmentMetadataTrack_) {
  45026. return;
  45027. }
  45028. var segment = segmentInfo.segment;
  45029. var start = segment.start;
  45030. var end = segment.end; // Do not try adding the cue if the start and end times are invalid.
  45031. if (!finite(start) || !finite(end)) {
  45032. return;
  45033. }
  45034. removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
  45035. var Cue = window$1.WebKitDataCue || window$1.VTTCue;
  45036. var value = {
  45037. custom: segment.custom,
  45038. dateTimeObject: segment.dateTimeObject,
  45039. dateTimeString: segment.dateTimeString,
  45040. bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
  45041. resolution: segmentInfo.playlist.attributes.RESOLUTION,
  45042. codecs: segmentInfo.playlist.attributes.CODECS,
  45043. byteLength: segmentInfo.byteLength,
  45044. uri: segmentInfo.uri,
  45045. timeline: segmentInfo.timeline,
  45046. playlist: segmentInfo.playlist.uri,
  45047. start: start,
  45048. end: end
  45049. };
  45050. var data = JSON.stringify(value);
  45051. var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
  45052. // the differences of WebKitDataCue in safari and VTTCue in other browsers
  45053. cue.value = value;
  45054. this.segmentMetadataTrack_.addCue(cue);
  45055. }
  45056. }]);
  45057. return SegmentLoader;
  45058. }(videojs$1.EventTarget);
  45059. var uint8ToUtf8 = function uint8ToUtf8(uintArray) {
  45060. return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
  45061. };
  45062. /**
  45063. * @file vtt-segment-loader.js
  45064. */
  45065. var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (char) {
  45066. return char.charCodeAt(0);
  45067. }));
  45068. /**
  45069. * An object that manages segment loading and appending.
  45070. *
  45071. * @class VTTSegmentLoader
  45072. * @param {Object} options required and optional options
  45073. * @extends videojs.EventTarget
  45074. */
  45075. var VTTSegmentLoader = function (_SegmentLoader) {
  45076. inherits$1(VTTSegmentLoader, _SegmentLoader);
  45077. function VTTSegmentLoader(settings) {
  45078. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  45079. classCallCheck$1(this, VTTSegmentLoader); // SegmentLoader requires a MediaSource be specified or it will throw an error;
  45080. // however, VTTSegmentLoader has no need of a media source, so delete the reference
  45081. var _this = possibleConstructorReturn$1(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options));
  45082. _this.mediaSource_ = null;
  45083. _this.subtitlesTrack_ = null;
  45084. return _this;
  45085. }
  45086. /**
  45087. * Indicates which time ranges are buffered
  45088. *
  45089. * @return {TimeRange}
  45090. * TimeRange object representing the current buffered ranges
  45091. */
  45092. createClass$1(VTTSegmentLoader, [{
  45093. key: 'buffered_',
  45094. value: function buffered_() {
  45095. if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) {
  45096. return videojs$1.createTimeRanges();
  45097. }
  45098. var cues = this.subtitlesTrack_.cues;
  45099. var start = cues[0].startTime;
  45100. var end = cues[cues.length - 1].startTime;
  45101. return videojs$1.createTimeRanges([[start, end]]);
  45102. }
  45103. /**
  45104. * Gets and sets init segment for the provided map
  45105. *
  45106. * @param {Object} map
  45107. * The map object representing the init segment to get or set
  45108. * @param {Boolean=} set
  45109. * If true, the init segment for the provided map should be saved
  45110. * @return {Object}
  45111. * map object for desired init segment
  45112. */
  45113. }, {
  45114. key: 'initSegment',
  45115. value: function initSegment(map) {
  45116. var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  45117. if (!map) {
  45118. return null;
  45119. }
  45120. var id = initSegmentId(map);
  45121. var storedMap = this.initSegments_[id];
  45122. if (set$$1 && !storedMap && map.bytes) {
  45123. // append WebVTT line terminators to the media initialization segment if it exists
  45124. // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
  45125. // requires two or more WebVTT line terminators between the WebVTT header and the
  45126. // rest of the file
  45127. var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
  45128. var combinedSegment = new Uint8Array(combinedByteLength);
  45129. combinedSegment.set(map.bytes);
  45130. combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
  45131. this.initSegments_[id] = storedMap = {
  45132. resolvedUri: map.resolvedUri,
  45133. byterange: map.byterange,
  45134. bytes: combinedSegment
  45135. };
  45136. }
  45137. return storedMap || map;
  45138. }
  45139. /**
  45140. * Returns true if all configuration required for loading is present, otherwise false.
  45141. *
  45142. * @return {Boolean} True if the all configuration is ready for loading
  45143. * @private
  45144. */
  45145. }, {
  45146. key: 'couldBeginLoading_',
  45147. value: function couldBeginLoading_() {
  45148. return this.playlist_ && this.subtitlesTrack_ && !this.paused();
  45149. }
  45150. /**
  45151. * Once all the starting parameters have been specified, begin
  45152. * operation. This method should only be invoked from the INIT
  45153. * state.
  45154. *
  45155. * @private
  45156. */
  45157. }, {
  45158. key: 'init_',
  45159. value: function init_() {
  45160. this.state = 'READY';
  45161. this.resetEverything();
  45162. return this.monitorBuffer_();
  45163. }
  45164. /**
  45165. * Set a subtitle track on the segment loader to add subtitles to
  45166. *
  45167. * @param {TextTrack=} track
  45168. * The text track to add loaded subtitles to
  45169. * @return {TextTrack}
  45170. * Returns the subtitles track
  45171. */
  45172. }, {
  45173. key: 'track',
  45174. value: function track(_track) {
  45175. if (typeof _track === 'undefined') {
  45176. return this.subtitlesTrack_;
  45177. }
  45178. this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start
  45179. // buffering now
  45180. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  45181. this.init_();
  45182. }
  45183. return this.subtitlesTrack_;
  45184. }
  45185. /**
  45186. * Remove any data in the source buffer between start and end times
  45187. * @param {Number} start - the start time of the region to remove from the buffer
  45188. * @param {Number} end - the end time of the region to remove from the buffer
  45189. */
  45190. }, {
  45191. key: 'remove',
  45192. value: function remove(start, end) {
  45193. removeCuesFromTrack(start, end, this.subtitlesTrack_);
  45194. }
  45195. /**
  45196. * fill the buffer with segements unless the sourceBuffers are
  45197. * currently updating
  45198. *
  45199. * Note: this function should only ever be called by monitorBuffer_
  45200. * and never directly
  45201. *
  45202. * @private
  45203. */
  45204. }, {
  45205. key: 'fillBuffer_',
  45206. value: function fillBuffer_() {
  45207. var _this2 = this;
  45208. if (!this.syncPoint_) {
  45209. this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  45210. } // see if we need to begin loading immediately
  45211. var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
  45212. segmentInfo = this.skipEmptySegments_(segmentInfo);
  45213. if (!segmentInfo) {
  45214. return;
  45215. }
  45216. if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
  45217. // We don't have the timestamp offset that we need to sync subtitles.
  45218. // Rerun on a timestamp offset or user interaction.
  45219. var checkTimestampOffset = function checkTimestampOffset() {
  45220. _this2.state = 'READY';
  45221. if (!_this2.paused()) {
  45222. // if not paused, queue a buffer check as soon as possible
  45223. _this2.monitorBuffer_();
  45224. }
  45225. };
  45226. this.syncController_.one('timestampoffset', checkTimestampOffset);
  45227. this.state = 'WAITING_ON_TIMELINE';
  45228. return;
  45229. }
  45230. this.loadSegment_(segmentInfo);
  45231. }
  45232. /**
  45233. * Prevents the segment loader from requesting segments we know contain no subtitles
  45234. * by walking forward until we find the next segment that we don't know whether it is
  45235. * empty or not.
  45236. *
  45237. * @param {Object} segmentInfo
  45238. * a segment info object that describes the current segment
  45239. * @return {Object}
  45240. * a segment info object that describes the current segment
  45241. */
  45242. }, {
  45243. key: 'skipEmptySegments_',
  45244. value: function skipEmptySegments_(segmentInfo) {
  45245. while (segmentInfo && segmentInfo.segment.empty) {
  45246. segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest);
  45247. }
  45248. return segmentInfo;
  45249. }
  45250. /**
  45251. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  45252. *
  45253. * @private
  45254. */
  45255. }, {
  45256. key: 'handleSegment_',
  45257. value: function handleSegment_() {
  45258. var _this3 = this;
  45259. if (!this.pendingSegment_ || !this.subtitlesTrack_) {
  45260. this.state = 'READY';
  45261. return;
  45262. }
  45263. this.state = 'APPENDING';
  45264. var segmentInfo = this.pendingSegment_;
  45265. var segment = segmentInfo.segment; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
  45266. if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) {
  45267. var loadHandler = function loadHandler() {
  45268. _this3.handleSegment_();
  45269. };
  45270. this.state = 'WAITING_ON_VTTJS';
  45271. this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler);
  45272. this.subtitlesTrack_.tech_.one('vttjserror', function () {
  45273. _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler);
  45274. _this3.error({
  45275. message: 'Error loading vtt.js'
  45276. });
  45277. _this3.state = 'READY';
  45278. _this3.pause();
  45279. _this3.trigger('error');
  45280. });
  45281. return;
  45282. }
  45283. segment.requested = true;
  45284. try {
  45285. this.parseVTTCues_(segmentInfo);
  45286. } catch (e) {
  45287. this.error({
  45288. message: e.message
  45289. });
  45290. this.state = 'READY';
  45291. this.pause();
  45292. return this.trigger('error');
  45293. }
  45294. this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
  45295. if (segmentInfo.isSyncRequest) {
  45296. this.trigger('syncinfoupdate');
  45297. this.pendingSegment_ = null;
  45298. this.state = 'READY';
  45299. return;
  45300. }
  45301. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  45302. this.mediaSecondsLoaded += segment.duration;
  45303. if (segmentInfo.cues.length) {
  45304. // remove any overlapping cues to prevent doubling
  45305. this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime);
  45306. }
  45307. segmentInfo.cues.forEach(function (cue) {
  45308. _this3.subtitlesTrack_.addCue(cue);
  45309. });
  45310. this.handleUpdateEnd_();
  45311. }
  45312. /**
  45313. * Uses the WebVTT parser to parse the segment response
  45314. *
  45315. * @param {Object} segmentInfo
  45316. * a segment info object that describes the current segment
  45317. * @private
  45318. */
  45319. }, {
  45320. key: 'parseVTTCues_',
  45321. value: function parseVTTCues_(segmentInfo) {
  45322. var decoder = void 0;
  45323. var decodeBytesToString = false;
  45324. if (typeof window$1.TextDecoder === 'function') {
  45325. decoder = new window$1.TextDecoder('utf8');
  45326. } else {
  45327. decoder = window$1.WebVTT.StringDecoder();
  45328. decodeBytesToString = true;
  45329. }
  45330. var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder);
  45331. segmentInfo.cues = [];
  45332. segmentInfo.timestampmap = {
  45333. MPEGTS: 0,
  45334. LOCAL: 0
  45335. };
  45336. parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
  45337. parser.ontimestampmap = function (map) {
  45338. return segmentInfo.timestampmap = map;
  45339. };
  45340. parser.onparsingerror = function (error) {
  45341. videojs$1.log.warn('Error encountered when parsing cues: ' + error.message);
  45342. };
  45343. if (segmentInfo.segment.map) {
  45344. var mapData = segmentInfo.segment.map.bytes;
  45345. if (decodeBytesToString) {
  45346. mapData = uint8ToUtf8(mapData);
  45347. }
  45348. parser.parse(mapData);
  45349. }
  45350. var segmentData = segmentInfo.bytes;
  45351. if (decodeBytesToString) {
  45352. segmentData = uint8ToUtf8(segmentData);
  45353. }
  45354. parser.parse(segmentData);
  45355. parser.flush();
  45356. }
  45357. /**
  45358. * Updates the start and end times of any cues parsed by the WebVTT parser using
  45359. * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
  45360. * from the SyncController
  45361. *
  45362. * @param {Object} segmentInfo
  45363. * a segment info object that describes the current segment
  45364. * @param {Object} mappingObj
  45365. * object containing a mapping from TS to media time
  45366. * @param {Object} playlist
  45367. * the playlist object containing the segment
  45368. * @private
  45369. */
  45370. }, {
  45371. key: 'updateTimeMapping_',
  45372. value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
  45373. var segment = segmentInfo.segment;
  45374. if (!mappingObj) {
  45375. // If the sync controller does not have a mapping of TS to Media Time for the
  45376. // timeline, then we don't have enough information to update the cue
  45377. // start/end times
  45378. return;
  45379. }
  45380. if (!segmentInfo.cues.length) {
  45381. // If there are no cues, we also do not have enough information to figure out
  45382. // segment timing. Mark that the segment contains no cues so we don't re-request
  45383. // an empty segment.
  45384. segment.empty = true;
  45385. return;
  45386. }
  45387. var timestampmap = segmentInfo.timestampmap;
  45388. var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping;
  45389. segmentInfo.cues.forEach(function (cue) {
  45390. // First convert cue time to TS time using the timestamp-map provided within the vtt
  45391. cue.startTime += diff;
  45392. cue.endTime += diff;
  45393. });
  45394. if (!playlist.syncInfo) {
  45395. var firstStart = segmentInfo.cues[0].startTime;
  45396. var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
  45397. playlist.syncInfo = {
  45398. mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
  45399. time: Math.min(firstStart, lastStart - segment.duration)
  45400. };
  45401. }
  45402. }
  45403. }]);
  45404. return VTTSegmentLoader;
  45405. }(SegmentLoader);
  45406. /**
  45407. * @file ad-cue-tags.js
  45408. */
  45409. /**
  45410. * Searches for an ad cue that overlaps with the given mediaTime
  45411. */
  45412. var findAdCue = function findAdCue(track, mediaTime) {
  45413. var cues = track.cues;
  45414. for (var i = 0; i < cues.length; i++) {
  45415. var cue = cues[i];
  45416. if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
  45417. return cue;
  45418. }
  45419. }
  45420. return null;
  45421. };
  45422. var updateAdCues = function updateAdCues(media, track) {
  45423. var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
  45424. if (!media.segments) {
  45425. return;
  45426. }
  45427. var mediaTime = offset;
  45428. var cue = void 0;
  45429. for (var i = 0; i < media.segments.length; i++) {
  45430. var segment = media.segments[i];
  45431. if (!cue) {
  45432. // Since the cues will span for at least the segment duration, adding a fudge
  45433. // factor of half segment duration will prevent duplicate cues from being
  45434. // created when timing info is not exact (e.g. cue start time initialized
  45435. // at 10.006677, but next call mediaTime is 10.003332 )
  45436. cue = findAdCue(track, mediaTime + segment.duration / 2);
  45437. }
  45438. if (cue) {
  45439. if ('cueIn' in segment) {
  45440. // Found a CUE-IN so end the cue
  45441. cue.endTime = mediaTime;
  45442. cue.adEndTime = mediaTime;
  45443. mediaTime += segment.duration;
  45444. cue = null;
  45445. continue;
  45446. }
  45447. if (mediaTime < cue.endTime) {
  45448. // Already processed this mediaTime for this cue
  45449. mediaTime += segment.duration;
  45450. continue;
  45451. } // otherwise extend cue until a CUE-IN is found
  45452. cue.endTime += segment.duration;
  45453. } else {
  45454. if ('cueOut' in segment) {
  45455. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
  45456. cue.adStartTime = mediaTime; // Assumes tag format to be
  45457. // #EXT-X-CUE-OUT:30
  45458. cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
  45459. track.addCue(cue);
  45460. }
  45461. if ('cueOutCont' in segment) {
  45462. // Entered into the middle of an ad cue
  45463. var adOffset = void 0;
  45464. var adTotal = void 0; // Assumes tag formate to be
  45465. // #EXT-X-CUE-OUT-CONT:10/30
  45466. var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat);
  45467. var _segment$cueOutCont$s2 = slicedToArray(_segment$cueOutCont$s, 2);
  45468. adOffset = _segment$cueOutCont$s2[0];
  45469. adTotal = _segment$cueOutCont$s2[1];
  45470. cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, '');
  45471. cue.adStartTime = mediaTime - adOffset;
  45472. cue.adEndTime = cue.adStartTime + adTotal;
  45473. track.addCue(cue);
  45474. }
  45475. }
  45476. mediaTime += segment.duration;
  45477. }
  45478. };
  45479. /**
  45480. * @file sync-controller.js
  45481. */
  45482. var tsprobe = tsInspector.inspect;
  45483. var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
  45484. // the equivalence display-time 0 === segment-index 0
  45485. {
  45486. name: 'VOD',
  45487. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  45488. if (duration$$1 !== Infinity) {
  45489. var syncPoint = {
  45490. time: 0,
  45491. segmentIndex: 0
  45492. };
  45493. return syncPoint;
  45494. }
  45495. return null;
  45496. }
  45497. }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
  45498. {
  45499. name: 'ProgramDateTime',
  45500. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  45501. if (!syncController.datetimeToDisplayTime) {
  45502. return null;
  45503. }
  45504. var segments = playlist.segments || [];
  45505. var syncPoint = null;
  45506. var lastDistance = null;
  45507. currentTime = currentTime || 0;
  45508. for (var i = 0; i < segments.length; i++) {
  45509. var segment = segments[i];
  45510. if (segment.dateTimeObject) {
  45511. var segmentTime = segment.dateTimeObject.getTime() / 1000;
  45512. var segmentStart = segmentTime + syncController.datetimeToDisplayTime;
  45513. var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, we have passed
  45514. // currentTime and can stop looking for better candidates
  45515. if (lastDistance !== null && lastDistance < distance) {
  45516. break;
  45517. }
  45518. lastDistance = distance;
  45519. syncPoint = {
  45520. time: segmentStart,
  45521. segmentIndex: i
  45522. };
  45523. }
  45524. }
  45525. return syncPoint;
  45526. }
  45527. }, // Stategy "Segment": We have a known time mapping for a timeline and a
  45528. // segment in the current timeline with timing data
  45529. {
  45530. name: 'Segment',
  45531. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  45532. var segments = playlist.segments || [];
  45533. var syncPoint = null;
  45534. var lastDistance = null;
  45535. currentTime = currentTime || 0;
  45536. for (var i = 0; i < segments.length; i++) {
  45537. var segment = segments[i];
  45538. if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') {
  45539. var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed
  45540. // currentTime and can stop looking for better candidates
  45541. if (lastDistance !== null && lastDistance < distance) {
  45542. break;
  45543. }
  45544. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  45545. lastDistance = distance;
  45546. syncPoint = {
  45547. time: segment.start,
  45548. segmentIndex: i
  45549. };
  45550. }
  45551. }
  45552. }
  45553. return syncPoint;
  45554. }
  45555. }, // Stategy "Discontinuity": We have a discontinuity with a known
  45556. // display-time
  45557. {
  45558. name: 'Discontinuity',
  45559. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  45560. var syncPoint = null;
  45561. currentTime = currentTime || 0;
  45562. if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  45563. var lastDistance = null;
  45564. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  45565. var segmentIndex = playlist.discontinuityStarts[i];
  45566. var discontinuity = playlist.discontinuitySequence + i + 1;
  45567. var discontinuitySync = syncController.discontinuities[discontinuity];
  45568. if (discontinuitySync) {
  45569. var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
  45570. // currentTime and can stop looking for better candidates
  45571. if (lastDistance !== null && lastDistance < distance) {
  45572. break;
  45573. }
  45574. if (!syncPoint || lastDistance === null || lastDistance >= distance) {
  45575. lastDistance = distance;
  45576. syncPoint = {
  45577. time: discontinuitySync.time,
  45578. segmentIndex: segmentIndex
  45579. };
  45580. }
  45581. }
  45582. }
  45583. }
  45584. return syncPoint;
  45585. }
  45586. }, // Stategy "Playlist": We have a playlist with a known mapping of
  45587. // segment index to display time
  45588. {
  45589. name: 'Playlist',
  45590. run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
  45591. if (playlist.syncInfo) {
  45592. var syncPoint = {
  45593. time: playlist.syncInfo.time,
  45594. segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence
  45595. };
  45596. return syncPoint;
  45597. }
  45598. return null;
  45599. }
  45600. }];
  45601. var SyncController = function (_videojs$EventTarget) {
  45602. inherits$1(SyncController, _videojs$EventTarget);
  45603. function SyncController() {
  45604. classCallCheck$1(this, SyncController); // Segment Loader state variables...
  45605. // ...for synching across variants
  45606. var _this = possibleConstructorReturn$1(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this));
  45607. _this.inspectCache_ = undefined; // ...for synching across variants
  45608. _this.timelines = [];
  45609. _this.discontinuities = [];
  45610. _this.datetimeToDisplayTime = null;
  45611. _this.logger_ = logger('SyncController');
  45612. return _this;
  45613. }
  45614. /**
  45615. * Find a sync-point for the playlist specified
  45616. *
  45617. * A sync-point is defined as a known mapping from display-time to
  45618. * a segment-index in the current playlist.
  45619. *
  45620. * @param {Playlist} playlist
  45621. * The playlist that needs a sync-point
  45622. * @param {Number} duration
  45623. * Duration of the MediaSource (Infinite if playing a live source)
  45624. * @param {Number} currentTimeline
  45625. * The last timeline from which a segment was loaded
  45626. * @returns {Object}
  45627. * A sync-point object
  45628. */
  45629. createClass$1(SyncController, [{
  45630. key: 'getSyncPoint',
  45631. value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) {
  45632. var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime);
  45633. if (!syncPoints.length) {
  45634. // Signal that we need to attempt to get a sync-point manually
  45635. // by fetching a segment in the playlist and constructing
  45636. // a sync-point from that information
  45637. return null;
  45638. } // Now find the sync-point that is closest to the currentTime because
  45639. // that should result in the most accurate guess about which segment
  45640. // to fetch
  45641. return this.selectSyncPoint_(syncPoints, {
  45642. key: 'time',
  45643. value: currentTime
  45644. });
  45645. }
  45646. /**
  45647. * Calculate the amount of time that has expired off the playlist during playback
  45648. *
  45649. * @param {Playlist} playlist
  45650. * Playlist object to calculate expired from
  45651. * @param {Number} duration
  45652. * Duration of the MediaSource (Infinity if playling a live source)
  45653. * @returns {Number|null}
  45654. * The amount of time that has expired off the playlist during playback. Null
  45655. * if no sync-points for the playlist can be found.
  45656. */
  45657. }, {
  45658. key: 'getExpiredTime',
  45659. value: function getExpiredTime(playlist, duration$$1) {
  45660. if (!playlist || !playlist.segments) {
  45661. return null;
  45662. }
  45663. var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
  45664. if (!syncPoints.length) {
  45665. return null;
  45666. }
  45667. var syncPoint = this.selectSyncPoint_(syncPoints, {
  45668. key: 'segmentIndex',
  45669. value: 0
  45670. }); // If the sync-point is beyond the start of the playlist, we want to subtract the
  45671. // duration from index 0 to syncPoint.segmentIndex instead of adding.
  45672. if (syncPoint.segmentIndex > 0) {
  45673. syncPoint.time *= -1;
  45674. }
  45675. return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0));
  45676. }
  45677. /**
  45678. * Runs each sync-point strategy and returns a list of sync-points returned by the
  45679. * strategies
  45680. *
  45681. * @private
  45682. * @param {Playlist} playlist
  45683. * The playlist that needs a sync-point
  45684. * @param {Number} duration
  45685. * Duration of the MediaSource (Infinity if playing a live source)
  45686. * @param {Number} currentTimeline
  45687. * The last timeline from which a segment was loaded
  45688. * @returns {Array}
  45689. * A list of sync-point objects
  45690. */
  45691. }, {
  45692. key: 'runStrategies_',
  45693. value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) {
  45694. var syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
  45695. for (var i = 0; i < syncPointStrategies.length; i++) {
  45696. var strategy = syncPointStrategies[i];
  45697. var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime);
  45698. if (syncPoint) {
  45699. syncPoint.strategy = strategy.name;
  45700. syncPoints.push({
  45701. strategy: strategy.name,
  45702. syncPoint: syncPoint
  45703. });
  45704. }
  45705. }
  45706. return syncPoints;
  45707. }
  45708. /**
  45709. * Selects the sync-point nearest the specified target
  45710. *
  45711. * @private
  45712. * @param {Array} syncPoints
  45713. * List of sync-points to select from
  45714. * @param {Object} target
  45715. * Object specifying the property and value we are targeting
  45716. * @param {String} target.key
  45717. * Specifies the property to target. Must be either 'time' or 'segmentIndex'
  45718. * @param {Number} target.value
  45719. * The value to target for the specified key.
  45720. * @returns {Object}
  45721. * The sync-point nearest the target
  45722. */
  45723. }, {
  45724. key: 'selectSyncPoint_',
  45725. value: function selectSyncPoint_(syncPoints, target) {
  45726. var bestSyncPoint = syncPoints[0].syncPoint;
  45727. var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
  45728. var bestStrategy = syncPoints[0].strategy;
  45729. for (var i = 1; i < syncPoints.length; i++) {
  45730. var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
  45731. if (newDistance < bestDistance) {
  45732. bestDistance = newDistance;
  45733. bestSyncPoint = syncPoints[i].syncPoint;
  45734. bestStrategy = syncPoints[i].strategy;
  45735. }
  45736. }
  45737. this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']'));
  45738. return bestSyncPoint;
  45739. }
  45740. /**
  45741. * Save any meta-data present on the segments when segments leave
  45742. * the live window to the playlist to allow for synchronization at the
  45743. * playlist level later.
  45744. *
  45745. * @param {Playlist} oldPlaylist - The previous active playlist
  45746. * @param {Playlist} newPlaylist - The updated and most current playlist
  45747. */
  45748. }, {
  45749. key: 'saveExpiredSegmentInfo',
  45750. value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
  45751. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time
  45752. // save that information as a possible sync-point reference in future
  45753. for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
  45754. var lastRemovedSegment = oldPlaylist.segments[i];
  45755. if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
  45756. newPlaylist.syncInfo = {
  45757. mediaSequence: oldPlaylist.mediaSequence + i,
  45758. time: lastRemovedSegment.start
  45759. };
  45760. this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']'));
  45761. this.trigger('syncinfoupdate');
  45762. break;
  45763. }
  45764. }
  45765. }
  45766. /**
  45767. * Save the mapping from playlist's ProgramDateTime to display. This should
  45768. * only ever happen once at the start of playback.
  45769. *
  45770. * @param {Playlist} playlist - The currently active playlist
  45771. */
  45772. }, {
  45773. key: 'setDateTimeMapping',
  45774. value: function setDateTimeMapping(playlist) {
  45775. if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
  45776. var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
  45777. this.datetimeToDisplayTime = -playlistTimestamp;
  45778. }
  45779. }
  45780. /**
  45781. * Reset the state of the inspection cache when we do a rendition
  45782. * switch
  45783. */
  45784. }, {
  45785. key: 'reset',
  45786. value: function reset() {
  45787. this.inspectCache_ = undefined;
  45788. }
  45789. /**
  45790. * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start
  45791. * and end of the segment in it's internal "media time". Used to generate
  45792. * mappings from that internal "media time" to the display time that is
  45793. * shown on the player.
  45794. *
  45795. * @param {SegmentInfo} segmentInfo - The current active request information
  45796. */
  45797. }, {
  45798. key: 'probeSegmentInfo',
  45799. value: function probeSegmentInfo(segmentInfo) {
  45800. var segment = segmentInfo.segment;
  45801. var playlist = segmentInfo.playlist;
  45802. var timingInfo = void 0;
  45803. if (segment.map) {
  45804. timingInfo = this.probeMp4Segment_(segmentInfo);
  45805. } else {
  45806. timingInfo = this.probeTsSegment_(segmentInfo);
  45807. }
  45808. if (timingInfo) {
  45809. if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) {
  45810. this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
  45811. // now with segment timing information
  45812. if (!playlist.syncInfo) {
  45813. playlist.syncInfo = {
  45814. mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
  45815. time: segment.start
  45816. };
  45817. }
  45818. }
  45819. }
  45820. return timingInfo;
  45821. }
  45822. /**
  45823. * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment
  45824. * in it's internal "media time".
  45825. *
  45826. * @private
  45827. * @param {SegmentInfo} segmentInfo - The current active request information
  45828. * @return {object} The start and end time of the current segment in "media time"
  45829. */
  45830. }, {
  45831. key: 'probeMp4Segment_',
  45832. value: function probeMp4Segment_(segmentInfo) {
  45833. var segment = segmentInfo.segment;
  45834. var timescales = probe.timescale(segment.map.bytes);
  45835. var startTime = probe.startTime(timescales, segmentInfo.bytes);
  45836. if (segmentInfo.timestampOffset !== null) {
  45837. segmentInfo.timestampOffset -= startTime;
  45838. }
  45839. return {
  45840. start: startTime,
  45841. end: startTime + segment.duration
  45842. };
  45843. }
  45844. /**
  45845. * Probe an mpeg2-ts segment to determine the start and end of the segment
  45846. * in it's internal "media time".
  45847. *
  45848. * @private
  45849. * @param {SegmentInfo} segmentInfo - The current active request information
  45850. * @return {object} The start and end time of the current segment in "media time"
  45851. */
  45852. }, {
  45853. key: 'probeTsSegment_',
  45854. value: function probeTsSegment_(segmentInfo) {
  45855. var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_);
  45856. var segmentStartTime = void 0;
  45857. var segmentEndTime = void 0;
  45858. if (!timeInfo) {
  45859. return null;
  45860. }
  45861. if (timeInfo.video && timeInfo.video.length === 2) {
  45862. this.inspectCache_ = timeInfo.video[1].dts;
  45863. segmentStartTime = timeInfo.video[0].dtsTime;
  45864. segmentEndTime = timeInfo.video[1].dtsTime;
  45865. } else if (timeInfo.audio && timeInfo.audio.length === 2) {
  45866. this.inspectCache_ = timeInfo.audio[1].dts;
  45867. segmentStartTime = timeInfo.audio[0].dtsTime;
  45868. segmentEndTime = timeInfo.audio[1].dtsTime;
  45869. }
  45870. var probedInfo = {
  45871. start: segmentStartTime,
  45872. end: segmentEndTime,
  45873. containsVideo: timeInfo.video && timeInfo.video.length === 2,
  45874. containsAudio: timeInfo.audio && timeInfo.audio.length === 2
  45875. };
  45876. return probedInfo;
  45877. }
  45878. }, {
  45879. key: 'timestampOffsetForTimeline',
  45880. value: function timestampOffsetForTimeline(timeline) {
  45881. if (typeof this.timelines[timeline] === 'undefined') {
  45882. return null;
  45883. }
  45884. return this.timelines[timeline].time;
  45885. }
  45886. }, {
  45887. key: 'mappingForTimeline',
  45888. value: function mappingForTimeline(timeline) {
  45889. if (typeof this.timelines[timeline] === 'undefined') {
  45890. return null;
  45891. }
  45892. return this.timelines[timeline].mapping;
  45893. }
  45894. /**
  45895. * Use the "media time" for a segment to generate a mapping to "display time" and
  45896. * save that display time to the segment.
  45897. *
  45898. * @private
  45899. * @param {SegmentInfo} segmentInfo
  45900. * The current active request information
  45901. * @param {object} timingInfo
  45902. * The start and end time of the current segment in "media time"
  45903. * @returns {Boolean}
  45904. * Returns false if segment time mapping could not be calculated
  45905. */
  45906. }, {
  45907. key: 'calculateSegmentTimeMapping_',
  45908. value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) {
  45909. var segment = segmentInfo.segment;
  45910. var mappingObj = this.timelines[segmentInfo.timeline];
  45911. if (segmentInfo.timestampOffset !== null) {
  45912. mappingObj = {
  45913. time: segmentInfo.startOfSegment,
  45914. mapping: segmentInfo.startOfSegment - timingInfo.start
  45915. };
  45916. this.timelines[segmentInfo.timeline] = mappingObj;
  45917. this.trigger('timestampoffset');
  45918. this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']'));
  45919. segment.start = segmentInfo.startOfSegment;
  45920. segment.end = timingInfo.end + mappingObj.mapping;
  45921. } else if (mappingObj) {
  45922. segment.start = timingInfo.start + mappingObj.mapping;
  45923. segment.end = timingInfo.end + mappingObj.mapping;
  45924. } else {
  45925. return false;
  45926. }
  45927. return true;
  45928. }
  45929. /**
  45930. * Each time we have discontinuity in the playlist, attempt to calculate the location
  45931. * in display of the start of the discontinuity and save that. We also save an accuracy
  45932. * value so that we save values with the most accuracy (closest to 0.)
  45933. *
  45934. * @private
  45935. * @param {SegmentInfo} segmentInfo - The current active request information
  45936. */
  45937. }, {
  45938. key: 'saveDiscontinuitySyncInfo_',
  45939. value: function saveDiscontinuitySyncInfo_(segmentInfo) {
  45940. var playlist = segmentInfo.playlist;
  45941. var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
  45942. // the start of the range and it's accuracy is 0 (greater accuracy values
  45943. // mean more approximation)
  45944. if (segment.discontinuity) {
  45945. this.discontinuities[segment.timeline] = {
  45946. time: segment.start,
  45947. accuracy: 0
  45948. };
  45949. } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
  45950. // Search for future discontinuities that we can provide better timing
  45951. // information for and save that information for sync purposes
  45952. for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
  45953. var segmentIndex = playlist.discontinuityStarts[i];
  45954. var discontinuity = playlist.discontinuitySequence + i + 1;
  45955. var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
  45956. var accuracy = Math.abs(mediaIndexDiff);
  45957. if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
  45958. var time = void 0;
  45959. if (mediaIndexDiff < 0) {
  45960. time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex);
  45961. } else {
  45962. time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex);
  45963. }
  45964. this.discontinuities[discontinuity] = {
  45965. time: time,
  45966. accuracy: accuracy
  45967. };
  45968. }
  45969. }
  45970. }
  45971. }
  45972. }]);
  45973. return SyncController;
  45974. }(videojs$1.EventTarget);
  45975. var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) {
  45976. var self = this;
  45977. var decrypterWorker = function () {
  45978. /*
  45979. * pkcs7.pad
  45980. * https://github.com/brightcove/pkcs7
  45981. *
  45982. * Copyright (c) 2014 Brightcove
  45983. * Licensed under the apache2 license.
  45984. */
  45985. /**
  45986. * Returns the subarray of a Uint8Array without PKCS#7 padding.
  45987. * @param padded {Uint8Array} unencrypted bytes that have been padded
  45988. * @return {Uint8Array} the unpadded bytes
  45989. * @see http://tools.ietf.org/html/rfc5652
  45990. */
  45991. function unpad(padded) {
  45992. return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
  45993. }
  45994. var classCallCheck = function classCallCheck(instance, Constructor) {
  45995. if (!(instance instanceof Constructor)) {
  45996. throw new TypeError("Cannot call a class as a function");
  45997. }
  45998. };
  45999. var createClass = function () {
  46000. function defineProperties(target, props) {
  46001. for (var i = 0; i < props.length; i++) {
  46002. var descriptor = props[i];
  46003. descriptor.enumerable = descriptor.enumerable || false;
  46004. descriptor.configurable = true;
  46005. if ("value" in descriptor) descriptor.writable = true;
  46006. Object.defineProperty(target, descriptor.key, descriptor);
  46007. }
  46008. }
  46009. return function (Constructor, protoProps, staticProps) {
  46010. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  46011. if (staticProps) defineProperties(Constructor, staticProps);
  46012. return Constructor;
  46013. };
  46014. }();
  46015. var inherits = function inherits(subClass, superClass) {
  46016. if (typeof superClass !== "function" && superClass !== null) {
  46017. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  46018. }
  46019. subClass.prototype = Object.create(superClass && superClass.prototype, {
  46020. constructor: {
  46021. value: subClass,
  46022. enumerable: false,
  46023. writable: true,
  46024. configurable: true
  46025. }
  46026. });
  46027. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  46028. };
  46029. var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
  46030. if (!self) {
  46031. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  46032. }
  46033. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  46034. };
  46035. /**
  46036. * @file aes.js
  46037. *
  46038. * This file contains an adaptation of the AES decryption algorithm
  46039. * from the Standford Javascript Cryptography Library. That work is
  46040. * covered by the following copyright and permissions notice:
  46041. *
  46042. * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
  46043. * All rights reserved.
  46044. *
  46045. * Redistribution and use in source and binary forms, with or without
  46046. * modification, are permitted provided that the following conditions are
  46047. * met:
  46048. *
  46049. * 1. Redistributions of source code must retain the above copyright
  46050. * notice, this list of conditions and the following disclaimer.
  46051. *
  46052. * 2. Redistributions in binary form must reproduce the above
  46053. * copyright notice, this list of conditions and the following
  46054. * disclaimer in the documentation and/or other materials provided
  46055. * with the distribution.
  46056. *
  46057. * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
  46058. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  46059. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  46060. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
  46061. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  46062. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  46063. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  46064. * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  46065. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  46066. * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
  46067. * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  46068. *
  46069. * The views and conclusions contained in the software and documentation
  46070. * are those of the authors and should not be interpreted as representing
  46071. * official policies, either expressed or implied, of the authors.
  46072. */
  46073. /**
  46074. * Expand the S-box tables.
  46075. *
  46076. * @private
  46077. */
  46078. var precompute = function precompute() {
  46079. var tables = [[[], [], [], [], []], [[], [], [], [], []]];
  46080. var encTable = tables[0];
  46081. var decTable = tables[1];
  46082. var sbox = encTable[4];
  46083. var sboxInv = decTable[4];
  46084. var i = void 0;
  46085. var x = void 0;
  46086. var xInv = void 0;
  46087. var d = [];
  46088. var th = [];
  46089. var x2 = void 0;
  46090. var x4 = void 0;
  46091. var x8 = void 0;
  46092. var s = void 0;
  46093. var tEnc = void 0;
  46094. var tDec = void 0; // Compute double and third tables
  46095. for (i = 0; i < 256; i++) {
  46096. th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
  46097. }
  46098. for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
  46099. // Compute sbox
  46100. s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
  46101. s = s >> 8 ^ s & 255 ^ 99;
  46102. sbox[x] = s;
  46103. sboxInv[s] = x; // Compute MixColumns
  46104. x8 = d[x4 = d[x2 = d[x]]];
  46105. tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
  46106. tEnc = d[s] * 0x101 ^ s * 0x1010100;
  46107. for (i = 0; i < 4; i++) {
  46108. encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
  46109. decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
  46110. }
  46111. } // Compactify. Considerable speedup on Firefox.
  46112. for (i = 0; i < 5; i++) {
  46113. encTable[i] = encTable[i].slice(0);
  46114. decTable[i] = decTable[i].slice(0);
  46115. }
  46116. return tables;
  46117. };
  46118. var aesTables = null;
  46119. /**
  46120. * Schedule out an AES key for both encryption and decryption. This
  46121. * is a low-level class. Use a cipher mode to do bulk encryption.
  46122. *
  46123. * @class AES
  46124. * @param key {Array} The key as an array of 4, 6 or 8 words.
  46125. */
  46126. var AES = function () {
  46127. function AES(key) {
  46128. classCallCheck(this, AES);
  46129. /**
  46130. * The expanded S-box and inverse S-box tables. These will be computed
  46131. * on the client so that we don't have to send them down the wire.
  46132. *
  46133. * There are two tables, _tables[0] is for encryption and
  46134. * _tables[1] is for decryption.
  46135. *
  46136. * The first 4 sub-tables are the expanded S-box with MixColumns. The
  46137. * last (_tables[01][4]) is the S-box itself.
  46138. *
  46139. * @private
  46140. */
  46141. // if we have yet to precompute the S-box tables
  46142. // do so now
  46143. if (!aesTables) {
  46144. aesTables = precompute();
  46145. } // then make a copy of that object for use
  46146. 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()]];
  46147. var i = void 0;
  46148. var j = void 0;
  46149. var tmp = void 0;
  46150. var encKey = void 0;
  46151. var decKey = void 0;
  46152. var sbox = this._tables[0][4];
  46153. var decTable = this._tables[1];
  46154. var keyLen = key.length;
  46155. var rcon = 1;
  46156. if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
  46157. throw new Error('Invalid aes key size');
  46158. }
  46159. encKey = key.slice(0);
  46160. decKey = [];
  46161. this._key = [encKey, decKey]; // schedule encryption keys
  46162. for (i = keyLen; i < 4 * keyLen + 28; i++) {
  46163. tmp = encKey[i - 1]; // apply sbox
  46164. if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
  46165. tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
  46166. if (i % keyLen === 0) {
  46167. tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
  46168. rcon = rcon << 1 ^ (rcon >> 7) * 283;
  46169. }
  46170. }
  46171. encKey[i] = encKey[i - keyLen] ^ tmp;
  46172. } // schedule decryption keys
  46173. for (j = 0; i; j++, i--) {
  46174. tmp = encKey[j & 3 ? i : i - 4];
  46175. if (i <= 4 || j < 4) {
  46176. decKey[j] = tmp;
  46177. } else {
  46178. decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
  46179. }
  46180. }
  46181. }
  46182. /**
  46183. * Decrypt 16 bytes, specified as four 32-bit words.
  46184. *
  46185. * @param {Number} encrypted0 the first word to decrypt
  46186. * @param {Number} encrypted1 the second word to decrypt
  46187. * @param {Number} encrypted2 the third word to decrypt
  46188. * @param {Number} encrypted3 the fourth word to decrypt
  46189. * @param {Int32Array} out the array to write the decrypted words
  46190. * into
  46191. * @param {Number} offset the offset into the output array to start
  46192. * writing results
  46193. * @return {Array} The plaintext.
  46194. */
  46195. AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
  46196. var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
  46197. var a = encrypted0 ^ key[0];
  46198. var b = encrypted3 ^ key[1];
  46199. var c = encrypted2 ^ key[2];
  46200. var d = encrypted1 ^ key[3];
  46201. var a2 = void 0;
  46202. var b2 = void 0;
  46203. var c2 = void 0; // key.length === 2 ?
  46204. var nInnerRounds = key.length / 4 - 2;
  46205. var i = void 0;
  46206. var kIndex = 4;
  46207. var table = this._tables[1]; // load up the tables
  46208. var table0 = table[0];
  46209. var table1 = table[1];
  46210. var table2 = table[2];
  46211. var table3 = table[3];
  46212. var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
  46213. for (i = 0; i < nInnerRounds; i++) {
  46214. a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
  46215. b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
  46216. c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
  46217. d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
  46218. kIndex += 4;
  46219. a = a2;
  46220. b = b2;
  46221. c = c2;
  46222. } // Last round.
  46223. for (i = 0; i < 4; i++) {
  46224. out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
  46225. a2 = a;
  46226. a = b;
  46227. b = c;
  46228. c = d;
  46229. d = a2;
  46230. }
  46231. };
  46232. return AES;
  46233. }();
  46234. /**
  46235. * @file stream.js
  46236. */
  46237. /**
  46238. * A lightweight readable stream implemention that handles event dispatching.
  46239. *
  46240. * @class Stream
  46241. */
  46242. var Stream = function () {
  46243. function Stream() {
  46244. classCallCheck(this, Stream);
  46245. this.listeners = {};
  46246. }
  46247. /**
  46248. * Add a listener for a specified event type.
  46249. *
  46250. * @param {String} type the event name
  46251. * @param {Function} listener the callback to be invoked when an event of
  46252. * the specified type occurs
  46253. */
  46254. Stream.prototype.on = function on(type, listener) {
  46255. if (!this.listeners[type]) {
  46256. this.listeners[type] = [];
  46257. }
  46258. this.listeners[type].push(listener);
  46259. };
  46260. /**
  46261. * Remove a listener for a specified event type.
  46262. *
  46263. * @param {String} type the event name
  46264. * @param {Function} listener a function previously registered for this
  46265. * type of event through `on`
  46266. * @return {Boolean} if we could turn it off or not
  46267. */
  46268. Stream.prototype.off = function off(type, listener) {
  46269. if (!this.listeners[type]) {
  46270. return false;
  46271. }
  46272. var index = this.listeners[type].indexOf(listener);
  46273. this.listeners[type].splice(index, 1);
  46274. return index > -1;
  46275. };
  46276. /**
  46277. * Trigger an event of the specified type on this stream. Any additional
  46278. * arguments to this function are passed as parameters to event listeners.
  46279. *
  46280. * @param {String} type the event name
  46281. */
  46282. Stream.prototype.trigger = function trigger(type) {
  46283. var callbacks = this.listeners[type];
  46284. if (!callbacks) {
  46285. return;
  46286. } // Slicing the arguments on every invocation of this method
  46287. // can add a significant amount of overhead. Avoid the
  46288. // intermediate object creation for the common case of a
  46289. // single callback argument
  46290. if (arguments.length === 2) {
  46291. var length = callbacks.length;
  46292. for (var i = 0; i < length; ++i) {
  46293. callbacks[i].call(this, arguments[1]);
  46294. }
  46295. } else {
  46296. var args = Array.prototype.slice.call(arguments, 1);
  46297. var _length = callbacks.length;
  46298. for (var _i = 0; _i < _length; ++_i) {
  46299. callbacks[_i].apply(this, args);
  46300. }
  46301. }
  46302. };
  46303. /**
  46304. * Destroys the stream and cleans up.
  46305. */
  46306. Stream.prototype.dispose = function dispose() {
  46307. this.listeners = {};
  46308. };
  46309. /**
  46310. * Forwards all `data` events on this stream to the destination stream. The
  46311. * destination stream should provide a method `push` to receive the data
  46312. * events as they arrive.
  46313. *
  46314. * @param {Stream} destination the stream that will receive all `data` events
  46315. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  46316. */
  46317. Stream.prototype.pipe = function pipe(destination) {
  46318. this.on('data', function (data) {
  46319. destination.push(data);
  46320. });
  46321. };
  46322. return Stream;
  46323. }();
  46324. /**
  46325. * @file async-stream.js
  46326. */
  46327. /**
  46328. * A wrapper around the Stream class to use setTiemout
  46329. * and run stream "jobs" Asynchronously
  46330. *
  46331. * @class AsyncStream
  46332. * @extends Stream
  46333. */
  46334. var AsyncStream$$1 = function (_Stream) {
  46335. inherits(AsyncStream$$1, _Stream);
  46336. function AsyncStream$$1() {
  46337. classCallCheck(this, AsyncStream$$1);
  46338. var _this = possibleConstructorReturn(this, _Stream.call(this, Stream));
  46339. _this.jobs = [];
  46340. _this.delay = 1;
  46341. _this.timeout_ = null;
  46342. return _this;
  46343. }
  46344. /**
  46345. * process an async job
  46346. *
  46347. * @private
  46348. */
  46349. AsyncStream$$1.prototype.processJob_ = function processJob_() {
  46350. this.jobs.shift()();
  46351. if (this.jobs.length) {
  46352. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  46353. } else {
  46354. this.timeout_ = null;
  46355. }
  46356. };
  46357. /**
  46358. * push a job into the stream
  46359. *
  46360. * @param {Function} job the job to push into the stream
  46361. */
  46362. AsyncStream$$1.prototype.push = function push(job) {
  46363. this.jobs.push(job);
  46364. if (!this.timeout_) {
  46365. this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
  46366. }
  46367. };
  46368. return AsyncStream$$1;
  46369. }(Stream);
  46370. /**
  46371. * @file decrypter.js
  46372. *
  46373. * An asynchronous implementation of AES-128 CBC decryption with
  46374. * PKCS#7 padding.
  46375. */
  46376. /**
  46377. * Convert network-order (big-endian) bytes into their little-endian
  46378. * representation.
  46379. */
  46380. var ntoh = function ntoh(word) {
  46381. return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
  46382. };
  46383. /**
  46384. * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
  46385. *
  46386. * @param {Uint8Array} encrypted the encrypted bytes
  46387. * @param {Uint32Array} key the bytes of the decryption key
  46388. * @param {Uint32Array} initVector the initialization vector (IV) to
  46389. * use for the first round of CBC.
  46390. * @return {Uint8Array} the decrypted bytes
  46391. *
  46392. * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
  46393. * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
  46394. * @see https://tools.ietf.org/html/rfc2315
  46395. */
  46396. var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) {
  46397. // word-level access to the encrypted bytes
  46398. var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
  46399. var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
  46400. var decrypted = new Uint8Array(encrypted.byteLength);
  46401. var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
  46402. // decrypted data
  46403. var init0 = void 0;
  46404. var init1 = void 0;
  46405. var init2 = void 0;
  46406. var init3 = void 0;
  46407. var encrypted0 = void 0;
  46408. var encrypted1 = void 0;
  46409. var encrypted2 = void 0;
  46410. var encrypted3 = void 0; // iteration variable
  46411. var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
  46412. // passed-in reference and easier access
  46413. init0 = initVector[0];
  46414. init1 = initVector[1];
  46415. init2 = initVector[2];
  46416. init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
  46417. // to each decrypted block
  46418. for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
  46419. // convert big-endian (network order) words into little-endian
  46420. // (javascript order)
  46421. encrypted0 = ntoh(encrypted32[wordIx]);
  46422. encrypted1 = ntoh(encrypted32[wordIx + 1]);
  46423. encrypted2 = ntoh(encrypted32[wordIx + 2]);
  46424. encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
  46425. decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
  46426. // plaintext
  46427. decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
  46428. decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
  46429. decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
  46430. decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
  46431. init0 = encrypted0;
  46432. init1 = encrypted1;
  46433. init2 = encrypted2;
  46434. init3 = encrypted3;
  46435. }
  46436. return decrypted;
  46437. };
  46438. /**
  46439. * The `Decrypter` class that manages decryption of AES
  46440. * data through `AsyncStream` objects and the `decrypt`
  46441. * function
  46442. *
  46443. * @param {Uint8Array} encrypted the encrypted bytes
  46444. * @param {Uint32Array} key the bytes of the decryption key
  46445. * @param {Uint32Array} initVector the initialization vector (IV) to
  46446. * @param {Function} done the function to run when done
  46447. * @class Decrypter
  46448. */
  46449. var Decrypter$$1 = function () {
  46450. function Decrypter$$1(encrypted, key, initVector, done) {
  46451. classCallCheck(this, Decrypter$$1);
  46452. var step = Decrypter$$1.STEP;
  46453. var encrypted32 = new Int32Array(encrypted.buffer);
  46454. var decrypted = new Uint8Array(encrypted.byteLength);
  46455. var i = 0;
  46456. this.asyncStream_ = new AsyncStream$$1(); // split up the encryption job and do the individual chunks asynchronously
  46457. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  46458. for (i = step; i < encrypted32.length; i += step) {
  46459. initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
  46460. this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
  46461. } // invoke the done() callback when everything is finished
  46462. this.asyncStream_.push(function () {
  46463. // remove pkcs#7 padding from the decrypted bytes
  46464. done(null, unpad(decrypted));
  46465. });
  46466. }
  46467. /**
  46468. * a getter for step the maximum number of bytes to process at one time
  46469. *
  46470. * @return {Number} the value of step 32000
  46471. */
  46472. /**
  46473. * @private
  46474. */
  46475. Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
  46476. return function () {
  46477. var bytes = decrypt$$1(encrypted, key, initVector);
  46478. decrypted.set(bytes, encrypted.byteOffset);
  46479. };
  46480. };
  46481. createClass(Decrypter$$1, null, [{
  46482. key: 'STEP',
  46483. get: function get$$1() {
  46484. // 4 * 8000;
  46485. return 32000;
  46486. }
  46487. }]);
  46488. return Decrypter$$1;
  46489. }();
  46490. /**
  46491. * @file bin-utils.js
  46492. */
  46493. /**
  46494. * Creates an object for sending to a web worker modifying properties that are TypedArrays
  46495. * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
  46496. *
  46497. * @param {Object} message
  46498. * Object of properties and values to send to the web worker
  46499. * @return {Object}
  46500. * Modified message with TypedArray values expanded
  46501. * @function createTransferableMessage
  46502. */
  46503. var createTransferableMessage = function createTransferableMessage(message) {
  46504. var transferable = {};
  46505. Object.keys(message).forEach(function (key) {
  46506. var value = message[key];
  46507. if (ArrayBuffer.isView(value)) {
  46508. transferable[key] = {
  46509. bytes: value.buffer,
  46510. byteOffset: value.byteOffset,
  46511. byteLength: value.byteLength
  46512. };
  46513. } else {
  46514. transferable[key] = value;
  46515. }
  46516. });
  46517. return transferable;
  46518. };
  46519. /**
  46520. * Our web worker interface so that things can talk to aes-decrypter
  46521. * that will be running in a web worker. the scope is passed to this by
  46522. * webworkify.
  46523. *
  46524. * @param {Object} self
  46525. * the scope for the web worker
  46526. */
  46527. var DecrypterWorker = function DecrypterWorker(self) {
  46528. self.onmessage = function (event) {
  46529. var data = event.data;
  46530. var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
  46531. var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
  46532. var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
  46533. /* eslint-disable no-new, handle-callback-err */
  46534. new Decrypter$$1(encrypted, key, iv, function (err, bytes) {
  46535. self.postMessage(createTransferableMessage({
  46536. source: data.source,
  46537. decrypted: bytes
  46538. }), [bytes.buffer]);
  46539. });
  46540. /* eslint-enable */
  46541. };
  46542. };
  46543. var decrypterWorker = new DecrypterWorker(self);
  46544. return decrypterWorker;
  46545. }();
  46546. });
  46547. /**
  46548. * Convert the properties of an HLS track into an audioTrackKind.
  46549. *
  46550. * @private
  46551. */
  46552. var audioTrackKind_ = function audioTrackKind_(properties) {
  46553. var kind = properties.default ? 'main' : 'alternative';
  46554. if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
  46555. kind = 'main-desc';
  46556. }
  46557. return kind;
  46558. };
  46559. /**
  46560. * Pause provided segment loader and playlist loader if active
  46561. *
  46562. * @param {SegmentLoader} segmentLoader
  46563. * SegmentLoader to pause
  46564. * @param {Object} mediaType
  46565. * Active media type
  46566. * @function stopLoaders
  46567. */
  46568. var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
  46569. segmentLoader.abort();
  46570. segmentLoader.pause();
  46571. if (mediaType && mediaType.activePlaylistLoader) {
  46572. mediaType.activePlaylistLoader.pause();
  46573. mediaType.activePlaylistLoader = null;
  46574. }
  46575. };
  46576. /**
  46577. * Start loading provided segment loader and playlist loader
  46578. *
  46579. * @param {PlaylistLoader} playlistLoader
  46580. * PlaylistLoader to start loading
  46581. * @param {Object} mediaType
  46582. * Active media type
  46583. * @function startLoaders
  46584. */
  46585. var startLoaders = function startLoaders(playlistLoader, mediaType) {
  46586. // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
  46587. // playlist loader
  46588. mediaType.activePlaylistLoader = playlistLoader;
  46589. playlistLoader.load();
  46590. };
  46591. /**
  46592. * Returns a function to be called when the media group changes. It performs a
  46593. * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
  46594. * change of group is merely a rendition switch of the same content at another encoding,
  46595. * rather than a change of content, such as switching audio from English to Spanish.
  46596. *
  46597. * @param {String} type
  46598. * MediaGroup type
  46599. * @param {Object} settings
  46600. * Object containing required information for media groups
  46601. * @return {Function}
  46602. * Handler for a non-destructive resync of SegmentLoader when the active media
  46603. * group changes.
  46604. * @function onGroupChanged
  46605. */
  46606. var onGroupChanged = function onGroupChanged(type, settings) {
  46607. return function () {
  46608. var _settings$segmentLoad = settings.segmentLoaders,
  46609. segmentLoader = _settings$segmentLoad[type],
  46610. mainSegmentLoader = _settings$segmentLoad.main,
  46611. mediaType = settings.mediaTypes[type];
  46612. var activeTrack = mediaType.activeTrack();
  46613. var activeGroup = mediaType.activeGroup(activeTrack);
  46614. var previousActiveLoader = mediaType.activePlaylistLoader;
  46615. stopLoaders(segmentLoader, mediaType);
  46616. if (!activeGroup) {
  46617. // there is no group active
  46618. return;
  46619. }
  46620. if (!activeGroup.playlistLoader) {
  46621. if (previousActiveLoader) {
  46622. // The previous group had a playlist loader but the new active group does not
  46623. // this means we are switching from demuxed to muxed audio. In this case we want to
  46624. // do a destructive reset of the main segment loader and not restart the audio
  46625. // loaders.
  46626. mainSegmentLoader.resetEverything();
  46627. }
  46628. return;
  46629. } // Non-destructive resync
  46630. segmentLoader.resyncLoader();
  46631. startLoaders(activeGroup.playlistLoader, mediaType);
  46632. };
  46633. };
  46634. /**
  46635. * Returns a function to be called when the media track changes. It performs a
  46636. * destructive reset of the SegmentLoader to ensure we start loading as close to
  46637. * currentTime as possible.
  46638. *
  46639. * @param {String} type
  46640. * MediaGroup type
  46641. * @param {Object} settings
  46642. * Object containing required information for media groups
  46643. * @return {Function}
  46644. * Handler for a destructive reset of SegmentLoader when the active media
  46645. * track changes.
  46646. * @function onTrackChanged
  46647. */
  46648. var onTrackChanged = function onTrackChanged(type, settings) {
  46649. return function () {
  46650. var _settings$segmentLoad2 = settings.segmentLoaders,
  46651. segmentLoader = _settings$segmentLoad2[type],
  46652. mainSegmentLoader = _settings$segmentLoad2.main,
  46653. mediaType = settings.mediaTypes[type];
  46654. var activeTrack = mediaType.activeTrack();
  46655. var activeGroup = mediaType.activeGroup(activeTrack);
  46656. var previousActiveLoader = mediaType.activePlaylistLoader;
  46657. stopLoaders(segmentLoader, mediaType);
  46658. if (!activeGroup) {
  46659. // there is no group active so we do not want to restart loaders
  46660. return;
  46661. }
  46662. if (!activeGroup.playlistLoader) {
  46663. // when switching from demuxed audio/video to muxed audio/video (noted by no playlist
  46664. // loader for the audio group), we want to do a destructive reset of the main segment
  46665. // loader and not restart the audio loaders
  46666. mainSegmentLoader.resetEverything();
  46667. return;
  46668. }
  46669. if (previousActiveLoader === activeGroup.playlistLoader) {
  46670. // Nothing has actually changed. This can happen because track change events can fire
  46671. // multiple times for a "single" change. One for enabling the new active track, and
  46672. // one for disabling the track that was active
  46673. startLoaders(activeGroup.playlistLoader, mediaType);
  46674. return;
  46675. }
  46676. if (segmentLoader.track) {
  46677. // For WebVTT, set the new text track in the segmentloader
  46678. segmentLoader.track(activeTrack);
  46679. } // destructive reset
  46680. segmentLoader.resetEverything();
  46681. startLoaders(activeGroup.playlistLoader, mediaType);
  46682. };
  46683. };
  46684. var onError = {
  46685. /**
  46686. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  46687. * an error.
  46688. *
  46689. * @param {String} type
  46690. * MediaGroup type
  46691. * @param {Object} settings
  46692. * Object containing required information for media groups
  46693. * @return {Function}
  46694. * Error handler. Logs warning (or error if the playlist is blacklisted) to
  46695. * console and switches back to default audio track.
  46696. * @function onError.AUDIO
  46697. */
  46698. AUDIO: function AUDIO(type, settings) {
  46699. return function () {
  46700. var segmentLoader = settings.segmentLoaders[type],
  46701. mediaType = settings.mediaTypes[type],
  46702. blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
  46703. stopLoaders(segmentLoader, mediaType); // switch back to default audio track
  46704. var activeTrack = mediaType.activeTrack();
  46705. var activeGroup = mediaType.activeGroup();
  46706. var id = (activeGroup.filter(function (group) {
  46707. return group.default;
  46708. })[0] || activeGroup[0]).id;
  46709. var defaultTrack = mediaType.tracks[id];
  46710. if (activeTrack === defaultTrack) {
  46711. // Default track encountered an error. All we can do now is blacklist the current
  46712. // rendition and hope another will switch audio groups
  46713. blacklistCurrentPlaylist({
  46714. message: 'Problem encountered loading the default audio track.'
  46715. });
  46716. return;
  46717. }
  46718. videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
  46719. for (var trackId in mediaType.tracks) {
  46720. mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
  46721. }
  46722. mediaType.onTrackChanged();
  46723. };
  46724. },
  46725. /**
  46726. * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
  46727. * an error.
  46728. *
  46729. * @param {String} type
  46730. * MediaGroup type
  46731. * @param {Object} settings
  46732. * Object containing required information for media groups
  46733. * @return {Function}
  46734. * Error handler. Logs warning to console and disables the active subtitle track
  46735. * @function onError.SUBTITLES
  46736. */
  46737. SUBTITLES: function SUBTITLES(type, settings) {
  46738. return function () {
  46739. var segmentLoader = settings.segmentLoaders[type],
  46740. mediaType = settings.mediaTypes[type];
  46741. videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
  46742. stopLoaders(segmentLoader, mediaType);
  46743. var track = mediaType.activeTrack();
  46744. if (track) {
  46745. track.mode = 'disabled';
  46746. }
  46747. mediaType.onTrackChanged();
  46748. };
  46749. }
  46750. };
  46751. var setupListeners = {
  46752. /**
  46753. * Setup event listeners for audio playlist loader
  46754. *
  46755. * @param {String} type
  46756. * MediaGroup type
  46757. * @param {PlaylistLoader|null} playlistLoader
  46758. * PlaylistLoader to register listeners on
  46759. * @param {Object} settings
  46760. * Object containing required information for media groups
  46761. * @function setupListeners.AUDIO
  46762. */
  46763. AUDIO: function AUDIO(type, playlistLoader, settings) {
  46764. if (!playlistLoader) {
  46765. // no playlist loader means audio will be muxed with the video
  46766. return;
  46767. }
  46768. var tech = settings.tech,
  46769. requestOptions = settings.requestOptions,
  46770. segmentLoader = settings.segmentLoaders[type];
  46771. playlistLoader.on('loadedmetadata', function () {
  46772. var media = playlistLoader.media();
  46773. segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
  46774. // permits, start downloading segments
  46775. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  46776. segmentLoader.load();
  46777. }
  46778. });
  46779. playlistLoader.on('loadedplaylist', function () {
  46780. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  46781. if (!tech.paused()) {
  46782. segmentLoader.load();
  46783. }
  46784. });
  46785. playlistLoader.on('error', onError[type](type, settings));
  46786. },
  46787. /**
  46788. * Setup event listeners for subtitle playlist loader
  46789. *
  46790. * @param {String} type
  46791. * MediaGroup type
  46792. * @param {PlaylistLoader|null} playlistLoader
  46793. * PlaylistLoader to register listeners on
  46794. * @param {Object} settings
  46795. * Object containing required information for media groups
  46796. * @function setupListeners.SUBTITLES
  46797. */
  46798. SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
  46799. var tech = settings.tech,
  46800. requestOptions = settings.requestOptions,
  46801. segmentLoader = settings.segmentLoaders[type],
  46802. mediaType = settings.mediaTypes[type];
  46803. playlistLoader.on('loadedmetadata', function () {
  46804. var media = playlistLoader.media();
  46805. segmentLoader.playlist(media, requestOptions);
  46806. segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
  46807. // permits, start downloading segments
  46808. if (!tech.paused() || media.endList && tech.preload() !== 'none') {
  46809. segmentLoader.load();
  46810. }
  46811. });
  46812. playlistLoader.on('loadedplaylist', function () {
  46813. segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
  46814. if (!tech.paused()) {
  46815. segmentLoader.load();
  46816. }
  46817. });
  46818. playlistLoader.on('error', onError[type](type, settings));
  46819. }
  46820. };
  46821. var byGroupId = function byGroupId(type, groupId) {
  46822. return function (playlist) {
  46823. return playlist.attributes[type] === groupId;
  46824. };
  46825. };
  46826. var byResolvedUri = function byResolvedUri(resolvedUri) {
  46827. return function (playlist) {
  46828. return playlist.resolvedUri === resolvedUri;
  46829. };
  46830. };
  46831. var initialize = {
  46832. /**
  46833. * Setup PlaylistLoaders and AudioTracks for the audio groups
  46834. *
  46835. * @param {String} type
  46836. * MediaGroup type
  46837. * @param {Object} settings
  46838. * Object containing required information for media groups
  46839. * @function initialize.AUDIO
  46840. */
  46841. 'AUDIO': function AUDIO(type, settings) {
  46842. var hls = settings.hls,
  46843. sourceType = settings.sourceType,
  46844. segmentLoader = settings.segmentLoaders[type],
  46845. requestOptions = settings.requestOptions,
  46846. _settings$master = settings.master,
  46847. mediaGroups = _settings$master.mediaGroups,
  46848. playlists = _settings$master.playlists,
  46849. _settings$mediaTypes$ = settings.mediaTypes[type],
  46850. groups = _settings$mediaTypes$.groups,
  46851. tracks = _settings$mediaTypes$.tracks,
  46852. masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none
  46853. if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
  46854. mediaGroups[type] = {
  46855. main: {
  46856. default: {
  46857. default: true
  46858. }
  46859. }
  46860. };
  46861. }
  46862. for (var groupId in mediaGroups[type]) {
  46863. if (!groups[groupId]) {
  46864. groups[groupId] = [];
  46865. } // List of playlists that have an AUDIO attribute value matching the current
  46866. // group ID
  46867. var groupPlaylists = playlists.filter(byGroupId(type, groupId));
  46868. for (var variantLabel in mediaGroups[type][groupId]) {
  46869. var properties = mediaGroups[type][groupId][variantLabel]; // List of playlists for the current group ID that have a matching uri with
  46870. // this alternate audio variant
  46871. var matchingPlaylists = groupPlaylists.filter(byResolvedUri(properties.resolvedUri));
  46872. if (matchingPlaylists.length) {
  46873. // If there is a playlist that has the same uri as this audio variant, assume
  46874. // that the playlist is audio only. We delete the resolvedUri property here
  46875. // to prevent a playlist loader from being created so that we don't have
  46876. // both the main and audio segment loaders loading the same audio segments
  46877. // from the same playlist.
  46878. delete properties.resolvedUri;
  46879. }
  46880. var playlistLoader = void 0;
  46881. if (properties.resolvedUri) {
  46882. playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
  46883. } else if (properties.playlists && sourceType === 'dash') {
  46884. playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
  46885. } else {
  46886. // no resolvedUri means the audio is muxed with the video when using this
  46887. // audio track
  46888. playlistLoader = null;
  46889. }
  46890. properties = videojs$1.mergeOptions({
  46891. id: variantLabel,
  46892. playlistLoader: playlistLoader
  46893. }, properties);
  46894. setupListeners[type](type, properties.playlistLoader, settings);
  46895. groups[groupId].push(properties);
  46896. if (typeof tracks[variantLabel] === 'undefined') {
  46897. var track = new videojs$1.AudioTrack({
  46898. id: variantLabel,
  46899. kind: audioTrackKind_(properties),
  46900. enabled: false,
  46901. language: properties.language,
  46902. default: properties.default,
  46903. label: variantLabel
  46904. });
  46905. tracks[variantLabel] = track;
  46906. }
  46907. }
  46908. } // setup single error event handler for the segment loader
  46909. segmentLoader.on('error', onError[type](type, settings));
  46910. },
  46911. /**
  46912. * Setup PlaylistLoaders and TextTracks for the subtitle groups
  46913. *
  46914. * @param {String} type
  46915. * MediaGroup type
  46916. * @param {Object} settings
  46917. * Object containing required information for media groups
  46918. * @function initialize.SUBTITLES
  46919. */
  46920. 'SUBTITLES': function SUBTITLES(type, settings) {
  46921. var tech = settings.tech,
  46922. hls = settings.hls,
  46923. sourceType = settings.sourceType,
  46924. segmentLoader = settings.segmentLoaders[type],
  46925. requestOptions = settings.requestOptions,
  46926. mediaGroups = settings.master.mediaGroups,
  46927. _settings$mediaTypes$2 = settings.mediaTypes[type],
  46928. groups = _settings$mediaTypes$2.groups,
  46929. tracks = _settings$mediaTypes$2.tracks,
  46930. masterPlaylistLoader = settings.masterPlaylistLoader;
  46931. for (var groupId in mediaGroups[type]) {
  46932. if (!groups[groupId]) {
  46933. groups[groupId] = [];
  46934. }
  46935. for (var variantLabel in mediaGroups[type][groupId]) {
  46936. if (mediaGroups[type][groupId][variantLabel].forced) {
  46937. // Subtitle playlists with the forced attribute are not selectable in Safari.
  46938. // According to Apple's HLS Authoring Specification:
  46939. // If content has forced subtitles and regular subtitles in a given language,
  46940. // the regular subtitles track in that language MUST contain both the forced
  46941. // subtitles and the regular subtitles for that language.
  46942. // Because of this requirement and that Safari does not add forced subtitles,
  46943. // forced subtitles are skipped here to maintain consistent experience across
  46944. // all platforms
  46945. continue;
  46946. }
  46947. var properties = mediaGroups[type][groupId][variantLabel];
  46948. var playlistLoader = void 0;
  46949. if (sourceType === 'hls') {
  46950. playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
  46951. } else if (sourceType === 'dash') {
  46952. playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
  46953. }
  46954. properties = videojs$1.mergeOptions({
  46955. id: variantLabel,
  46956. playlistLoader: playlistLoader
  46957. }, properties);
  46958. setupListeners[type](type, properties.playlistLoader, settings);
  46959. groups[groupId].push(properties);
  46960. if (typeof tracks[variantLabel] === 'undefined') {
  46961. var track = tech.addRemoteTextTrack({
  46962. id: variantLabel,
  46963. kind: 'subtitles',
  46964. default: properties.default && properties.autoselect,
  46965. language: properties.language,
  46966. label: variantLabel
  46967. }, false).track;
  46968. tracks[variantLabel] = track;
  46969. }
  46970. }
  46971. } // setup single error event handler for the segment loader
  46972. segmentLoader.on('error', onError[type](type, settings));
  46973. },
  46974. /**
  46975. * Setup TextTracks for the closed-caption groups
  46976. *
  46977. * @param {String} type
  46978. * MediaGroup type
  46979. * @param {Object} settings
  46980. * Object containing required information for media groups
  46981. * @function initialize['CLOSED-CAPTIONS']
  46982. */
  46983. 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
  46984. var tech = settings.tech,
  46985. mediaGroups = settings.master.mediaGroups,
  46986. _settings$mediaTypes$3 = settings.mediaTypes[type],
  46987. groups = _settings$mediaTypes$3.groups,
  46988. tracks = _settings$mediaTypes$3.tracks;
  46989. for (var groupId in mediaGroups[type]) {
  46990. if (!groups[groupId]) {
  46991. groups[groupId] = [];
  46992. }
  46993. for (var variantLabel in mediaGroups[type][groupId]) {
  46994. var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that
  46995. // doesn't use a CCx INSTREAM-ID
  46996. if (!properties.instreamId.match(/CC\d/)) {
  46997. continue;
  46998. } // No PlaylistLoader is required for Closed-Captions because the captions are
  46999. // embedded within the video stream
  47000. groups[groupId].push(videojs$1.mergeOptions({
  47001. id: variantLabel
  47002. }, properties));
  47003. if (typeof tracks[variantLabel] === 'undefined') {
  47004. var track = tech.addRemoteTextTrack({
  47005. id: properties.instreamId,
  47006. kind: 'captions',
  47007. default: properties.default && properties.autoselect,
  47008. language: properties.language,
  47009. label: variantLabel
  47010. }, false).track;
  47011. tracks[variantLabel] = track;
  47012. }
  47013. }
  47014. }
  47015. }
  47016. };
  47017. /**
  47018. * Returns a function used to get the active group of the provided type
  47019. *
  47020. * @param {String} type
  47021. * MediaGroup type
  47022. * @param {Object} settings
  47023. * Object containing required information for media groups
  47024. * @return {Function}
  47025. * Function that returns the active media group for the provided type. Takes an
  47026. * optional parameter {TextTrack} track. If no track is provided, a list of all
  47027. * variants in the group, otherwise the variant corresponding to the provided
  47028. * track is returned.
  47029. * @function activeGroup
  47030. */
  47031. var activeGroup = function activeGroup(type, settings) {
  47032. return function (track) {
  47033. var masterPlaylistLoader = settings.masterPlaylistLoader,
  47034. groups = settings.mediaTypes[type].groups;
  47035. var media = masterPlaylistLoader.media();
  47036. if (!media) {
  47037. return null;
  47038. }
  47039. var variants = null;
  47040. if (media.attributes[type]) {
  47041. variants = groups[media.attributes[type]];
  47042. }
  47043. variants = variants || groups.main;
  47044. if (typeof track === 'undefined') {
  47045. return variants;
  47046. }
  47047. if (track === null) {
  47048. // An active track was specified so a corresponding group is expected. track === null
  47049. // means no track is currently active so there is no corresponding group
  47050. return null;
  47051. }
  47052. return variants.filter(function (props) {
  47053. return props.id === track.id;
  47054. })[0] || null;
  47055. };
  47056. };
  47057. var activeTrack = {
  47058. /**
  47059. * Returns a function used to get the active track of type provided
  47060. *
  47061. * @param {String} type
  47062. * MediaGroup type
  47063. * @param {Object} settings
  47064. * Object containing required information for media groups
  47065. * @return {Function}
  47066. * Function that returns the active media track for the provided type. Returns
  47067. * null if no track is active
  47068. * @function activeTrack.AUDIO
  47069. */
  47070. AUDIO: function AUDIO(type, settings) {
  47071. return function () {
  47072. var tracks = settings.mediaTypes[type].tracks;
  47073. for (var id in tracks) {
  47074. if (tracks[id].enabled) {
  47075. return tracks[id];
  47076. }
  47077. }
  47078. return null;
  47079. };
  47080. },
  47081. /**
  47082. * Returns a function used to get the active track of type provided
  47083. *
  47084. * @param {String} type
  47085. * MediaGroup type
  47086. * @param {Object} settings
  47087. * Object containing required information for media groups
  47088. * @return {Function}
  47089. * Function that returns the active media track for the provided type. Returns
  47090. * null if no track is active
  47091. * @function activeTrack.SUBTITLES
  47092. */
  47093. SUBTITLES: function SUBTITLES(type, settings) {
  47094. return function () {
  47095. var tracks = settings.mediaTypes[type].tracks;
  47096. for (var id in tracks) {
  47097. if (tracks[id].mode === 'showing') {
  47098. return tracks[id];
  47099. }
  47100. }
  47101. return null;
  47102. };
  47103. }
  47104. };
  47105. /**
  47106. * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
  47107. * Closed-Captions) specified in the master manifest.
  47108. *
  47109. * @param {Object} settings
  47110. * Object containing required information for setting up the media groups
  47111. * @param {SegmentLoader} settings.segmentLoaders.AUDIO
  47112. * Audio segment loader
  47113. * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES
  47114. * Subtitle segment loader
  47115. * @param {SegmentLoader} settings.segmentLoaders.main
  47116. * Main segment loader
  47117. * @param {Tech} settings.tech
  47118. * The tech of the player
  47119. * @param {Object} settings.requestOptions
  47120. * XHR request options used by the segment loaders
  47121. * @param {PlaylistLoader} settings.masterPlaylistLoader
  47122. * PlaylistLoader for the master source
  47123. * @param {HlsHandler} settings.hls
  47124. * HLS SourceHandler
  47125. * @param {Object} settings.master
  47126. * The parsed master manifest
  47127. * @param {Object} settings.mediaTypes
  47128. * Object to store the loaders, tracks, and utility methods for each media type
  47129. * @param {Function} settings.blacklistCurrentPlaylist
  47130. * Blacklists the current rendition and forces a rendition switch.
  47131. * @function setupMediaGroups
  47132. */
  47133. var setupMediaGroups = function setupMediaGroups(settings) {
  47134. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  47135. initialize[type](type, settings);
  47136. });
  47137. var mediaTypes = settings.mediaTypes,
  47138. masterPlaylistLoader = settings.masterPlaylistLoader,
  47139. tech = settings.tech,
  47140. hls = settings.hls; // setup active group and track getters and change event handlers
  47141. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  47142. mediaTypes[type].activeGroup = activeGroup(type, settings);
  47143. mediaTypes[type].activeTrack = activeTrack[type](type, settings);
  47144. mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
  47145. mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
  47146. }); // DO NOT enable the default subtitle or caption track.
  47147. // DO enable the default audio track
  47148. var audioGroup = mediaTypes.AUDIO.activeGroup();
  47149. var groupId = (audioGroup.filter(function (group) {
  47150. return group.default;
  47151. })[0] || audioGroup[0]).id;
  47152. mediaTypes.AUDIO.tracks[groupId].enabled = true;
  47153. mediaTypes.AUDIO.onTrackChanged();
  47154. masterPlaylistLoader.on('mediachange', function () {
  47155. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  47156. return mediaTypes[type].onGroupChanged();
  47157. });
  47158. }); // custom audio track change event handler for usage event
  47159. var onAudioTrackChanged = function onAudioTrackChanged() {
  47160. mediaTypes.AUDIO.onTrackChanged();
  47161. tech.trigger({
  47162. type: 'usage',
  47163. name: 'hls-audio-change'
  47164. });
  47165. };
  47166. tech.audioTracks().addEventListener('change', onAudioTrackChanged);
  47167. tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  47168. hls.on('dispose', function () {
  47169. tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
  47170. tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
  47171. }); // clear existing audio tracks and add the ones we just created
  47172. tech.clearTracks('audio');
  47173. for (var id in mediaTypes.AUDIO.tracks) {
  47174. tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
  47175. }
  47176. };
  47177. /**
  47178. * Creates skeleton object used to store the loaders, tracks, and utility methods for each
  47179. * media type
  47180. *
  47181. * @return {Object}
  47182. * Object to store the loaders, tracks, and utility methods for each media type
  47183. * @function createMediaTypes
  47184. */
  47185. var createMediaTypes = function createMediaTypes() {
  47186. var mediaTypes = {};
  47187. ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
  47188. mediaTypes[type] = {
  47189. groups: {},
  47190. tracks: {},
  47191. activePlaylistLoader: null,
  47192. activeGroup: noop$1,
  47193. activeTrack: noop$1,
  47194. onGroupChanged: noop$1,
  47195. onTrackChanged: noop$1
  47196. };
  47197. });
  47198. return mediaTypes;
  47199. };
  47200. /**
  47201. * @file master-playlist-controller.js
  47202. */
  47203. var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
  47204. var Hls = void 0; // SegmentLoader stats that need to have each loader's
  47205. // values summed to calculate the final value
  47206. var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred'];
  47207. var sumLoaderStat = function sumLoaderStat(stat) {
  47208. return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
  47209. };
  47210. /**
  47211. * the master playlist controller controller all interactons
  47212. * between playlists and segmentloaders. At this time this mainly
  47213. * involves a master playlist and a series of audio playlists
  47214. * if they are available
  47215. *
  47216. * @class MasterPlaylistController
  47217. * @extends videojs.EventTarget
  47218. */
  47219. var MasterPlaylistController = function (_videojs$EventTarget) {
  47220. inherits$1(MasterPlaylistController, _videojs$EventTarget);
  47221. function MasterPlaylistController(options) {
  47222. classCallCheck$1(this, MasterPlaylistController);
  47223. var _this = possibleConstructorReturn$1(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this));
  47224. var url = options.url,
  47225. handleManifestRedirects = options.handleManifestRedirects,
  47226. withCredentials = options.withCredentials,
  47227. tech = options.tech,
  47228. bandwidth = options.bandwidth,
  47229. externHls = options.externHls,
  47230. useCueTags = options.useCueTags,
  47231. blacklistDuration = options.blacklistDuration,
  47232. enableLowInitialPlaylist = options.enableLowInitialPlaylist,
  47233. sourceType = options.sourceType,
  47234. seekTo = options.seekTo;
  47235. if (!url) {
  47236. throw new Error('A non-empty playlist URL is required');
  47237. }
  47238. Hls = externHls;
  47239. _this.withCredentials = withCredentials;
  47240. _this.tech_ = tech;
  47241. _this.hls_ = tech.hls;
  47242. _this.seekTo_ = seekTo;
  47243. _this.sourceType_ = sourceType;
  47244. _this.useCueTags_ = useCueTags;
  47245. _this.blacklistDuration = blacklistDuration;
  47246. _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
  47247. if (_this.useCueTags_) {
  47248. _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
  47249. _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
  47250. }
  47251. _this.requestOptions_ = {
  47252. withCredentials: withCredentials,
  47253. handleManifestRedirects: handleManifestRedirects,
  47254. timeout: null
  47255. };
  47256. _this.mediaTypes_ = createMediaTypes();
  47257. _this.mediaSource = new videojs$1.MediaSource(); // load the media source into the player
  47258. _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this));
  47259. _this.seekable_ = videojs$1.createTimeRanges();
  47260. _this.hasPlayed_ = function () {
  47261. return false;
  47262. };
  47263. _this.syncController_ = new SyncController(options);
  47264. _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
  47265. kind: 'metadata',
  47266. label: 'segment-metadata'
  47267. }, false).track;
  47268. _this.decrypter_ = new Decrypter$1();
  47269. _this.inbandTextTracks_ = {};
  47270. var segmentLoaderSettings = {
  47271. hls: _this.hls_,
  47272. mediaSource: _this.mediaSource,
  47273. currentTime: _this.tech_.currentTime.bind(_this.tech_),
  47274. seekable: function seekable$$1() {
  47275. return _this.seekable();
  47276. },
  47277. seeking: function seeking() {
  47278. return _this.tech_.seeking();
  47279. },
  47280. duration: function duration$$1() {
  47281. return _this.mediaSource.duration;
  47282. },
  47283. hasPlayed: function hasPlayed() {
  47284. return _this.hasPlayed_();
  47285. },
  47286. goalBufferLength: function goalBufferLength() {
  47287. return _this.goalBufferLength();
  47288. },
  47289. bandwidth: bandwidth,
  47290. syncController: _this.syncController_,
  47291. decrypter: _this.decrypter_,
  47292. sourceType: _this.sourceType_,
  47293. inbandTextTracks: _this.inbandTextTracks_
  47294. };
  47295. _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.requestOptions_) : new PlaylistLoader(url, _this.hls_, _this.requestOptions_);
  47296. _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders
  47297. // combined audio/video or just video when alternate audio track is selected
  47298. _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  47299. segmentMetadataTrack: _this.segmentMetadataTrack_,
  47300. loaderType: 'main'
  47301. }), options); // alternate audio track
  47302. _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  47303. loaderType: 'audio'
  47304. }), options);
  47305. _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
  47306. loaderType: 'vtt'
  47307. }), options);
  47308. _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters
  47309. loaderStats.forEach(function (stat) {
  47310. _this[stat + '_'] = sumLoaderStat.bind(_this, stat);
  47311. });
  47312. _this.logger_ = logger('MPC');
  47313. _this.masterPlaylistLoader_.load();
  47314. return _this;
  47315. }
  47316. /**
  47317. * Register event handlers on the master playlist loader. A helper
  47318. * function for construction time.
  47319. *
  47320. * @private
  47321. */
  47322. createClass$1(MasterPlaylistController, [{
  47323. key: 'setupMasterPlaylistLoaderListeners_',
  47324. value: function setupMasterPlaylistLoaderListeners_() {
  47325. var _this2 = this;
  47326. this.masterPlaylistLoader_.on('loadedmetadata', function () {
  47327. var media = _this2.masterPlaylistLoader_.media();
  47328. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  47329. // timeout the request.
  47330. if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  47331. _this2.requestOptions_.timeout = 0;
  47332. } else {
  47333. _this2.requestOptions_.timeout = requestTimeout;
  47334. } // if this isn't a live video and preload permits, start
  47335. // downloading segments
  47336. if (media.endList && _this2.tech_.preload() !== 'none') {
  47337. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  47338. _this2.mainSegmentLoader_.load();
  47339. }
  47340. setupMediaGroups({
  47341. sourceType: _this2.sourceType_,
  47342. segmentLoaders: {
  47343. AUDIO: _this2.audioSegmentLoader_,
  47344. SUBTITLES: _this2.subtitleSegmentLoader_,
  47345. main: _this2.mainSegmentLoader_
  47346. },
  47347. tech: _this2.tech_,
  47348. requestOptions: _this2.requestOptions_,
  47349. masterPlaylistLoader: _this2.masterPlaylistLoader_,
  47350. hls: _this2.hls_,
  47351. master: _this2.master(),
  47352. mediaTypes: _this2.mediaTypes_,
  47353. blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2)
  47354. });
  47355. _this2.triggerPresenceUsage_(_this2.master(), media);
  47356. try {
  47357. _this2.setupSourceBuffers_();
  47358. } catch (e) {
  47359. videojs$1.log.warn('Failed to create SourceBuffers', e);
  47360. return _this2.mediaSource.endOfStream('decode');
  47361. }
  47362. _this2.setupFirstPlay();
  47363. if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
  47364. _this2.trigger('selectedinitialmedia');
  47365. } else {
  47366. // We must wait for the active audio playlist loader to
  47367. // finish setting up before triggering this event so the
  47368. // representations API and EME setup is correct
  47369. _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () {
  47370. _this2.trigger('selectedinitialmedia');
  47371. });
  47372. }
  47373. });
  47374. this.masterPlaylistLoader_.on('loadedplaylist', function () {
  47375. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  47376. if (!updatedPlaylist) {
  47377. // blacklist any variants that are not supported by the browser before selecting
  47378. // an initial media as the playlist selectors do not consider browser support
  47379. _this2.excludeUnsupportedVariants_();
  47380. var selectedMedia = void 0;
  47381. if (_this2.enableLowInitialPlaylist) {
  47382. selectedMedia = _this2.selectInitialPlaylist();
  47383. }
  47384. if (!selectedMedia) {
  47385. selectedMedia = _this2.selectPlaylist();
  47386. }
  47387. _this2.initialMedia_ = selectedMedia;
  47388. _this2.masterPlaylistLoader_.media(_this2.initialMedia_);
  47389. return;
  47390. }
  47391. if (_this2.useCueTags_) {
  47392. _this2.updateAdCues_(updatedPlaylist);
  47393. } // TODO: Create a new event on the PlaylistLoader that signals
  47394. // that the segments have changed in some way and use that to
  47395. // update the SegmentLoader instead of doing it twice here and
  47396. // on `mediachange`
  47397. _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_);
  47398. _this2.updateDuration(); // If the player isn't paused, ensure that the segment loader is running,
  47399. // as it is possible that it was temporarily stopped while waiting for
  47400. // a playlist (e.g., in case the playlist errored and we re-requested it).
  47401. if (!_this2.tech_.paused()) {
  47402. _this2.mainSegmentLoader_.load();
  47403. if (_this2.audioSegmentLoader_) {
  47404. _this2.audioSegmentLoader_.load();
  47405. }
  47406. }
  47407. if (!updatedPlaylist.endList) {
  47408. var addSeekableRange = function addSeekableRange() {
  47409. var seekable$$1 = _this2.seekable();
  47410. if (seekable$$1.length !== 0) {
  47411. _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0));
  47412. }
  47413. };
  47414. if (_this2.duration() !== Infinity) {
  47415. var onDurationchange = function onDurationchange() {
  47416. if (_this2.duration() === Infinity) {
  47417. addSeekableRange();
  47418. } else {
  47419. _this2.tech_.one('durationchange', onDurationchange);
  47420. }
  47421. };
  47422. _this2.tech_.one('durationchange', onDurationchange);
  47423. } else {
  47424. addSeekableRange();
  47425. }
  47426. }
  47427. });
  47428. this.masterPlaylistLoader_.on('error', function () {
  47429. _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error);
  47430. });
  47431. this.masterPlaylistLoader_.on('mediachanging', function () {
  47432. _this2.mainSegmentLoader_.abort();
  47433. _this2.mainSegmentLoader_.pause();
  47434. });
  47435. this.masterPlaylistLoader_.on('mediachange', function () {
  47436. var media = _this2.masterPlaylistLoader_.media();
  47437. var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
  47438. // timeout the request.
  47439. if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  47440. _this2.requestOptions_.timeout = 0;
  47441. } else {
  47442. _this2.requestOptions_.timeout = requestTimeout;
  47443. } // TODO: Create a new event on the PlaylistLoader that signals
  47444. // that the segments have changed in some way and use that to
  47445. // update the SegmentLoader instead of doing it twice here and
  47446. // on `loadedplaylist`
  47447. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  47448. _this2.mainSegmentLoader_.load();
  47449. _this2.tech_.trigger({
  47450. type: 'mediachange',
  47451. bubbles: true
  47452. });
  47453. });
  47454. this.masterPlaylistLoader_.on('playlistunchanged', function () {
  47455. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  47456. var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist);
  47457. if (playlistOutdated) {
  47458. // Playlist has stopped updating and we're stuck at its end. Try to
  47459. // blacklist it and switch to another playlist in the hope that that
  47460. // one is updating (and give the player a chance to re-adjust to the
  47461. // safe live point).
  47462. _this2.blacklistCurrentPlaylist({
  47463. message: 'Playlist no longer updating.'
  47464. }); // useful for monitoring QoS
  47465. _this2.tech_.trigger('playliststuck');
  47466. }
  47467. });
  47468. this.masterPlaylistLoader_.on('renditiondisabled', function () {
  47469. _this2.tech_.trigger({
  47470. type: 'usage',
  47471. name: 'hls-rendition-disabled'
  47472. });
  47473. });
  47474. this.masterPlaylistLoader_.on('renditionenabled', function () {
  47475. _this2.tech_.trigger({
  47476. type: 'usage',
  47477. name: 'hls-rendition-enabled'
  47478. });
  47479. });
  47480. }
  47481. /**
  47482. * A helper function for triggerring presence usage events once per source
  47483. *
  47484. * @private
  47485. */
  47486. }, {
  47487. key: 'triggerPresenceUsage_',
  47488. value: function triggerPresenceUsage_(master, media) {
  47489. var mediaGroups = master.mediaGroups || {};
  47490. var defaultDemuxed = true;
  47491. var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
  47492. for (var mediaGroup in mediaGroups.AUDIO) {
  47493. for (var label in mediaGroups.AUDIO[mediaGroup]) {
  47494. var properties = mediaGroups.AUDIO[mediaGroup][label];
  47495. if (!properties.uri) {
  47496. defaultDemuxed = false;
  47497. }
  47498. }
  47499. }
  47500. if (defaultDemuxed) {
  47501. this.tech_.trigger({
  47502. type: 'usage',
  47503. name: 'hls-demuxed'
  47504. });
  47505. }
  47506. if (Object.keys(mediaGroups.SUBTITLES).length) {
  47507. this.tech_.trigger({
  47508. type: 'usage',
  47509. name: 'hls-webvtt'
  47510. });
  47511. }
  47512. if (Hls.Playlist.isAes(media)) {
  47513. this.tech_.trigger({
  47514. type: 'usage',
  47515. name: 'hls-aes'
  47516. });
  47517. }
  47518. if (Hls.Playlist.isFmp4(media)) {
  47519. this.tech_.trigger({
  47520. type: 'usage',
  47521. name: 'hls-fmp4'
  47522. });
  47523. }
  47524. if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
  47525. this.tech_.trigger({
  47526. type: 'usage',
  47527. name: 'hls-alternate-audio'
  47528. });
  47529. }
  47530. if (this.useCueTags_) {
  47531. this.tech_.trigger({
  47532. type: 'usage',
  47533. name: 'hls-playlist-cue-tags'
  47534. });
  47535. }
  47536. }
  47537. /**
  47538. * Register event handlers on the segment loaders. A helper function
  47539. * for construction time.
  47540. *
  47541. * @private
  47542. */
  47543. }, {
  47544. key: 'setupSegmentLoaderListeners_',
  47545. value: function setupSegmentLoaderListeners_() {
  47546. var _this3 = this;
  47547. this.mainSegmentLoader_.on('bandwidthupdate', function () {
  47548. var nextPlaylist = _this3.selectPlaylist();
  47549. var currentPlaylist = _this3.masterPlaylistLoader_.media();
  47550. var buffered = _this3.tech_.buffered();
  47551. var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0;
  47552. var bufferLowWaterLine = _this3.bufferLowWaterLine(); // If the playlist is live, then we want to not take low water line into account.
  47553. // This is because in LIVE, the player plays 3 segments from the end of the
  47554. // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
  47555. // in those segments, a viewer will never experience a rendition upswitch.
  47556. if (!currentPlaylist.endList || // For the same reason as LIVE, we ignore the low water line when the VOD
  47557. // duration is below the max potential low water line
  47558. _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE || // we want to switch down to lower resolutions quickly to continue playback, but
  47559. nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH || // ensure we have some buffer before we switch up to prevent us running out of
  47560. // buffer while loading a higher rendition.
  47561. forwardBuffer >= bufferLowWaterLine) {
  47562. _this3.masterPlaylistLoader_.media(nextPlaylist);
  47563. }
  47564. _this3.tech_.trigger('bandwidthupdate');
  47565. });
  47566. this.mainSegmentLoader_.on('progress', function () {
  47567. _this3.trigger('progress');
  47568. });
  47569. this.mainSegmentLoader_.on('error', function () {
  47570. _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error());
  47571. });
  47572. this.mainSegmentLoader_.on('syncinfoupdate', function () {
  47573. _this3.onSyncInfoUpdate_();
  47574. });
  47575. this.mainSegmentLoader_.on('timestampoffset', function () {
  47576. _this3.tech_.trigger({
  47577. type: 'usage',
  47578. name: 'hls-timestamp-offset'
  47579. });
  47580. });
  47581. this.audioSegmentLoader_.on('syncinfoupdate', function () {
  47582. _this3.onSyncInfoUpdate_();
  47583. });
  47584. this.mainSegmentLoader_.on('ended', function () {
  47585. _this3.onEndOfStream();
  47586. });
  47587. this.mainSegmentLoader_.on('earlyabort', function () {
  47588. _this3.blacklistCurrentPlaylist({
  47589. message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
  47590. }, ABORT_EARLY_BLACKLIST_SECONDS);
  47591. });
  47592. this.mainSegmentLoader_.on('reseteverything', function () {
  47593. // If playing an MTS stream, a videojs.MediaSource is listening for
  47594. // hls-reset to reset caption parsing state in the transmuxer
  47595. _this3.tech_.trigger('hls-reset');
  47596. });
  47597. this.mainSegmentLoader_.on('segmenttimemapping', function (event) {
  47598. // If playing an MTS stream in html, a videojs.MediaSource is listening for
  47599. // hls-segment-time-mapping update its internal mapping of stream to display time
  47600. _this3.tech_.trigger({
  47601. type: 'hls-segment-time-mapping',
  47602. mapping: event.mapping
  47603. });
  47604. });
  47605. this.audioSegmentLoader_.on('ended', function () {
  47606. _this3.onEndOfStream();
  47607. });
  47608. }
  47609. }, {
  47610. key: 'mediaSecondsLoaded_',
  47611. value: function mediaSecondsLoaded_() {
  47612. return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
  47613. }
  47614. /**
  47615. * Call load on our SegmentLoaders
  47616. */
  47617. }, {
  47618. key: 'load',
  47619. value: function load() {
  47620. this.mainSegmentLoader_.load();
  47621. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  47622. this.audioSegmentLoader_.load();
  47623. }
  47624. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  47625. this.subtitleSegmentLoader_.load();
  47626. }
  47627. }
  47628. /**
  47629. * Re-tune playback quality level for the current player
  47630. * conditions without performing destructive actions, like
  47631. * removing already buffered content
  47632. *
  47633. * @private
  47634. */
  47635. }, {
  47636. key: 'smoothQualityChange_',
  47637. value: function smoothQualityChange_() {
  47638. var media = this.selectPlaylist();
  47639. if (media !== this.masterPlaylistLoader_.media()) {
  47640. this.masterPlaylistLoader_.media(media);
  47641. this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes
  47642. }
  47643. }
  47644. /**
  47645. * Re-tune playback quality level for the current player
  47646. * conditions. This method will perform destructive actions like removing
  47647. * already buffered content in order to readjust the currently active
  47648. * playlist quickly. This is good for manual quality changes
  47649. *
  47650. * @private
  47651. */
  47652. }, {
  47653. key: 'fastQualityChange_',
  47654. value: function fastQualityChange_() {
  47655. var _this4 = this;
  47656. var media = this.selectPlaylist();
  47657. if (media === this.masterPlaylistLoader_.media()) {
  47658. return;
  47659. }
  47660. this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give
  47661. // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
  47662. // ahead is roughly the minimum that will accomplish this across a variety of content
  47663. // in IE and Edge, but seeking in place is sufficient on all other browsers)
  47664. // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
  47665. // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
  47666. this.mainSegmentLoader_.resetEverything(function () {
  47667. // Since this is not a typical seek, we avoid the seekTo method which can cause segments
  47668. // from the previously enabled rendition to load before the new playlist has finished loading
  47669. if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) {
  47670. _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04);
  47671. } else {
  47672. _this4.tech_.setCurrentTime(_this4.tech_.currentTime());
  47673. }
  47674. }); // don't need to reset audio as it is reset when media changes
  47675. }
  47676. /**
  47677. * Begin playback.
  47678. */
  47679. }, {
  47680. key: 'play',
  47681. value: function play() {
  47682. if (this.setupFirstPlay()) {
  47683. return;
  47684. }
  47685. if (this.tech_.ended()) {
  47686. this.seekTo_(0);
  47687. }
  47688. if (this.hasPlayed_()) {
  47689. this.load();
  47690. }
  47691. var seekable$$1 = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
  47692. // seek forward to the live point
  47693. if (this.tech_.duration() === Infinity) {
  47694. if (this.tech_.currentTime() < seekable$$1.start(0)) {
  47695. return this.seekTo_(seekable$$1.end(seekable$$1.length - 1));
  47696. }
  47697. }
  47698. }
  47699. /**
  47700. * Seek to the latest media position if this is a live video and the
  47701. * player and video are loaded and initialized.
  47702. */
  47703. }, {
  47704. key: 'setupFirstPlay',
  47705. value: function setupFirstPlay() {
  47706. var _this5 = this;
  47707. var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
  47708. // If 1) there is no active media
  47709. // 2) the player is paused
  47710. // 3) the first play has already been setup
  47711. // then exit early
  47712. if (!media || this.tech_.paused() || this.hasPlayed_()) {
  47713. return false;
  47714. } // when the video is a live stream
  47715. if (!media.endList) {
  47716. var seekable$$1 = this.seekable();
  47717. if (!seekable$$1.length) {
  47718. // without a seekable range, the player cannot seek to begin buffering at the live
  47719. // point
  47720. return false;
  47721. }
  47722. if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) {
  47723. // IE11 throws an InvalidStateError if you try to set currentTime while the
  47724. // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
  47725. this.tech_.one('loadedmetadata', function () {
  47726. _this5.trigger('firstplay');
  47727. _this5.seekTo_(seekable$$1.end(0));
  47728. _this5.hasPlayed_ = function () {
  47729. return true;
  47730. };
  47731. });
  47732. return false;
  47733. } // trigger firstplay to inform the source handler to ignore the next seek event
  47734. this.trigger('firstplay'); // seek to the live point
  47735. this.seekTo_(seekable$$1.end(0));
  47736. }
  47737. this.hasPlayed_ = function () {
  47738. return true;
  47739. }; // we can begin loading now that everything is ready
  47740. this.load();
  47741. return true;
  47742. }
  47743. /**
  47744. * handle the sourceopen event on the MediaSource
  47745. *
  47746. * @private
  47747. */
  47748. }, {
  47749. key: 'handleSourceOpen_',
  47750. value: function handleSourceOpen_() {
  47751. // Only attempt to create the source buffer if none already exist.
  47752. // handleSourceOpen is also called when we are "re-opening" a source buffer
  47753. // after `endOfStream` has been called (in response to a seek for instance)
  47754. try {
  47755. this.setupSourceBuffers_();
  47756. } catch (e) {
  47757. videojs$1.log.warn('Failed to create Source Buffers', e);
  47758. return this.mediaSource.endOfStream('decode');
  47759. } // if autoplay is enabled, begin playback. This is duplicative of
  47760. // code in video.js but is required because play() must be invoked
  47761. // *after* the media source has opened.
  47762. if (this.tech_.autoplay()) {
  47763. var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
  47764. // on browsers which return a promise
  47765. if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
  47766. playPromise.then(null, function (e) {});
  47767. }
  47768. }
  47769. this.trigger('sourceopen');
  47770. }
  47771. /**
  47772. * Calls endOfStream on the media source when all active stream types have called
  47773. * endOfStream
  47774. *
  47775. * @param {string} streamType
  47776. * Stream type of the segment loader that called endOfStream
  47777. * @private
  47778. */
  47779. }, {
  47780. key: 'onEndOfStream',
  47781. value: function onEndOfStream() {
  47782. var isEndOfStream = this.mainSegmentLoader_.ended_;
  47783. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  47784. // if the audio playlist loader exists, then alternate audio is active
  47785. if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) {
  47786. // if we do not know if the main segment loader contains video yet or if we
  47787. // definitively know the main segment loader contains video, then we need to wait
  47788. // for both main and audio segment loaders to call endOfStream
  47789. isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
  47790. } else {
  47791. // otherwise just rely on the audio loader
  47792. isEndOfStream = this.audioSegmentLoader_.ended_;
  47793. }
  47794. }
  47795. if (!isEndOfStream) {
  47796. return;
  47797. }
  47798. this.logger_('calling mediaSource.endOfStream()'); // on chrome calling endOfStream can sometimes cause an exception,
  47799. // even when the media source is in a valid state.
  47800. try {
  47801. this.mediaSource.endOfStream();
  47802. } catch (e) {
  47803. videojs$1.log.warn('Failed to call media source endOfStream', e);
  47804. }
  47805. }
  47806. /**
  47807. * Check if a playlist has stopped being updated
  47808. * @param {Object} playlist the media playlist object
  47809. * @return {boolean} whether the playlist has stopped being updated or not
  47810. */
  47811. }, {
  47812. key: 'stuckAtPlaylistEnd_',
  47813. value: function stuckAtPlaylistEnd_(playlist) {
  47814. var seekable$$1 = this.seekable();
  47815. if (!seekable$$1.length) {
  47816. // playlist doesn't have enough information to determine whether we are stuck
  47817. return false;
  47818. }
  47819. var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration);
  47820. if (expired === null) {
  47821. return false;
  47822. } // does not use the safe live end to calculate playlist end, since we
  47823. // don't want to say we are stuck while there is still content
  47824. var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired);
  47825. var currentTime = this.tech_.currentTime();
  47826. var buffered = this.tech_.buffered();
  47827. if (!buffered.length) {
  47828. // return true if the playhead reached the absolute end of the playlist
  47829. return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
  47830. }
  47831. var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
  47832. // end of playlist
  47833. return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
  47834. }
  47835. /**
  47836. * Blacklists a playlist when an error occurs for a set amount of time
  47837. * making it unavailable for selection by the rendition selection algorithm
  47838. * and then forces a new playlist (rendition) selection.
  47839. *
  47840. * @param {Object=} error an optional error that may include the playlist
  47841. * to blacklist
  47842. * @param {Number=} blacklistDuration an optional number of seconds to blacklist the
  47843. * playlist
  47844. */
  47845. }, {
  47846. key: 'blacklistCurrentPlaylist',
  47847. value: function blacklistCurrentPlaylist() {
  47848. var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  47849. var blacklistDuration = arguments[1];
  47850. var currentPlaylist = void 0;
  47851. var nextPlaylist = void 0; // If the `error` was generated by the playlist loader, it will contain
  47852. // the playlist we were trying to load (but failed) and that should be
  47853. // blacklisted instead of the currently selected playlist which is likely
  47854. // out-of-date in this scenario
  47855. currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
  47856. blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were
  47857. // trying to load the master OR while we were disposing of the tech
  47858. if (!currentPlaylist) {
  47859. this.error = error;
  47860. try {
  47861. return this.mediaSource.endOfStream('network');
  47862. } catch (e) {
  47863. return this.trigger('error');
  47864. }
  47865. }
  47866. var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1;
  47867. if (isFinalRendition) {
  47868. // Never blacklisting this playlist because it's final rendition
  47869. videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the final playlist.');
  47870. this.tech_.trigger('retryplaylist');
  47871. return this.masterPlaylistLoader_.load(isFinalRendition);
  47872. } // Blacklist this playlist
  47873. currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000;
  47874. this.tech_.trigger('blacklistplaylist');
  47875. this.tech_.trigger({
  47876. type: 'usage',
  47877. name: 'hls-rendition-blacklisted'
  47878. }); // Select a new playlist
  47879. nextPlaylist = this.selectPlaylist();
  47880. videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.');
  47881. return this.masterPlaylistLoader_.media(nextPlaylist);
  47882. }
  47883. /**
  47884. * Pause all segment loaders
  47885. */
  47886. }, {
  47887. key: 'pauseLoading',
  47888. value: function pauseLoading() {
  47889. this.mainSegmentLoader_.pause();
  47890. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  47891. this.audioSegmentLoader_.pause();
  47892. }
  47893. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  47894. this.subtitleSegmentLoader_.pause();
  47895. }
  47896. }
  47897. /**
  47898. * set the current time on all segment loaders
  47899. *
  47900. * @param {TimeRange} currentTime the current time to set
  47901. * @return {TimeRange} the current time
  47902. */
  47903. }, {
  47904. key: 'setCurrentTime',
  47905. value: function setCurrentTime(currentTime) {
  47906. var buffered = findRange(this.tech_.buffered(), currentTime);
  47907. if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
  47908. // return immediately if the metadata is not ready yet
  47909. return 0;
  47910. } // it's clearly an edge-case but don't thrown an error if asked to
  47911. // seek within an empty playlist
  47912. if (!this.masterPlaylistLoader_.media().segments) {
  47913. return 0;
  47914. } // In flash playback, the segment loaders should be reset on every seek, even
  47915. // in buffer seeks. If the seek location is already buffered, continue buffering as
  47916. // usual
  47917. // TODO: redo this comment
  47918. if (buffered && buffered.length) {
  47919. return currentTime;
  47920. } // cancel outstanding requests so we begin buffering at the new
  47921. // location
  47922. this.mainSegmentLoader_.resetEverything();
  47923. this.mainSegmentLoader_.abort();
  47924. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  47925. this.audioSegmentLoader_.resetEverything();
  47926. this.audioSegmentLoader_.abort();
  47927. }
  47928. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  47929. this.subtitleSegmentLoader_.resetEverything();
  47930. this.subtitleSegmentLoader_.abort();
  47931. } // start segment loader loading in case they are paused
  47932. this.load();
  47933. }
  47934. /**
  47935. * get the current duration
  47936. *
  47937. * @return {TimeRange} the duration
  47938. */
  47939. }, {
  47940. key: 'duration',
  47941. value: function duration$$1() {
  47942. if (!this.masterPlaylistLoader_) {
  47943. return 0;
  47944. }
  47945. if (this.mediaSource) {
  47946. return this.mediaSource.duration;
  47947. }
  47948. return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  47949. }
  47950. /**
  47951. * check the seekable range
  47952. *
  47953. * @return {TimeRange} the seekable range
  47954. */
  47955. }, {
  47956. key: 'seekable',
  47957. value: function seekable$$1() {
  47958. return this.seekable_;
  47959. }
  47960. }, {
  47961. key: 'onSyncInfoUpdate_',
  47962. value: function onSyncInfoUpdate_() {
  47963. var mainSeekable = void 0;
  47964. var audioSeekable = void 0;
  47965. if (!this.masterPlaylistLoader_) {
  47966. return;
  47967. }
  47968. var media = this.masterPlaylistLoader_.media();
  47969. if (!media) {
  47970. return;
  47971. }
  47972. var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  47973. if (expired === null) {
  47974. // not enough information to update seekable
  47975. return;
  47976. }
  47977. mainSeekable = Hls.Playlist.seekable(media, expired);
  47978. if (mainSeekable.length === 0) {
  47979. return;
  47980. }
  47981. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  47982. media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
  47983. expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  47984. if (expired === null) {
  47985. return;
  47986. }
  47987. audioSeekable = Hls.Playlist.seekable(media, expired);
  47988. if (audioSeekable.length === 0) {
  47989. return;
  47990. }
  47991. }
  47992. var oldEnd = void 0;
  47993. var oldStart = void 0;
  47994. if (this.seekable_ && this.seekable_.length) {
  47995. oldEnd = this.seekable_.end(0);
  47996. oldStart = this.seekable_.start(0);
  47997. }
  47998. if (!audioSeekable) {
  47999. // seekable has been calculated based on buffering video data so it
  48000. // can be returned directly
  48001. this.seekable_ = mainSeekable;
  48002. } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
  48003. // seekables are pretty far off, rely on main
  48004. this.seekable_ = mainSeekable;
  48005. } else {
  48006. 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)]]);
  48007. } // seekable is the same as last time
  48008. if (this.seekable_ && this.seekable_.length) {
  48009. if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
  48010. return;
  48011. }
  48012. }
  48013. this.logger_('seekable updated [' + printableRange(this.seekable_) + ']');
  48014. this.tech_.trigger('seekablechanged');
  48015. }
  48016. /**
  48017. * Update the player duration
  48018. */
  48019. }, {
  48020. key: 'updateDuration',
  48021. value: function updateDuration() {
  48022. var _this6 = this;
  48023. var oldDuration = this.mediaSource.duration;
  48024. var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  48025. var buffered = this.tech_.buffered();
  48026. var setDuration = function setDuration() {
  48027. // on firefox setting the duration may sometimes cause an exception
  48028. // even if the media source is open and source buffers are not
  48029. // updating, something about the media source being in an invalid state.
  48030. _this6.logger_('Setting duration from ' + _this6.mediaSource.duration + ' => ' + newDuration);
  48031. try {
  48032. _this6.mediaSource.duration = newDuration;
  48033. } catch (e) {
  48034. videojs$1.log.warn('Failed to set media source duration', e);
  48035. }
  48036. _this6.tech_.trigger('durationchange');
  48037. _this6.mediaSource.removeEventListener('sourceopen', setDuration);
  48038. };
  48039. if (buffered.length > 0) {
  48040. newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
  48041. } // if the duration has changed, invalidate the cached value
  48042. if (oldDuration !== newDuration) {
  48043. // update the duration
  48044. if (this.mediaSource.readyState !== 'open') {
  48045. this.mediaSource.addEventListener('sourceopen', setDuration);
  48046. } else {
  48047. setDuration();
  48048. }
  48049. }
  48050. }
  48051. /**
  48052. * dispose of the MasterPlaylistController and everything
  48053. * that it controls
  48054. */
  48055. }, {
  48056. key: 'dispose',
  48057. value: function dispose() {
  48058. var _this7 = this;
  48059. this.decrypter_.terminate();
  48060. this.masterPlaylistLoader_.dispose();
  48061. this.mainSegmentLoader_.dispose();
  48062. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  48063. var groups = _this7.mediaTypes_[type].groups;
  48064. for (var id in groups) {
  48065. groups[id].forEach(function (group) {
  48066. if (group.playlistLoader) {
  48067. group.playlistLoader.dispose();
  48068. }
  48069. });
  48070. }
  48071. });
  48072. this.audioSegmentLoader_.dispose();
  48073. this.subtitleSegmentLoader_.dispose();
  48074. }
  48075. /**
  48076. * return the master playlist object if we have one
  48077. *
  48078. * @return {Object} the master playlist object that we parsed
  48079. */
  48080. }, {
  48081. key: 'master',
  48082. value: function master() {
  48083. return this.masterPlaylistLoader_.master;
  48084. }
  48085. /**
  48086. * return the currently selected playlist
  48087. *
  48088. * @return {Object} the currently selected playlist object that we parsed
  48089. */
  48090. }, {
  48091. key: 'media',
  48092. value: function media() {
  48093. // playlist loader will not return media if it has not been fully loaded
  48094. return this.masterPlaylistLoader_.media() || this.initialMedia_;
  48095. }
  48096. /**
  48097. * setup our internal source buffers on our segment Loaders
  48098. *
  48099. * @private
  48100. */
  48101. }, {
  48102. key: 'setupSourceBuffers_',
  48103. value: function setupSourceBuffers_() {
  48104. var media = this.masterPlaylistLoader_.media();
  48105. var mimeTypes = void 0; // wait until a media playlist is available and the Media Source is
  48106. // attached
  48107. if (!media || this.mediaSource.readyState !== 'open') {
  48108. return;
  48109. }
  48110. mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media);
  48111. if (mimeTypes.length < 1) {
  48112. this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri;
  48113. return this.mediaSource.endOfStream('decode');
  48114. }
  48115. this.configureLoaderMimeTypes_(mimeTypes); // exclude any incompatible variant streams from future playlist
  48116. // selection
  48117. this.excludeIncompatibleVariants_(media);
  48118. }
  48119. }, {
  48120. key: 'configureLoaderMimeTypes_',
  48121. value: function configureLoaderMimeTypes_(mimeTypes) {
  48122. // If the content is demuxed, we can't start appending segments to a source buffer
  48123. // until both source buffers are set up, or else the browser may not let us add the
  48124. // second source buffer (it will assume we are playing either audio only or video
  48125. // only).
  48126. var sourceBufferEmitter = // If there is more than one mime type
  48127. mimeTypes.length > 1 && // and the first mime type does not have muxed video and audio
  48128. mimeTypes[0].indexOf(',') === -1 && // and the two mime types are different (they can be the same in the case of audio
  48129. // only with alternate audio)
  48130. mimeTypes[0] !== mimeTypes[1] ? // then we want to wait on the second source buffer
  48131. new videojs$1.EventTarget() : // otherwise there is no need to wait as the content is either audio only,
  48132. // video only, or muxed content.
  48133. null;
  48134. this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter);
  48135. if (mimeTypes[1]) {
  48136. this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter);
  48137. }
  48138. }
  48139. /**
  48140. * Blacklists playlists with codecs that are unsupported by the browser.
  48141. */
  48142. }, {
  48143. key: 'excludeUnsupportedVariants_',
  48144. value: function excludeUnsupportedVariants_() {
  48145. this.master().playlists.forEach(function (variant) {
  48146. if (variant.attributes.CODECS && window$1.MediaSource && window$1.MediaSource.isTypeSupported && !window$1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) {
  48147. variant.excludeUntil = Infinity;
  48148. }
  48149. });
  48150. }
  48151. /**
  48152. * Blacklist playlists that are known to be codec or
  48153. * stream-incompatible with the SourceBuffer configuration. For
  48154. * instance, Media Source Extensions would cause the video element to
  48155. * stall waiting for video data if you switched from a variant with
  48156. * video and audio to an audio-only one.
  48157. *
  48158. * @param {Object} media a media playlist compatible with the current
  48159. * set of SourceBuffers. Variants in the current master playlist that
  48160. * do not appear to have compatible codec or stream configurations
  48161. * will be excluded from the default playlist selection algorithm
  48162. * indefinitely.
  48163. * @private
  48164. */
  48165. }, {
  48166. key: 'excludeIncompatibleVariants_',
  48167. value: function excludeIncompatibleVariants_(media) {
  48168. var codecCount = 2;
  48169. var videoCodec = null;
  48170. var codecs = void 0;
  48171. if (media.attributes.CODECS) {
  48172. codecs = parseCodecs(media.attributes.CODECS);
  48173. videoCodec = codecs.videoCodec;
  48174. codecCount = codecs.codecCount;
  48175. }
  48176. this.master().playlists.forEach(function (variant) {
  48177. var variantCodecs = {
  48178. codecCount: 2,
  48179. videoCodec: null
  48180. };
  48181. if (variant.attributes.CODECS) {
  48182. variantCodecs = parseCodecs(variant.attributes.CODECS);
  48183. } // if the streams differ in the presence or absence of audio or
  48184. // video, they are incompatible
  48185. if (variantCodecs.codecCount !== codecCount) {
  48186. variant.excludeUntil = Infinity;
  48187. } // if h.264 is specified on the current playlist, some flavor of
  48188. // it must be specified on all compatible variants
  48189. if (variantCodecs.videoCodec !== videoCodec) {
  48190. variant.excludeUntil = Infinity;
  48191. }
  48192. });
  48193. }
  48194. }, {
  48195. key: 'updateAdCues_',
  48196. value: function updateAdCues_(media) {
  48197. var offset = 0;
  48198. var seekable$$1 = this.seekable();
  48199. if (seekable$$1.length) {
  48200. offset = seekable$$1.start(0);
  48201. }
  48202. updateAdCues(media, this.cueTagsTrack_, offset);
  48203. }
  48204. /**
  48205. * Calculates the desired forward buffer length based on current time
  48206. *
  48207. * @return {Number} Desired forward buffer length in seconds
  48208. */
  48209. }, {
  48210. key: 'goalBufferLength',
  48211. value: function goalBufferLength() {
  48212. var currentTime = this.tech_.currentTime();
  48213. var initial = Config.GOAL_BUFFER_LENGTH;
  48214. var rate = Config.GOAL_BUFFER_LENGTH_RATE;
  48215. var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
  48216. return Math.min(initial + currentTime * rate, max);
  48217. }
  48218. /**
  48219. * Calculates the desired buffer low water line based on current time
  48220. *
  48221. * @return {Number} Desired buffer low water line in seconds
  48222. */
  48223. }, {
  48224. key: 'bufferLowWaterLine',
  48225. value: function bufferLowWaterLine() {
  48226. var currentTime = this.tech_.currentTime();
  48227. var initial = Config.BUFFER_LOW_WATER_LINE;
  48228. var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
  48229. var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
  48230. return Math.min(initial + currentTime * rate, max);
  48231. }
  48232. }]);
  48233. return MasterPlaylistController;
  48234. }(videojs$1.EventTarget);
  48235. /**
  48236. * Returns a function that acts as the Enable/disable playlist function.
  48237. *
  48238. * @param {PlaylistLoader} loader - The master playlist loader
  48239. * @param {String} playlistUri - uri of the playlist
  48240. * @param {Function} changePlaylistFn - A function to be called after a
  48241. * playlist's enabled-state has been changed. Will NOT be called if a
  48242. * playlist's enabled-state is unchanged
  48243. * @param {Boolean=} enable - Value to set the playlist enabled-state to
  48244. * or if undefined returns the current enabled-state for the playlist
  48245. * @return {Function} Function for setting/getting enabled
  48246. */
  48247. var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) {
  48248. return function (enable) {
  48249. var playlist = loader.master.playlists[playlistUri];
  48250. var incompatible = isIncompatible(playlist);
  48251. var currentlyEnabled = isEnabled(playlist);
  48252. if (typeof enable === 'undefined') {
  48253. return currentlyEnabled;
  48254. }
  48255. if (enable) {
  48256. delete playlist.disabled;
  48257. } else {
  48258. playlist.disabled = true;
  48259. }
  48260. if (enable !== currentlyEnabled && !incompatible) {
  48261. // Ensure the outside world knows about our changes
  48262. changePlaylistFn();
  48263. if (enable) {
  48264. loader.trigger('renditionenabled');
  48265. } else {
  48266. loader.trigger('renditiondisabled');
  48267. }
  48268. }
  48269. return enable;
  48270. };
  48271. };
  48272. /**
  48273. * The representation object encapsulates the publicly visible information
  48274. * in a media playlist along with a setter/getter-type function (enabled)
  48275. * for changing the enabled-state of a particular playlist entry
  48276. *
  48277. * @class Representation
  48278. */
  48279. var Representation = function Representation(hlsHandler, playlist, id) {
  48280. classCallCheck$1(this, Representation);
  48281. var mpc = hlsHandler.masterPlaylistController_,
  48282. smoothQualityChange = hlsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function
  48283. var changeType = smoothQualityChange ? 'smooth' : 'fast';
  48284. var qualityChangeFunction = mpc[changeType + 'QualityChange_'].bind(mpc); // some playlist attributes are optional
  48285. if (playlist.attributes.RESOLUTION) {
  48286. var resolution = playlist.attributes.RESOLUTION;
  48287. this.width = resolution.width;
  48288. this.height = resolution.height;
  48289. }
  48290. this.bandwidth = playlist.attributes.BANDWIDTH; // The id is simply the ordinality of the media playlist
  48291. // within the master playlist
  48292. this.id = id; // Partially-apply the enableFunction to create a playlist-
  48293. // specific variant
  48294. this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, qualityChangeFunction);
  48295. };
  48296. /**
  48297. * A mixin function that adds the `representations` api to an instance
  48298. * of the HlsHandler class
  48299. * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the
  48300. * representation API into
  48301. */
  48302. var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) {
  48303. var playlists = hlsHandler.playlists; // Add a single API-specific function to the HlsHandler instance
  48304. hlsHandler.representations = function () {
  48305. return playlists.master.playlists.filter(function (media) {
  48306. return !isIncompatible(media);
  48307. }).map(function (e, i) {
  48308. return new Representation(hlsHandler, e, e.uri);
  48309. });
  48310. };
  48311. };
  48312. /**
  48313. * @file playback-watcher.js
  48314. *
  48315. * Playback starts, and now my watch begins. It shall not end until my death. I shall
  48316. * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
  48317. * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
  48318. * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
  48319. * my life and honor to the Playback Watch, for this Player and all the Players to come.
  48320. */
  48321. // Set of events that reset the playback-watcher time check logic and clear the timeout
  48322. var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
  48323. /**
  48324. * @class PlaybackWatcher
  48325. */
  48326. var PlaybackWatcher = function () {
  48327. /**
  48328. * Represents an PlaybackWatcher object.
  48329. * @constructor
  48330. * @param {object} options an object that includes the tech and settings
  48331. */
  48332. function PlaybackWatcher(options) {
  48333. var _this = this;
  48334. classCallCheck$1(this, PlaybackWatcher);
  48335. this.tech_ = options.tech;
  48336. this.seekable = options.seekable;
  48337. this.seekTo = options.seekTo;
  48338. this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
  48339. this.media = options.media;
  48340. this.consecutiveUpdates = 0;
  48341. this.lastRecordedTime = null;
  48342. this.timer_ = null;
  48343. this.checkCurrentTimeTimeout_ = null;
  48344. this.logger_ = logger('PlaybackWatcher');
  48345. this.logger_('initialize');
  48346. var canPlayHandler = function canPlayHandler() {
  48347. return _this.monitorCurrentTime_();
  48348. };
  48349. var waitingHandler = function waitingHandler() {
  48350. return _this.techWaiting_();
  48351. };
  48352. var cancelTimerHandler = function cancelTimerHandler() {
  48353. return _this.cancelTimer_();
  48354. };
  48355. var fixesBadSeeksHandler = function fixesBadSeeksHandler() {
  48356. return _this.fixesBadSeeks_();
  48357. };
  48358. this.tech_.on('seekablechanged', fixesBadSeeksHandler);
  48359. this.tech_.on('waiting', waitingHandler);
  48360. this.tech_.on(timerCancelEvents, cancelTimerHandler);
  48361. this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events
  48362. this.dispose = function () {
  48363. _this.logger_('dispose');
  48364. _this.tech_.off('seekablechanged', fixesBadSeeksHandler);
  48365. _this.tech_.off('waiting', waitingHandler);
  48366. _this.tech_.off(timerCancelEvents, cancelTimerHandler);
  48367. _this.tech_.off('canplay', canPlayHandler);
  48368. if (_this.checkCurrentTimeTimeout_) {
  48369. window$1.clearTimeout(_this.checkCurrentTimeTimeout_);
  48370. }
  48371. _this.cancelTimer_();
  48372. };
  48373. }
  48374. /**
  48375. * Periodically check current time to see if playback stopped
  48376. *
  48377. * @private
  48378. */
  48379. createClass$1(PlaybackWatcher, [{
  48380. key: 'monitorCurrentTime_',
  48381. value: function monitorCurrentTime_() {
  48382. this.checkCurrentTime_();
  48383. if (this.checkCurrentTimeTimeout_) {
  48384. window$1.clearTimeout(this.checkCurrentTimeTimeout_);
  48385. } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  48386. this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
  48387. }
  48388. /**
  48389. * The purpose of this function is to emulate the "waiting" event on
  48390. * browsers that do not emit it when they are waiting for more
  48391. * data to continue playback
  48392. *
  48393. * @private
  48394. */
  48395. }, {
  48396. key: 'checkCurrentTime_',
  48397. value: function checkCurrentTime_() {
  48398. if (this.tech_.seeking() && this.fixesBadSeeks_()) {
  48399. this.consecutiveUpdates = 0;
  48400. this.lastRecordedTime = this.tech_.currentTime();
  48401. return;
  48402. }
  48403. if (this.tech_.paused() || this.tech_.seeking()) {
  48404. return;
  48405. }
  48406. var currentTime = this.tech_.currentTime();
  48407. var buffered = this.tech_.buffered();
  48408. if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
  48409. // If current time is at the end of the final buffered region, then any playback
  48410. // stall is most likely caused by buffering in a low bandwidth environment. The tech
  48411. // should fire a `waiting` event in this scenario, but due to browser and tech
  48412. // inconsistencies. Calling `techWaiting_` here allows us to simulate
  48413. // responding to a native `waiting` event when the tech fails to emit one.
  48414. return this.techWaiting_();
  48415. }
  48416. if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
  48417. this.consecutiveUpdates++;
  48418. this.waiting_();
  48419. } else if (currentTime === this.lastRecordedTime) {
  48420. this.consecutiveUpdates++;
  48421. } else {
  48422. this.consecutiveUpdates = 0;
  48423. this.lastRecordedTime = currentTime;
  48424. }
  48425. }
  48426. /**
  48427. * Cancels any pending timers and resets the 'timeupdate' mechanism
  48428. * designed to detect that we are stalled
  48429. *
  48430. * @private
  48431. */
  48432. }, {
  48433. key: 'cancelTimer_',
  48434. value: function cancelTimer_() {
  48435. this.consecutiveUpdates = 0;
  48436. if (this.timer_) {
  48437. this.logger_('cancelTimer_');
  48438. clearTimeout(this.timer_);
  48439. }
  48440. this.timer_ = null;
  48441. }
  48442. /**
  48443. * Fixes situations where there's a bad seek
  48444. *
  48445. * @return {Boolean} whether an action was taken to fix the seek
  48446. * @private
  48447. */
  48448. }, {
  48449. key: 'fixesBadSeeks_',
  48450. value: function fixesBadSeeks_() {
  48451. var seeking = this.tech_.seeking();
  48452. if (!seeking) {
  48453. return false;
  48454. }
  48455. var seekable = this.seekable();
  48456. var currentTime = this.tech_.currentTime();
  48457. var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
  48458. var seekTo = void 0;
  48459. if (isAfterSeekableRange) {
  48460. var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
  48461. seekTo = seekableEnd;
  48462. }
  48463. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  48464. var seekableStart = seekable.start(0); // sync to the beginning of the live window
  48465. // provide a buffer of .1 seconds to handle rounding/imprecise numbers
  48466. seekTo = seekableStart + SAFE_TIME_DELTA;
  48467. }
  48468. if (typeof seekTo !== 'undefined') {
  48469. this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.'));
  48470. this.seekTo(seekTo);
  48471. return true;
  48472. }
  48473. return false;
  48474. }
  48475. /**
  48476. * Handler for situations when we determine the player is waiting.
  48477. *
  48478. * @private
  48479. */
  48480. }, {
  48481. key: 'waiting_',
  48482. value: function waiting_() {
  48483. if (this.techWaiting_()) {
  48484. return;
  48485. } // All tech waiting checks failed. Use last resort correction
  48486. var currentTime = this.tech_.currentTime();
  48487. var buffered = this.tech_.buffered();
  48488. var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
  48489. // region with no indication that anything is amiss (seen in Firefox). Seeking to
  48490. // currentTime is usually enough to kickstart the player. This checks that the player
  48491. // is currently within a buffered region before attempting a corrective seek.
  48492. // Chrome does not appear to continue `timeupdate` events after a `waiting` event
  48493. // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
  48494. // make sure there is ~3 seconds of forward buffer before taking any corrective action
  48495. // to avoid triggering an `unknownwaiting` event when the network is slow.
  48496. if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
  48497. this.cancelTimer_();
  48498. this.seekTo(currentTime);
  48499. 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
  48500. this.tech_.trigger({
  48501. type: 'usage',
  48502. name: 'hls-unknown-waiting'
  48503. });
  48504. return;
  48505. }
  48506. }
  48507. /**
  48508. * Handler for situations when the tech fires a `waiting` event
  48509. *
  48510. * @return {Boolean}
  48511. * True if an action (or none) was needed to correct the waiting. False if no
  48512. * checks passed
  48513. * @private
  48514. */
  48515. }, {
  48516. key: 'techWaiting_',
  48517. value: function techWaiting_() {
  48518. var seekable = this.seekable();
  48519. var currentTime = this.tech_.currentTime();
  48520. if (this.tech_.seeking() && this.fixesBadSeeks_()) {
  48521. // Tech is seeking or bad seek fixed, no action needed
  48522. return true;
  48523. }
  48524. if (this.tech_.seeking() || this.timer_ !== null) {
  48525. // Tech is seeking or already waiting on another action, no action needed
  48526. return true;
  48527. }
  48528. if (this.beforeSeekableWindow_(seekable, currentTime)) {
  48529. var livePoint = seekable.end(seekable.length - 1);
  48530. this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint));
  48531. this.cancelTimer_();
  48532. this.seekTo(livePoint); // live window resyncs may be useful for monitoring QoS
  48533. this.tech_.trigger({
  48534. type: 'usage',
  48535. name: 'hls-live-resync'
  48536. });
  48537. return true;
  48538. }
  48539. var buffered = this.tech_.buffered();
  48540. var nextRange = findNextRange(buffered, currentTime);
  48541. if (this.videoUnderflow_(nextRange, buffered, currentTime)) {
  48542. // Even though the video underflowed and was stuck in a gap, the audio overplayed
  48543. // the gap, leading currentTime into a buffered range. Seeking to currentTime
  48544. // allows the video to catch up to the audio position without losing any audio
  48545. // (only suffering ~3 seconds of frozen video and a pause in audio playback).
  48546. this.cancelTimer_();
  48547. this.seekTo(currentTime); // video underflow may be useful for monitoring QoS
  48548. this.tech_.trigger({
  48549. type: 'usage',
  48550. name: 'hls-video-underflow'
  48551. });
  48552. return true;
  48553. } // check for gap
  48554. if (nextRange.length > 0) {
  48555. var difference = nextRange.start(0) - currentTime;
  48556. this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0)));
  48557. this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
  48558. return true;
  48559. } // All checks failed. Returning false to indicate failure to correct waiting
  48560. return false;
  48561. }
  48562. }, {
  48563. key: 'afterSeekableWindow_',
  48564. value: function afterSeekableWindow_(seekable, currentTime, playlist) {
  48565. var allowSeeksWithinUnsafeLiveWindow = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
  48566. if (!seekable.length) {
  48567. // we can't make a solid case if there's no seekable, default to false
  48568. return false;
  48569. }
  48570. var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
  48571. var isLive = !playlist.endList;
  48572. if (isLive && allowSeeksWithinUnsafeLiveWindow) {
  48573. allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
  48574. }
  48575. if (currentTime > allowedEnd) {
  48576. return true;
  48577. }
  48578. return false;
  48579. }
  48580. }, {
  48581. key: 'beforeSeekableWindow_',
  48582. value: function beforeSeekableWindow_(seekable, currentTime) {
  48583. if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream
  48584. seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) {
  48585. return true;
  48586. }
  48587. return false;
  48588. }
  48589. }, {
  48590. key: 'videoUnderflow_',
  48591. value: function videoUnderflow_(nextRange, buffered, currentTime) {
  48592. if (nextRange.length === 0) {
  48593. // Even if there is no available next range, there is still a possibility we are
  48594. // stuck in a gap due to video underflow.
  48595. var gap = this.gapFromVideoUnderflow_(buffered, currentTime);
  48596. if (gap) {
  48597. this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime));
  48598. return true;
  48599. }
  48600. }
  48601. return false;
  48602. }
  48603. /**
  48604. * Timer callback. If playback still has not proceeded, then we seek
  48605. * to the start of the next buffered region.
  48606. *
  48607. * @private
  48608. */
  48609. }, {
  48610. key: 'skipTheGap_',
  48611. value: function skipTheGap_(scheduledCurrentTime) {
  48612. var buffered = this.tech_.buffered();
  48613. var currentTime = this.tech_.currentTime();
  48614. var nextRange = findNextRange(buffered, currentTime);
  48615. this.cancelTimer_();
  48616. if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
  48617. return;
  48618. }
  48619. this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
  48620. this.seekTo(nextRange.start(0) + TIME_FUDGE_FACTOR);
  48621. this.tech_.trigger({
  48622. type: 'usage',
  48623. name: 'hls-gap-skip'
  48624. });
  48625. }
  48626. }, {
  48627. key: 'gapFromVideoUnderflow_',
  48628. value: function gapFromVideoUnderflow_(buffered, currentTime) {
  48629. // At least in Chrome, if there is a gap in the video buffer, the audio will continue
  48630. // playing for ~3 seconds after the video gap starts. This is done to account for
  48631. // video buffer underflow/underrun (note that this is not done when there is audio
  48632. // buffer underflow/underrun -- in that case the video will stop as soon as it
  48633. // encounters the gap, as audio stalls are more noticeable/jarring to a user than
  48634. // video stalls). The player's time will reflect the playthrough of audio, so the
  48635. // time will appear as if we are in a buffered region, even if we are stuck in a
  48636. // "gap."
  48637. //
  48638. // Example:
  48639. // video buffer: 0 => 10.1, 10.2 => 20
  48640. // audio buffer: 0 => 20
  48641. // overall buffer: 0 => 10.1, 10.2 => 20
  48642. // current time: 13
  48643. //
  48644. // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
  48645. // however, the audio continued playing until it reached ~3 seconds past the gap
  48646. // (13 seconds), at which point it stops as well. Since current time is past the
  48647. // gap, findNextRange will return no ranges.
  48648. //
  48649. // To check for this issue, we see if there is a gap that starts somewhere within
  48650. // a 3 second range (3 seconds +/- 1 second) back from our current time.
  48651. var gaps = findGaps(buffered);
  48652. for (var i = 0; i < gaps.length; i++) {
  48653. var start = gaps.start(i);
  48654. var end = gaps.end(i); // gap is starts no more than 4 seconds back
  48655. if (currentTime - start < 4 && currentTime - start > 2) {
  48656. return {
  48657. start: start,
  48658. end: end
  48659. };
  48660. }
  48661. }
  48662. return null;
  48663. }
  48664. }]);
  48665. return PlaybackWatcher;
  48666. }();
  48667. var defaultOptions = {
  48668. errorInterval: 30,
  48669. getSource: function getSource(next) {
  48670. var tech = this.tech({
  48671. IWillNotUseThisInPlugins: true
  48672. });
  48673. var sourceObj = tech.currentSource_;
  48674. return next(sourceObj);
  48675. }
  48676. };
  48677. /**
  48678. * Main entry point for the plugin
  48679. *
  48680. * @param {Player} player a reference to a videojs Player instance
  48681. * @param {Object} [options] an object with plugin options
  48682. * @private
  48683. */
  48684. var initPlugin = function initPlugin(player, options) {
  48685. var lastCalled = 0;
  48686. var seekTo = 0;
  48687. var localOptions = videojs$1.mergeOptions(defaultOptions, options);
  48688. player.ready(function () {
  48689. player.trigger({
  48690. type: 'usage',
  48691. name: 'hls-error-reload-initialized'
  48692. });
  48693. });
  48694. /**
  48695. * Player modifications to perform that must wait until `loadedmetadata`
  48696. * has been triggered
  48697. *
  48698. * @private
  48699. */
  48700. var loadedMetadataHandler = function loadedMetadataHandler() {
  48701. if (seekTo) {
  48702. player.currentTime(seekTo);
  48703. }
  48704. };
  48705. /**
  48706. * Set the source on the player element, play, and seek if necessary
  48707. *
  48708. * @param {Object} sourceObj An object specifying the source url and mime-type to play
  48709. * @private
  48710. */
  48711. var setSource = function setSource(sourceObj) {
  48712. if (sourceObj === null || sourceObj === undefined) {
  48713. return;
  48714. }
  48715. seekTo = player.duration() !== Infinity && player.currentTime() || 0;
  48716. player.one('loadedmetadata', loadedMetadataHandler);
  48717. player.src(sourceObj);
  48718. player.trigger({
  48719. type: 'usage',
  48720. name: 'hls-error-reload'
  48721. });
  48722. player.play();
  48723. };
  48724. /**
  48725. * Attempt to get a source from either the built-in getSource function
  48726. * or a custom function provided via the options
  48727. *
  48728. * @private
  48729. */
  48730. var errorHandler = function errorHandler() {
  48731. // Do not attempt to reload the source if a source-reload occurred before
  48732. // 'errorInterval' time has elapsed since the last source-reload
  48733. if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
  48734. player.trigger({
  48735. type: 'usage',
  48736. name: 'hls-error-reload-canceled'
  48737. });
  48738. return;
  48739. }
  48740. if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
  48741. videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
  48742. return;
  48743. }
  48744. lastCalled = Date.now();
  48745. return localOptions.getSource.call(player, setSource);
  48746. };
  48747. /**
  48748. * Unbind any event handlers that were bound by the plugin
  48749. *
  48750. * @private
  48751. */
  48752. var cleanupEvents = function cleanupEvents() {
  48753. player.off('loadedmetadata', loadedMetadataHandler);
  48754. player.off('error', errorHandler);
  48755. player.off('dispose', cleanupEvents);
  48756. };
  48757. /**
  48758. * Cleanup before re-initializing the plugin
  48759. *
  48760. * @param {Object} [newOptions] an object with plugin options
  48761. * @private
  48762. */
  48763. var reinitPlugin = function reinitPlugin(newOptions) {
  48764. cleanupEvents();
  48765. initPlugin(player, newOptions);
  48766. };
  48767. player.on('error', errorHandler);
  48768. player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
  48769. // initializing the plugin
  48770. player.reloadSourceOnError = reinitPlugin;
  48771. };
  48772. /**
  48773. * Reload the source when an error is detected as long as there
  48774. * wasn't an error previously within the last 30 seconds
  48775. *
  48776. * @param {Object} [options] an object with plugin options
  48777. */
  48778. var reloadSourceOnError = function reloadSourceOnError(options) {
  48779. initPlugin(this, options);
  48780. };
  48781. var version$3 = "1.9.3"; // since VHS handles HLS and DASH (and in the future, more types), use * to capture all
  48782. videojs$1.use('*', function (player) {
  48783. return {
  48784. setSource: function setSource(srcObj, next) {
  48785. // pass null as the first argument to indicate that the source is not rejected
  48786. next(null, srcObj);
  48787. },
  48788. // VHS needs to know when seeks happen. For external seeks (generated at the player
  48789. // level), this middleware will capture the action. For internal seeks (generated at
  48790. // the tech level), we use a wrapped function so that we can handle it on our own
  48791. // (specified elsewhere).
  48792. setCurrentTime: function setCurrentTime(time) {
  48793. if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
  48794. player.vhs.setCurrentTime(time);
  48795. }
  48796. return time;
  48797. },
  48798. // Sync VHS after play requests.
  48799. // This specifically handles replay where the order of actions is
  48800. // play, video element will seek to 0 (skipping the setCurrentTime middleware)
  48801. // then triggers a play event.
  48802. play: function play() {
  48803. if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
  48804. player.vhs.setCurrentTime(player.tech_.currentTime());
  48805. }
  48806. }
  48807. };
  48808. });
  48809. /**
  48810. * @file videojs-http-streaming.js
  48811. *
  48812. * The main file for the HLS project.
  48813. * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE
  48814. */
  48815. var Hls$1 = {
  48816. PlaylistLoader: PlaylistLoader,
  48817. Playlist: Playlist,
  48818. Decrypter: Decrypter,
  48819. AsyncStream: AsyncStream,
  48820. decrypt: decrypt,
  48821. utils: utils$1,
  48822. STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
  48823. INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
  48824. comparePlaylistBandwidth: comparePlaylistBandwidth,
  48825. comparePlaylistResolution: comparePlaylistResolution,
  48826. xhr: xhrFactory()
  48827. }; // Define getter/setters for config properites
  48828. ['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) {
  48829. Object.defineProperty(Hls$1, prop, {
  48830. get: function get$$1() {
  48831. videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
  48832. return Config[prop];
  48833. },
  48834. set: function set$$1(value) {
  48835. videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
  48836. if (typeof value !== 'number' || value < 0) {
  48837. videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0');
  48838. return;
  48839. }
  48840. Config[prop] = value;
  48841. }
  48842. });
  48843. });
  48844. var LOCAL_STORAGE_KEY$1 = 'videojs-vhs';
  48845. var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
  48846. var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
  48847. if (mpegurlRE.test(type)) {
  48848. return 'hls';
  48849. }
  48850. var dashRE = /^application\/dash\+xml/i;
  48851. if (dashRE.test(type)) {
  48852. return 'dash';
  48853. }
  48854. return null;
  48855. };
  48856. /**
  48857. * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls.
  48858. *
  48859. * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
  48860. * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
  48861. * @function handleHlsMediaChange
  48862. */
  48863. var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) {
  48864. var newPlaylist = playlistLoader.media();
  48865. var selectedIndex = -1;
  48866. for (var i = 0; i < qualityLevels.length; i++) {
  48867. if (qualityLevels[i].id === newPlaylist.uri) {
  48868. selectedIndex = i;
  48869. break;
  48870. }
  48871. }
  48872. qualityLevels.selectedIndex_ = selectedIndex;
  48873. qualityLevels.trigger({
  48874. selectedIndex: selectedIndex,
  48875. type: 'change'
  48876. });
  48877. };
  48878. /**
  48879. * Adds quality levels to list once playlist metadata is available
  48880. *
  48881. * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
  48882. * @param {Object} hls Hls object to listen to for media events.
  48883. * @function handleHlsLoadedMetadata
  48884. */
  48885. var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
  48886. hls.representations().forEach(function (rep) {
  48887. qualityLevels.addQualityLevel(rep);
  48888. });
  48889. handleHlsMediaChange(qualityLevels, hls.playlists);
  48890. }; // HLS is a source handler, not a tech. Make sure attempts to use it
  48891. // as one do not cause exceptions.
  48892. Hls$1.canPlaySource = function () {
  48893. return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  48894. };
  48895. var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) {
  48896. if (!keySystemOptions) {
  48897. return keySystemOptions;
  48898. } // upsert the content types based on the selected playlist
  48899. var keySystemContentTypes = {};
  48900. for (var keySystem in keySystemOptions) {
  48901. keySystemContentTypes[keySystem] = {
  48902. audioContentType: 'audio/mp4; codecs="' + audioPlaylist.attributes.CODECS + '"',
  48903. videoContentType: 'video/mp4; codecs="' + videoPlaylist.attributes.CODECS + '"'
  48904. };
  48905. if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) {
  48906. keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh;
  48907. } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
  48908. // so we need to prevent overwriting the URL entirely
  48909. if (typeof keySystemOptions[keySystem] === 'string') {
  48910. keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
  48911. }
  48912. }
  48913. return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes);
  48914. };
  48915. var setupEmeOptions = function setupEmeOptions(hlsHandler) {
  48916. if (hlsHandler.options_.sourceType !== 'dash') {
  48917. return;
  48918. }
  48919. var player = videojs$1.players[hlsHandler.tech_.options_.playerId];
  48920. if (player.eme) {
  48921. var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, hlsHandler.playlists.media(), hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media());
  48922. if (sourceOptions) {
  48923. player.currentSource().keySystems = sourceOptions; // works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449
  48924. if (player.eme.initializeMediaKeys) {
  48925. player.eme.initializeMediaKeys();
  48926. }
  48927. }
  48928. }
  48929. };
  48930. var getVhsLocalStorage = function getVhsLocalStorage() {
  48931. if (!window.localStorage) {
  48932. return null;
  48933. }
  48934. var storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY$1);
  48935. if (!storedObject) {
  48936. return null;
  48937. }
  48938. try {
  48939. return JSON.parse(storedObject);
  48940. } catch (e) {
  48941. // someone may have tampered with the value
  48942. return null;
  48943. }
  48944. };
  48945. var updateVhsLocalStorage = function updateVhsLocalStorage(options) {
  48946. if (!window.localStorage) {
  48947. return false;
  48948. }
  48949. var objectToStore = getVhsLocalStorage();
  48950. objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options;
  48951. try {
  48952. window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore));
  48953. } catch (e) {
  48954. // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
  48955. // storage is set to 0).
  48956. // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
  48957. // No need to perform any operation.
  48958. return false;
  48959. }
  48960. return objectToStore;
  48961. };
  48962. /**
  48963. * Whether the browser has built-in HLS support.
  48964. */
  48965. Hls$1.supportsNativeHls = function () {
  48966. var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
  48967. if (!videojs$1.getTech('Html5').isSupported()) {
  48968. return false;
  48969. } // HLS manifests can go by many mime-types
  48970. var canPlay = [// Apple santioned
  48971. 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility
  48972. 'audio/mpegurl', // Very common
  48973. 'audio/x-mpegurl', // Very common
  48974. 'application/x-mpegurl', // Included for completeness
  48975. 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
  48976. return canPlay.some(function (canItPlay) {
  48977. return /maybe|probably/i.test(video.canPlayType(canItPlay));
  48978. });
  48979. }();
  48980. Hls$1.supportsNativeDash = function () {
  48981. if (!videojs$1.getTech('Html5').isSupported()) {
  48982. return false;
  48983. }
  48984. return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
  48985. }();
  48986. Hls$1.supportsTypeNatively = function (type) {
  48987. if (type === 'hls') {
  48988. return Hls$1.supportsNativeHls;
  48989. }
  48990. if (type === 'dash') {
  48991. return Hls$1.supportsNativeDash;
  48992. }
  48993. return false;
  48994. };
  48995. /**
  48996. * HLS is a source handler, not a tech. Make sure attempts to use it
  48997. * as one do not cause exceptions.
  48998. */
  48999. Hls$1.isSupported = function () {
  49000. return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
  49001. };
  49002. var Component$1 = videojs$1.getComponent('Component');
  49003. /**
  49004. * The Hls Handler object, where we orchestrate all of the parts
  49005. * of HLS to interact with video.js
  49006. *
  49007. * @class HlsHandler
  49008. * @extends videojs.Component
  49009. * @param {Object} source the soruce object
  49010. * @param {Tech} tech the parent tech object
  49011. * @param {Object} options optional and required options
  49012. */
  49013. var HlsHandler = function (_Component) {
  49014. inherits$1(HlsHandler, _Component);
  49015. function HlsHandler(source, tech, options) {
  49016. classCallCheck$1(this, HlsHandler); // tech.player() is deprecated but setup a reference to HLS for
  49017. // backwards-compatibility
  49018. var _this = possibleConstructorReturn$1(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls));
  49019. if (tech.options_ && tech.options_.playerId) {
  49020. var _player = videojs$1(tech.options_.playerId);
  49021. if (!_player.hasOwnProperty('hls')) {
  49022. Object.defineProperty(_player, 'hls', {
  49023. get: function get$$1() {
  49024. videojs$1.log.warn('player.hls is deprecated. Use player.tech().hls instead.');
  49025. tech.trigger({
  49026. type: 'usage',
  49027. name: 'hls-player-access'
  49028. });
  49029. return _this;
  49030. },
  49031. configurable: true
  49032. });
  49033. } // Set up a reference to the HlsHandler from player.vhs. This allows users to start
  49034. // migrating from player.tech_.hls... to player.vhs... for API access. Although this
  49035. // isn't the most appropriate form of reference for video.js (since all APIs should
  49036. // be provided through core video.js), it is a common pattern for plugins, and vhs
  49037. // will act accordingly.
  49038. _player.vhs = _this; // deprecated, for backwards compatibility
  49039. _player.dash = _this;
  49040. _this.player_ = _player;
  49041. }
  49042. _this.tech_ = tech;
  49043. _this.source_ = source;
  49044. _this.stats = {};
  49045. _this.setOptions_();
  49046. if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
  49047. tech.overrideNativeAudioTracks(true);
  49048. tech.overrideNativeVideoTracks(true);
  49049. } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
  49050. // overriding native HLS only works if audio tracks have been emulated
  49051. // error early if we're misconfigured
  49052. throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
  49053. } // listen for fullscreenchange events for this player so that we
  49054. // can adjust our quality selection quickly
  49055. _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
  49056. var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
  49057. if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
  49058. _this.masterPlaylistController_.smoothQualityChange_();
  49059. }
  49060. }); // Handle seeking when looping - middleware doesn't handle this seek event from the tech
  49061. _this.on(_this.tech_, 'seeking', function () {
  49062. if (this.tech_.currentTime() === 0 && this.tech_.player_.loop()) {
  49063. this.setCurrentTime(0);
  49064. }
  49065. });
  49066. _this.on(_this.tech_, 'error', function () {
  49067. if (this.masterPlaylistController_) {
  49068. this.masterPlaylistController_.pauseLoading();
  49069. }
  49070. });
  49071. _this.on(_this.tech_, 'play', _this.play);
  49072. return _this;
  49073. }
  49074. createClass$1(HlsHandler, [{
  49075. key: 'setOptions_',
  49076. value: function setOptions_() {
  49077. var _this2 = this; // defaults
  49078. this.options_.withCredentials = this.options_.withCredentials || false;
  49079. this.options_.handleManifestRedirects = this.options_.handleManifestRedirects || false;
  49080. this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
  49081. this.options_.smoothQualityChange = this.options_.smoothQualityChange || false;
  49082. this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
  49083. this.options_.customTagParsers = this.options_.customTagParsers || [];
  49084. this.options_.customTagMappers = this.options_.customTagMappers || [];
  49085. if (typeof this.options_.blacklistDuration !== 'number') {
  49086. this.options_.blacklistDuration = 5 * 60;
  49087. }
  49088. if (typeof this.options_.bandwidth !== 'number') {
  49089. if (this.options_.useBandwidthFromLocalStorage) {
  49090. var storedObject = getVhsLocalStorage();
  49091. if (storedObject && storedObject.bandwidth) {
  49092. this.options_.bandwidth = storedObject.bandwidth;
  49093. this.tech_.trigger({
  49094. type: 'usage',
  49095. name: 'hls-bandwidth-from-local-storage'
  49096. });
  49097. }
  49098. if (storedObject && storedObject.throughput) {
  49099. this.options_.throughput = storedObject.throughput;
  49100. this.tech_.trigger({
  49101. type: 'usage',
  49102. name: 'hls-throughput-from-local-storage'
  49103. });
  49104. }
  49105. }
  49106. } // if bandwidth was not set by options or pulled from local storage, start playlist
  49107. // selection at a reasonable bandwidth
  49108. if (typeof this.options_.bandwidth !== 'number') {
  49109. this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
  49110. } // If the bandwidth number is unchanged from the initial setting
  49111. // then this takes precedence over the enableLowInitialPlaylist option
  49112. this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
  49113. ['withCredentials', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects'].forEach(function (option) {
  49114. if (typeof _this2.source_[option] !== 'undefined') {
  49115. _this2.options_[option] = _this2.source_[option];
  49116. }
  49117. });
  49118. this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
  49119. }
  49120. /**
  49121. * called when player.src gets called, handle a new source
  49122. *
  49123. * @param {Object} src the source object to handle
  49124. */
  49125. }, {
  49126. key: 'src',
  49127. value: function src(_src, type) {
  49128. var _this3 = this; // do nothing if the src is falsey
  49129. if (!_src) {
  49130. return;
  49131. }
  49132. this.setOptions_(); // add master playlist controller options
  49133. this.options_.url = this.source_.src;
  49134. this.options_.tech = this.tech_;
  49135. this.options_.externHls = Hls$1;
  49136. this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update both the tech and call our own
  49137. // setCurrentTime function. This is needed because "seeking" events aren't always
  49138. // reliable. External seeks (via the player object) are handled via middleware.
  49139. this.options_.seekTo = function (time) {
  49140. _this3.tech_.setCurrentTime(time);
  49141. _this3.setCurrentTime(time);
  49142. };
  49143. this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
  49144. this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, {
  49145. seekable: function seekable$$1() {
  49146. return _this3.seekable();
  49147. },
  49148. media: function media() {
  49149. return _this3.masterPlaylistController_.media();
  49150. }
  49151. }));
  49152. this.masterPlaylistController_.on('error', function () {
  49153. var player = videojs$1.players[_this3.tech_.options_.playerId];
  49154. player.error(_this3.masterPlaylistController_.error);
  49155. }); // `this` in selectPlaylist should be the HlsHandler for backwards
  49156. // compatibility with < v2
  49157. this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this);
  49158. this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
  49159. this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
  49160. this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist
  49161. // controller. Using a custom property for backwards compatibility
  49162. // with < v2
  49163. Object.defineProperties(this, {
  49164. selectPlaylist: {
  49165. get: function get$$1() {
  49166. return this.masterPlaylistController_.selectPlaylist;
  49167. },
  49168. set: function set$$1(selectPlaylist) {
  49169. this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
  49170. }
  49171. },
  49172. throughput: {
  49173. get: function get$$1() {
  49174. return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
  49175. },
  49176. set: function set$$1(throughput) {
  49177. this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
  49178. // for the cumulative average
  49179. this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
  49180. }
  49181. },
  49182. bandwidth: {
  49183. get: function get$$1() {
  49184. return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
  49185. },
  49186. set: function set$$1(bandwidth) {
  49187. this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
  49188. // `count` is set to zero that current value of `rate` isn't included
  49189. // in the cumulative average
  49190. this.masterPlaylistController_.mainSegmentLoader_.throughput = {
  49191. rate: 0,
  49192. count: 0
  49193. };
  49194. }
  49195. },
  49196. /**
  49197. * `systemBandwidth` is a combination of two serial processes bit-rates. The first
  49198. * is the network bitrate provided by `bandwidth` and the second is the bitrate of
  49199. * the entire process after that - decryption, transmuxing, and appending - provided
  49200. * by `throughput`.
  49201. *
  49202. * Since the two process are serial, the overall system bandwidth is given by:
  49203. * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
  49204. */
  49205. systemBandwidth: {
  49206. get: function get$$1() {
  49207. var invBandwidth = 1 / (this.bandwidth || 1);
  49208. var invThroughput = void 0;
  49209. if (this.throughput > 0) {
  49210. invThroughput = 1 / this.throughput;
  49211. } else {
  49212. invThroughput = 0;
  49213. }
  49214. var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
  49215. return systemBitrate;
  49216. },
  49217. set: function set$$1() {
  49218. videojs$1.log.error('The "systemBandwidth" property is read-only');
  49219. }
  49220. }
  49221. });
  49222. if (this.options_.bandwidth) {
  49223. this.bandwidth = this.options_.bandwidth;
  49224. }
  49225. if (this.options_.throughput) {
  49226. this.throughput = this.options_.throughput;
  49227. }
  49228. Object.defineProperties(this.stats, {
  49229. bandwidth: {
  49230. get: function get$$1() {
  49231. return _this3.bandwidth || 0;
  49232. },
  49233. enumerable: true
  49234. },
  49235. mediaRequests: {
  49236. get: function get$$1() {
  49237. return _this3.masterPlaylistController_.mediaRequests_() || 0;
  49238. },
  49239. enumerable: true
  49240. },
  49241. mediaRequestsAborted: {
  49242. get: function get$$1() {
  49243. return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
  49244. },
  49245. enumerable: true
  49246. },
  49247. mediaRequestsTimedout: {
  49248. get: function get$$1() {
  49249. return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
  49250. },
  49251. enumerable: true
  49252. },
  49253. mediaRequestsErrored: {
  49254. get: function get$$1() {
  49255. return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
  49256. },
  49257. enumerable: true
  49258. },
  49259. mediaTransferDuration: {
  49260. get: function get$$1() {
  49261. return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
  49262. },
  49263. enumerable: true
  49264. },
  49265. mediaBytesTransferred: {
  49266. get: function get$$1() {
  49267. return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
  49268. },
  49269. enumerable: true
  49270. },
  49271. mediaSecondsLoaded: {
  49272. get: function get$$1() {
  49273. return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
  49274. },
  49275. enumerable: true
  49276. },
  49277. buffered: {
  49278. get: function get$$1() {
  49279. return timeRangesToArray(_this3.tech_.buffered());
  49280. },
  49281. enumerable: true
  49282. },
  49283. currentTime: {
  49284. get: function get$$1() {
  49285. return _this3.tech_.currentTime();
  49286. },
  49287. enumerable: true
  49288. },
  49289. currentSource: {
  49290. get: function get$$1() {
  49291. return _this3.tech_.currentSource_;
  49292. },
  49293. enumerable: true
  49294. },
  49295. currentTech: {
  49296. get: function get$$1() {
  49297. return _this3.tech_.name_;
  49298. },
  49299. enumerable: true
  49300. },
  49301. duration: {
  49302. get: function get$$1() {
  49303. return _this3.tech_.duration();
  49304. },
  49305. enumerable: true
  49306. },
  49307. master: {
  49308. get: function get$$1() {
  49309. return _this3.playlists.master;
  49310. },
  49311. enumerable: true
  49312. },
  49313. playerDimensions: {
  49314. get: function get$$1() {
  49315. return _this3.tech_.currentDimensions();
  49316. },
  49317. enumerable: true
  49318. },
  49319. seekable: {
  49320. get: function get$$1() {
  49321. return timeRangesToArray(_this3.tech_.seekable());
  49322. },
  49323. enumerable: true
  49324. },
  49325. timestamp: {
  49326. get: function get$$1() {
  49327. return Date.now();
  49328. },
  49329. enumerable: true
  49330. },
  49331. videoPlaybackQuality: {
  49332. get: function get$$1() {
  49333. return _this3.tech_.getVideoPlaybackQuality();
  49334. },
  49335. enumerable: true
  49336. }
  49337. });
  49338. this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
  49339. this.tech_.on('bandwidthupdate', function () {
  49340. if (_this3.options_.useBandwidthFromLocalStorage) {
  49341. updateVhsLocalStorage({
  49342. bandwidth: _this3.bandwidth,
  49343. throughput: Math.round(_this3.throughput)
  49344. });
  49345. }
  49346. });
  49347. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  49348. // Add the manual rendition mix-in to HlsHandler
  49349. renditionSelectionMixin(_this3);
  49350. setupEmeOptions(_this3);
  49351. }); // the bandwidth of the primary segment loader is our best
  49352. // estimate of overall bandwidth
  49353. this.on(this.masterPlaylistController_, 'progress', function () {
  49354. this.tech_.trigger('progress');
  49355. });
  49356. this.tech_.ready(function () {
  49357. return _this3.setupQualityLevels_();
  49358. }); // do nothing if the tech has been disposed already
  49359. // this can occur if someone sets the src in player.ready(), for instance
  49360. if (!this.tech_.el()) {
  49361. return;
  49362. }
  49363. this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource));
  49364. }
  49365. /**
  49366. * Initializes the quality levels and sets listeners to update them.
  49367. *
  49368. * @method setupQualityLevels_
  49369. * @private
  49370. */
  49371. }, {
  49372. key: 'setupQualityLevels_',
  49373. value: function setupQualityLevels_() {
  49374. var _this4 = this;
  49375. var player = videojs$1.players[this.tech_.options_.playerId];
  49376. if (player && player.qualityLevels) {
  49377. this.qualityLevels_ = player.qualityLevels();
  49378. this.masterPlaylistController_.on('selectedinitialmedia', function () {
  49379. handleHlsLoadedMetadata(_this4.qualityLevels_, _this4);
  49380. });
  49381. this.playlists.on('mediachange', function () {
  49382. handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists);
  49383. });
  49384. }
  49385. }
  49386. /**
  49387. * Begin playing the video.
  49388. */
  49389. }, {
  49390. key: 'play',
  49391. value: function play() {
  49392. this.masterPlaylistController_.play();
  49393. }
  49394. /**
  49395. * a wrapper around the function in MasterPlaylistController
  49396. */
  49397. }, {
  49398. key: 'setCurrentTime',
  49399. value: function setCurrentTime(currentTime) {
  49400. this.masterPlaylistController_.setCurrentTime(currentTime);
  49401. }
  49402. /**
  49403. * a wrapper around the function in MasterPlaylistController
  49404. */
  49405. }, {
  49406. key: 'duration',
  49407. value: function duration$$1() {
  49408. return this.masterPlaylistController_.duration();
  49409. }
  49410. /**
  49411. * a wrapper around the function in MasterPlaylistController
  49412. */
  49413. }, {
  49414. key: 'seekable',
  49415. value: function seekable$$1() {
  49416. return this.masterPlaylistController_.seekable();
  49417. }
  49418. /**
  49419. * Abort all outstanding work and cleanup.
  49420. */
  49421. }, {
  49422. key: 'dispose',
  49423. value: function dispose() {
  49424. if (this.playbackWatcher_) {
  49425. this.playbackWatcher_.dispose();
  49426. }
  49427. if (this.masterPlaylistController_) {
  49428. this.masterPlaylistController_.dispose();
  49429. }
  49430. if (this.qualityLevels_) {
  49431. this.qualityLevels_.dispose();
  49432. }
  49433. if (this.player_) {
  49434. delete this.player_.vhs;
  49435. delete this.player_.dash;
  49436. delete this.player_.hls;
  49437. }
  49438. if (this.tech_ && this.tech_.hls) {
  49439. delete this.tech_.hls;
  49440. }
  49441. get$1(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this);
  49442. }
  49443. }, {
  49444. key: 'convertToProgramTime',
  49445. value: function convertToProgramTime(time, callback) {
  49446. return getProgramTime({
  49447. playlist: this.masterPlaylistController_.media(),
  49448. time: time,
  49449. callback: callback
  49450. });
  49451. } // the player must be playing before calling this
  49452. }, {
  49453. key: 'seekToProgramTime',
  49454. value: function seekToProgramTime$$1(programTime, callback) {
  49455. var pauseAfterSeek = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
  49456. var retryCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2;
  49457. return seekToProgramTime({
  49458. programTime: programTime,
  49459. playlist: this.masterPlaylistController_.media(),
  49460. retryCount: retryCount,
  49461. pauseAfterSeek: pauseAfterSeek,
  49462. seekTo: this.options_.seekTo,
  49463. tech: this.options_.tech,
  49464. callback: callback
  49465. });
  49466. }
  49467. }]);
  49468. return HlsHandler;
  49469. }(Component$1);
  49470. /**
  49471. * The Source Handler object, which informs video.js what additional
  49472. * MIME types are supported and sets up playback. It is registered
  49473. * automatically to the appropriate tech based on the capabilities of
  49474. * the browser it is running in. It is not necessary to use or modify
  49475. * this object in normal usage.
  49476. */
  49477. var HlsSourceHandler = {
  49478. name: 'videojs-http-streaming',
  49479. VERSION: version$3,
  49480. canHandleSource: function canHandleSource(srcObj) {
  49481. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  49482. var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
  49483. return HlsSourceHandler.canPlayType(srcObj.type, localOptions);
  49484. },
  49485. handleSource: function handleSource(source, tech) {
  49486. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  49487. var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
  49488. tech.hls = new HlsHandler(source, tech, localOptions);
  49489. tech.hls.xhr = xhrFactory();
  49490. tech.hls.src(source.src, source.type);
  49491. return tech.hls;
  49492. },
  49493. canPlayType: function canPlayType(type) {
  49494. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  49495. var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options),
  49496. overrideNative = _videojs$mergeOptions.hls.overrideNative;
  49497. var supportedType = simpleTypeFromSourceType(type);
  49498. var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative);
  49499. return canUseMsePlayback ? 'maybe' : '';
  49500. }
  49501. };
  49502. if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') {
  49503. videojs$1.MediaSource = MediaSource;
  49504. videojs$1.URL = URL$1;
  49505. } // register source handlers with the appropriate techs
  49506. if (MediaSource.supportsNativeMediaSources()) {
  49507. videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0);
  49508. }
  49509. videojs$1.HlsHandler = HlsHandler;
  49510. videojs$1.HlsSourceHandler = HlsSourceHandler;
  49511. videojs$1.Hls = Hls$1;
  49512. if (!videojs$1.use) {
  49513. videojs$1.registerComponent('Hls', Hls$1);
  49514. }
  49515. videojs$1.options.hls = videojs$1.options.hls || {};
  49516. if (videojs$1.registerPlugin) {
  49517. videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError);
  49518. } else {
  49519. videojs$1.plugin('reloadSourceOnError', reloadSourceOnError);
  49520. }
  49521. return videojs$1;
  49522. }));